001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.reading.imageop;
018:
019: import java.awt.image.BufferedImage;
020: import java.awt.image.ColorModel;
021: import java.awt.image.WritableRaster;
022:
023: import javax.imageio.ImageIO;
024: import javax.imageio.ImageTypeSpecifier;
025: import javax.imageio.ImageWriter;
026: import javax.imageio.stream.ImageOutputStream;
027: import javax.imageio.spi.ImageWriterSpi;
028:
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.io.Serializable;
032:
033: import java.util.ArrayList;
034: import java.util.Hashtable;
035: import java.util.Iterator;
036: import java.util.Map;
037:
038: import org.apache.avalon.framework.configuration.Configurable;
039: import org.apache.avalon.framework.configuration.Configuration;
040: import org.apache.avalon.framework.configuration.ConfigurationException;
041:
042: import org.apache.avalon.framework.component.Composable;
043: import org.apache.avalon.framework.component.ComponentException;
044: import org.apache.avalon.framework.component.ComponentManager;
045: import org.apache.avalon.framework.component.ComponentSelector;
046:
047: import org.apache.avalon.framework.parameters.Parameters;
048:
049: import org.apache.cocoon.ProcessingException;
050:
051: import org.apache.cocoon.environment.SourceResolver;
052:
053: import org.apache.cocoon.reading.ResourceReader;
054:
055: import org.xml.sax.SAXException;
056:
057: /**
058: * The <code>ImageOpReader</code> component is used to serve binary image data
059: * in a sitemap pipeline. It makes use of HTTP Headers to determine if
060: * the requested resource should be written to the <code>OutputStream</code>
061: * or if it can signal that it hasn't changed.
062: */
063: final public class ImageOpReader extends ResourceReader implements
064: Configurable, Composable {
065: private final static String FORMAT_DEFAULT = "png";
066:
067: private String m_Format;
068: private ArrayList m_EffectsStack;
069: private ComponentSelector m_OperationSelector;
070:
071: /**
072: * Read reader configuration
073: */
074: public void configure(Configuration configuration)
075: throws ConfigurationException {
076: super .configure(configuration);
077: Configuration effects = configuration.getChild("effects");
078: try {
079: configureEffects(effects);
080: } catch (ComponentException e) {
081: throw new ConfigurationException(
082: "Unable to configure ImageOperations", e);
083: }
084: }
085:
086: public void compose(ComponentManager man) throws ComponentException {
087: m_OperationSelector = (ComponentSelector) man
088: .lookup("org.apache.cocoon.reading.imageop.ImageOperationSelector");
089: }
090:
091: public void setup(SourceResolver resolver, Map objectModel,
092: String src, Parameters par) throws ProcessingException,
093: SAXException, IOException {
094: super .setup(resolver, objectModel, src, par);
095: m_Format = par.getParameter("output-format", FORMAT_DEFAULT);
096: if (getLogger().isInfoEnabled()) {
097: getLogger().info(src + " --> " + m_Format);
098: }
099: setupEffectsStack(par);
100: }
101:
102: protected void processStream(InputStream inputStream)
103: throws IOException, ProcessingException {
104: if (m_EffectsStack.size() > 0) {
105: // since we create the image on the fly
106: response.setHeader("Accept-Ranges", "none");
107:
108: BufferedImage image = ImageIO.read(inputStream);
109: if (image == null)
110: throw new ProcessingException(
111: "Unable to decode the InputStream. Possibly an unknown format.");
112:
113: image = applyEffectsStack(image);
114:
115: write(image);
116: } else {
117: // only read the resource - no modifications requested
118: if (getLogger().isDebugEnabled()) {
119: getLogger().debug("passing original resource");
120: }
121: super .processStream(inputStream);
122: }
123: }
124:
125: /**
126: * Generate the unique key.
127: * This key must be unique inside the space of this component.
128: *
129: * @return The generated key consists of the src and width and height, and the color transform
130: * parameters
131: */
132: public Serializable getKey() {
133: StringBuffer b = new StringBuffer(200);
134: b.append(this .inputSource.getURI());
135: b.append(':');
136: b.append(m_Format);
137: b.append(':');
138: b.append(super .getKey());
139: b.append(':');
140: Iterator list = m_EffectsStack.iterator();
141: while (list.hasNext()) {
142: ImageOperation op = (ImageOperation) list.next();
143: b.append(op.getKey());
144: b.append(':');
145: }
146: String key = b.toString();
147: b.setLength(0); // Seems to be something odd (memory leak?)
148: // going on if this isn't done. (JDK1.4.2)
149: return key;
150: }
151:
152: private void configureEffects(Configuration conf)
153: throws ConfigurationException, ComponentException {
154: m_EffectsStack = new ArrayList();
155:
156: Configuration[] ops = conf.getChildren("op");
157: for (int i = 0; i < ops.length; i++) {
158: String type = ops[i].getAttribute("type");
159: String prefix = ops[i].getAttribute("prefix", type + "-");
160: ImageOperation op = (ImageOperation) m_OperationSelector
161: .select(type);
162: op.setPrefix(prefix);
163: m_EffectsStack.add(op);
164: }
165: }
166:
167: private void setupEffectsStack(Parameters params)
168: throws ProcessingException {
169: Iterator list = m_EffectsStack.iterator();
170: while (list.hasNext()) {
171: ImageOperation op = (ImageOperation) list.next();
172: op.setup(params);
173: }
174: }
175:
176: private BufferedImage applyEffectsStack(BufferedImage image) {
177: if (m_EffectsStack.size() == 0)
178: return image;
179: Iterator list = m_EffectsStack.iterator();
180: WritableRaster src = image.getRaster();
181: while (list.hasNext()) {
182: ImageOperation op = (ImageOperation) list.next();
183: WritableRaster r = op.apply(src);
184: if (getLogger().isDebugEnabled()) {
185: getLogger().debug("In Bounds: " + r.getBounds());
186: }
187: src = r.createWritableTranslatedChild(0, 0);
188: }
189: ColorModel cm = image.getColorModel();
190: if (getLogger().isDebugEnabled()) {
191: getLogger().debug("Out Bounds: " + src.getBounds());
192: }
193: BufferedImage newImage = new BufferedImage(cm, src, true,
194: new Hashtable());
195: // Not sure what this should really be --------------^^^^^
196:
197: int minX = newImage.getMinX();
198: int minY = newImage.getMinY();
199: int width = newImage.getWidth();
200: int height = newImage.getHeight();
201: if (getLogger().isInfoEnabled()) {
202: getLogger().info(
203: "Image: " + minX + ", " + minY + ", " + width
204: + ", " + height);
205: }
206:
207: return newImage;
208: }
209:
210: private void write(BufferedImage image) throws ProcessingException,
211: IOException {
212: ImageTypeSpecifier its = ImageTypeSpecifier
213: .createFromRenderedImage(image);
214: Iterator writers = ImageIO.getImageWriters(its, m_Format);
215: ImageWriter writer = null;
216: if (writers.hasNext()) {
217: writer = (ImageWriter) writers.next();
218: }
219: if (writer == null)
220: throw new ProcessingException(
221: "Unable to find a ImageWriter: " + m_Format);
222:
223: ImageWriterSpi spi = writer.getOriginatingProvider();
224: String[] mimetypes = spi.getMIMETypes();
225: if (getLogger().isInfoEnabled()) {
226: getLogger().info("Setting content-type: " + mimetypes[0]);
227: }
228: response.setHeader("Content-Type", mimetypes[0]);
229: ImageOutputStream output = ImageIO.createImageOutputStream(out);
230: try {
231: writer.setOutput(output);
232: writer.write(image);
233: } finally {
234: writer.dispose();
235: output.close();
236: out.flush();
237: // Niclas Hedhman: Stream is closed in superclass.
238: }
239: }
240: /*
241: private void printRaster( WritableRaster r )
242: {
243: DataBuffer data = r.getDataBuffer();
244: int numBanks = data.getNumBanks();
245: int size = data.getSize();
246: for( int i=0 ; i < size ; i++ )
247: {
248: long value = 0;
249: for( int j=0 ; j < numBanks ; j++ )
250: {
251: int v = data.getElem( j, i );
252: if( v < 256 )
253: value = value << 8 ;
254: else
255: value = value << 16;
256: value = value + v;
257: }
258: if(getLogger().isDebugEnabled()) {
259: getLogger().debug( Long.toHexString( value ) );
260: }
261: }
262: }
263: */
264: }
|