changeset 85:4e6757e8ccd4

New enhanced ImageLoader stuff. Now uses Subsampling and image regions on read. Now implements enhance, rotate and mirror for ImageLoader/Java2D
author robcast
date Thu, 27 Feb 2003 15:07:29 +0100
parents ed1b698b4f0a
children 997ba69afb81
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/io/FileOps.java servlet/src/digilib/servlet/DigilibConfiguration.java servlet/src/digilib/servlet/Scaler.java
diffstat 8 files changed, 517 insertions(+), 119 deletions(-) [+]
line wrap: on
line diff
--- a/servlet/src/digilib/image/DocuImage.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/image/DocuImage.java	Thu Feb 27 15:07:29 2003 +0100
@@ -2,7 +2,7 @@
 
   Digital Image Library servlet components
 
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+  Copyright (C) 2001, 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 
   This program is free software; you can redistribute  it and/or modify it
   under  the terms of  the GNU General  Public License as published by the
@@ -20,6 +20,7 @@
 
 package digilib.image;
 
+import java.awt.Rectangle;
 import java.io.File;
 import java.io.OutputStream;
 
@@ -47,6 +48,35 @@
 	 */
 	public void loadImage(File f) throws FileOpException;
 
+	/** This DocuImage support the loadSubImage operation.
+	 * 
+	 * @return boolean
+	 */
+	public boolean isSubimageSupported();
+
+	/** Load only a subsampled region of the image file.
+	 * 
+	 * @param f
+	 * @param region
+	 * @param subsample
+	 * @throws FileOpException
+	 */
+	public void loadSubimage(File f, Rectangle region, int subsample)
+		throws FileOpException;
+
+	/** This DocuImage support the preloadImage operation for getWidth/getHeight.
+	 * 
+	 * @return boolean
+	 */
+	public boolean isPreloadSupported();
+
+	/** Preload image file into a state to use getWidth/getHeight.
+	 * 
+	 * @param f
+	 * @throws FileOpException
+	 */
+	public void preloadImage(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
@@ -121,17 +151,18 @@
 		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.
+	 * around the point <code>x</code>,<code>y</code> 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
@@ -143,7 +174,7 @@
 	 * @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.
--- a/servlet/src/digilib/image/DocuImageImpl.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/image/DocuImageImpl.java	Thu Feb 27 15:07:29 2003 +0100
@@ -2,7 +2,7 @@
 
   Digital Image Library servlet components
 
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+  Copyright (C) 2001, 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 
   This program is free software; you can redistribute  it and/or modify it
   under  the terms of  the GNU General  Public License as published by the
@@ -20,7 +20,11 @@
 
 package digilib.image;
 
+import java.awt.Rectangle;
+import java.io.File;
+
 import digilib.Utils;
+import digilib.io.FileOpException;
 
 /** Simple abstract implementation of the <code>DocuImage</code> interface.
  *
@@ -37,6 +41,9 @@
 
 	/** Interpolation quality. */
 	protected int quality = 0;
+	
+	// epsilon for float comparisons
+	public final double epsilon = 1e-5;
 
 	/** Default constructor. */
 	public DocuImageImpl() {
@@ -121,4 +128,23 @@
 		// just a do-nothing implementation
 	}
 
+	public void preloadImage(File f) throws FileOpException {
+		// just a do-nothing implementation
+	}
+
+	public boolean isPreloadSupported() {
+		// preload per default not supported
+		return false;
+	}
+
+	public boolean isSubimageSupported() {
+		// partial loading per default not supported
+		return false;
+	}
+
+	public void loadSubimage(File f, Rectangle region, int subsample)
+		throws FileOpException {
+		// empty implementation
+	}
+
 }
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java	Thu Feb 27 15:07:29 2003 +0100
@@ -2,7 +2,7 @@
 
   Digital Image Library servlet components
 
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+  Copyright (C) 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 
   This program is free software; you can redistribute  it and/or modify it
   under  the terms of  the GNU General  Public License as published by the
@@ -20,16 +20,21 @@
 
 package digilib.image;
 
+import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.awt.image.AffineTransformOp;
 import java.awt.image.BufferedImage;
+import java.awt.image.RescaleOp;
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Iterator;
 
 import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
 
-import digilib.Utils;
 import digilib.io.FileOpException;
 
 /** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
@@ -37,13 +42,59 @@
 
 	private BufferedImage img;
 
-	private int scaleInt;
+	private int interpol;
+
+	// ImageIO image reader
+	ImageReader reader;
 
-	public ImageLoaderDocuImage() {
+	/* preload is supported. */
+	public boolean isPreloadSupported() {
+		return true;
+	}
+
+	/* loadSubimage is supported. */
+	public boolean isSubimageSupported() {
+		return true;
 	}
 
-	public ImageLoaderDocuImage(Utils u) {
-		util = u;
+	public void setQuality(int qual) {
+		quality = qual;
+		// setup interpolation quality
+		if (qual > 0) {
+			util.dprintln(4, "quality q1");
+			interpol = AffineTransformOp.TYPE_BILINEAR;
+		} else {
+			util.dprintln(4, "quality q0");
+			interpol = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
+		}
+	}
+	
+	public int getHeight() {
+		int h = 0;
+		try {
+			if (img == null) {
+				h = reader.getHeight(0);
+			} else {
+				h = img.getHeight();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return h;
+	}
+
+	public int getWidth() {
+		int w = 0;
+		try {
+			if (img == null) {
+				w = reader.getWidth(0);
+			} else {
+				w = img.getWidth();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return w;
 	}
 
 	/**
@@ -63,6 +114,48 @@
 		}
 	}
 
+	/* Get an ImageReader for the image file. */
+	public void preloadImage(File f) throws FileOpException {
+		System.gc();
+		try {
+			ImageInputStream istream = ImageIO.createImageInputStream(f);
+			Iterator readers = ImageIO.getImageReaders(istream);
+			reader = (ImageReader) readers.next();
+			reader.setInput(istream);
+		} catch (IOException e) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!" + e);
+		}
+		if (reader == null) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+	}
+
+	/* Load an image file into the Object. */
+	public void loadSubimage(File f, Rectangle region, int prescale)
+		throws FileOpException {
+		System.gc();
+		try {
+			if (reader == null) {
+				preloadImage(f);
+			}
+			// set up reader parameters
+			ImageReadParam readParam = reader.getDefaultReadParam();
+			readParam.setSourceRegion(region);
+			readParam.setSourceSubsampling(prescale, prescale, 0, 0);
+			// read image
+			img = reader.read(0, readParam);
+		} catch (IOException e) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+		if (img == null) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+	}
+
 	/**
 	 *  write image of type mt to Stream
 	 */
@@ -94,26 +187,12 @@
 		}
 	}
 
-	public int getWidth() {
-		if (img != null) {
-			return img.getWidth();
-		}
-		return 0;
-	}
-
-	public int getHeight() {
-		if (img != null) {
-			return img.getHeight();
-		}
-		return 0;
-	}
-
 	public void scale(double scale) throws ImageOpException {
 		// setup scale
 		AffineTransformOp scaleOp =
 			new AffineTransformOp(
 				AffineTransform.getScaleInstance(scale, scale),
-				scaleInt);
+				interpol);
 		BufferedImage scaledImg = scaleOp.filter(img, null);
 
 		if (scaledImg == null) {
@@ -141,16 +220,102 @@
 		img = croppedImg;
 	}
 
-	public void setQuality(int qual) {
-		quality = qual;
-		// setup interpolation quality
-		if (qual > 0) {
-			util.dprintln(4, "quality q1");
-			scaleInt = AffineTransformOp.TYPE_BILINEAR;
+	public void enhance(double mult, double add) throws ImageOpException {
+		/* The number of constants must match the number of bands in the image.
+		 * We only handle 1 (greyscale) or 3 (RGB) bands.
+		 */
+		float[] dm;
+		float[] da;
+		int ncol = img.getColorModel().getNumColorComponents(); 
+		if (ncol == 3) {
+			float[] f1 = {(float)mult, (float)mult, (float)mult};
+			dm = f1;
+			float[] f2 = {(float)add, (float)add, (float)add};
+			da = f2;
+		} else if (ncol == 1) {
+			float[] f1 = {(float)mult};
+			dm = f1;
+			float[] f2 = {(float)add};
+			da = f2;
 		} else {
-			util.dprintln(4, "quality q0");
-			scaleInt = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
+			util.dprintln(2, "ERROR(enhance): unknown number of color bands ("+ncol+")");
+			return;
+		}
+		RescaleOp scaleOp =
+			new RescaleOp(
+				dm, da,
+				null);
+		scaleOp.filter(img, img);
+
+		/* Operation with only one constant should work regardless of the number of bands 
+		 * according to the JDK spec.
+		 * Doesn't work on JDK 1.4 for OSX PR10 (at least).
+		 */
+		/*
+		RescaleOp scaleOp =
+			new RescaleOp(
+				(float)mult, (float)add,
+				null);
+		scaleOp.filter(img, img);
+		*/
+	}
+	
+	public void rotate(double angle) throws ImageOpException {
+		// setup rotation
+		double rangle = Math.toRadians(angle);
+		double x = getWidth()/2;
+		double y = getHeight()/2;
+		AffineTransformOp rotOp =
+			new AffineTransformOp(
+				AffineTransform.getRotateInstance(rangle, x, y),
+				interpol);
+		BufferedImage rotImg = rotOp.filter(img, null);
+
+		if (rotImg == null) {
+			util.dprintln(2, "ERROR: error in rotate");
+			throw new ImageOpException("Unable to rotate");
 		}
+		img = rotImg;
+	}
+
+	public void mirror(double angle) throws ImageOpException {
+		// 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 = getWidth();
+		} else if (Math.abs(angle - 90) < epsilon) {
+			// 90 degree
+			my = -1;
+			ty = getHeight();
+		} else if (Math.abs(angle - 180) < epsilon) {
+			// 180 degree
+			mx = -1;
+			tx = getWidth();
+		} else if (Math.abs(angle - 270) < epsilon) {
+			// 270 degree
+			my = -1;
+			ty = getHeight();
+		} else if (Math.abs(angle - 360) < epsilon) {
+			// 360 degree
+			mx = -1;
+			tx = getWidth();
+		}
+		AffineTransformOp mirOp =
+			new AffineTransformOp(
+				new AffineTransform(mx, 0 , 0, my, tx, ty),
+				interpol);
+		BufferedImage mirImg = mirOp.filter(img, null);
+
+		if (mirImg == null) {
+			util.dprintln(2, "ERROR: error in mirror");
+			throw new ImageOpException("Unable to mirror");
+		}
+		img = mirImg;
 	}
 
 }
--- a/servlet/src/digilib/image/JAIDocuImage.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/image/JAIDocuImage.java	Thu Feb 27 15:07:29 2003 +0100
@@ -2,7 +2,7 @@
 
   Digital Image Library servlet components
 
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+  Copyright (C) 2001, 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 
   This program is free software; you can redistribute  it and/or modify it
   under  the terms of  the GNU General  Public License as published by the
@@ -40,9 +40,6 @@
 	protected RenderedImage img;
 	protected Interpolation interpol = null;
 
-	// epsilon for float comparisons
-	public final double epsilon = 1e-5;
-
 	/** Default constructor. */
 	public JAIDocuImage() {
 	}
@@ -203,9 +200,8 @@
 		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;
+		float x = getWidth()/2;
+		float y = getHeight()/2;
 
 		// optimize rotation by right angles
 		TransposeType rotOp = null;
@@ -235,8 +231,8 @@
 			// setup "normal" rotation
 			ParameterBlock param = new ParameterBlock();
 			param.addSource(img);
-			param.add((float) xoff);
-			param.add((float) yoff);
+			param.add(x);
+			param.add(y);
 			param.add((float) rangle);
 			param.add(interpol);
 			// hint with border extender
@@ -251,9 +247,9 @@
 		util.dprintln(
 			3,
 			"ROTATE: "
-				+ xoff
+				+ x
 				+ ","
-				+ yoff
+				+ y
 				+ ", "
 				+ angle
 				+ " ("
--- a/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Thu Feb 27 15:07:29 2003 +0100
@@ -2,7 +2,7 @@
 
   Digital Image Library servlet components
 
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+  Copyright (C) 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 
   This program is free software; you can redistribute  it and/or modify it
   under  the terms of  the GNU General  Public License as published by the
@@ -20,13 +20,21 @@
 
 package digilib.image;
 
+import java.awt.Rectangle;
 import java.awt.image.renderable.ParameterBlock;
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Iterator;
 
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
 import javax.media.jai.JAI;
 
+import com.sun.media.jai.operator.ImageReadDescriptor;
+
 import digilib.io.FileOpException;
 
 /** DocuImage implementation using the Java Advanced Imaging API and the ImageLoader
@@ -34,6 +42,46 @@
  */
 public class JAIImageLoaderDocuImage extends JAIDocuImage {
 
+	// ImageIO image reader
+	ImageReader reader;
+
+	/* preload is supported. */
+	public boolean isPreloadSupported() {
+		return true;
+	}
+
+	/* loadSubimage is supported. */
+	public boolean isSubimageSupported() {
+		return true;
+	}
+
+	public int getHeight() {
+		int h = 0;
+		try {
+			if (img == null) {
+				h = reader.getHeight(0);
+			} else {
+				h = img.getHeight();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return h;
+	}
+
+	public int getWidth() {
+		int w = 0;
+		try {
+			if (img == null) {
+				w = reader.getWidth(0);
+			} else {
+				w = img.getWidth();
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return w;
+	}
 
 	/* Load an image file into the Object. */
 	public void loadImage(File f) throws FileOpException {
@@ -45,6 +93,62 @@
 		}
 	}
 
+	/* Load an image file into the Object. */
+	public void loadSubimage(File f, Rectangle region, int prescale)
+		throws FileOpException {
+		System.gc();
+		try {
+			if (reader == null) {
+				preloadImage(f);
+			}
+			ImageInputStream istream = (ImageInputStream) reader.getInput();
+			ImageReadParam readParam = reader.getDefaultReadParam();
+			readParam.setSourceRegion(region);
+			readParam.setSourceSubsampling(prescale, prescale, 0, 0);
+			/* Parameter for ImageRead operation:
+				Input, ImageChoice, ReadMetadata, ReadThumbnails, VerifyInput,
+				Listeners, Locale, ReadParam, Reader, RenderingHints
+			 */
+			img =
+				ImageReadDescriptor.create(
+					istream,
+					new Integer(0),
+					Boolean.TRUE,
+					Boolean.FALSE,
+					Boolean.FALSE,
+					null,
+					null,
+					readParam,
+					reader,
+					null);
+		} catch (IOException e) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+		if (img == null) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+	}
+
+	/* Get an ImageReader for the image file. */
+	public void preloadImage(File f) throws FileOpException {
+		System.gc();
+		try {
+			ImageInputStream istream = ImageIO.createImageInputStream(f);
+			Iterator readers = ImageIO.getImageReaders(istream);
+			reader = (ImageReader) readers.next();
+			reader.setInput(istream);
+		} catch (IOException e) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!" + e);
+		}
+		if (reader == null) {
+			util.dprintln(3, "ERROR(loadImage): unable to load file");
+			throw new FileOpException("Unable to load File!");
+		}
+	}
+
 	/* Write the current image to an OutputStream. */
 	public void writeImage(String mt, OutputStream ostream)
 		throws FileOpException {
--- a/servlet/src/digilib/io/FileOps.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/io/FileOps.java	Thu Feb 27 15:07:29 2003 +0100
@@ -1,4 +1,4 @@
-/*  FileOps -- Utility class for file operations
+/* FileOps -- Utility class for file operations
 
   Digital Image Library servlet components
 
--- a/servlet/src/digilib/servlet/DigilibConfiguration.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/servlet/DigilibConfiguration.java	Thu Feb 27 15:07:29 2003 +0100
@@ -77,7 +77,7 @@
 	private int debugLevel = 5;
 	private String debugLevelParam = "debug-level";
 	// Utils instance
-	private Utils util = new Utils();
+	private Utils util = new Utils(debugLevel);
 	// HashTable for parameters
 	private Hashtable confTable = null;
 	// Type of DocuImage instance
@@ -86,6 +86,9 @@
 	// part of URL used to indicate authorized access
 	private String authURLPath = "authenticated/";
 	private String AuthURLPathParam = "auth-url-path";
+	// degree of subsampling on image load
+	private float subsampleDistance = 0;
+	private String subsampleDistanceParam = "subsample-distance";
 
 	/** Constructor taking a ServletConfig.
 	 * Reads the config file location from an init parameter and loads the
@@ -117,7 +120,7 @@
 		XMLListLoader lilo =
 			new XMLListLoader("digilib-config", "parameter", "name", "value");
 		confTable = lilo.loadURL(f.toURL().toString());
-		dlConfPath = fn;
+		dlConfPath = f.getCanonicalPath();
 
 		/* 
 		 * read parameters
@@ -125,6 +128,7 @@
 		 
 		// debugLevel
 		debugLevel = tryToGetInitParam(debugLevelParam, debugLevel);
+		util.setDebugLevel(debugLevel);
 		// errorImgFileName
 		errorImgFileName = tryToGetInitParam(errorImgParam, errorImgFileName);
 		// denyImgFileName
@@ -151,6 +155,8 @@
 			authConfPath = tryToGetInitParam(authConfParam, authConfPath);
 			authOp = new XMLAuthOps(util, authConfPath);
 		}
+		// subsampleDistance
+		subsampleDistance = tryToGetInitParam(subsampleDistanceParam, subsampleDistance);
 	}
 
 	/**
@@ -455,4 +461,19 @@
 		return dlConfPath;
 	}
 
+	/**
+	 * @return float
+	 */
+	public float getSubsampleDistance() {
+		return subsampleDistance;
+	}
+
+	/**
+	 * Sets the subsampleDistance.
+	 * @param subsampleDistance The subsampleDistance to set
+	 */
+	public void setSubsampleDistance(float subsampleDistance) {
+		this.subsampleDistance = subsampleDistance;
+	}
+
 }
--- a/servlet/src/digilib/servlet/Scaler.java	Mon Feb 10 17:09:28 2003 +0100
+++ b/servlet/src/digilib/servlet/Scaler.java	Thu Feb 27 15:07:29 2003 +0100
@@ -52,7 +52,7 @@
 public class Scaler extends HttpServlet {
 
 	// digilib servlet version (for all components)
-	public static final String dlVersion = "1.6b";
+	public static final String dlVersion = "1.6b3";
 
 	// Utils instance with debuglevel
 	Utils util;
@@ -331,7 +331,12 @@
 			}
 
 			// finally load the file
-			docuImage.loadImage(fileToLoad);
+			if (docuImage.isPreloadSupported()) {
+				// only preload if supported
+				docuImage.preloadImage(fileToLoad);
+			} else {
+				docuImage.loadImage(fileToLoad);
+			}
 
 			/*
 			 *  crop and scale the image
@@ -348,20 +353,21 @@
 
 			// coordinates using Java2D
 			// image size
-			Rectangle2D imgBounds = new Rectangle2D.Double(0, 0, imgWidth, imgHeight);
+			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)
-			};
+			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
 			double areaXoff;
 			double areaYoff;
@@ -371,27 +377,68 @@
 			double scaleY;
 			double scaleXY;
 
+			/*			if (scaleToFit) {
+							// calculate absolute from relative coordinates
+							areaXoff = paramWX * imgWidth;
+							areaYoff = paramWY * imgHeight;
+							areaWidth = paramWW * imgWidth;
+							areaHeight = paramWH * imgHeight;
+							// calculate scaling factors
+							scaleX = paramDW / areaWidth * paramWS;
+							scaleY = paramDH / areaHeight * paramWS;
+							scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
+						} else {
+							// crop to fit
+							// calculate absolute from relative coordinates
+							areaXoff = paramWX * imgWidth;
+							areaYoff = paramWY * imgHeight;
+							areaWidth = paramDW;
+							areaHeight = paramDH;
+							// calculate scaling factors
+							scaleX = 1f;
+							scaleY = 1f;
+							scaleXY = 1f;
+						}
+			
+						util.dprintln(
+							1,
+							"Scale "
+								+ scaleXY
+								+ "("
+								+ scaleX
+								+ ","
+								+ scaleY
+								+ ") on "
+								+ areaXoff
+								+ ","
+								+ areaYoff
+								+ " "
+								+ areaWidth
+								+ "x"
+								+ areaHeight);
+			*/
+			// Java2D 
+			// area in image pixel coordinates
+			Point2D[] imgAreaC = { null, null, null, null };
+			// transform user coordinate area to image coordinate area
+			imgTrafo.transform(userAreaC, 0, imgAreaC, 0, 4);
+			areaXoff = imgAreaC[0].getX();
+			areaYoff = imgAreaC[0].getY();
+			// calculate scaling factors
 			if (scaleToFit) {
-				// calculate absolute from relative coordinates
-				areaXoff = paramWX * imgWidth;
-				areaYoff = paramWY * imgHeight;
-				areaWidth = paramWW * imgWidth;
-				areaHeight = paramWH * imgHeight;
-				// calculate scaling factors
+				areaWidth = imgAreaC[0].distance(imgAreaC[1]);
+				areaHeight = imgAreaC[0].distance(imgAreaC[2]);
 				scaleX = paramDW / areaWidth * paramWS;
 				scaleY = paramDH / areaHeight * paramWS;
 				scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
 			} else {
 				// crop to fit
-				// calculate absolute from relative coordinates
-				areaXoff = paramWX * imgWidth;
-				areaYoff = paramWY * imgHeight;
-				areaWidth = paramDW;
-				areaHeight = paramDH;
-				// calculate scaling factors
+				areaWidth = paramDW * paramWS;
+				areaHeight = paramDH * paramWS;
 				scaleX = 1f;
 				scaleY = 1f;
 				scaleXY = 1f;
+
 			}
 
 			util.dprintln(
@@ -411,42 +458,6 @@
 					+ "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 =
 				(areaXoff + areaWidth > imgWidth)
@@ -457,13 +468,22 @@
 					? imgHeight - areaYoff
 					: areaHeight;
 			*/
+
+			// create new rectangle from coordinates
+			Rectangle2D imgArea =
+				new Rectangle2D.Double(
+					areaXoff,
+					areaYoff,
+					areaWidth,
+					areaHeight);
+			// clip area at the image border
 			imgArea = imgArea.createIntersection(imgBounds);
 			areaWidth = imgArea.getWidth();
 			areaHeight = imgArea.getHeight();
 
 			util.dprintln(
 				2,
-				"cropped: "
+				"crop: "
 					+ areaXoff
 					+ ","
 					+ areaYoff
@@ -481,14 +501,48 @@
 				throw new ImageOpException("Invalid scale parameter set!");
 			}
 
-			// crop and scale image
-			docuImage.crop(
-				(int) areaXoff,
-				(int) areaYoff,
-				(int) areaWidth,
-				(int) areaHeight);
+			/* 
+			 * crop and scale image
+			 */
+
+			// use subimage loading if possible
+			if (docuImage.isSubimageSupported()) {
+				System.out.println(
+					"Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
+				double subf = 1d;
+				double subsamp = 1d;
+				if (scaleXY < 1) {
+					subf = 1 / scaleXY;
+					subsamp = Math.floor(subf);
+					scaleXY = subsamp / subf;
+					System.out.println(
+						"Using subsampling: " + subsamp + " rest " + scaleXY);
+				}
 
-			docuImage.scale(scaleXY);
+				docuImage.loadSubimage(
+					fileToLoad,
+					imgArea.getBounds(),
+					(int) subsamp);
+
+				System.out.println(
+					"SUBSAMP: "
+						+ subsamp
+						+ " -> "
+						+ docuImage.getWidth()
+						+ "x"
+						+ docuImage.getHeight());
+
+				docuImage.scale(scaleXY);
+
+			} else {
+				docuImage.crop(
+					(int) areaXoff,
+					(int) areaYoff,
+					(int) areaWidth,
+					(int) areaHeight);
+
+				docuImage.scale(scaleXY);
+			}
 
 			// mirror image
 			if (doMirror) {
@@ -497,12 +551,13 @@
 
 			// rotate image (first shot :-)
 			if (paramROT != 0) {
-				docuImage.rotate(paramROT);
+				docuImage.rotate(
+					paramROT);
 			}
 
 			// contrast and brightness enhancement
 			if ((paramCONT != 0) || (paramBRGT != 0)) {
-				double mult = Math.pow(2, paramCONT); 
+				double mult = Math.pow(2, paramCONT);
 				docuImage.enhance(mult, paramBRGT);
 			}