changeset 1032:4e368c85cce4

CLOSED - # 22: wrong contrast setting. dito #23 (at least on OSX 10.7) mostly juggling color channels. also separate version number for DocuImage class.
author robcast
date Sat, 10 Mar 2012 23:09:38 +0100
parents e077f52205a7
children d5e181e3401c
files common/src/main/java/digilib/image/DocuImage.java common/src/main/java/digilib/image/DocuImageImpl.java common/src/main/java/digilib/image/ImageLoaderDocuImage.java servlet2/src/main/java/digilib/servlet/DigilibServletConfiguration.java servlet2/src/main/java/digilib/servlet/Scaler.java servlet3/src/main/java/digilib/servlet/DigilibServletConfiguration.java servlet3/src/main/java/digilib/servlet/Scaler.java
diffstat 7 files changed, 743 insertions(+), 672 deletions(-) [+]
line wrap: on
line diff
--- a/common/src/main/java/digilib/image/DocuImage.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/common/src/main/java/digilib/image/DocuImage.java	Sat Mar 10 23:09:38 2012 +0100
@@ -16,7 +16,7 @@
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
-*/
+ */
 
 package digilib.image;
 
@@ -25,13 +25,13 @@
 import java.io.OutputStream;
 import java.util.Iterator;
 
-
 import digilib.io.FileOpException;
 import digilib.io.ImageInput;
 import digilib.util.ImageSize;
 
-/** The basic class for the representation of a digilib image.
- *
+/**
+ * The basic class for the representation of a digilib image.
+ * 
  * The actual image object is hidden in the class, only methods for loading,
  * manipulation, and saving are exported. This strategy enables implementations
  * using different toolkits that rely on different image base classes (like
@@ -39,210 +39,245 @@
  */
 public interface DocuImage {
 
-	/** Loads an image file into the Object.
-	 * 
-	 * @param ii Image File.
-	 * @throws FileOpException Exception thrown if any error occurs.
-	 */
-	public void loadImage(ImageInput ii) throws FileOpException;
+    /**
+     * Loads an image file into the Object.
+     * 
+     * @param ii
+     *            Image File.
+     * @throws FileOpException
+     *             Exception thrown if any error occurs.
+     */
+    public void loadImage(ImageInput ii) throws FileOpException;
 
-	/** This DocuImage supports the loadSubImage operation.
-	 * 
-	 * @return boolean
-	 */
-	public boolean isSubimageSupported();
+    /**
+     * This DocuImage supports the loadSubImage operation.
+     * 
+     * @return boolean
+     */
+    public boolean isSubimageSupported();
 
-	/** Load only a subsampled region of the image file.
-	 * 
-	 * @param ii
-	 * @param region
-	 * @param subsample
-	 * @throws FileOpException
-	 */
-	public void loadSubimage(ImageInput ii, Rectangle region, int subsample)
-		throws FileOpException;
+    /**
+     * Load only a subsampled region of the image file.
+     * 
+     * @param ii
+     * @param region
+     * @param subsample
+     * @throws FileOpException
+     */
+    public void loadSubimage(ImageInput ii, Rectangle region, int subsample) throws FileOpException;
 
-	/** Writes the current image to an OutputStream.
-	 *
-	 * The image is encoded to the mime-type <code>mt</code> and sent to the output
-	 * stream <code>ostream</code>.
-	 *
-	 * Currently only mime-types "image/jpeg" and "image/png" are supported.
-	 * 
-	 * @param mt mime-type of the image to be sent.
-	 * @param ostream OutputStream where the image is sent.
-	 * @throws ImageOpException Exception in other cases.
-	 * @throws FileOpException Exception on sending data
-	 */
-	public void writeImage(String mt, OutputStream ostream)
-		throws ImageOpException, FileOpException;
+    /**
+     * Writes the current image to an OutputStream.
+     * 
+     * The image is encoded to the mime-type <code>mt</code> and sent to the
+     * output stream <code>ostream</code>.
+     * 
+     * Currently only mime-types "image/jpeg" and "image/png" are supported.
+     * 
+     * @param mt
+     *            mime-type of the image to be sent.
+     * @param ostream
+     *            OutputStream where the image is sent.
+     * @throws ImageOpException
+     *             Exception in other cases.
+     * @throws FileOpException
+     *             Exception on sending data
+     */
+    public void writeImage(String mt, OutputStream ostream) throws ImageOpException, FileOpException;
+
+    /**
+     * The width of the current image in pixel.
+     * 
+     * @return Image width in pixels.
+     */
+    public int getWidth();
 
-	/** The width of the current image in pixel.
-	 * 
-	 * @return Image width in pixels.
-	 */
-	public int getWidth();
+    /**
+     * The height of the current image in pixel.
+     * 
+     * @return Image height in pixels.
+     */
+    public int getHeight();
 
-	/** The height of the current image in pixel.
-	 * 
-	 * @return Image height in pixels.
-	 */
-	public int getHeight();
-	
-	/** The size of the current image in pixel.
-	 * 
-	 * @return
-	 */
-	public ImageSize getSize();
+    /**
+     * The size of the current image in pixel.
+     * 
+     * @return
+     */
+    public ImageSize getSize();
 
-	/** The mime-type of the image, i.e. the mime-type of the input that was read.
-	 * 
-	 * @return String the mime-type of this image.
-	 */
-	public String getMimetype();
+    /**
+     * The mime-type of the image, i.e. the mime-type of the input that was
+     * read.
+     * 
+     * @return String the mime-type of this image.
+     */
+    public String getMimetype();
 
-	/** Crops the current image.
-	 * 
-	 * Cuts out a region of the size <code>width</code> x <code>height</code> at
-	 * the offset <code>xoff</code>, <code>yoff</code> from the current image
-	 * and replaces the current image with the result.
-	 * 
-	 * @param xoff X offset of crop region
-	 * @param yoff Y offset of crop region
-	 * @param width width of crop region
-	 * @param height height of crop region
-	 * @throws ImageOpException
-	 */
-	public void crop(int xoff, int yoff, int width, int height)
-		throws ImageOpException;
+    /**
+     * Crops the current image.
+     * 
+     * Cuts out a region of the size <code>width</code> x <code>height</code> at
+     * the offset <code>xoff</code>, <code>yoff</code> from the current image
+     * and replaces the current image with the result.
+     * 
+     * @param xoff
+     *            X offset of crop region
+     * @param yoff
+     *            Y offset of crop region
+     * @param width
+     *            width of crop region
+     * @param height
+     *            height of crop region
+     * @throws ImageOpException
+     */
+    public void crop(int xoff, int yoff, int width, int height) throws ImageOpException;
 
-	/** Scales the current image.
-	 * 
-	 * Replaces the current image with an image scaled by the factor
-	 * <code>scale</code>.
-	 * 
-	 * @param scale scaling factor
-	 * @throws ImageOpException
-	 */
-	public void scale(double scaleX, double scaleY) throws ImageOpException;
+    /**
+     * Scales the current image.
+     * 
+     * Replaces the current image with an image scaled by the factor
+     * <code>scale</code>.
+     * 
+     * @param scale
+     *            scaling factor
+     * @throws ImageOpException
+     */
+    public void scale(double scaleX, double scaleY) throws ImageOpException;
 
-	/** Crops and scales the current image.
-	 *
-	 * The current image is cropped to a rectangle of <code>width</code>,
-	 * <code>height</code> at position <code>x_off</code>, <code>y_off</code>. The
-	 * resulting image is scaled by the factor <code>scale</code> using the
-	 * interpolation quality <code>qual</code> (0=worst).
-	 * 
-	 * @param x_off x offset of the crop rectangle in pixel.
-	 * @param y_off y offset of the crop rectangle in pixel.
-	 * @param width width of the crop rectangle in pixel.
-	 * @param height height of the crop rectangle in pixel.
-	 * @param scale scaling factor.
-	 * @param qual interpolation quality (0=worst).
-	 * @throws ImageOpException exception thrown on any error.
-	 */
-	public void cropAndScale(
-		int x_off,
-		int y_off,
-		int width,
-		int height,
-		double scale,
-		int qual)
-		throws ImageOpException;
+    /**
+     * Crops and scales the current image.
+     * 
+     * The current image is cropped to a rectangle of <code>width</code>,
+     * <code>height</code> at position <code>x_off</code>, <code>y_off</code>.
+     * The resulting image is scaled by the factor <code>scale</code> using the
+     * interpolation quality <code>qual</code> (0=worst).
+     * 
+     * @param x_off
+     *            x offset of the crop rectangle in pixel.
+     * @param y_off
+     *            y offset of the crop rectangle in pixel.
+     * @param width
+     *            width of the crop rectangle in pixel.
+     * @param height
+     *            height of the crop rectangle in pixel.
+     * @param scale
+     *            scaling factor.
+     * @param qual
+     *            interpolation quality (0=worst).
+     * @throws ImageOpException
+     *             exception thrown on any error.
+     */
+    public void cropAndScale(int x_off, int y_off, int width, int height, double scale, int qual) throws ImageOpException;
 
-	/** Rotates the current image.
-	 * 
-	 * Replaces the current image with a rotated image. The image is rotated
-	 * around the center by the <code>angle</code> 
-	 * given in degrees [0, 360] clockwise.
-	 * Image size and aspect ratio are likely to change.
-	 * 
-	 * @param angle rotation angle in degree
-	 */
-	public void rotate(double angle) throws ImageOpException;
+    /**
+     * Rotates the current image.
+     * 
+     * Replaces the current image with a rotated image. The image is rotated
+     * around the center by the <code>angle</code> given in degrees [0, 360]
+     * clockwise. Image size and aspect ratio are likely to change.
+     * 
+     * @param angle
+     *            rotation angle in degree
+     */
+    public void rotate(double angle) throws ImageOpException;
 
-	/** Mirrors the current image.
-	 * 
-	 * Replaces  the current image with a mirrored image. The mirror axis goes
-	 * through the center of the image and is rotated by <code>angle</code>
-	 * degrees. Currently only horizontal and vertical mirroring (0 and 90
-	 * degree) are supported. 
-	 * 
-	 * @param angle angle of mirror axis
-	 * @throws ImageOpException
-	 */
-	public void mirror(double angle) throws ImageOpException;
+    /**
+     * Mirrors the current image.
+     * 
+     * Replaces the current image with a mirrored image. The mirror axis goes
+     * through the center of the image and is rotated by <code>angle</code>
+     * degrees. Currently only horizontal and vertical mirroring (0 and 90
+     * degree) are supported.
+     * 
+     * @param angle
+     *            angle of mirror axis
+     * @throws ImageOpException
+     */
+    public void mirror(double angle) throws ImageOpException;
+
+    /**
+     * Enhances brightness and contrast of the current image.
+     * 
+     * Replaces the current image with a brightness and contrast enhanced image.
+     * Contrast is enhanced by multiplying the pixel value with the constant
+     * <code>mult</code>. Brightness is enhanced by adding the constant
+     * <code>add</code> to the pixel value. Operation: p1 = (p0*mult)+add.
+     * 
+     * @param mult
+     *            multiplicative constant for contrast enhancement
+     * @param add
+     *            additive constant for brightness enhancement
+     * @throws ImageOpException
+     */
+    public void enhance(float mult, float add) throws ImageOpException;
 
-	/** Enhances brightness and contrast of the current image.
-	 * 
-	 * Replaces the current image with a brightness and contrast enhanced image.
-	 * Contrast is enhanced by multiplying the pixel value with the constant
-	 * <code>mult</code>. Brightness is enhanced by adding the constant
-	 * <code>add</code> to the pixel value. Operation: p1 = (p0*mult)+add.
-	 * 
-	 * @param mult multiplicative constant for contrast enhancement
-	 * @param add additive constant for brightness enhancement
-	 * @throws ImageOpException
-	 */
-	public void enhance(float mult, float add) throws ImageOpException;
+    /**
+     * Manipulates the colors of the current image.
+     * 
+     * Replaces the current image with a color modified image. For the red,
+     * green and blue color channels all pixel values are multiplied by the
+     * constant <code>m</code> and added to the constant <code>a</code>.
+     * Operation: p1 = (p0*m)+a.
+     * 
+     * @param rgbm
+     *            multiplicative constants for red, green, blue
+     * @param rgba
+     *            additive constant for red, green, blue
+     * @throws ImageOpException
+     */
+    public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException;
 
-	/** Manipulates the colors of the current image.
-	 * 
-	 * Replaces the current image with a color modified image.
-	 * For the red, green and blue color channels all pixel values are multiplied
-	 * by the constant
-	 * <code>m</code> and added to the constant
-	 * <code>a</code>. Operation: p1 = (p0*m)+a.
-	 * 
-	 * @param rgbm multiplicative constants for red, green, blue
-	 * @param rgba additive constant for red, green, blue
-	 * @throws ImageOpException
-	 */
-	public void enhanceRGB(float[] rgbm, float[] rgba)
-		throws ImageOpException;
-
+    /**
+     * Operations for colorOps.
+     * 
+     * GRAYSCALE: cast color image to grayscale NTSC_GRAY: convert color image
+     * to grayscale using NTSC formula INVERT: invert colors (every channel
+     * separately) MAP_GRAY_BGR: false color image from grayscale (0: blue, 128:
+     * green, 255: red)
+     * 
+     */
+    public enum ColorOp {
+        GRAYSCALE, NTSC_GRAY, INVERT, MAP_GRAY_BGR
+    };
 
-	/** Operations for colorOps.
-	 * 
-     * GRAYSCALE: cast color image to grayscale
-     * NTSC_GRAY: convert color image to grayscale using NTSC formula
-     * INVERT: invert colors (every channel separately)
-     * MAP_GRAY_BGR: false color image from grayscale (0: blue, 128: green, 255: red)
-	 *
-	 */
-	public enum ColorOp {GRAYSCALE, NTSC_GRAY, INVERT, MAP_GRAY_BGR};
+    /**
+     * Changes the colors of the current image.
+     * 
+     * Changes the colors of the current image. Operations are instances of
+     * ColorOp:
+     * 
+     * GRAYSCALE: cast color image to grayscale NTSC_GRAY: convert color image
+     * to grayscale using NTSC formula INVERT: invert colors (every channel
+     * separately) MAP_GRAY_BGR: false color image from grayscale (0: blue, 128:
+     * green, 255: red)
+     * 
+     * @throws ImageOpException
+     */
+    public void colorOp(ColorOp op) throws ImageOpException;
 
-	/** Changes the colors of the current image.
-	 * 
-	 * Changes the colors of the current image. Operations are instances of ColorOp:
-	 * 
-	 * GRAYSCALE: cast color image to grayscale
-     * NTSC_GRAY: convert color image to grayscale using NTSC formula
-	 * INVERT: invert colors (every channel separately)
-	 * MAP_GRAY_BGR: false color image from grayscale (0: blue, 128: green, 255: red)
-	 * 
-	 * @throws ImageOpException
-	 */
-	public void colorOp(ColorOp op) throws ImageOpException;
+    /**
+     * Returns the interpolation quality.
+     * 
+     * @return int
+     */
+    public int getQuality();
 
-	/**
-	 * Returns the interpolation quality.
-	 * @return int
-	 */
-	public int getQuality();
+    /**
+     * Sets the interpolation quality.
+     * 
+     * @param quality
+     *            The quality to set
+     */
+    public void setQuality(int quality);
 
-	/**
-	 * Sets the interpolation quality.
-	 * @param quality The quality to set
-	 */
-	public void setQuality(int quality);
-	
-	/** Frees all resources bound to the DocuImage.
-	 * 
-	 * Things that should be freed are image objects and open files.
-	 * 
-	 */
-	public void dispose();
+    /**
+     * Frees all resources bound to the DocuImage.
+     * 
+     * Things that should be freed are image objects and open files.
+     * 
+     */
+    public void dispose();
 
     /**
      * Check image size and type and store in ImageInput ii
@@ -252,12 +287,19 @@
     /**
      * Returns a list of supported image formats
      */
-	public Iterator<String> getSupportedFormats();
-	
-	/**
-	 * returns the underlying image as java.awt.Image (if possible, or null)
-	 * @return
-	 */
-	public java.awt.Image getAwtImage();
+    public Iterator<String> getSupportedFormats();
+
+    /**
+     * returns the underlying image as java.awt.Image (if possible, or null)
+     * 
+     * @return
+     */
+    public java.awt.Image getAwtImage();
+    
+    /**
+     * returns the version of the DocuImage implementation.
+     * @return
+     */
+    public String getVersion();
 
 }
--- a/common/src/main/java/digilib/image/DocuImageImpl.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/common/src/main/java/digilib/image/DocuImageImpl.java	Sat Mar 10 23:09:38 2012 +0100
@@ -16,7 +16,7 @@
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
-*/
+ */
 
 package digilib.image;
 
@@ -28,88 +28,112 @@
 import java.util.LinkedList;
 import java.util.List;
 
-
 import org.apache.log4j.Logger;
 
 import digilib.io.FileOpException;
 import digilib.io.ImageInput;
 import digilib.util.ImageSize;
 
-/** Simple abstract implementation of the <code>DocuImage</code> interface.
- *
+/**
+ * Simple abstract implementation of the <code>DocuImage</code> interface.
+ * 
  * This implementation provides basic functionality for the utility methods like
- * <code>getKnownFileTypes</code>. Image methods like
- * <code>loadImage</code>, <code>writeImage</code>, <code>getWidth</code>,
- * <code>getHeight</code>, <code>crop</code> and <code>scale</code> must be
- * implemented by derived classes.
+ * <code>getKnownFileTypes</code>. Image methods like <code>loadImage</code>,
+ * <code>writeImage</code>, <code>getWidth</code>, <code>getHeight</code>,
+ * <code>crop</code> and <code>scale</code> must be implemented by derived
+ * classes.
  */
 public abstract class DocuImageImpl implements DocuImage {
 
-	/** logger */
-	protected static final Logger logger = Logger.getLogger(DocuImage.class);
-	
-	/** Interpolation quality. */
-	protected int quality = 0;
-	
-	/** epsilon for float comparisons. */
-	public static final double epsilon = 1e-5;
+    /** DocuImage version */
+    public static final String version = "DocuImageImpl 2.1";
+    
+    /** logger */
+    protected static final Logger logger = Logger.getLogger(DocuImage.class);
 
-	/** image size */
+    /** Interpolation quality. */
+    protected int quality = 0;
+
+    /** epsilon for float comparisons. */
+    public static final double epsilon = 1e-5;
+
+    /** image size */
     protected ImageSize imgSize = null;
 
     /** ImageInput that was read */
     protected ImageInput input;
 
-	/**
-	 * Returns the quality.
-	 * @return int
-	 */
-	public int getQuality() {
-		return quality;
-	}
+    /** 
+     * Returns the version.
+     * @return the version
+     */
+    public String getVersion() {
+        return version;
+    }
 
-	/**
-	 * Sets the quality.
-	 * @param quality The quality to set
-	 */
-	public void setQuality(int quality) {
-		this.quality = quality;
-	}
+    /**
+     * Returns the quality.
+     * 
+     * @return int
+     */
+    public int getQuality() {
+        return quality;
+    }
+
+    /**
+     * Sets the quality.
+     * 
+     * @param quality
+     *            The quality to set
+     */
+    public void setQuality(int quality) {
+        this.quality = quality;
+    }
 
-	/** Crop and scale the current image.
-	 *
-	 * The current image is cropped to a rectangle of width, height at position
-	 * x_off, y_off. The resulting image is scaled by the factor scale using the
-	 * interpolation quality qual (0=worst).
-	 * 
-	 * @param x_off X offset of the crop rectangle in pixel.
-	 * @param y_off Y offset of the crop rectangle in pixel.
-	 * @param width Width of the crop rectangle in pixel.
-	 * @param height Height of the crop rectangle in pixel.
-	 * @param scale Scaling factor.
-	 * @param qual Interpolation quality (0=worst).
-	 * @throws ImageOpException Exception thrown on any error.
-	 */
-	public void cropAndScale(
-		int x_off, int y_off, int width, int height, double scale, int qual) 
-		throws ImageOpException {
-		// default implementation: first crop, then scale
-		setQuality(qual);
-		crop(x_off, y_off, width, height);
-		scale(scale, scale);
-	}
-	
-	/* (non-Javadoc)
-	 * @see digilib.image.DocuImage#getMimetype()
-	 */
-	public String getMimetype() {
-	    if (input != null) {
-	        return input.getMimetype();
-	    }
-	    return null;
-	}
+    /**
+     * Crop and scale the current image.
+     * 
+     * The current image is cropped to a rectangle of width, height at position
+     * x_off, y_off. The resulting image is scaled by the factor scale using the
+     * interpolation quality qual (0=worst).
+     * 
+     * @param x_off
+     *            X offset of the crop rectangle in pixel.
+     * @param y_off
+     *            Y offset of the crop rectangle in pixel.
+     * @param width
+     *            Width of the crop rectangle in pixel.
+     * @param height
+     *            Height of the crop rectangle in pixel.
+     * @param scale
+     *            Scaling factor.
+     * @param qual
+     *            Interpolation quality (0=worst).
+     * @throws ImageOpException
+     *             Exception thrown on any error.
+     */
+    public void cropAndScale(int x_off, int y_off, int width, int height, double scale, int qual) throws ImageOpException {
+        // default implementation: first crop, then scale
+        setQuality(qual);
+        crop(x_off, y_off, width, height);
+        scale(scale, scale);
+    }
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.image.DocuImage#getMimetype()
+     */
+    public String getMimetype() {
+        if (input != null) {
+            return input.getMimetype();
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
      * @see digilib.image.DocuImage#identify(digilib.io.ImageFile)
      */
     public ImageInput identify(ImageInput ii) throws IOException {
@@ -117,48 +141,45 @@
         return null;
     }
 
-	public void rotate(double angle) throws ImageOpException {
-		// just a do-nothing implementation
-	}
+    public void rotate(double angle) throws ImageOpException {
+        // just a do-nothing implementation
+    }
 
-	public void mirror(double angle) throws ImageOpException {
-		// just a do-nothing implementation
-	}
+    public void mirror(double angle) throws ImageOpException {
+        // just a do-nothing implementation
+    }
 
-	public void enhance(float mult, float add) throws ImageOpException {
-		// just a do-nothing implementation
-	}
+    public void enhance(float mult, float add) throws ImageOpException {
+        // just a do-nothing implementation
+    }
 
-	public boolean isSubimageSupported() {
-		// partial loading not supported per default
-		return false;
-	}
+    public boolean isSubimageSupported() {
+        // partial loading not supported per default
+        return false;
+    }
 
-	public void loadSubimage(ImageInput ii, Rectangle region, int subsample)
-		throws FileOpException {
-		// empty implementation
-	}
+    public void loadSubimage(ImageInput ii, Rectangle region, int subsample) throws FileOpException {
+        // empty implementation
+    }
 
-	public void enhanceRGB(float[] rgbm, float[] rgba)
-		throws ImageOpException {
-		// emtpy implementation
-	}
+    public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException {
+        // emtpy implementation
+    }
 
-	public void colorOp(ColorOp op) throws ImageOpException {
-		// emtpy implementation
-	}
-
-	public void dispose() {
-		// emtpy implementation
-	}
+    public void colorOp(ColorOp op) throws ImageOpException {
+        // emtpy implementation
+    }
 
-	public Iterator<String> getSupportedFormats() {
-		List<String> empty = new LinkedList<String>();
-		return empty.iterator();
-	}
+    public void dispose() {
+        // emtpy implementation
+    }
 
-    public void crop(int xoff, int yoff, int width, int height)
-            throws ImageOpException {
+    public Iterator<String> getSupportedFormats() {
+        List<String> empty = new LinkedList<String>();
+        return empty.iterator();
+    }
+
+    public void crop(int xoff, int yoff, int width, int height) throws ImageOpException {
         // emtpy implementation
     }
 
@@ -191,8 +212,6 @@
 
     public abstract void scale(double scaleX, double scaleY) throws ImageOpException;
 
-    public abstract void writeImage(String mt, OutputStream ostream)
-            throws ImageOpException, FileOpException;
+    public abstract void writeImage(String mt, OutputStream ostream) throws ImageOpException, FileOpException;
 
-	
 }
--- a/common/src/main/java/digilib/image/ImageLoaderDocuImage.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/common/src/main/java/digilib/image/ImageLoaderDocuImage.java	Sat Mar 10 23:09:38 2012 +0100
@@ -59,117 +59,125 @@
 import digilib.io.ImageInput;
 import digilib.util.ImageSize;
 
-/** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
+/**
+ * Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D.
+ */
 public class ImageLoaderDocuImage extends ImageInfoDocuImage {
+
+    /** DocuImage version */
+    public static final String version = "ImageLoaderDocuImage 2.1.2";
     
-	/** image object */
-	protected BufferedImage img;
-	
-	/** the reader object */
-	protected ImageReader reader = null;
-	
+    /** image object */
+    protected BufferedImage img;
+
+    /** the reader object */
+    protected ImageReader reader = null;
+
     /** try to reuse reader object */
     public boolean reuseReader = false;
-    
-	/** interpolation type */
-	protected RenderingHints renderHint = null;
+
+    /** interpolation type */
+    protected RenderingHints renderHint = null;
 
-	/** convolution kernels for blur() */
-	protected static Kernel[] convolutionKernels = {
-	        null,
-	        new Kernel(1, 1, new float[] {1f}),
-            new Kernel(2, 2, new float[] {0.25f, 0.25f, 0.25f, 0.25f}),
-            new Kernel(3, 3, new float[] {1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f, 1f/9f})
-	};
+    /** convolution kernels for blur() */
+    protected static Kernel[] convolutionKernels = { 
+            null, new Kernel(1, 1, new float[] { 1f }),
+            new Kernel(2, 2, new float[] { 0.25f, 0.25f, 0.25f, 0.25f }),
+            new Kernel(3, 3, new float[] { 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f, 1f / 9f }) };
 
-	/* lookup tables for inverting images (byte) */
-	protected static LookupTable invertSingleByteTable;
+    /* lookup tables for inverting images (byte) */
+    protected static LookupTable invertSingleByteTable;
     protected static LookupTable invertRgbaByteTable;
     protected static boolean needsInvertRgba = false;
-	/* RescaleOp for contrast/brightness operation */
+    /* RescaleOp for contrast/brightness operation */
     protected static boolean needsRescaleRgba = false;
     /* lookup table for false-color */
     protected static LookupTable mapBgrByteTable;
     protected static boolean needsMapBgr = false;
-    
-	static {
-	    /*
-	     * create static lookup tables
-	     */
-		byte[] invertByte = new byte[256];
-		byte[] orderedByte = new byte[256];
-		byte[] nullByte = new byte[256];
+
+    static {
+        /*
+         * create static lookup tables
+         */
+        byte[] invertByte = new byte[256];
+        byte[] orderedByte = new byte[256];
+        byte[] nullByte = new byte[256];
         byte[] mapR = new byte[256];
         byte[] mapG = new byte[256];
         byte[] mapB = new byte[256];
-		for (int i = 0; i < 256; ++i) {
-		    // counting down
-			invertByte[i] = (byte) (256 - i);
-			// counting up
-			orderedByte[i] = (byte) i;
-			// constant 0
-			nullByte[i] = 0;
-			// three overlapping slopes
-			if (i < 64) {
-			    mapR[i] = 0;
-			    mapG[i] = (byte) (4 * i);
-			    mapB[i] = (byte) 255;
-			} else if (i >= 64 && i < 192) {
+        for (int i = 0; i < 256; ++i) {
+            // counting down
+            invertByte[i] = (byte) (255 - i);
+            // counting up
+            orderedByte[i] = (byte) i;
+            // constant 0
+            nullByte[i] = 0;
+            // three overlapping slopes
+            if (i < 64) {
+                mapR[i] = 0;
+                mapG[i] = (byte) (4 * i);
+                mapB[i] = (byte) 255;
+            } else if (i >= 64 && i < 192) {
                 mapR[i] = (byte) (2 * (i - 64));
                 mapG[i] = (byte) 255;
                 mapB[i] = (byte) (255 - 2 * (i - 64));
-			} else {
+            } else {
                 mapR[i] = (byte) 255;
                 mapG[i] = (byte) (255 - (4 * (i - 192)));
                 mapB[i] = 0;
-			}
-		}
-		// should(!) work for all color models
-		invertSingleByteTable = new ByteLookupTable(0, invertByte);
-		// but doesn't work with alpha channel on all platforms
-		String ver = System.getProperty("java.version");
-		String os =  System.getProperty("os.name");
-		logger.debug("os="+os+" ver="+ver);
-		if (os.startsWith("Linux") && ver.startsWith("1.6")) {
-			// GRAB(WTF?) works in Linux JDK1.6 with transparency
-			invertRgbaByteTable = new ByteLookupTable(0, new byte[][] {
-					invertByte, invertByte, orderedByte, invertByte});
-			needsInvertRgba = true;
-			needsRescaleRgba = true;
-			needsMapBgr = true;
-		} else {
-			invertRgbaByteTable = invertSingleByteTable;
-		}
-		// this hopefully works for all
-        mapBgrByteTable = new ByteLookupTable(0, new byte[][] {
-                mapR, mapG, mapB});
-	}
-	
-	/** the size of the current image */
+            }
+        }
+        // should(!) work for all color models
+        invertSingleByteTable = new ByteLookupTable(0, invertByte);
+        // but doesn't work with alpha channel on all platforms
+        String ver = System.getProperty("java.version");
+        String os = System.getProperty("os.name");
+        String osver = System.getProperty("os.version");
+        logger.debug("os="+os+" ver="+osver+" java_version="+ver);
+        if ((os.startsWith("Linux")) || (os.startsWith("Mac OS X") && osver.startsWith("10.7"))) {
+            // GRAB(WTF?) works for Linux JDK1.6 with transparency
+            needsInvertRgba = true;
+            invertRgbaByteTable = new ByteLookupTable(0, new byte[][] { invertByte, invertByte, orderedByte, invertByte });
+            needsRescaleRgba = true;
+            needsMapBgr = true;
+        } else {
+            invertRgbaByteTable = invertSingleByteTable;
+        }
+        // this hopefully works for all
+        mapBgrByteTable = new ByteLookupTable(0, new byte[][] { mapR, mapG, mapB });
+        logger.debug("ImageIO Hacks: needsRescaleRgba="+needsRescaleRgba+" needsInvertRgba="+needsInvertRgba+
+                " needsMapBgr="+needsMapBgr);
+    }
+
+    /** the size of the current image */
     protected ImageSize imageSize;
-	
-	
-	/* loadSubimage is supported. */
-	public boolean isSubimageSupported() {
-		return true;
-	}
+
+    /**
+     * @return the version
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /* loadSubimage is supported. */
+    public boolean isSubimageSupported() {
+        return true;
+    }
 
-	public void setQuality(int qual) {
-		quality = qual;
-		renderHint = new RenderingHints(null);
-		// hint.put(RenderingHints.KEY_ANTIALIASING,
-		// RenderingHints.VALUE_ANTIALIAS_OFF);
-		// setup interpolation quality
-		if (qual > 0) {
-			logger.debug("quality q1");
-			renderHint.put(RenderingHints.KEY_INTERPOLATION,
-					RenderingHints.VALUE_INTERPOLATION_BICUBIC);
-		} else {
-			logger.debug("quality q0");
-			renderHint.put(RenderingHints.KEY_INTERPOLATION,
-					RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
-		}
-	}
+    public void setQuality(int qual) {
+        quality = qual;
+        renderHint = new RenderingHints(null);
+        // hint.put(RenderingHints.KEY_ANTIALIASING,
+        // RenderingHints.VALUE_ANTIALIAS_OFF);
+        // setup interpolation quality
+        if (qual > 0) {
+            logger.debug("quality q1");
+            renderHint.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+        } else {
+            logger.debug("quality q0");
+            renderHint.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+        }
+    }
 
     /* returns the size of the current image */
     public ImageSize getSize() {
@@ -195,11 +203,11 @@
         return imageSize;
     }
 
-	/* returns a list of supported image formats */
-	public Iterator<String> getSupportedFormats() {
-		String[] formats = ImageIO.getReaderFormatNames();
-		return Arrays.asList(formats).iterator();
-	}
+    /* returns a list of supported image formats */
+    public Iterator<String> getSupportedFormats() {
+        String[] formats = ImageIO.getReaderFormatNames();
+        return Arrays.asList(formats).iterator();
+    }
 
     /* Check image size and type and store in ImageInput */
     public ImageInput identify(ImageInput input) throws IOException {
@@ -242,183 +250,182 @@
             }
         }
     }
-    
+
     /* load image file */
-	public void loadImage(ImageInput ii) throws FileOpException {
-		logger.debug("loadImage: " + ii);
-		this.input = ii;
-		try {
-		    if (ii.hasImageInputStream()) {
+    public void loadImage(ImageInput ii) throws FileOpException {
+        logger.debug("loadImage: " + ii);
+        this.input = ii;
+        try {
+            if (ii.hasImageInputStream()) {
                 img = ImageIO.read(ii.getImageInputStream());
-		    } else if (ii.hasFile()) {
-		        img = ImageIO.read(ii.getFile());
-		    }
-		} catch (IOException e) {
-			throw new FileOpException("Error reading image!", e);
-		}
-		if (img == null) {
-		    throw new FileOpException("Unable to read image!");
-		}
-	}
+            } else if (ii.hasFile()) {
+                img = ImageIO.read(ii.getFile());
+            }
+        } catch (IOException e) {
+            throw new FileOpException("Error reading image!", e);
+        }
+        if (img == null) {
+            throw new FileOpException("Unable to read image!");
+        }
+    }
 
-	/**
-	 * Get an ImageReader for the image file.
-	 * 
-	 * @return
-	 */
-	public ImageReader getReader(ImageInput input) throws IOException {
-		logger.debug("get ImageReader for " + input);
-		if (reuseReader && reader != null) {
-		    logger.debug("reuseing ImageReader");
-		    return reader;
-		}
-		ImageInputStream istream = null;
-		if (input.hasImageInputStream()) {
-			// ImageInputStream input
-			istream = input.getImageInputStream();
-		} else if (input.hasFile()) {
-			// file only input
-			RandomAccessFile rf = new RandomAccessFile(input.getFile(), "r");
-			istream = new FileImageInputStream(rf);
-		} else {
-			throw new FileOpException("Unable to get data from ImageInput");
-		}
-		Iterator<ImageReader> readers;
-		String mt = null;
-		if (input.hasMimetype()) {
-	        // check hasMimetype first or we might get into a loop
-		    mt = input.getMimetype();
-		} else {
-		    // try file extension
+    /**
+     * Get an ImageReader for the image file.
+     * 
+     * @return
+     */
+    public ImageReader getReader(ImageInput input) throws IOException {
+        logger.debug("get ImageReader for " + input);
+        if (reuseReader && reader != null) {
+            logger.debug("reuseing ImageReader");
+            return reader;
+        }
+        ImageInputStream istream = null;
+        if (input.hasImageInputStream()) {
+            // ImageInputStream input
+            istream = input.getImageInputStream();
+        } else if (input.hasFile()) {
+            // file only input
+            RandomAccessFile rf = new RandomAccessFile(input.getFile(), "r");
+            istream = new FileImageInputStream(rf);
+        } else {
+            throw new FileOpException("Unable to get data from ImageInput");
+        }
+        Iterator<ImageReader> readers;
+        String mt = null;
+        if (input.hasMimetype()) {
+            // check hasMimetype first or we might get into a loop
+            mt = input.getMimetype();
+        } else {
+            // try file extension
             mt = FileOps.mimeForFile(input.getFile());
-		}
-		if (mt == null) {
-			logger.debug("No mime-type. Trying automagic.");
-			readers = ImageIO.getImageReaders(istream);
-		} else {
-			logger.debug("File type:" + mt);
-			readers = ImageIO.getImageReadersByMIMEType(mt);
-		}
-		if (!readers.hasNext()) {
-			throw new FileOpException("Can't find Reader to load File!");
-		}
-		ImageReader reader = readers.next();
-		/* are there more readers? */
-		logger.debug("ImageIO: this reader: " + reader.getClass());
-		/* while (readers.hasNext()) {
-			logger.debug("ImageIO: next reader: " + readers.next().getClass());
-		} */
-		reader.setInput(istream);
-		return reader;
-	}
+        }
+        if (mt == null) {
+            logger.debug("No mime-type. Trying automagic.");
+            readers = ImageIO.getImageReaders(istream);
+        } else {
+            logger.debug("File type:" + mt);
+            readers = ImageIO.getImageReadersByMIMEType(mt);
+        }
+        if (!readers.hasNext()) {
+            throw new FileOpException("Can't find Reader to load File!");
+        }
+        ImageReader reader = readers.next();
+        /* are there more readers? */
+        logger.debug("ImageIO: this reader: " + reader.getClass());
+        /*
+         * while (readers.hasNext()) { logger.debug("ImageIO: next reader: " +
+         * readers.next().getClass()); }
+         */
+        reader.setInput(istream);
+        return reader;
+    }
 
     /* Load an image file into the Object. */
-    public void loadSubimage(ImageInput ii, Rectangle region, int prescale)
-			throws FileOpException {
-		logger.debug("loadSubimage");
+    public void loadSubimage(ImageInput ii, Rectangle region, int prescale) throws FileOpException {
+        logger.debug("loadSubimage");
         this.input = ii;
-        //ImageReader reader = null;
-		try {
-		    reader = getReader(ii);
-			// set up reader parameters
-			ImageReadParam readParam = reader.getDefaultReadParam();
-			readParam.setSourceRegion(region);
-			if (prescale > 1) {
-				readParam.setSourceSubsampling(prescale, prescale, 0, 0);
-			}
-			// try to restrict target color space to sRGB
-			for (Iterator<ImageTypeSpecifier> i = reader.getImageTypes(0); i.hasNext(); ) {
-			    ImageTypeSpecifier type = (ImageTypeSpecifier) i.next();
-			    ColorModel cm = type.getColorModel();
-			    ColorSpace cs = cm.getColorSpace();
-                //logger.debug("loadSubimage: possible color model:"+cm+" color space:"+cs);
-			    if (cs.isCS_sRGB()) {
-	                logger.debug("loadSubimage: substituted sRGB destination type "+type);
-			        readParam.setDestinationType(type);
-			        //break;
-			    }
-			}
-			// read image
-			logger.debug("loadSubimage: loading..");
-			img = reader.read(0, readParam);
-			logger.debug("loadSubimage: loaded");
-			// invalidate image size
-			imageSize = null;
-			// downconvert highcolor images
-	        if (img.getColorModel().getComponentSize(0) > 8) {
-	            logger.debug("loadSubimage: converting to 8bit");
-	            int type = BufferedImage.TYPE_INT_RGB;
-	            if (img.getColorModel().hasAlpha()) {
-	                type = BufferedImage.TYPE_INT_ARGB;
-	            }
-	            BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(), type);
-	            dest.createGraphics().drawImage(img, null, 0, 0);
-	            img = dest;
-	        }
-		} catch (IOException e) {
-			throw new FileOpException("Unable to load File!", e);
-		} finally {
-		    if (!reuseReader && reader != null) {
-		        reader.dispose();
-		    }
-		}
-	}
+        // ImageReader reader = null;
+        try {
+            reader = getReader(ii);
+            // set up reader parameters
+            ImageReadParam readParam = reader.getDefaultReadParam();
+            readParam.setSourceRegion(region);
+            if (prescale > 1) {
+                readParam.setSourceSubsampling(prescale, prescale, 0, 0);
+            }
+            // try to restrict target color space to sRGB
+            for (Iterator<ImageTypeSpecifier> i = reader.getImageTypes(0); i.hasNext();) {
+                ImageTypeSpecifier type = (ImageTypeSpecifier) i.next();
+                ColorModel cm = type.getColorModel();
+                ColorSpace cs = cm.getColorSpace();
+                // logger.debug("loadSubimage: possible color model:"+cm+" color space:"+cs);
+                if (cs.isCS_sRGB()) {
+                    logger.debug("loadSubimage: substituted sRGB destination type " + type);
+                    readParam.setDestinationType(type);
+                    // break;
+                }
+            }
+            // read image
+            logger.debug("loadSubimage: loading..");
+            img = reader.read(0, readParam);
+            logger.debug("loadSubimage: loaded");
+            // invalidate image size
+            imageSize = null;
+            // downconvert highcolor images
+            if (img.getColorModel().getComponentSize(0) > 8) {
+                logger.debug("loadSubimage: converting to 8bit");
+                int type = BufferedImage.TYPE_INT_RGB;
+                if (img.getColorModel().hasAlpha()) {
+                    type = BufferedImage.TYPE_INT_ARGB;
+                }
+                BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(), type);
+                dest.createGraphics().drawImage(img, null, 0, 0);
+                img = dest;
+            }
+        } catch (IOException e) {
+            throw new FileOpException("Unable to load File!", e);
+        } finally {
+            if (!reuseReader && reader != null) {
+                reader.dispose();
+            }
+        }
+    }
 
-	/* write image of type mt to Stream */
-	public void writeImage(String mt, OutputStream ostream)
-			throws ImageOpException, FileOpException {
-		logger.debug("writeImage");
-		// setup output
-		ImageWriter writer = null;
-		ImageOutputStream imgout = null;
-		try {
-			imgout = ImageIO.createImageOutputStream(ostream);
-			if (mt == "image/jpeg") {
-				/*
-				 * JPEG doesn't do transparency so we have to convert any RGBA
-				 * image to RGB or we the client will think its CMYK :-( *Java2D BUG*
-				 */
-				if (img.getColorModel().hasAlpha()) {
-					logger.debug("BARF: JPEG with transparency!!");
-                    BufferedImage rgbImg = new BufferedImage(img.getWidth(),
-                            img.getHeight(), BufferedImage.TYPE_INT_RGB);
-					rgbImg.createGraphics().drawImage(img, null, 0, 0);
-					img = rgbImg;
-				}
-				writer = ImageIO.getImageWritersByFormatName("jpeg").next();
-				if (writer == null) {
-					throw new ImageOpException("Unable to get JPEG writer");
-				}
-				ImageWriteParam param = writer.getDefaultWriteParam();
-				if (quality > 1) {
-					// change JPEG compression quality
-					param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
-					param.setCompressionQuality(0.9f);
-				}
-				writer.setOutput(imgout);
-				// render output
-				logger.debug("writing JPEG");
-				writer.write(null, new IIOImage(img, null, null), param);
-			} else if (mt == "image/png") {
-				// render output
-				writer = ImageIO.getImageWritersByFormatName("png").next();
-				if (writer == null) {
-					throw new ImageOpException("Unable to get PNG writer");
-				}
-				writer.setOutput(imgout);
-				logger.debug("writing PNG");
-				writer.write(img);
-			} else {
-				// unknown mime type
-				throw new ImageOpException("Unknown mime type: " + mt);
-			}
+    /* write image of type mt to Stream */
+    public void writeImage(String mt, OutputStream ostream) throws ImageOpException, FileOpException {
+        logger.debug("writeImage");
+        // setup output
+        ImageWriter writer = null;
+        ImageOutputStream imgout = null;
+        try {
+            imgout = ImageIO.createImageOutputStream(ostream);
+            if (mt == "image/jpeg") {
+                /*
+                 * JPEG doesn't do transparency so we have to convert any RGBA
+                 * image to RGB or we the client will think its CMYK :-( *Java2D
+                 * BUG*
+                 */
+                if (img.getColorModel().hasAlpha()) {
+                    logger.debug("BARF: JPEG with transparency!!");
+                    BufferedImage rgbImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
+                    rgbImg.createGraphics().drawImage(img, null, 0, 0);
+                    img = rgbImg;
+                }
+                writer = ImageIO.getImageWritersByFormatName("jpeg").next();
+                if (writer == null) {
+                    throw new ImageOpException("Unable to get JPEG writer");
+                }
+                ImageWriteParam param = writer.getDefaultWriteParam();
+                if (quality > 1) {
+                    // change JPEG compression quality
+                    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+                    param.setCompressionQuality(0.9f);
+                }
+                writer.setOutput(imgout);
+                // render output
+                logger.debug("writing JPEG");
+                writer.write(null, new IIOImage(img, null, null), param);
+            } else if (mt == "image/png") {
+                // render output
+                writer = ImageIO.getImageWritersByFormatName("png").next();
+                if (writer == null) {
+                    throw new ImageOpException("Unable to get PNG writer");
+                }
+                writer.setOutput(imgout);
+                logger.debug("writing PNG");
+                writer.write(img);
+            } else {
+                // unknown mime type
+                throw new ImageOpException("Unknown mime type: " + mt);
+            }
 
-		} catch (IOException e) {
-		    logger.error("Error writing image:", e);
-			throw new FileOpException("Error writing image!", e);
-		}
-		// TODO: should we: finally { writer.dispose(); }
-	}
+        } catch (IOException e) {
+            logger.error("Error writing image:", e);
+            throw new FileOpException("Error writing image!", e);
+        }
+        // TODO: should we: finally { writer.dispose(); }
+    }
 
     public void scale(double scaleX, double scaleY) throws ImageOpException {
         logger.debug("scale: " + scaleX);
@@ -428,24 +435,22 @@
             blur(bl);
         }
         /* then scaled */
-        AffineTransformOp scaleOp = new AffineTransformOp(
-                AffineTransform.getScaleInstance(scaleX, scaleY), renderHint);
+        AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform.getScaleInstance(scaleX, scaleY), renderHint);
         img = scaleOp.filter(img, null);
-        logger.debug("scaled to " + img.getWidth() + "x" + img.getHeight()
-                + " img=" + img);
+        logger.debug("scaled to " + img.getWidth() + "x" + img.getHeight() + " img=" + img);
         // invalidate image size
         imageSize = null;
     }
 
-	public void blur(int radius) throws ImageOpException {
-		logger.debug("blur: " + radius);
-		// minimum radius is 2
-		int klen = Math.max(radius, 2);
-		Kernel blur = null;
-		if (klen < convolutionKernels.length) {
-		    // use precalculated Kernel
+    public void blur(int radius) throws ImageOpException {
+        logger.debug("blur: " + radius);
+        // minimum radius is 2
+        int klen = Math.max(radius, 2);
+        Kernel blur = null;
+        if (klen < convolutionKernels.length) {
+            // use precalculated Kernel
             blur = convolutionKernels[klen];
-		} else {
+        } else {
             // calculate our own kernel
             int ksize = klen * klen;
             // kernel is constant 1/k
@@ -455,90 +460,86 @@
                 kern[i] = f;
             }
             blur = new Kernel(klen, klen, kern);
-		}
-		// blur with convolve operation
-		ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP,
-				renderHint);
-		BufferedImage dest = null;
+        }
+        // blur with convolve operation
+        ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP, renderHint);
+        BufferedImage dest = null;
         // blur needs explicit destination image type for 3BYTE_BGR *Java2D BUG*
-		if (img.getType() == BufferedImage.TYPE_3BYTE_BGR) {
-		    logger.debug("blur: fixing destination image type");
-		    dest = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
-		}
-		img = blurOp.filter(img, dest);
-		logger.debug("blurred: "+img);
-	}
+        if (img.getType() == BufferedImage.TYPE_3BYTE_BGR) {
+            logger.debug("blur: fixing destination image type");
+            dest = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+        }
+        img = blurOp.filter(img, dest);
+        logger.debug("blurred: " + img);
+    }
 
-	public void crop(int x_off, int y_off, int width, int height)
-			throws ImageOpException {
-		// setup Crop
-		img = img.getSubimage(x_off, y_off, width, height);
-		logger.debug("CROP:" + img.getWidth() + "x"
-				+ img.getHeight());
+    public void crop(int x_off, int y_off, int width, int height) throws ImageOpException {
+        // setup Crop
+        img = img.getSubimage(x_off, y_off, width, height);
+        logger.debug("CROP:" + img.getWidth() + "x" + img.getHeight());
         // invalidate image size
         imageSize = null;
-	}
+    }
 
-	public void rotate(double angle) throws ImageOpException {
+    public void rotate(double angle) throws ImageOpException {
         logger.debug("rotate: " + angle);
-		// setup rotation
-		double rangle = Math.toRadians(angle);
-		// center of rotation is center of image
+        // setup rotation
+        double rangle = Math.toRadians(angle);
+        // center of rotation is center of image
         double w = img.getWidth();
         double h = img.getHeight();
-		double x = (w / 2);
-		double y = (h / 2);
+        double x = (w / 2);
+        double y = (h / 2);
         AffineTransform trafo = AffineTransform.getRotateInstance(rangle, x, y);
-		AffineTransformOp rotOp = new AffineTransformOp(trafo, renderHint);
+        AffineTransformOp rotOp = new AffineTransformOp(trafo, renderHint);
         // rotate bounds to see how much of the image would be off screen
-		Rectangle2D rotbounds = rotOp.getBounds2D(img);
-		double xoff = rotbounds.getX();
-		double yoff = rotbounds.getY();
-		if (Math.abs(xoff) > epsilon || Math.abs(yoff) > epsilon) {
-		    // move image back on screen
-		    logger.debug("move rotation: xoff="+xoff+" yoff="+yoff);
-		    trafo.preConcatenate(AffineTransform.getTranslateInstance(-xoff, -yoff));
-	        rotOp = new AffineTransformOp(trafo, renderHint);
-		}
-		// transform image
-		img = rotOp.filter(img, null);
-		logger.debug("rotated: "+img);
+        Rectangle2D rotbounds = rotOp.getBounds2D(img);
+        double xoff = rotbounds.getX();
+        double yoff = rotbounds.getY();
+        if (Math.abs(xoff) > epsilon || Math.abs(yoff) > epsilon) {
+            // move image back on screen
+            logger.debug("move rotation: xoff=" + xoff + " yoff=" + yoff);
+            trafo.preConcatenate(AffineTransform.getTranslateInstance(-xoff, -yoff));
+            rotOp = new AffineTransformOp(trafo, renderHint);
+        }
+        // transform image
+        img = rotOp.filter(img, null);
+        logger.debug("rotated: " + img);
         // invalidate image size
         imageSize = null;
-	}
+    }
 
-	public void mirror(double angle) throws ImageOpException {
+    public void mirror(double angle) throws ImageOpException {
         logger.debug("mirror: " + angle);
-		// setup mirror
-		double mx = 1;
-		double my = 1;
-		double tx = 0;
-		double ty = 0;
-		if (Math.abs(angle - 0) < epsilon) { // 0 degree
-			mx = -1;
-			tx = img.getWidth();
-		} else if (Math.abs(angle - 90) < epsilon) { // 90 degree
-			my = -1;
-			ty = img.getHeight();
-		} else if (Math.abs(angle - 180) < epsilon) { // 180 degree
-			mx = -1;
-			tx = img.getWidth();
-		} else if (Math.abs(angle - 270) < epsilon) { // 270 degree
-			my = -1;
-			ty = img.getHeight();
-		} else if (Math.abs(angle - 360) < epsilon) { // 360 degree
-			mx = -1;
-			tx = img.getWidth();
-		} else {
-		    logger.error("invalid mirror angle "+angle);
-		    return;
-		}
-		AffineTransformOp mirOp = new AffineTransformOp(new AffineTransform(mx,
-				0, 0, my, tx, ty), renderHint);
-		img = mirOp.filter(img, null);
+        // setup mirror
+        double mx = 1;
+        double my = 1;
+        double tx = 0;
+        double ty = 0;
+        if (Math.abs(angle - 0) < epsilon) { // 0 degree
+            mx = -1;
+            tx = img.getWidth();
+        } else if (Math.abs(angle - 90) < epsilon) { // 90 degree
+            my = -1;
+            ty = img.getHeight();
+        } else if (Math.abs(angle - 180) < epsilon) { // 180 degree
+            mx = -1;
+            tx = img.getWidth();
+        } else if (Math.abs(angle - 270) < epsilon) { // 270 degree
+            my = -1;
+            ty = img.getHeight();
+        } else if (Math.abs(angle - 360) < epsilon) { // 360 degree
+            mx = -1;
+            tx = img.getWidth();
+        } else {
+            logger.error("invalid mirror angle " + angle);
+            return;
+        }
+        AffineTransformOp mirOp = new AffineTransformOp(new AffineTransform(mx, 0, 0, my, tx, ty), renderHint);
+        img = mirOp.filter(img, null);
         // invalidate image size
         imageSize = null;
-	}
+    }
 
     public void enhance(float mult, float add) throws ImageOpException {
         RescaleOp op = null;
@@ -567,21 +568,20 @@
     }
 
     public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException {
-        logger.debug("enhanceRGB: rgbm="+rgbm+" rgba="+rgba);
+        logger.debug("enhanceRGB: rgbm=" + rgbm + " rgba=" + rgba);
         /*
          * The number of constants must match the number of bands in the image.
          * We do only 3 (RGB) bands.
          */
         int ncol = img.getColorModel().getNumColorComponents();
         if ((ncol != 3) || (rgbm.length != 3) || (rgba.length != 3)) {
-            logger.error("enhanceRGB: unknown number of color bands or coefficients ("
-                            + ncol + ")");
+            logger.error("enhanceRGB: unknown number of color bands or coefficients (" + ncol + ")");
             return;
         }
         if (img.getColorModel().hasAlpha()) {
             // add constant for alpha
-            rgbm = new float[] {rgbm[0], rgbm[1], rgbm[2], 1};
-            rgba = new float[] {rgba[0], rgba[1], rgba[2], 0};
+            rgbm = new float[] { rgbm[0], rgbm[1], rgbm[2], 1 };
+            rgba = new float[] { rgba[0], rgba[1], rgba[2], 0 };
         }
         RescaleOp scaleOp = new RescaleOp(rgbm, rgba, renderHint);
         scaleOp.filter(img, img);
@@ -605,8 +605,7 @@
                 logger.debug("Color op: not grayscaling");
                 return;
             }
-            ColorConvertOp op = new ColorConvertOp(
-                    ColorSpace.getInstance(ColorSpace.CS_GRAY), renderHint);
+            ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), renderHint);
             // let filter create new image
             img = op.filter(img, null);
         } else if (colop == ColorOp.NTSC_GRAY) {
@@ -615,7 +614,7 @@
              * 0.5870*green + 0.1140*blue
              */
             logger.debug("Color op: NTSC gray");
-            logger.debug("img="+img);
+            logger.debug("img=" + img);
             ColorModel cm = img.getColorModel();
             if (cm.getNumColorComponents() < 3 || cm instanceof IndexColorModel) {
                 // grayscale already or not possible
@@ -627,8 +626,7 @@
             BandCombineOp op = new BandCombineOp(combineFn, renderHint);
             // BandCombineOp only works on Rasters so we create a
             // new image and use its Raster
-            BufferedImage dest = new BufferedImage(img.getWidth(),
-                    img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
+            BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
             op.filter(img.getRaster(), dest.getRaster());
             img = dest;
         } else if (colop == ColorOp.INVERT) {
@@ -659,16 +657,18 @@
              */
             logger.debug("Color op: map_gray_bgr");
             // convert to grayscale
-            ColorConvertOp grayOp = new ColorConvertOp(
-                    ColorSpace.getInstance(ColorSpace.CS_GRAY), renderHint);
+            ColorConvertOp grayOp = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), renderHint);
             // create new 3-channel image
             int destType = BufferedImage.TYPE_INT_RGB;
             if (needsMapBgr) {
-                // special case for broken Java2Ds
-                destType = BufferedImage.TYPE_3BYTE_BGR;
+                // special case for funny Java2D implementations
+                if (img.getColorModel().hasAlpha()) {
+                    destType = BufferedImage.TYPE_4BYTE_ABGR_PRE;
+                } else {
+                    destType = BufferedImage.TYPE_3BYTE_BGR;
+                }
             }
-            BufferedImage dest = new BufferedImage(img.getWidth(),
-                    img.getHeight(), destType);
+            BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(), destType);
             img = grayOp.filter(img, dest);
             logger.debug("map_gray: image=" + img);
             // convert to false color
@@ -678,17 +678,16 @@
         }
     }
 
-	public void dispose() {
-	    if (reader != null) {
-	        reader.dispose();
-	    }
-	    // is this necessary?
-		img = null;
-	}
+    public void dispose() {
+        if (reader != null) {
+            reader.dispose();
+        }
+        // is this necessary?
+        img = null;
+    }
 
-	public Image getAwtImage(){
-		return (Image) img;
-	}
-	
-	
+    public Image getAwtImage() {
+        return (Image) img;
+    }
+
 }
--- a/servlet2/src/main/java/digilib/servlet/DigilibServletConfiguration.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/servlet2/src/main/java/digilib/servlet/DigilibServletConfiguration.java	Sat Mar 10 23:09:38 2012 +0100
@@ -72,6 +72,9 @@
 			digilib.image.ImageLoaderDocuImage.class,
 			null,
 			's');
+	        // DocuImage version
+	        newParameter("servlet.docuimage.version",
+	                "?", null, 's');
 		// AuthOps instance for authentication
 		newParameter("servlet.auth.op", null, null, 's');
         // Executor for image operations
@@ -235,6 +238,7 @@
         // initialise static DocuImage class instance
         DigilibServletConfiguration.docuImageClass = (Class<DocuImageImpl>) Class
                 .forName(getAsString("docuimage-class"));
+        setValue("servlet.docuimage.version", getDocuImageInstance().getVersion());
     }
 
 }
--- a/servlet2/src/main/java/digilib/servlet/Scaler.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/servlet2/src/main/java/digilib/servlet/Scaler.java	Sat Mar 10 23:09:38 2012 +0100
@@ -34,7 +34,7 @@
     private static final long serialVersionUID = -5439198888139362735L;
 
     /** digilib servlet version (for all components) */
-    public static final String version = "2.1b1 noasync";
+    public static final String version = "2.1b2 noasync";
 
     /** servlet error codes */
     public static enum Error {UNKNOWN, AUTH, FILE, IMAGE};
@@ -104,6 +104,8 @@
             // no Configuration
             throw new ServletException("No Configuration!");
         }
+        // log DocuImage version
+        logger.info("Scaler uses " + dlConfig.getValue("servlet.docuimage.version"));
         // set our AuthOps
         useAuthorization = dlConfig.getAsBoolean("use-authorization");
         authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
--- a/servlet3/src/main/java/digilib/servlet/DigilibServletConfiguration.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/servlet3/src/main/java/digilib/servlet/DigilibServletConfiguration.java	Sat Mar 10 23:09:38 2012 +0100
@@ -79,6 +79,9 @@
         // DocuImage class instance
         newParameter("servlet.docuimage.class",
                 digilib.image.ImageLoaderDocuImage.class, null, 's');
+        // DocuImage version
+        newParameter("servlet.docuimage.version",
+                "?", null, 's');
         // AuthOps instance for authentication
         newParameter("servlet.auth.op", null, null, 's');
         // Executor for image operations
@@ -226,13 +229,13 @@
                 }
             } else {
                 // parameter unknown -- just add
-                newParameter(confEntry.getKey(), null, confEntry.getValue(),
-                        'f');
+                newParameter(confEntry.getKey(), null, confEntry.getValue(), 'f');
             }
         }
         // initialise static DocuImage class instance
         DigilibServletConfiguration.docuImageClass = (Class<DocuImageImpl>) Class
                 .forName(getAsString("docuimage-class"));
+        setValue("servlet.docuimage.version", getDocuImageInstance().getVersion());
     }
 
 }
--- a/servlet3/src/main/java/digilib/servlet/Scaler.java	Fri Mar 09 20:47:06 2012 +0100
+++ b/servlet3/src/main/java/digilib/servlet/Scaler.java	Sat Mar 10 23:09:38 2012 +0100
@@ -31,7 +31,7 @@
     private static final long serialVersionUID = 5289386646192471549L;
 
     /** digilib servlet version (for all components) */
-    public static final String version = "2.1b1 async";
+    public static final String version = "2.1b2 async";
 
     /** servlet error codes */
     public static enum Error {
@@ -108,6 +108,8 @@
             // no Configuration
             throw new ServletException("No Configuration!");
         }
+        // log DocuImage version
+        logger.info("Scaler uses " + dlConfig.getValue("servlet.docuimage.version"));
         // set our AuthOps
         useAuthorization = dlConfig.getAsBoolean("use-authorization");
         authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");