Hello everyone, as I was building an image gallery app I was requested to add
the ability to tint an image to coordinate color schemes with the website in
general. To do this I patched the ImageReader to perform a transform on the
RGB color channels. This is configured in exactly the same manner as scaling is
now done.


See http://www.grumpykitty.biz/index.html for the results.

This required modification of the

org.apache.cocoon.reading.ImageReader

class only.

The changes include

      *   Support for creating a grayscale jpeg
      *   Support for tinting ( scale red, green or blue color channel )

* Cache key now *always* contains scaling and color coefficients

This does mean that instead of scaling a decoded Raster, a decoded
BufferedImage is used. A BufferedImage consists of a Raster + color
information and is required for the color transform. I consider this the
most pivotal change.

The actual color transform is not applied unless specifically requested
in the sitemap, so I believe the change is benign.

A cvs diff -u style patch to

/src/java/org/apache/cocoon/reading/ImageReader.java

is attached. Please review and consider it for inclusion in Cocoon.



All the best, and thank you

Peter




Index: ImageReader.java
===================================================================
RCS file: 
/home/cvspublic/cocoon-2.1/src/java/org/apache/cocoon/reading/ImageReader.java,v
retrieving revision 1.4
diff -u -r1.4 ImageReader.java
--- ImageReader.java    12 Dec 2003 09:41:33 -0000      1.4
+++ ImageReader.java    10 Jan 2004 04:32:33 -0000
@@ -62,9 +62,13 @@
 import com.sun.image.codec.jpeg.JPEGImageEncoder;
 import org.xml.sax.SAXException;
 
+import java.awt.color.ColorSpace;
 import java.awt.geom.AffineTransform;
 import java.awt.image.AffineTransformOp;
-import java.awt.image.Raster;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorConvertOp;
+import java.awt.image.DataBuffer;
+import java.awt.image.RescaleOp;
 import java.awt.image.WritableRaster;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -87,19 +91,41 @@
  *     <dd>This parameter is optional. When specified it determines the height
  *         of the image that should be served.
  *     </dd>
+ *     <dt>&lt;scale(Red|Green|Blue)&gt;</dt>
+ *     <dd>This parameter is optional. When specified it will cause the 
+ *     specified color component in the image to be multiplied by the 
+ *     specified floating point value.
+ *     </dd>
+ *     <dt>&lt;offset(Red|Green|Blue)&gt;</dt>
+ *     <dd>This parameter is optional. When specified it will cause the 
+ *     specified color component in the image to be incremented by the 
+ *     specified floating point value.
+ *     </dd>
+ *     <dt>&lt;grayscale&gt;</dt>
+ *     <dd>This parameter is optional. When specified and set to true it
+ *     will cause each image pixel to be normalized. It cannot be used
+ *     alongside the scale and offset parameters.
+ *     </dd>
  *   </dl>
  *
  * @author <a href="mailto:[EMAIL PROTECTED]">Stefano Mazzocchi</a>
  * @author <a href="mailto:[EMAIL PROTECTED]">Stephan Michels</a>
  * @author <a href="mailto:[EMAIL PROTECTED]">Torsten Curdt</a>
- * @version CVS $Id: ImageReader.java,v 1.4 2003/12/12 09:41:33 huber Exp $
+ * @version CVS $Id: ImageReader.java,v 1.3 2003/09/24 21:41:11 cziegeler Exp $
  */
 final public class ImageReader extends ResourceReader {
 
     private int width;
     private int height;
+    private float[] scaleColor = new float[3];
+    private float[] offsetColor = new float[3];
+    private ColorConvertOp grayscaleFilter = null;
+    private RescaleOp colorFilter = null;
+    
     private boolean enlarge;
     private final static String ENLARGE_DEFAULT = "true";
+    
+    private final static String GRAYSCALE_DEFAULT = "false";
 
     public void setup(SourceResolver resolver, Map objectModel, String src, 
Parameters par)
             throws ProcessingException, SAXException, IOException {
@@ -108,7 +134,38 @@
 
         width = par.getParameterAsInteger("width", 0);
         height = par.getParameterAsInteger("height", 0);
-
+       
+        scaleColor[0] = par.getParameterAsFloat("scaleRed",-1.0f);
+        scaleColor[1] = par.getParameterAsFloat("scaleGreen",-1.0f);
+        scaleColor[2] = par.getParameterAsFloat("scaleBlue",-1.0f);
+       offsetColor[0] = par.getParameterAsFloat("offsetBlue",0.0f);
+       offsetColor[1] = par.getParameterAsFloat("offsetBlue",0.0f);
+       offsetColor[2] = par.getParameterAsFloat("offsetBlue",0.0f);    
+       
+       boolean filterColor = false;
+       
+       for(int i = 0; i < 3; ++i)
+       {
+               if(scaleColor[i] != -1.0f) filterColor = true; else scaleColor[i] = 
1.0f;
+               if(offsetColor[i] != 0.0f) filterColor = true;
+       }
+       
+       if(true == filterColor)
+       {
+               colorFilter = new RescaleOp(scaleColor, offsetColor, null);
+       }               
+       else 
+       {
+               colorFilter = null;
+       }
+       
+        String grayscalePar = par.getParameter("grayscale", GRAYSCALE_DEFAULT);
+        if ("true".equalsIgnoreCase(grayscalePar) || 
"yes".equalsIgnoreCase(grayscalePar)){            
+            grayscaleFilter = new 
ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY),null);
+        } else {
+            grayscaleFilter = null;
+        }      
+       
         String enlargePar = par.getParameter("allow-enlarging", ENLARGE_DEFAULT);
         if ("true".equalsIgnoreCase(enlargePar) || 
"yes".equalsIgnoreCase(enlargePar)){
             enlarge = true;
@@ -121,7 +178,7 @@
      * Returns the affine transform that implements the scaling.
      * The behavior is the following: if both the new width and height values
      * are positive, the image is rescaled according to these new values and
-     * the original aspect ration is lost.
+     * the original aspect ratio is lost.
      * Otherwise, if one of the two parameters is zero or negative, the
      * aspect ratio is maintained and the positive parameter indicates the
      * scaling.
@@ -160,7 +217,7 @@
     }
 
     protected void processStream() throws IOException, ProcessingException {
-        if (width > 0 || height > 0) {
+        if (width > 0 || height > 0 || null != colorFilter) {
             if (getLogger().isDebugEnabled()) {
                 getLogger().debug("image " + ((width==0)?"?":Integer.toString(width))
                                   + "x" + ((height==0)?"?":Integer.toString(height))
@@ -193,40 +250,48 @@
              */
 
             try {
+               
                 JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(inputStream);
-                Raster original = decoder.decodeAsRaster();
+                BufferedImage original = decoder.decodeAsBufferedImage();
                 JPEGDecodeParam decodeParam = decoder.getJPEGDecodeParam();
-                double ow = decodeParam.getWidth();
-                double oh = decodeParam.getHeight();
-                AffineTransformOp filter = new AffineTransformOp(getTransform(ow, oh, 
width, height), AffineTransformOp.TYPE_BILINEAR);
-                WritableRaster scaled = filter.createCompatibleDestRaster(original);
-                filter.filter(original, scaled);
-
-                if (!handleJVMBug()) {
-                    if (getLogger().isDebugEnabled()) {
-                        getLogger().debug( "No need to handle JVM bug" );
-                    }
-                    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
-                    encoder.encode(scaled);
-                } else {
-                    if (getLogger().isDebugEnabled()) {
-                        getLogger().debug( "Need to handle JVM bug" );
-                    }
-                    ByteArrayOutputStream bstream = new ByteArrayOutputStream();
-                    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bstream);
-                    encoder.encode(scaled);
-                    out.write(bstream.toByteArray());
-                }
+                
+               BufferedImage currentImage = original;
+                
+                if(width > 0 || height > 0)
+                {
+                       double ow = decodeParam.getWidth();
+                       double oh = decodeParam.getHeight();
+
+                       AffineTransformOp filter = new 
AffineTransformOp(getTransform(ow, oh, width, height), 
AffineTransformOp.TYPE_BILINEAR);
+                       WritableRaster intermediateRaster = 
filter.createCompatibleDestRaster(currentImage.getRaster());
+                       
+                       filter.filter(currentImage.getRaster(), intermediateRaster);
+                       
+                       currentImage = new BufferedImage(original.getColorModel(), 
intermediateRaster, true, null);
+               }
+               
+               if(null != grayscaleFilter)
+               {
+                       grayscaleFilter.filter(currentImage, currentImage);
+               }
+                if(null != colorFilter)
+                {
+                       colorFilter.filter(currentImage, currentImage);
+               }
+               
+                // JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
+
+                ByteArrayOutputStream bstream = new ByteArrayOutputStream();
+                JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bstream);
+                encoder.encode(currentImage);
+                out.write(bstream.toByteArray());
 
                 out.flush();
             } catch (ImageFormatException e) {
                 throw new ProcessingException("Error reading the image. Note that 
only JPEG images are currently supported.");
-            } finally {
-              // Bugzilla Bug 25069, close inputStream in finally block
-              // this will close inputStream even if processStream throws
-              // an exception
-              inputStream.close();
             }
+
+            inputStream.close();
         } else {
             // only read the resource - no modifications requested
             if (getLogger().isDebugEnabled()) {
@@ -240,48 +305,20 @@
      * Generate the unique key.
      * This key must be unique inside the space of this component.
      *
-     * @return The generated key consists from src and width and height
+     * @return The generated key consists of the src and width and height, and the 
color transform
      * parameters
     */
     public Serializable getKey() {
-        if (width > 0 || height > 0) {
-            return this.inputSource.getURI() + ':' + this.width + ':' + this.height;
-        } else {
-            return super.getKey();
-        }
-    }
-
-    /**
-     * Determine if workaround for Bug Id 4502892 is neccessary.
-     * This method assumes that Bug is present if 
-     * java.version is undeterminable, and for java.version
-     * 1.1, 1.2, 1.3, all other java.version do not need the Bug handling
-     *
-     * @return true if we should handle the JVM bug, else false
-     */
-    protected boolean handleJVMBug() {
-        // java.version=1.4.0
-        String java_version = System.getProperty( "java.version", "0.0.0" );
-        boolean handleJVMBug = true;
-        
-        char major = java_version.charAt(0);
-        char minor = java_version.charAt(2);
-        
-        // make 0.0, 1.1, 1.2, 1.3 handleJVMBug = true
-        if (major == '0' || major == '1') {
-            if (minor == '0' || minor == '1' || minor == '2' || minor == '3') {
-                handleJVMBug = true;
-            } else {
-                handleJVMBug = false;
-            }
-        } else {
-            handleJVMBug = true;
-        }
-        if (getLogger().isDebugEnabled()) {
-            getLogger().debug( "Running java.version " + String.valueOf(java_version) 
+ 
-              " need to handle JVM bug " + String.valueOf(handleJVMBug) );
-        }
-        
-        return handleJVMBug;
+    return this.inputSource.getURI() 
+               + ':' + this.width 
+               + ':' + this.height 
+               + ":" + this.scaleColor[0]
+               + ":" + this.scaleColor[1]
+               + ":" + this.scaleColor[2]
+               + ":" + this.offsetColor[0]
+               + ":" + this.offsetColor[1]
+               + ":" + this.offsetColor[2]
+               + ":" + ((null == this.grayscaleFilter)?"grayscale":"color")
+               + ":" + super.getKey();
     }
 }

Reply via email to