| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.reading.imageop; |
| |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorModel; |
| import java.awt.image.WritableRaster; |
| import javax.imageio.ImageIO; |
| import javax.imageio.ImageTypeSpecifier; |
| import javax.imageio.ImageWriter; |
| import javax.imageio.stream.ImageOutputStream; |
| import javax.imageio.spi.ImageWriterSpi; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.activity.Disposable; |
| import org.apache.avalon.framework.configuration.Configurable; |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.parameters.Parameters; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.ServiceSelector; |
| import org.apache.avalon.framework.service.Serviceable; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.environment.SourceResolver; |
| import org.apache.cocoon.reading.ResourceReader; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * The <code>ImageOpReader</code> component is used to serve binary image data |
| * in a sitemap pipeline. It makes use of HTTP Headers to determine if |
| * the requested resource should be written to the <code>OutputStream</code> |
| * or if it can signal that it hasn't changed. |
| */ |
| final public class ImageOpReader |
| extends ResourceReader |
| implements Configurable, Serviceable, Disposable { |
| |
| private final static String FORMAT_DEFAULT = "png"; |
| |
| private String format; |
| private ArrayList effectsStack; |
| private ServiceSelector operationSelector; |
| private ServiceManager manager; |
| |
| /** |
| * Read reader configuration |
| */ |
| public void configure(Configuration configuration) |
| throws ConfigurationException { |
| super.configure( configuration ); |
| Configuration effects = configuration.getChild( "effects" ); |
| try { |
| configureEffects( effects ); |
| } catch( ServiceException e ) { |
| throw new ConfigurationException( "Unable to configure ImageOperations", e ); |
| } |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) |
| */ |
| public void service( ServiceManager man ) |
| throws ServiceException { |
| this.manager = man; |
| operationSelector = (ServiceSelector) man.lookup( ImageOperation.ROLE + "Selector" ); |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.activity.Disposable#dispose() |
| */ |
| public void dispose() { |
| if ( this.manager != null ) { |
| this.manager.release(this.operationSelector); |
| this.operationSelector = null; |
| this.manager = null; |
| } |
| } |
| |
| /** |
| * @see org.apache.cocoon.reading.ResourceReader#setup(org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters) |
| */ |
| public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) |
| throws ProcessingException, SAXException, IOException { |
| super.setup(resolver, objectModel, src, par); |
| format = par.getParameter("output-format", FORMAT_DEFAULT); |
| if(getLogger().isInfoEnabled()) { |
| getLogger().info( src + " --> " + format ); |
| } |
| setupEffectsStack( par ); |
| } |
| |
| protected void processStream( InputStream inputStream ) |
| throws IOException, ProcessingException { |
| if( effectsStack.size() > 0 ) { |
| // since we create the image on the fly |
| response.setHeader("Accept-Ranges", "none"); |
| |
| BufferedImage image = ImageIO.read( inputStream ); |
| if( image == null ) { |
| throw new ProcessingException( "Unable to decode the InputStream. Possibly an unknown format." ); |
| } |
| image = applyEffectsStack( image ); |
| |
| write( image ); |
| } else { |
| // only read the resource - no modifications requested |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("passing original resource"); |
| } |
| super.processStream(inputStream); |
| } |
| } |
| |
| /** |
| * Generate the unique key. |
| * This key must be unique inside the space of this component. |
| * |
| * @return The generated key consists of the src and width and height, and the color transform |
| * parameters |
| */ |
| public Serializable getKey() { |
| StringBuffer b = new StringBuffer( 200 ); |
| b.append( this.inputSource.getURI() ); |
| b.append( ':' ); |
| b.append( format ); |
| b.append( ':' ); |
| b.append( super.getKey() ); |
| b.append( ':' ); |
| Iterator list = effectsStack.iterator(); |
| while( list.hasNext() ) { |
| ImageOperation op = (ImageOperation) list.next(); |
| b.append( op.getKey() ); |
| b.append( ':' ); |
| } |
| String key = b.toString(); |
| b.setLength( 0 ); // Seems to be something odd (memory leak?) |
| // going on if this isn't done. (JDK1.4.2) |
| return key; |
| } |
| |
| private void configureEffects( Configuration conf ) |
| throws ConfigurationException, ServiceException { |
| effectsStack = new ArrayList(); |
| |
| Configuration[] ops = conf.getChildren( "op" ); |
| for( int i=0 ; i < ops.length ; i++ ) { |
| String type = ops[i].getAttribute( "type" ); |
| String prefix = ops[i].getAttribute( "prefix", type + "-" ); |
| ImageOperation op = (ImageOperation) operationSelector.select( type ); |
| op.setPrefix( prefix ); |
| effectsStack.add( op ); |
| } |
| } |
| |
| private void setupEffectsStack( Parameters params ) |
| throws ProcessingException { |
| Iterator list = effectsStack.iterator(); |
| while( list.hasNext() ) { |
| ImageOperation op = (ImageOperation) list.next(); |
| op.setup( params ); |
| } |
| } |
| |
| private BufferedImage applyEffectsStack( BufferedImage image ) { |
| if( effectsStack.size() == 0 ) { |
| return image; |
| } |
| Iterator list = effectsStack.iterator(); |
| WritableRaster src = image.getRaster(); |
| while( list.hasNext() ) { |
| ImageOperation op = (ImageOperation) list.next(); |
| WritableRaster r = op.apply( src ); |
| if(getLogger().isDebugEnabled()) { |
| getLogger().debug( "In Bounds: " + r.getBounds() ); |
| } |
| src = r.createWritableTranslatedChild( 0, 0 ); |
| } |
| ColorModel cm = image.getColorModel(); |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug( "Out Bounds: " + src.getBounds() ); |
| } |
| BufferedImage newImage = new BufferedImage( cm, src, true, new Hashtable() ); |
| // Not sure what this should really be --------------^^^^^ |
| |
| int minX = newImage.getMinX(); |
| int minY = newImage.getMinY(); |
| int width = newImage.getWidth(); |
| int height = newImage.getHeight(); |
| if(getLogger().isInfoEnabled()) { |
| getLogger().info( "Image: " + minX + ", " + minY + ", " + width + ", " + height ); |
| } |
| |
| return newImage; |
| } |
| |
| private void write( BufferedImage image ) |
| throws ProcessingException, IOException { |
| ImageTypeSpecifier its = ImageTypeSpecifier.createFromRenderedImage( image ); |
| Iterator writers = ImageIO.getImageWriters( its, format ); |
| ImageWriter writer = null; |
| if( writers.hasNext() ) { |
| writer = (ImageWriter) writers.next(); |
| } |
| if( writer == null ) { |
| throw new ProcessingException( "Unable to find a ImageWriter: " + format ); |
| } |
| |
| ImageWriterSpi spi = writer.getOriginatingProvider(); |
| String[] mimetypes = spi.getMIMETypes(); |
| if (getLogger().isInfoEnabled()) { |
| getLogger().info( "Setting content-type: " + mimetypes[0] ); |
| } |
| response.setHeader("Content-Type", mimetypes[0] ); |
| ImageOutputStream output = ImageIO.createImageOutputStream( out ); |
| try { |
| writer.setOutput( output ); |
| writer.write( image ); |
| } finally { |
| writer.dispose(); |
| output.close(); |
| out.flush(); |
| // Niclas Hedhman: Stream is closed in superclass. |
| } |
| } |
| /* |
| private void printRaster( WritableRaster r ) |
| { |
| DataBuffer data = r.getDataBuffer(); |
| int numBanks = data.getNumBanks(); |
| int size = data.getSize(); |
| for( int i=0 ; i < size ; i++ ) |
| { |
| long value = 0; |
| for( int j=0 ; j < numBanks ; j++ ) |
| { |
| int v = data.getElem( j, i ); |
| if( v < 256 ) |
| value = value << 8 ; |
| else |
| value = value << 16; |
| value = value + v; |
| } |
| if(getLogger().isDebugEnabled()) { |
| getLogger().debug( Long.toHexString( value ) ); |
| } |
| } |
| } |
| */ |
| } |