changeset 79:63c8186455c1

Servlet version 1.6b. Further cleanup and new functionality: * mirroring * contrast/brightness * rotation (not finished)
author robcast
date Mon, 03 Feb 2003 16:04:53 +0100
parents e0dcac9c66fa
children 4d0e75042673
files servlet/src/digilib/image/DocuImage.java servlet/src/digilib/image/DocuImageImpl.java servlet/src/digilib/image/ImageLoaderDocuImage.java servlet/src/digilib/image/JAIDocuImage.java servlet/src/digilib/image/JAIImageLoaderDocuImage.java servlet/src/digilib/image/JIMIDocuImage.java servlet/src/digilib/servlet/DigilibRequest.java servlet/src/digilib/servlet/DocumentBean.java servlet/src/digilib/servlet/Scaler.java
diffstat 9 files changed, 935 insertions(+), 590 deletions(-) [+]
line wrap: on
line diff
--- a/servlet/src/digilib/image/DocuImage.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/image/DocuImage.java	Mon Feb 03 16:04:53 2003 +0100
@@ -21,12 +21,10 @@
 package digilib.image;
 
 import java.io.File;
-
-import javax.servlet.ServletResponse;
+import java.io.OutputStream;
 
 import digilib.io.FileOpException;
 
-
 /** The basic class for the representation of a digilib image.
  *
  * The actual image object is hidden in the class, only methods for loading,
@@ -36,61 +34,139 @@
  */
 public interface DocuImage {
 
-  /** Returns the list of image file types known to the DocuImage implementation.
-   * 
-   * @return List of image file types. Strings are standard file extensions.
-   */    
-  public String[] getKnownFileTypes();
+	/** Returns the list of image file types known to the DocuImage implementation.
+	 * 
+	 * @return List of image file types. Strings are standard file extensions.
+	 */
+	public String[] getKnownFileTypes();
+
+	/** Loads an image file into the Object.
+	 * 
+	 * @param f Image File.
+	 * @throws FileOpException Exception thrown if any error occurs.
+	 */
+	public void loadImage(File f) throws FileOpException;
 
-  /** Loads an image file into the Object.
-   * 
-   * @param f Image File.
-   * @throws FileOpException Exception thrown if any error occurs.
-   */
-  public void loadImage(File f) throws FileOpException;
+	/** Writes the current image to a ServletResponse.
+	 *
+	 * The image is encoded to the mime-type <code>mt</code> and sent to the output
+	 * stream of the <code>ServletResponse</code> <code>res</code>.
+	 *
+	 * Currently only mime-types "image/jpeg" and "image/png" are supported.
+	 * 
+	 * @param mt mime-type of the image to be sent.
+	 * @param res ServletResponse where the image is sent.
+	 * @throws FileOpException Exception thrown on any error.
+	 */
+	public void writeImage(String mt, OutputStream ostream)
+		throws FileOpException;
 
-  /** Writes the current image to a ServletResponse.
-   *
-   * The image is encoded to the mime-type <code>mt</code> and sent to the output
-   * stream of the <code>ServletResponse</code> <code>res</code>.
-   *
-   * Currently only mime-types "image/jpeg" and "image/png" are supported.
-   * 
-   * @param mt mime-type of the image to be sent.
-   * @param res ServletResponse where the image is sent.
-   * @throws FileOpException Exception thrown on any error.
-   */
-  public void writeImage(String mt, ServletResponse res) throws FileOpException;
+	/** 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();
+
+	/** 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 scale) throws ImageOpException;
 
-  /** 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();
+	/** 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 <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;
+	
+	/** Enhaces 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(double mult, double add) 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,
-                float scale, int qual) throws ImageOpException;
+	/**
+	 * Returns the interpolation quality.
+	 * @return int
+	 */
+	public int getQuality();
+
+	/**
+	 * Sets the interpolation quality.
+	 * @param quality The quality to set
+	 */
+	public void setQuality(int quality);
+
 }
--- a/servlet/src/digilib/image/DocuImageImpl.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/image/DocuImageImpl.java	Mon Feb 03 16:04:53 2003 +0100
@@ -20,61 +20,105 @@
 
 package digilib.image;
 
-import java.io.*;
-import javax.servlet.ServletResponse;
-
-import digilib.*;
-import digilib.io.*;
+import digilib.Utils;
 
 /** Simple abstract implementation of the <code>DocuImage</code> interface.
  *
  * This implementation provides basic functionality for the utility methods like
  * <code>SetUtils</code>, and <code>getKnownFileTypes</code>. Image methods like
  * <code>loadImage</code>, <code>writeImage</code>, <code>getWidth</code>,
- * <code>getHeight</code> and <code>cropAndScale</code> must be implemented by
- * derived classes.
+ * <code>getHeight</code>, <code>crop</code> and <code>scale</code> must be
+ * implemented by derived classes.
  */
 public abstract class DocuImageImpl implements DocuImage {
 
-  /** Internal utils object. */    
-  protected Utils util = null;
+	/** Internal utils object. */
+	protected Utils util = null;
+
+	/** Interpolation quality. */
+	protected int quality = 0;
 
-  /** Default constructor. */  
-  public DocuImageImpl() {
-    util = new Utils();
-  }
+	/** Default constructor. */
+	public DocuImageImpl() {
+		util = new Utils();
+	}
+
+	/** Contructor taking an utils object.
+	 * 
+	 * @param u Utils object.
+	 */
+	public DocuImageImpl(Utils u) {
+		util = u;
+	}
 
-  /** Contructor taking an utils object.
-   * 
-   * @param u Utils object.
-   */  
-  public DocuImageImpl(Utils u) {
-    util = u;
-  }
+	/** Set local Utils object.
+	 * 
+	 * @param u Utils object.
+	 */
+	public void setUtils(Utils u) {
+		util = u;
+	}
+
+	/** Internal knownFileTypes. */
+	protected String[] knownFileTypes = { "jpg", "png", "gif", "tiff" };
+
+	/** Returns the list of image file types known to the DocuImage implementation.
+	 * 
+	 * @return List of image file types. Strings are standard file extensions.
+	 */
+	public String[] getKnownFileTypes() {
+		return knownFileTypes;
+	}
 
-  /** Set local Utils object.
-   * 
-   * @param u Utils object.
-   */  
-  public void setUtils(Utils u) {
-    util = u;
-  }
+	/**
+	 * Returns the quality.
+	 * @return int
+	 */
+	public int getQuality() {
+		return quality;
+	}
 
-  /** Internal knownFileTypes. */  
-  protected String[] knownFileTypes = {"jpg", "png", "gif", "tiff"};
+	/**
+	 * Sets the quality.
+	 * @param quality The quality to set
+	 */
+	public void setQuality(int quality) {
+		this.quality = quality;
+	}
 
-  /** Returns the list of image file types known to the DocuImage implementation.
-   * 
-   * @return List of image file types. Strings are standard file extensions.
-   */    
-  public String[] getKnownFileTypes() {
-    return knownFileTypes;
-  }
+	/** 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 {
 
+		setQuality(qual);
+		crop(x_off, y_off, width, height);
+		scale(scale);
+	}
+	
+	public void rotate(double angle) throws ImageOpException {
+		// just a do-nothing implementation
+	}
 
-  public abstract void loadImage(File f) throws FileOpException;
-  public abstract void writeImage(String mt, ServletResponse res) throws FileOpException;
-  public abstract int getWidth();
-  public abstract int getHeight();
-  public abstract void cropAndScale(int x_off, int y_off, int width, int height, float scale, int qual)  throws ImageOpException;
+	public void mirror(double angle) throws ImageOpException {
+		// just a do-nothing implementation
+	}
+
+	public void enhance(double mult, double add) throws ImageOpException {
+		// just a do-nothing implementation
+	}
+
 }
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java	Mon Feb 03 16:04:53 2003 +0100
@@ -25,9 +25,9 @@
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 
 import javax.imageio.ImageIO;
-import javax.servlet.ServletResponse;
 
 import digilib.Utils;
 import digilib.io.FileOpException;
@@ -35,128 +35,122 @@
 /** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
 public class ImageLoaderDocuImage extends DocuImageImpl {
 
-  private BufferedImage img;
+	private BufferedImage img;
+
+	private int scaleInt;
+
+	public ImageLoaderDocuImage() {
+	}
+
+	public ImageLoaderDocuImage(Utils u) {
+		util = u;
+	}
 
-  public ImageLoaderDocuImage() {
-  }
-
-  public ImageLoaderDocuImage(Utils u) {
-    util = u;
-  }
+	/**
+	 *  load image file
+	 */
+	public void loadImage(File f) throws FileOpException {
+		util.dprintln(10, "loadImage!");
+		System.gc();
+		try {
+			img = ImageIO.read(f);
+			if (img == null) {
+				util.dprintln(3, "ERROR(loadImage): unable to load file");
+				throw new FileOpException("Unable to load File!");
+			}
+		} catch (IOException e) {
+			throw new FileOpException("Error reading image.");
+		}
+	}
 
-  /**
-   *  load image file
-   */
-  public void loadImage(File f) throws FileOpException {
-    util.dprintln(10, "loadImage!");
-    System.gc();
-    try {
-      for (int i = 0; i < ImageIO.getReaderFormatNames().length; i++) {
-    System.out.println("ImageLoader reader:"+ImageIO.getReaderFormatNames()[i]);
-      }
-      for (int i = 0; i < ImageIO.getWriterFormatNames().length; i++) {
-    System.out.println("ImageLoader writer:"+ImageIO.getWriterFormatNames()[i]);
-      }
-      img = ImageIO.read(f);
-      if (img == null) {
-        util.dprintln(3, "ERROR(loadImage): unable to load file");
-        throw new FileOpException("Unable to load File!");
-      }
-    }
-    catch (IOException e) {
-      throw new FileOpException("Error reading image.");
-    }
-  }
+	/**
+	 *  write image of type mt to Stream
+	 */
+	public void writeImage(String mt, OutputStream ostream)
+		throws FileOpException {
+		util.dprintln(10, "writeImage!");
+		try {
+			// setup output
+			String type = "png";
+			if (mt == "image/jpeg") {
+				type = "jpeg";
+			} else if (mt == "image/png") {
+				type = "png";
+			} else {
+				// unknown mime type
+				util.dprintln(2, "ERROR(writeImage): Unknown mime type " + mt);
+				throw new FileOpException("Unknown mime type: " + mt);
+			}
+			// render output
+			if (ImageIO.write(img, type, ostream)) {
+				// writing was OK
+				return;
+			} else {
+				throw new FileOpException("Error writing image: Unknown image format!");
+			}
+		} catch (IOException e) {
+			// e.printStackTrace();
+			throw new FileOpException("Error writing image.");
+		}
+	}
 
-  /**
-   *  write image of type mt to Stream
-   */
-  public void writeImage(String mt, ServletResponse res)
-         throws FileOpException {
-    util.dprintln(10, "writeImage!");
-    try {
-      // setup output
-      String type = "png";
-      if (mt == "image/jpeg") {
-        type = "jpeg";
-      } else if (mt == "image/png") {
-        type = "png";
-      } else {
-        // unknown mime type
-        util.dprintln(2, "ERROR(writeImage): Unknown mime type "+mt);
-        throw new FileOpException("Unknown mime type: "+mt);
-      }
-      res.setContentType(mt);
-      // render output
-      if (ImageIO.write(img, type, res.getOutputStream())) {
-        // writing was OK
-        return;
-      } else {
-        throw new FileOpException("Error writing image: Unknown image format!");
-      }
-    } catch (IOException e) {
-      // e.printStackTrace();
-      throw new FileOpException("Error writing image.");
-    }
-  }
+	public int getWidth() {
+		if (img != null) {
+			return img.getWidth();
+		}
+		return 0;
+	}
+
+	public int getHeight() {
+		if (img != null) {
+			return img.getHeight();
+		}
+		return 0;
+	}
 
-  public int getWidth() {
-    if (img != null) {
-      return img.getWidth();
-    }
-    return 0;
-  }
+	public void scale(double scale) throws ImageOpException {
+		// setup scale
+		AffineTransformOp scaleOp =
+			new AffineTransformOp(
+				AffineTransform.getScaleInstance(scale, scale),
+				scaleInt);
+		BufferedImage scaledImg = scaleOp.filter(img, null);
+
+		if (scaledImg == null) {
+			util.dprintln(2, "ERROR(cropAndScale): error in scale");
+			throw new ImageOpException("Unable to scale");
+		}
+		img = scaledImg;
+	}
 
-  public int getHeight() {
-    if (img != null) {
-      return img.getHeight();
-    }
-    return 0;
-  }
+	public void crop(int x_off, int y_off, int width, int height)
+		throws ImageOpException {
+		// setup Crop
+		BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height);
 
-  /**
-   *  crop and scale image
-   *    take rectangle width,height at position x_off,y_off
-   *    and scale by scale
-   */
-   public void cropAndScale(int x_off, int y_off, int width, int height,
-         float scale, int qual) throws ImageOpException {
-    util.dprintln(10, "cropAndScale!");
-
-    int scaleInt = 0;
-    // setup interpolation quality
-    if (qual > 0) {
-      util.dprintln(4, "quality q1");
-      scaleInt = AffineTransformOp.TYPE_BILINEAR;
-    } else {
-      util.dprintln(4, "quality q0");
-      scaleInt = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
-    }
+		util.dprintln(
+			3,
+			"CROP:" + croppedImg.getWidth() + "x" + croppedImg.getHeight());
+		//DEBUG
+		//    util.dprintln(2, "  time "+(System.currentTimeMillis()-startTime)+"ms");
 
-    // setup Crop
-    BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height);
-
-    img = null; // free img
-    util.dprintln(3, "CROP:"+croppedImg.getWidth()+"x"+croppedImg.getHeight()); //DEBUG
-//    util.dprintln(2, "  time "+(System.currentTimeMillis()-startTime)+"ms");
-
-    if (croppedImg == null) {
-      util.dprintln(2, "ERROR(cropAndScale): error in crop");
-      throw new ImageOpException("Unable to crop");
-    }
+		if (croppedImg == null) {
+			util.dprintln(2, "ERROR(cropAndScale): error in crop");
+			throw new ImageOpException("Unable to crop");
+		}
+		img = croppedImg;
+	}
 
-    // setup scale
-    AffineTransformOp scaleOp = new AffineTransformOp(
-                      AffineTransform.getScaleInstance(scale, scale),
-                      scaleInt);
-    BufferedImage scaledImg = scaleOp.filter(croppedImg, null);
-    croppedImg = null; // free opCrop
-
-    if (scaledImg == null) {
-      util.dprintln(2, "ERROR(cropAndScale): error in scale");
-      throw new ImageOpException("Unable to scale");
-    }
-    img = scaledImg;
-  }
+	public void setQuality(int qual) {
+		quality = qual;
+		// setup interpolation quality
+		if (qual > 0) {
+			util.dprintln(4, "quality q1");
+			scaleInt = AffineTransformOp.TYPE_BILINEAR;
+		} else {
+			util.dprintln(4, "quality q0");
+			scaleInt = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
+		}
+	}
 
 }
--- a/servlet/src/digilib/image/JAIDocuImage.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/image/JAIDocuImage.java	Mon Feb 03 16:04:53 2003 +0100
@@ -20,14 +20,16 @@
 
 package digilib.image;
 
+import java.awt.RenderingHints;
 import java.awt.image.RenderedImage;
 import java.awt.image.renderable.ParameterBlock;
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 
-import javax.media.jai.Interpolation;
-import javax.media.jai.JAI;
-import javax.servlet.ServletResponse;
+import javax.media.jai.*;
+import javax.media.jai.operator.TransposeDescriptor;
+import javax.media.jai.operator.TransposeType;
 
 import digilib.Utils;
 import digilib.io.FileOpException;
@@ -35,7 +37,11 @@
 /** A DocuImage implementation using Java Advanced Imaging Library. */
 public class JAIDocuImage extends DocuImageImpl {
 
-	private RenderedImage img;
+	protected RenderedImage img;
+	protected Interpolation interpol = null;
+
+	// epsilon for float comparisons
+	public final double epsilon = 1e-5;
 
 	/** Default constructor. */
 	public JAIDocuImage() {
@@ -48,11 +54,7 @@
 		util = u;
 	}
 
-	/** Load an image file into the Object.
-	 *
-	 * @param f Image File.
-	 * @throws FileOpException Exception thrown if any error occurs.
-	 */
+	/* Load an image file into the Object. */
 	public void loadImage(File f) throws FileOpException {
 		System.gc();
 		img = JAI.create("fileload", f.getAbsolutePath());
@@ -62,23 +64,14 @@
 		}
 	}
 
-	/** Write the current image to a ServletResponse.
-	 *
-	 * The image is encoded to the mime-type mt and sent to the output stream of the
-	 * ServletResponse res.
-	 *
-	 * Currently only mime-types "image/jpeg" and "image/png" are allowed.
-	 * @param mt mime-type of the image to be sent.
-	 * @param res ServletResponse where the image is sent.
-	 * @throws FileOpException Exception thrown on any error.
-	 */
-	public void writeImage(String mt, ServletResponse res)
+	/* Write the current image to an OutputStream. */
+	public void writeImage(String mt, OutputStream ostream)
 		throws FileOpException {
 		try {
 			// setup output
 			ParameterBlock pb3 = new ParameterBlock();
 			pb3.addSource(img);
-			pb3.add(res.getOutputStream());
+			pb3.add(ostream);
 			if (mt == "image/jpeg") {
 				pb3.add("JPEG");
 			} else if (mt == "image/png") {
@@ -88,7 +81,6 @@
 				util.dprintln(2, "ERROR(writeImage): Unknown mime type " + mt);
 				throw new FileOpException("Unknown mime type: " + mt);
 			}
-			res.setContentType(mt);
 			// render output
 			JAI.create("encode", pb3);
 
@@ -97,6 +89,24 @@
 		}
 	}
 
+	/* Real setQuality implementation. 
+	 * Creates the correct Interpolation.
+	 */
+	public void setQuality(int qual) {
+		quality = qual;
+		// setup interpolation quality
+		if (qual > 1) {
+			util.dprintln(4, "quality q2");
+			interpol = Interpolation.getInstance(Interpolation.INTERP_BICUBIC);
+		} else if (qual == 1) {
+			util.dprintln(4, "quality q1");
+			interpol = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
+		} else {
+			util.dprintln(4, "quality q0");
+			interpol = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
+		}
+	}
+
 	/** The width of the curent image in pixel.
 	 * @return Image width in pixels.
 	 */
@@ -117,94 +127,230 @@
 		return 0;
 	}
 
-	/** 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,
-		float scale,
-		int qual)
-		throws ImageOpException {
+	/* scales the current image */
+	public void scale(double scale) throws ImageOpException {
+		float sf = (float)scale;
+		// setup scale
+		ParameterBlock param = new ParameterBlock();
+		param.addSource(img);
+		param.add(sf);
+		param.add(sf);
+		param.add(0f);
+		param.add(0f);
+		param.add(interpol);
+		// hint with border extender
+		RenderingHints hint =
+			new RenderingHints(
+				JAI.KEY_BORDER_EXTENDER,
+				BorderExtender.createInstance(BorderExtender.BORDER_COPY));
+
+		RenderedImage scaledImg = JAI.create("scale", param, hint);
 
-		Interpolation scaleInt = null;
-		// setup interpolation quality
-		if (qual > 1) {
-			util.dprintln(4, "quality q2");
-			scaleInt = Interpolation.getInstance(Interpolation.INTERP_BICUBIC);
-		} else if (qual == 1) {
-			util.dprintln(4, "quality q1");
-			scaleInt = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
-		} else {
-			util.dprintln(4, "quality q0");
-			scaleInt = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
+		//DEBUG
+		util.dprintln(
+			3,
+			"SCALE: "
+				+ scale
+				+ " ->"
+				+ scaledImg.getWidth()
+				+ "x"
+				+ scaledImg.getHeight());
+
+		if (scaledImg == null) {
+			util.dprintln(2, "ERROR(scale): error in scale");
+			throw new ImageOpException("Unable to scale");
 		}
+		img = scaledImg;
+	}
 
+	/* crops the current image */
+	public void crop(int x_off, int y_off, int width, int height)
+		throws ImageOpException {
 		// setup Crop
-		ParameterBlock pb1 = new ParameterBlock();
-		pb1.addSource(img);
-		pb1.add((float) x_off);
-		pb1.add((float) y_off);
-		pb1.add((float) width);
-		pb1.add((float) height);
-		RenderedImage croppedImg = JAI.create("crop", pb1);
-		img = null; // free img
+		ParameterBlock param = new ParameterBlock();
+		param.addSource(img);
+		param.add((float) x_off);
+		param.add((float) y_off);
+		param.add((float) width);
+		param.add((float) height);
+		RenderedImage croppedImg = JAI.create("crop", param);
 
 		util.dprintln(
 			3,
-			"CROP:" + croppedImg.getWidth() + "x" + croppedImg.getHeight());
+			"CROP: "
+				+ x_off
+				+ ","
+				+ y_off
+				+ ", "
+				+ width
+				+ ","
+				+ height
+				+ " ->"
+				+ croppedImg.getWidth()
+				+ "x"
+				+ croppedImg.getHeight());
 		//DEBUG
 
 		if (croppedImg == null) {
-			util.dprintln(2, "ERROR(cropAndScale): error in crop");
+			util.dprintln(2, "ERROR(crop): error in crop");
 			throw new ImageOpException("Unable to crop");
 		}
+		img = croppedImg;
+	}
+
+	/* rotates the current image */
+	public void rotate(double angle) throws ImageOpException {
+		RenderedImage rotImg;
+		// convert degrees to radians
+		double rangle = Math.toRadians(angle);
+		// rotate about the image center
+		double xoff = img.getWidth() / 2;
+		double yoff = img.getHeight() / 2;
+
+		// optimize rotation by right angles
+		TransposeType rotOp = null;
+		if (Math.abs(angle - 0) < epsilon) {
+			// 0 degree
+			return;
+		} else if (Math.abs(angle - 90) < epsilon) {
+			// 90 degree
+			rotOp = TransposeDescriptor.ROTATE_90;
+		} else if (Math.abs(angle - 180) < epsilon) {
+			// 180 degree
+			rotOp = TransposeDescriptor.ROTATE_180;
+		} else if (Math.abs(angle - 270) < epsilon) {
+			// 270 degree
+			rotOp = TransposeDescriptor.ROTATE_270;
+		} else if (Math.abs(angle - 360) < epsilon) {
+			// 360 degree
+			return;
+		}
+		if (rotOp != null) {
+			// use Transpose operation
+			ParameterBlock pb = new ParameterBlock();
+			pb.addSource(img);
+			pb.add(rotOp);
+			rotImg = JAI.create("transpose", pb);
+		} else {
+			// setup "normal" rotation
+			ParameterBlock param = new ParameterBlock();
+			param.addSource(img);
+			param.add((float) xoff);
+			param.add((float) yoff);
+			param.add((float) rangle);
+			param.add(interpol);
+			// hint with border extender
+			RenderingHints hint =
+				new RenderingHints(
+					JAI.KEY_BORDER_EXTENDER,
+					BorderExtender.createInstance(BorderExtender.BORDER_COPY));
+
+			rotImg = JAI.create("rotate", param, hint);
+		}
 
-		// setup scale
-		RenderedImage scaledImg;
+		util.dprintln(
+			3,
+			"ROTATE: "
+				+ xoff
+				+ ","
+				+ yoff
+				+ ", "
+				+ angle
+				+ " ("
+				+ rangle
+				+ ")"
+				+ " ->"
+				+ rotImg.getWidth()
+				+ "x"
+				+ rotImg.getHeight());
+		//DEBUG
 
-		if (scale != 1f) {
-			ParameterBlock pb2 = new ParameterBlock();
-			pb2.addSource(croppedImg);
-			pb2.add(scale);
-			pb2.add(scale);
-			pb2.add(0f);
-			pb2.add(0f);
-			pb2.add(scaleInt);
-			// the following is nice but way too slow...
-			//if (opCrop.getColorModel().getPixelSize() < 8) {
-			// change color model if necessary
-			//  util.dprintln("converting color model...");
-			//  BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY);
-			//  ImageLayout lay = new ImageLayout(bi);
-			//  rh = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, lay);
-			//}
-			scaledImg = JAI.create("scale", pb2);
-			croppedImg = null; // free opCrop
-		} else {
-			// no scaling
-			scaledImg = croppedImg;
+		if (rotImg == null) {
+			util.dprintln(2, "ERROR: error in rotate");
+			throw new ImageOpException("Unable to rotate");
 		}
-
-		if (scaledImg == null) {
-			util.dprintln(2, "ERROR(cropAndScale): error in scale");
-			throw new ImageOpException("Unable to scale");
-		}
-
-		img = scaledImg;
+		img = rotImg;
 	}
 
+	/* mirrors the current image
+	 * works only horizontal and vertical
+	 */
+	public void mirror(double angle) throws ImageOpException {
+		RenderedImage mirImg;
+
+		// only mirroring by right angles
+		TransposeType rotOp = null;
+		if (Math.abs(angle - 0) < epsilon) {
+			// 0 degree
+			rotOp = TransposeDescriptor.FLIP_HORIZONTAL;
+		} else if (Math.abs(angle - 90) < epsilon) {
+			// 90 degree
+			rotOp = TransposeDescriptor.FLIP_VERTICAL;
+		} else if (Math.abs(angle - 180) < epsilon) {
+			// 180 degree
+			rotOp = TransposeDescriptor.FLIP_HORIZONTAL;
+		} else if (Math.abs(angle - 270) < epsilon) {
+			// 270 degree
+			rotOp = TransposeDescriptor.FLIP_VERTICAL;
+		} else if (Math.abs(angle - 360) < epsilon) {
+			// 360 degree
+			rotOp = TransposeDescriptor.FLIP_HORIZONTAL;
+		}
+		if (rotOp != null) {
+			// use Transpose operation
+			ParameterBlock param = new ParameterBlock();
+			param.addSource(img);
+			param.add(rotOp);
+			mirImg = JAI.create("transpose", param);
+
+			util.dprintln(
+				3,
+				"MIRROR: "
+					+ angle
+					+ " ->"
+					+ mirImg.getWidth()
+					+ "x"
+					+ mirImg.getHeight());
+			//DEBUG
+
+			if (mirImg == null) {
+				util.dprintln(2, "ERROR(mirror): error in mirror");
+				throw new ImageOpException("Unable to mirror");
+			}
+			img = mirImg;
+		}
+	}
+
+	/* contrast and brightness enhancement */
+	public void enhance(double mult, double add) throws ImageOpException {
+		RenderedImage enhImg;
+		double[] ma = { mult };
+		double[] aa = { add };
+		// use Rescale operation
+		ParameterBlock param = new ParameterBlock();
+		param.addSource(img);
+		param.add(ma);
+		param.add(aa);
+		enhImg = JAI.create("rescale", param);
+
+		util.dprintln(
+			3,
+			"ENHANCE: *"
+				+ mult
+				+ ", +"
+				+ add
+				+ " ->"
+				+ enhImg.getWidth()
+				+ "x"
+				+ enhImg.getHeight());
+		//DEBUG
+
+		if (enhImg == null) {
+			util.dprintln(2, "ERROR(enhance): error in enhance");
+			throw new ImageOpException("Unable to enhance");
+		}
+		img = enhImg;
+	}
+
+
 }
--- a/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Mon Feb 03 16:04:53 2003 +0100
@@ -20,40 +20,22 @@
 
 package digilib.image;
 
-import java.awt.image.RenderedImage;
 import java.awt.image.renderable.ParameterBlock;
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 
-import javax.media.jai.Interpolation;
 import javax.media.jai.JAI;
-import javax.servlet.ServletResponse;
 
-import digilib.Utils;
 import digilib.io.FileOpException;
 
 /** DocuImage implementation using the Java Advanced Imaging API and the ImageLoader
  * API of Java 1.4.
  */
-public class JAIImageLoaderDocuImage extends DocuImageImpl {
-
-	private RenderedImage img;
-
-	/** Default constructor. */
-	public JAIImageLoaderDocuImage() {
-	}
+public class JAIImageLoaderDocuImage extends JAIDocuImage {
 
-	/** Constructor using an utils object.
-	 * @param u Utils object.
-	 */
-	public JAIImageLoaderDocuImage(Utils u) {
-		util = u;
-	}
 
-	/** Load an image file into the Object.
-	 * @param f Image file.
-	 * @throws FileOpException Exception thrown on any error.
-	 */
+	/* Load an image file into the Object. */
 	public void loadImage(File f) throws FileOpException {
 		System.gc();
 		img = JAI.create("ImageRead", f.getAbsolutePath());
@@ -63,23 +45,14 @@
 		}
 	}
 
-	/** Write the current image to a ServletResponse.
-	 *
-	 * The image is encoded to the mime-type mt and sent to the output stream of the
-	 * ServletResponse res.
-	 *
-	 * Currently only mime-types "image/jpeg" and "image/png" are allowed.
-	 * @param mt mime-type of the image to be sent.
-	 * @param res ServletResponse where the image is sent.
-	 * @throws FileOpException Exception thrown on any error.
-	 */
-	public void writeImage(String mt, ServletResponse res)
+	/* Write the current image to an OutputStream. */
+	public void writeImage(String mt, OutputStream ostream)
 		throws FileOpException {
 		try {
 			// setup output
 			ParameterBlock pb3 = new ParameterBlock();
 			pb3.addSource(img);
-			pb3.add(res.getOutputStream());
+			pb3.add(ostream);
 			if (mt == "image/jpeg") {
 				pb3.add("JPEG");
 			} else if (mt == "image/png") {
@@ -89,7 +62,6 @@
 				util.dprintln(2, "ERROR(writeImage): Unknown mime type " + mt);
 				throw new FileOpException("Unknown mime type: " + mt);
 			}
-			res.setContentType(mt);
 			// render output
 			JAI.create("ImageWrite", pb3);
 
@@ -98,111 +70,4 @@
 		}
 	}
 
-	/** The width of the curent image in pixel.
-	 * @return Image width in pixels.
-	 */
-	public int getWidth() {
-		if (img != null) {
-			return img.getWidth();
-		}
-		return 0;
-	}
-
-	/** The height of the curent image in pixel.
-	 * @return Image height in pixels.
-	 */
-	public int getHeight() {
-		if (img != null) {
-			return img.getHeight();
-		}
-		return 0;
-	}
-
-	/** 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,
-		float scale,
-		int qual)
-		throws ImageOpException {
-
-		Interpolation scaleInt = null;
-		// setup interpolation quality
-		if (qual > 1) {
-			util.dprintln(4, "quality q2");
-			scaleInt = Interpolation.getInstance(Interpolation.INTERP_BICUBIC);
-		} else if (qual == 1) {
-			util.dprintln(4, "quality q1");
-			scaleInt = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
-		} else {
-			util.dprintln(4, "quality q0");
-			scaleInt = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
-		}
-
-		// setup Crop
-		ParameterBlock pb1 = new ParameterBlock();
-		pb1.addSource(img);
-		pb1.add((float) x_off);
-		pb1.add((float) y_off);
-		pb1.add((float) width);
-		pb1.add((float) height);
-		RenderedImage croppedImg = JAI.create("crop", pb1);
-
-		util.dprintln(
-			3,
-			"CROP:" + croppedImg.getWidth() + "x" + croppedImg.getHeight());
-		//DEBUG
-
-		if (croppedImg == null) {
-			util.dprintln(2, "ERROR(cropAndScale): error in crop");
-			throw new ImageOpException("Unable to crop");
-		}
-
-		RenderedImage scaledImg;
-
-		if (scale != 1f) {
-			// setup scale
-			ParameterBlock pb2 = new ParameterBlock();
-			pb2.addSource(croppedImg);
-			pb2.add(scale);
-			pb2.add(scale);
-			pb2.add(0f);
-			pb2.add(0f);
-			pb2.add(scaleInt);
-			// the following is nice but way too slow...
-			//if (opCrop.getColorModel().getPixelSize() < 8) {
-			// change color model if necessary
-			//  util.dprintln("converting color model...");
-			//  BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY);
-			//  ImageLayout lay = new ImageLayout(bi);
-			//  rh = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, lay);
-			//}
-			scaledImg = JAI.create("scale", pb2);
-		} else {
-			// no scaling
-			scaledImg = croppedImg;
-		}
-
-		if (scaledImg == null) {
-			util.dprintln(2, "ERROR(cropAndScale): error in scale");
-			throw new ImageOpException("Unable to scale");
-		}
-
-		img = scaledImg;
-	}
-
 }
--- a/servlet/src/digilib/image/JIMIDocuImage.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/image/JIMIDocuImage.java	Mon Feb 03 16:04:53 2003 +0100
@@ -20,125 +20,121 @@
 
 package digilib.image;
 
-import javax.servlet.*;
-import javax.servlet.http.*;
-import java.io.*;
-import java.util.*;
+import java.awt.image.FilteredImageSource;
+import java.awt.image.ImageFilter;
+import java.awt.image.ImageProducer;
+import java.io.File;
+import java.io.OutputStream;
 
-import com.sun.jimi.core.*;
-import com.sun.jimi.core.raster.*;
-import com.sun.jimi.core.filters.*;
+import com.sun.jimi.core.Jimi;
+import com.sun.jimi.core.JimiException;
+import com.sun.jimi.core.filters.AreaAverageScaleFilter;
+import com.sun.jimi.core.filters.ReplicatingScaleFilter;
+import com.sun.jimi.core.raster.JimiRasterImage;
 
-import java.awt.*;
-import java.awt.image.*;
-
-import digilib.*;
-import digilib.io.*;
-
+import digilib.Utils;
+import digilib.io.FileOpException;
 
 /** Implementation of DocuImage using the JIMI image Library. */
 public class JIMIDocuImage extends DocuImageImpl {
 
-  private JimiRasterImage img;
-  private ImageProducer imgp;
+	private JimiRasterImage img;
+	private ImageProducer imgp;
+	private int imgWidth = 0;
+	private int imgHeight = 0;
 
-  public JIMIDocuImage() {
-  }
+	public JIMIDocuImage() {
+	}
 
-  public JIMIDocuImage(Utils u) {
-    util = u;
-  }
+	public JIMIDocuImage(Utils u) {
+		util = u;
+	}
 
-  /**
-   *  load image file
-   */
-  public void loadImage(File f) throws FileOpException {
-    System.gc();
-    try {
-    img = Jimi.getRasterImage(f.toURL());
-    } catch (java.net.MalformedURLException e) {
-      util.dprintln(3, "ERROR(loadImage): MalformedURLException");
-    } catch (JimiException e) {
-      util.dprintln(3, "ERROR(loadImage): JIMIException");
-      throw new FileOpException("Unable to load File!"+e);
-    }
-    if (img == null) {
-      util.dprintln(3, "ERROR(loadImage): unable to load file");
-      throw new FileOpException("Unable to load File!");
-    }
-  }
+	/**
+	 *  load image file
+	 */
+	public void loadImage(File f) throws FileOpException {
+		System.gc();
+		try {
+			img = Jimi.getRasterImage(f.toURL());
+		} catch (java.net.MalformedURLException e) {
+			util.dprintln(3, "ERROR(loadImage): MalformedURLException");
+		} catch (JimiException e) {
+			util.dprintln(3, "ERROR(loadImage): JIMIException");
+			throw new FileOpException("Unable to load File!" + e);
+		}
+		if (img == null) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+		imgp = img.getImageProducer();
+		imgWidth = img.getWidth();
+		imgHeight = img.getHeight();
+	}
 
-  /**
-   *  write image of type mt to Stream
-   */
-  public void writeImage(String mt, ServletResponse res)
-         throws FileOpException {
-    try {
-    // setup output
-    res.setContentType(mt);
-    // render output
-    Jimi.putImage(mt, imgp, res.getOutputStream());
+	/**
+	 *  write image of type mt to Stream
+	 */
+	public void writeImage(String mt, OutputStream ostream)
+		throws FileOpException {
+		try {
+			// render output
+			Jimi.putImage(mt, imgp, ostream);
 
-    } catch (JimiException e) {
-      throw new FileOpException("Error writing image!"+e);
-    } catch (IOException e) {
-      throw new FileOpException("Error writing image."+e);
-    }
-  }
+		} catch (JimiException e) {
+			throw new FileOpException("Error writing image!" + e);
+		}
+	}
 
-  public int getWidth() {
-    if (img != null) {
-      return img.getWidth();
-    }
-    return 0;
-  }
+	public int getWidth() {
+		return imgWidth;
+	}
+
+	public int getHeight() {
+		return imgHeight;
+	}
+
+	public void scale(double scale) throws ImageOpException {
 
-  public int getHeight() {
-    if (img != null) {
-      return img.getHeight();
-    }
-    return 0;
-  }
-
+		ImageFilter scaleFilter;
+		int destWidth = (int) (scale * (float) imgWidth);
+		int destHeight = (int) (scale * (float) imgHeight);
 
-  /**
-   *  crop and scale image
-   *    take rectangle width,height at position x_off,y_off
-   *    and scale by scale
-   */
-   public void cropAndScale(int x_off, int y_off, int width, int height,
-         float scale, int qual) throws ImageOpException {
+		// setup scale and interpolation quality
+		if (quality > 0) {
+			util.dprintln(4, "quality q1");
+			scaleFilter = new AreaAverageScaleFilter(destWidth, destHeight);
+		} else {
+			util.dprintln(4, "quality q0");
+			scaleFilter = new ReplicatingScaleFilter(destWidth, destHeight);
+		}
 
-    ImageFilter scaleFilter;
-    int destWidth = (int)(scale * (float)width);
-    int destHeight = (int)(scale * (float)height);
+		ImageProducer scaledImg = new FilteredImageSource(imgp, scaleFilter);
 
-    // setup Crop
-    ImageProducer croppedImg = img.getCroppedImageProducer(x_off, y_off, width, height);
-    //util.dprintln(3, "CROP:"+croppedImg.getWidth()+"x"+croppedImg.getHeight()); //DEBUG
+		if (scaledImg == null) {
+			util.dprintln(2, "ERROR(cropAndScale): error in scale");
+			throw new ImageOpException("Unable to scale");
+		}
 
-    if (croppedImg == null) {
-      util.dprintln(2, "ERROR(cropAndScale): error in crop");
-      throw new ImageOpException("Unable to crop");
-    }
+		imgp = scaledImg;
+		imgWidth = destWidth;
+		imgHeight = destHeight;
+	}
 
-    // setup scale and interpolation quality
-    if (qual > 0) {
-      util.dprintln(4, "quality q1");
-      scaleFilter = new AreaAverageScaleFilter(destWidth, destHeight);
-    } else {
-      util.dprintln(4, "quality q0");
-      scaleFilter = new ReplicatingScaleFilter(destWidth, destHeight);
-    }
+	public void crop(int x_off, int y_off, int width, int height)
+		throws ImageOpException {
+		// setup Crop
+		ImageProducer croppedImg =
+			img.getCroppedImageProducer(x_off, y_off, width, height);
+		//util.dprintln(3, "CROP:"+croppedImg.getWidth()+"x"+croppedImg.getHeight()); //DEBUG
 
-    ImageProducer scaledImg = new FilteredImageSource(croppedImg, scaleFilter);
-
-    if (scaledImg == null) {
-      util.dprintln(2, "ERROR(cropAndScale): error in scale");
-      throw new ImageOpException("Unable to scale");
-    }
-
-    imgp = scaledImg;
-  }
+		if (croppedImg == null) {
+			util.dprintln(2, "ERROR(cropAndScale): error in crop");
+			throw new ImageOpException("Unable to crop");
+		}
+		imgp = croppedImg;
+		imgWidth = width;
+		imgHeight = height;
+	}
 
 }
--- a/servlet/src/digilib/servlet/DigilibRequest.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/servlet/DigilibRequest.java	Mon Feb 03 16:04:53 2003 +0100
@@ -72,6 +72,13 @@
 	private int pt; // total number of pages (generated by sevlet)
 	private String pt_s;
 	private String baseURL; // base URL (from http:// to below /servlet)
+	private float rot; // rotation angle in degrees
+	private String rot_s; 
+	private float cont; // contrast enhancement factor
+	private String cont_s;	
+	private float brgt; // brightness enhancement factor
+	private String brgt_s;	
+	
 	private DocuImage image; // internal DocuImage instance for this request
 	private ServletRequest servletRequest; // internal ServletRequest
 
@@ -91,6 +98,11 @@
 		setWithRequest(request);
 	}
 
+	/** Populate the request object with data from a ServletRequest.
+	 * 
+	 * 
+	 * @param request
+	 */
 	public void setWithRequest(ServletRequest request) {
 		servletRequest = request;
 		// decide if it's old-style or new-style
@@ -292,6 +304,15 @@
 		if (mk != null) {
 			s += "&mk=" + mk;
 		}
+		if (rot_s != null) {
+			s += "&rot=" + rot_s;
+		}
+		if (cont_s != null) {
+			s += "&cont=" + cont_s;
+		}
+		if (brgt_s != null) {
+			s += "&brgt=" + brgt_s;
+		}
 		if (pt_s != null) {
 			s += "&pt=" + pt_s;
 		}
@@ -368,6 +389,18 @@
 		if (s != null) {
 			setDh(s);
 		}
+		s = request.getParameter("rot");
+		if (s != null) {
+			setRot(s);
+		}
+		s = request.getParameter("cont");
+		if (s != null) {
+			setCont(s);
+		}
+		s = request.getParameter("brgt");
+		if (s != null) {
+			setBrgt(s);
+		}
 		s = request.getParameter("pt");
 		if (s != null) {
 			setPt(s);
@@ -402,6 +435,12 @@
 		mk = null; // marks
 		pt = 0; // total number of pages
 		pt_s = null;
+		rot = 0;
+		rot_s = null;
+		cont = 0;
+		cont_s = null;
+		brgt = 0;
+		brgt_s = null;
 		baseURL = null;
 		image = null;
 		servletRequest = null;
@@ -431,6 +470,12 @@
 		mk = ""; // marks
 		pt = 0; // total number of pages
 		pt_s = null;
+		rot = 0;
+		rot_s = null;
+		cont = 0;
+		cont_s = null;
+		brgt = 0;
+		brgt_s = null;
 		baseURL = null;
 		image = null;
 		servletRequest = null;
@@ -822,4 +867,81 @@
 		this.servletRequest = servletRequest;
 	}
 
+	/**
+	 * Returns the rot.
+	 * @return float
+	 */
+	public float getRot() {
+		return rot;
+	}
+
+	/**
+	 * Sets the rot.
+	 * @param rot The rot to set
+	 */
+	public void setRot(float rot) {
+		this.rot = rot;
+		rot_s = Float.toString(rot);
+	}
+	public void setRot(String rot) {
+		try {
+			float f = Float.parseFloat(rot);
+			this.rot = f;
+			this.rot_s = rot;
+		} catch (Exception e) {
+			//util.dprintln(4, "trytoGetParam(int) failed on param "+s);
+		}
+	}
+
+	/**
+	 * Returns the rot.
+	 * @return float
+	 */
+	public float getCont() {
+		return cont;
+	}
+
+	/**
+	 * Sets the rot.
+	 * @param rot The rot to set
+	 */
+	public void setCont(float cont) {
+		this.cont = cont;
+		rot_s = Float.toString(cont);
+	}
+	public void setCont(String cont) {
+		try {
+			float f = Float.parseFloat(cont);
+			this.cont = f;
+			this.cont_s = cont;
+		} catch (Exception e) {
+			//util.dprintln(4, "trytoGetParam(int) failed on param "+s);
+		}
+	}
+	
+	/**
+	 * Returns the brgt.
+	 * @return float
+	 */
+	public float getBrgt() {
+		return brgt;
+	}
+
+	/**
+	 * Sets the brgt.
+	 * @param brgt The brgt to set
+	 */
+	public void setBrgt(float brgt) {
+		this.brgt = brgt;
+		brgt_s = Float.toString(brgt);
+	}
+	public void setBrgt(String brgt) {
+		try {
+			float f = Float.parseFloat(brgt);
+			this.brgt = f;
+			this.brgt_s = brgt;
+		} catch (Exception e) {
+			//util.dprintln(4, "trytoGetParam(int) failed on param "+s);
+		}
+	}
 }
--- a/servlet/src/digilib/servlet/DocumentBean.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/servlet/DocumentBean.java	Mon Feb 03 16:04:53 2003 +0100
@@ -130,6 +130,10 @@
 		HttpServletResponse response)
 		throws Exception {
 		util.dprintln(10, "doAuthentication");
+		if (! useAuthentication) {
+			// shortcut if no authentication
+			return true;
+		}
 		// check if we are already authenticated
 		if (((HttpServletRequest) request.getServletRequest()).getRemoteUser()
 			== null) {
--- a/servlet/src/digilib/servlet/Scaler.java	Fri Jan 24 22:13:49 2003 +0100
+++ b/servlet/src/digilib/servlet/Scaler.java	Mon Feb 03 16:04:53 2003 +0100
@@ -20,15 +20,27 @@
 
 package digilib.servlet;
 
-import javax.servlet.*;
-import javax.servlet.http.*;
-import java.io.*;
-import java.util.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
 
-import digilib.*;
-import digilib.io.*;
-import digilib.image.*;
-import digilib.auth.*;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import digilib.Utils;
+import digilib.auth.AuthOpException;
+import digilib.auth.AuthOps;
+import digilib.image.DocuImage;
+import digilib.image.ImageOpException;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
 
 //import tilecachetool.*;
 
@@ -40,8 +52,8 @@
 public class Scaler extends HttpServlet {
 
 	// digilib servlet version (for all components)
-	public static final String dlVersion = "1.5b";
-	
+	public static final String dlVersion = "1.6b";
+
 	// Utils instance with debuglevel
 	Utils util;
 	// FileOps instance
@@ -103,9 +115,7 @@
 		// set with request parameters
 		dlReq.setWithRequest(request);
 		// add DigilibRequest to ServletRequest
-		request.setAttribute(
-			"digilib.servlet.request",
-			dlReq);
+		request.setAttribute("digilib.servlet.request", dlReq);
 		// do the processing
 		processRequest(request, response);
 	}
@@ -121,9 +131,7 @@
 		// set with request parameters
 		dlReq.setWithRequest(request);
 		// add DigilibRequest to ServletRequest
-		request.setAttribute(
-			"digilib.servlet.request",
-			dlReq);
+		request.setAttribute("digilib.servlet.request", dlReq);
 		// do the processing
 		processRequest(request, response);
 	}
@@ -155,6 +163,10 @@
 		int scaleQual = 0;
 		// send html error message (or image file)
 		boolean errorMsgHtml = false;
+		// mirror the image
+		boolean doMirror = false;
+		// angle of mirror axis
+		double mirrorAngle = 0;
 
 		/*
 		 *  request parameters
@@ -168,15 +180,21 @@
 		// destination image height
 		int paramDH = dlRequest.getDh();
 		// relative area x_offset (0..1)
-		float paramWX = dlRequest.getWx();
+		double paramWX = dlRequest.getWx();
 		// relative area y_offset
-		float paramWY = dlRequest.getWy();
+		double paramWY = dlRequest.getWy();
 		// relative area width (0..1)
-		float paramWW = dlRequest.getWw();
+		double paramWW = dlRequest.getWw();
 		// relative area height
-		float paramWH = dlRequest.getWh();
+		double paramWH = dlRequest.getWh();
 		// scale factor (additional to dw/width, dh/height)
-		float paramWS = dlRequest.getWs();
+		double paramWS = dlRequest.getWs();
+		// rotation angle
+		double paramROT = dlRequest.getRot();
+		// contrast enhancement
+		double paramCONT = dlRequest.getCont();
+		// brightness enhancement
+		double paramBRGT = dlRequest.getBrgt();
 
 		/* operation mode: "fit": always fit to page, 
 		 * "clip": send original resolution cropped, "file": send whole file (if
@@ -219,6 +237,14 @@
 		} else if (dlRequest.isOption("hires")) {
 			preScaledFirst = false;
 		}
+		// operation mode: "hmir": mirror horizontally, "vmir": mirror vertically
+		if (dlRequest.isOption("hmir")) {
+			doMirror = true;
+			mirrorAngle = 0;
+		} else if (dlRequest.isOption("vmir")) {
+			doMirror = true;
+			mirrorAngle = 90;
+		}
 
 		//"big" try for all file/image actions
 		try {
@@ -228,10 +254,9 @@
 			if (docuImage == null) {
 				throw new ImageOpException("Unable to load DocuImage class!");
 			}
-			//DocuImage docuImage = new JAIDocuImage(util);
-			//DocuImage docuImage = new JIMIDocuImage(util);
-			//DocuImage docuImage = new ImageLoaderDocuImage(util);
-			//DocuImage docuImage = new JAIImageLoaderDocuImage(util);
+
+			// set interpolation quality
+			docuImage.setQuality(scaleQual);
 
 			/*
 			 *  find the file to load/send
@@ -243,7 +268,7 @@
 			if ((paramWW < 1f) || (paramWH < 1f)) {
 				preScaledFirst = false;
 			}
-			
+
 			/*
 			 * check permissions
 			 */
@@ -312,7 +337,7 @@
 			 *  crop and scale the image
 			 */
 
-			// get size
+			// get image size
 			int imgWidth = docuImage.getWidth();
 			int imgHeight = docuImage.getHeight();
 
@@ -321,14 +346,30 @@
 				2,
 				"time " + (System.currentTimeMillis() - startTime) + "ms");
 
+			// coordinates using Java2D
+			// image size
+			Rectangle2D imgBounds = new Rectangle2D.Double(0, 0, imgWidth, imgHeight);
+			// user window area in 4-point form (ul, ur, ll, lr)
+			Point2D[] userAreaC = {
+				new Point2D.Double(paramWX, paramWY),
+				new Point2D.Double(paramWX + paramWW, paramWY),
+				new Point2D.Double(paramWX, paramWY + paramWH),
+				new Point2D.Double(paramWX + paramWW, paramWY + paramWH)
+			};
+			// transformation from relative [0,1] to image coordinates.
+			AffineTransform imgTrafo = new AffineTransform();
+			imgTrafo.scale(imgWidth, imgHeight);
+			// rotate coordinates
+			//imgTrafo.rotate(Math.toRadians(-paramROT));
+			
 			// coordinates and scaling
-			float areaXoff;
-			float areaYoff;
-			float areaWidth;
-			float areaHeight;
-			float scaleX;
-			float scaleY;
-			float scaleXY;
+			double areaXoff;
+			double areaYoff;
+			double areaWidth;
+			double areaHeight;
+			double scaleX;
+			double scaleY;
+			double scaleXY;
 
 			if (scaleToFit) {
 				// calculate absolute from relative coordinates
@@ -370,8 +411,44 @@
 					+ "x"
 					+ areaHeight);
 
+			// Java2D 
+			Point2D[] imgAreaC = { null, null, null, null };
+
+			imgTrafo.transform(userAreaC, 0, imgAreaC, 0, 4);
+			areaXoff = imgAreaC[0].getX();
+			areaYoff = imgAreaC[0].getY();
+			areaWidth = imgAreaC[0].distance(imgAreaC[1]);
+			areaHeight = imgAreaC[0].distance(imgAreaC[2]);
+			Rectangle2D imgArea =
+				new Rectangle2D.Double(
+					areaXoff,
+					areaYoff,
+					areaWidth,
+					areaHeight);
+			// calculate scaling factors
+			scaleX = paramDW / areaWidth * paramWS;
+			scaleY = paramDH / areaHeight * paramWS;
+			scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
+
+			util.dprintln(
+				1,
+				"Scale "
+					+ scaleXY
+					+ "("
+					+ scaleX
+					+ ","
+					+ scaleY
+					+ ") on "
+					+ areaXoff
+					+ ","
+					+ areaYoff
+					+ " "
+					+ areaWidth
+					+ "x"
+					+ areaHeight);
+
 			// clip area at the image border
-			areaWidth =
+			/* areaWidth =
 				(areaXoff + areaWidth > imgWidth)
 					? imgWidth - areaXoff
 					: areaWidth;
@@ -379,6 +456,10 @@
 				(areaYoff + areaHeight > imgHeight)
 					? imgHeight - areaYoff
 					: areaHeight;
+			*/
+			imgArea = imgArea.createIntersection(imgBounds);
+			areaWidth = imgArea.getWidth();
+			areaHeight = imgArea.getHeight();
 
 			util.dprintln(
 				2,
@@ -401,13 +482,29 @@
 			}
 
 			// crop and scale image
-			docuImage.cropAndScale(
+			docuImage.crop(
 				(int) areaXoff,
 				(int) areaYoff,
 				(int) areaWidth,
-				(int) areaHeight,
-				scaleXY,
-				scaleQual);
+				(int) areaHeight);
+
+			docuImage.scale(scaleXY);
+
+			// mirror image
+			if (doMirror) {
+				docuImage.mirror(mirrorAngle);
+			}
+
+			// rotate image (first shot :-)
+			if (paramROT != 0) {
+				docuImage.rotate(paramROT);
+			}
+
+			// contrast and brightness enhancement
+			if ((paramCONT != 0) || (paramBRGT != 0)) {
+				double mult = Math.pow(2, paramCONT); 
+				docuImage.enhance(mult, paramBRGT);
+			}
 
 			util.dprintln(
 				2,
@@ -421,9 +518,10 @@
 			if (mimeType != "image/jpeg") {
 				mimeType = "image/png";
 			}
+			response.setContentType(mimeType);
 
 			// write the image
-			docuImage.writeImage(mimeType, response);
+			docuImage.writeImage(mimeType, response.getOutputStream());
 
 			util.dprintln(
 				1,