changeset 1204:a6532856dae1

IIIF region in pixel units works now. small cleanups.
author robcast
date Fri, 19 Jul 2013 19:08:39 +0200
parents 5d0eeccca1d9
children a5aea37fac03
files common/src/main/java/digilib/conf/DigilibRequest.java common/src/main/java/digilib/image/ImageJobDescription.java common/src/main/java/digilib/image/ImageWorker.java servlet/src/main/java/digilib/conf/DigilibServletRequest.java
diffstat 4 files changed, 586 insertions(+), 525 deletions(-) [+]
line wrap: on
line diff
--- a/common/src/main/java/digilib/conf/DigilibRequest.java	Fri Jul 19 14:08:29 2013 +0200
+++ b/common/src/main/java/digilib/conf/DigilibRequest.java	Fri Jul 19 19:08:39 2013 +0200
@@ -55,7 +55,7 @@
  * wh: height of image area(float from 0 to 1). <br>
  * ws: scale factor. <br>
  * mo: special options like 'fit'. <br>
- * ...et alii
+ * ...et al
  * 
  * @author casties
  * 
@@ -88,8 +88,7 @@
     }
 
     /**
-     * set up parameters.
-     * 
+     * Define and set up parameters with default values.
      */
     protected void initParams() {
         /*
@@ -266,7 +265,7 @@
     }
 
     /**
-     * Populate a request from a string with an IIIF Image API path.
+     * Populate a request from a string with an IIIF image API path.
      * 
      * path should be non-URL-decoded and have no leading slash.
      * 
@@ -285,7 +284,9 @@
         // enable passing of delimiter to get empty parameters
         StringTokenizer query = new StringTokenizer(path, "/", true);
         String token;
-        // first parameter prefix
+        /*
+         * first parameter prefix
+         */
         if (query.hasMoreTokens()) {
             token = query.nextToken();
             if (!token.equals(iiifPrefix)) {
@@ -297,7 +298,9 @@
                 query.nextToken();
             }
         }
-        // second parameter FN (encoded)
+        /*
+         * second parameter FN (encoded)
+         */
         if (query.hasMoreTokens()) {
             token = query.nextToken();
             if (!token.equals("/")) {
@@ -312,7 +315,9 @@
                 }
             }
         }
-        // third parameter region
+        /*
+         * third parameter region
+         */
         if (query.hasMoreTokens()) {
             token = query.nextToken();
             if (!token.equals("/")) {
@@ -323,7 +328,7 @@
                 } else if (token.equals("full")) {
                     // full region -- default
                 } else if (token.startsWith("pct:")){
-                    // region in % of original image
+                    // pct:x,y,w,h -- region in % of original image
                     String[] parms = token.substring(4).split(",");
                     try {
                         float x = Float.parseFloat(parms[0]);
@@ -338,8 +343,17 @@
                         logger.error("Error parsing range parameter in IIIF path!", e);
                     }
                 } else {
-                    // region in pixel of original image :-(
-                    logger.error("pixel region not yet implemented");
+                    // x,y,w,h -- region in pixel of original image :-(
+                    String[] parms = token.split(",");
+                    if (parms.length != 4) {
+                        logger.error("Error parsing range parameter in IIIF path!");
+                    } else {
+                        options.setOption("pxarea");
+                        setValueFromString("wx", parms[0]);                            
+                        setValueFromString("wy", parms[1]);                            
+                        setValueFromString("ww", parms[2]);                            
+                        setValueFromString("wh", parms[3]);                            
+                    }
                 }
                 // skip /
                 if (query.hasMoreTokens()) {
@@ -351,7 +365,9 @@
             options.setOption("info");
             return;
         }
-        // fourth parameter size
+        /*
+         * fourth parameter size
+         */
         if (query.hasMoreTokens()) {
             token = query.nextToken();
             if (!token.equals("/")) {
@@ -401,7 +417,9 @@
             setValue("scale", 1f);
             return;
         }
-        // fifth parameter rotation
+        /*
+         * fifth parameter rotation
+         */
         if (query.hasMoreTokens()) {
             token = query.nextToken();
             if (!token.equals("/")) {
@@ -412,7 +430,9 @@
                 }
             }
         }
-        // sixth parameter quality.format
+        /*
+         * sixth parameter quality.format
+         */
         if (query.hasMoreTokens()) {
             token = query.nextToken();
             // quality.format -- color depth and output format
--- a/common/src/main/java/digilib/image/ImageJobDescription.java	Fri Jul 19 14:08:29 2013 +0200
+++ b/common/src/main/java/digilib/image/ImageJobDescription.java	Fri Jul 19 19:08:39 2013 +0200
@@ -47,7 +47,7 @@
 import digilib.util.ParameterMap;
 
 /**
- * A class for storing the set of parameters necessary for scaling images 
+ * A class for storing the set of parameters necessary for scaling images
  * with an ImageWorker.
  * 
  * This contains the functionality formerly found in Scaler.processRequest(),
@@ -58,107 +58,119 @@
  */
 
 public class ImageJobDescription extends ParameterMap {
-	
-	DigilibConfiguration dlConfig = null;
-	protected static Logger logger = Logger.getLogger("digilib.servlet");
+
+    DigilibConfiguration dlConfig = null;
+    protected static Logger logger = Logger.getLogger("digilib.servlet");
 
-	ImageInput input = null;
-	ImageSet imageSet = null;
-	DocuDirectory fileDir = null;
-	DocuImage docuImage = null;
-	String filePath = null;
-	ImageSize expectedSourceSize = null;
-	Float scaleXY = null;
-	Rectangle2D userImgArea = null;
-	Rectangle2D outerUserImgArea = null;
-	Boolean imageSendable = null;
-	String mimeType = null;
-	Integer paramDW = null;
-	Integer paramDH = null;
+    /* 
+     * variables for caching values
+     */
+    ImageInput input = null;
+    ImageSet imageSet = null;
+    DocuDirectory fileDir = null;
+    DocuImage docuImage = null;
+    String filePath = null;
+    ImageSize expectedSourceSize = null;
+    Float scaleXY = null;
+    Rectangle2D userImgArea = null;
+    Rectangle2D outerUserImgArea = null;
+    Boolean imageSendable = null;
+    String mimeType = null;
+    Integer paramDW = null;
+    Integer paramDH = null;
 
-	/** create empty ImageJobDescription.
-	 * @param dlcfg
-	 */
-	public ImageJobDescription(DigilibConfiguration dlcfg) {
-		super(30);
-		dlConfig = dlcfg;
-	}
-
+    /**
+     * create empty ImageJobDescription.
+     * 
+     * @param dlcfg
+     */
+    public ImageJobDescription(DigilibConfiguration dlcfg) {
+        super(30);
+        dlConfig = dlcfg;
+    }
 
-	/** set up Parameters
-	 * @see digilib.util.ParameterMap#initParams()
-	 */
-	@Override
-	protected void initParams() {
-		// url of the page/document (second part)
-		newParameter("fn", "", null, 's');
-		// page number
-		newParameter("pn", new Integer(1), null, 's');
-		// width of client in pixels
-		newParameter("dw", new Integer(0), null, 's');
-		// height of client in pixels
-		newParameter("dh", new Integer(0), null, 's');
-		// left edge of image (float from 0 to 1)
-		newParameter("wx", new Float(0), null, 's');
-		// top edge in image (float from 0 to 1)
-		newParameter("wy", new Float(0), null, 's');
-		// width of image (float from 0 to 1)
-		newParameter("ww", new Float(1), null, 's');
-		// height of image (float from 0 to 1)
-		newParameter("wh", new Float(1), null, 's');
-		// scale factor
-		newParameter("ws", new Float(1), null, 's');
-		// special options like 'fit' for gifs
-		newParameter("mo", this.options, null, 's');
-		// rotation angle (degree)
-		newParameter("rot", new Float(0), null, 's');
-		// contrast enhancement factor
-		newParameter("cont", new Float(0), null, 's');
-		// brightness enhancement factor
-		newParameter("brgt", new Float(0), null, 's');
-		// color multiplicative factors
-		newParameter("rgbm", "0/0/0", null, 's');
-		// color additive factors
-		newParameter("rgba", "0/0/0", null, 's');
-		// display dpi resolution (total)
-		newParameter("ddpi", new Float(0), null, 's');
-		// display dpi X resolution
-		newParameter("ddpix", new Float(0), null, 's');
-		// display dpi Y resolution
-		newParameter("ddpiy", new Float(0), null, 's');
-		// scale factor for mo=ascale
-		newParameter("scale", new Float(1), null, 's');
-		// color conversion operation
-		newParameter("colop", "", null, 's');
-	}
+    /**
+     * set up Parameters
+     * 
+     * @see digilib.util.ParameterMap#initParams()
+     */
+    @Override
+    protected void initParams() {
+        // url of the page/document (second part)
+        newParameter("fn", "", null, 's');
+        // page number
+        newParameter("pn", new Integer(1), null, 's');
+        // width of client in pixels
+        newParameter("dw", new Integer(0), null, 's');
+        // height of client in pixels
+        newParameter("dh", new Integer(0), null, 's');
+        // left edge of image (float from 0 to 1)
+        newParameter("wx", new Float(0), null, 's');
+        // top edge in image (float from 0 to 1)
+        newParameter("wy", new Float(0), null, 's');
+        // width of image (float from 0 to 1)
+        newParameter("ww", new Float(1), null, 's');
+        // height of image (float from 0 to 1)
+        newParameter("wh", new Float(1), null, 's');
+        // scale factor
+        newParameter("ws", new Float(1), null, 's');
+        // special options like 'fit' for gifs
+        newParameter("mo", this.options, null, 's');
+        // rotation angle (degree)
+        newParameter("rot", new Float(0), null, 's');
+        // contrast enhancement factor
+        newParameter("cont", new Float(0), null, 's');
+        // brightness enhancement factor
+        newParameter("brgt", new Float(0), null, 's');
+        // color multiplicative factors
+        newParameter("rgbm", "0/0/0", null, 's');
+        // color additive factors
+        newParameter("rgba", "0/0/0", null, 's');
+        // display dpi resolution (total)
+        newParameter("ddpi", new Float(0), null, 's');
+        // display dpi X resolution
+        newParameter("ddpix", new Float(0), null, 's');
+        // display dpi Y resolution
+        newParameter("ddpiy", new Float(0), null, 's');
+        // scale factor for mo=ascale
+        newParameter("scale", new Float(1), null, 's');
+        // color conversion operation
+        newParameter("colop", "", null, 's');
+    }
 
-
-	/* (non-Javadoc)
-	 * @see digilib.servlet.ParameterMap#initOptions()
-	 */
-	@Override
-	protected void initOptions() {
-		String s = this.getAsString("mo");
-		options = new OptionsSet(s);
-	}
-
+    /*
+     * (non-Javadoc)
+     * 
+     * @see digilib.servlet.ParameterMap#initOptions()
+     */
+    @Override
+    protected void initOptions() {
+        String s = this.getAsString("mo");
+        options = new OptionsSet(s);
+    }
 
-	/** Creates new ImageJobDescription by merging Parameters from a DigilibRequest.
-	 * @param dlReq
-	 * @param dlcfg
-	 * @return
-	 */
-	public static ImageJobDescription getInstance(DigilibRequest dlReq, DigilibConfiguration dlcfg) {
-		ImageJobDescription newMap = new ImageJobDescription(dlcfg);
-		// add all params to this map
-		newMap.params.putAll(dlReq.getParams());
-		newMap.initOptions();
-		// add ImageJobDescription back into DigilibRequest
-		dlReq.setJobDescription(newMap);
-		return newMap;
-	}
+    /**
+     * Creates new ImageJobDescription by merging Parameters from a
+     * DigilibRequest.
+     * 
+     * @param dlReq
+     * @param dlcfg
+     * @return
+     */
+    public static ImageJobDescription getInstance(DigilibRequest dlReq, DigilibConfiguration dlcfg) {
+        ImageJobDescription newMap = new ImageJobDescription(dlcfg);
+        // add all params to this map
+        newMap.params.putAll(dlReq.getParams());
+        newMap.initOptions();
+        // add ImageJobDescription back into DigilibRequest
+        dlReq.setJobDescription(newMap);
+        return newMap;
+    }
 
-    /** Creates new ImageJobDescription by merging Parameters from another ParameterMap.
+    /**
+     * Creates new ImageJobDescription by merging Parameters from another
+     * ParameterMap.
+     * 
      * @param pm
      * @param dlcfg
      * @return
@@ -171,410 +183,439 @@
         return newMap;
     }
 
-	
-	/** Returns the mime-type (of the input). 
-	 * @return
-	 * @throws IOException
-	 */
-	public String getMimeType() throws IOException {
-		if (mimeType == null) {
-			input = getInput();
-			mimeType = input.getMimetype();
-		}
-		return mimeType;
-	}
-	
-	/**
-     * @param input the input to set
+    /**
+     * Returns the mime-type (of the input).
+     * 
+     * @return
+     * @throws IOException
+     */
+    public String getMimeType() throws IOException {
+        if (mimeType == null) {
+            input = getInput();
+            mimeType = input.getMimetype();
+        }
+        return mimeType;
+    }
+
+    /**
+     * @param input
+     *            the input to set
      */
     public void setInput(ImageInput input) {
         this.input = input;
     }
 
+    /**
+     * Returns the ImageInput to use.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public ImageInput getInput() throws IOException {
+        if (input == null) {
+            imageSet = getImageSet();
 
-    /** Returns the ImageInput to use.
-	 * @return
-	 * @throws IOException
-	 */
-	public ImageInput getInput() throws IOException {
-		if(input == null){
-			imageSet = getImageSet();
-			
-			/* select a resolution */
-			if (isHiresOnly()) {
-				// get first element (= highest resolution)
-				input = imageSet.getBiggest();
-			} else if (isLoresOnly()) {
-				// enforced lores uses next smaller resolution
-				input = imageSet.getNextSmaller(getExpectedSourceSize());
-				if (input == null) {
-					// this is the smallest we have
-					input = imageSet.getSmallest();
-				}
-			} else {
-				// autores: use next higher resolution
-				input = imageSet.getNextBigger(getExpectedSourceSize());
-				if (input == null) {
-					// this is the highest we have
-					input = imageSet.getBiggest();
-				}
-			}
-			if (input == null || input.getMimetype() == null) {
-			    throw new FileOpException("Unable to load "+input);
-			}
+            /* select a resolution */
+            if (isHiresOnly()) {
+                // get first element (= highest resolution)
+                input = imageSet.getBiggest();
+            } else if (isLoresOnly()) {
+                // enforced lores uses next smaller resolution
+                input = imageSet.getNextSmaller(getExpectedSourceSize());
+                if (input == null) {
+                    // this is the smallest we have
+                    input = imageSet.getSmallest();
+                }
+            } else {
+                // autores: use next higher resolution
+                input = imageSet.getNextBigger(getExpectedSourceSize());
+                if (input == null) {
+                    // this is the highest we have
+                    input = imageSet.getBiggest();
+                }
+            }
+            if (input == null || input.getMimetype() == null) {
+                throw new FileOpException("Unable to load " + input);
+            }
             logger.info("Planning to load: " + input);
-		}
-		return input;
-	}
-	
-	/** Returns the DocuDirectory for the input (file). 
-	 * @return
-	 * @throws FileOpException
-	 */
-	public DocuDirectory getFileDirectory() throws FileOpException {
-		if(fileDir == null){
-			DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
-			String fp = getFilePath();
-			fileDir = dirCache.getDirectory(fp);
-			if (fileDir == null) {
-				throw new FileOpException("Directory " + getFilePath() + " not found.");
-			}
-		}
-		return fileDir;
-	}
-	
-    /** Returns the ImageSet to load.
+        }
+        return input;
+    }
+
+    /**
+     * Returns the DocuDirectory for the input (file).
+     * 
+     * @return
+     * @throws FileOpException
+     */
+    public DocuDirectory getFileDirectory() throws FileOpException {
+        if (fileDir == null) {
+            DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+            String fp = getFilePath();
+            fileDir = dirCache.getDirectory(fp);
+            if (fileDir == null) {
+                throw new FileOpException("Directory " + getFilePath() + " not found.");
+            }
+        }
+        return fileDir;
+    }
+
+    /**
+     * Returns the ImageSet to load.
+     * 
      * @return
      * @throws FileOpException
      */
     public ImageSet getImageSet() throws FileOpException {
-        if(imageSet==null){
+        if (imageSet == null) {
             DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
-    
+
             imageSet = (ImageSet) dirCache.getFile(getFilePath(), getAsInt("pn"), FileClass.IMAGE);
             if (imageSet == null) {
-                throw new FileOpException("File " + getFilePath() + "("
-                        + getAsInt("pn") + ") not found.");
+                throw new FileOpException("File " + getFilePath() + "(" + getAsInt("pn") + ") not found.");
             }
         }
         return imageSet;
     }
-    
-	/** Returns the file path name from the request.
-	 * @return
-	 */
-	public String getFilePath() {
-		if(filePath == null){
-			String s = this.getAsString("request.path");
-			s += this.getAsString("fn");
-			filePath = FileOps.normalName(s);
-		}
-		return filePath;
-	}
+
+    /**
+     * Returns the file path name from the request.
+     * 
+     * @return
+     */
+    public String getFilePath() {
+        if (filePath == null) {
+            String s = this.getAsString("request.path");
+            s += this.getAsString("fn");
+            filePath = FileOps.normalName(s);
+        }
+        return filePath;
+    }
 
-	public boolean isHiresOnly(){
-		return hasOption("clip") || hasOption("hires");
-	}
-	
-	public boolean isLoresOnly(){
-		return hasOption("lores");
-	}
+    public boolean isHiresOnly() {
+        return hasOption("clip") || hasOption("hires") || hasOption("pxarea");
+    }
+
+    public boolean isLoresOnly() {
+        return hasOption("lores");
+    }
 
-	public boolean isScaleToFit() {
-		return !(hasOption("clip") || hasOption("osize") || hasOption("ascale"));
-	}
+    public boolean isScaleToFit() {
+        return !(hasOption("clip") || hasOption("osize") || hasOption("ascale"));
+    }
+
+    public boolean isAbsoluteScale() {
+        return hasOption("osize") || hasOption("ascale");
+    }
 
-	public boolean isAbsoluteScale(){
-		return hasOption("osize") || hasOption("ascale");
-	}
-	
-	
-	/** Returns the minimum size the source image should have for scaling.
-	 * @return
-	 * @throws IOException
-	 */
-	public ImageSize getExpectedSourceSize() throws IOException {
-		if (expectedSourceSize == null){
-			expectedSourceSize = new ImageSize();
-			if (isScaleToFit()) {
-				// scale to fit -- calculate minimum source size
-				float scale = (1 / Math.min(getAsFloat("ww"), getAsFloat("wh"))) * getAsFloat("ws");
-				expectedSourceSize.setSize((int) (getDw() * scale),
-						(int) (getDh() * scale));
-			} else if (isAbsoluteScale() && hasOption("ascale")) {
-				// absolute scale -- apply scale to hires size
-				expectedSourceSize = getHiresSize().getScaled(getAsFloat("scale"));
-			} else {
-				// clip to fit -- source = destination size
-				expectedSourceSize.setSize((int) (getDw() * getAsFloat("ws")),
-						(int) (getDh() * getAsFloat("ws")));
-			}
-		}
-		return expectedSourceSize;
-	}
-	
-	/** Returns the size of the highest resolution image.
-	 * @return
-	 * @throws IOException
-	 */
-	public ImageSize getHiresSize() throws IOException {
-		logger.debug("get_hiresSize()");
+    /**
+     * Returns the minimum size the source image should have for scaling.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public ImageSize getExpectedSourceSize() throws IOException {
+        if (expectedSourceSize == null) {
+            expectedSourceSize = new ImageSize();
+            if (isScaleToFit()) {
+                // scale to fit -- calculate minimum source size
+                float scale = (1 / Math.min(getAsFloat("ww"), getAsFloat("wh"))) * getAsFloat("ws");
+                expectedSourceSize.setSize((int) (getDw() * scale), (int) (getDh() * scale));
+            } else if (isAbsoluteScale() && hasOption("ascale")) {
+                // absolute scale -- apply scale to hires size
+                expectedSourceSize = getHiresSize().getScaled(getAsFloat("scale"));
+            } else {
+                // clip to fit -- source = destination size
+                expectedSourceSize.setSize((int) (getDw() * getAsFloat("ws")), (int) (getDh() * getAsFloat("ws")));
+            }
+        }
+        return expectedSourceSize;
+    }
+
+    /**
+     * Returns the size of the highest resolution image.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public ImageSize getHiresSize() throws IOException {
+        logger.debug("get_hiresSize()");
+
+        ImageSize hiresSize = null;
+        ImageSet fileset = getImageSet();
+        if (isAbsoluteScale()) {
+            ImageInput hiresFile = fileset.getBiggest();
+            hiresSize = hiresFile.getSize();
+        }
+        return hiresSize;
+    }
+
+    /**
+     * Returns image scaling factor.
+     * Uses image size and user parameters.
+     * Modifies scaleXY, userImgArea.
+     * 
+     * @return
+     * @throws IOException
+     * @throws ImageOpException
+     */
+    public float getScaleXY() throws IOException, ImageOpException {
+        // logger.debug("get_scaleXY()");
+        if (scaleXY != null) {
+            return (float) scaleXY;
+        }
+
+        /*
+         * calculate region of interest
+         */
+        float areaWidth;
+        float areaHeight;
+        // size of the currently selected input image
+        ImageSize imgSize = getInput().getSize();
+        if (!options.hasOption("pxarea")) {
+            // user area is in [0,1] coordinates
+            Rectangle2D relUserArea = new Rectangle2D.Float(getAsFloat("wx"), getAsFloat("wy"), getAsFloat("ww"), getAsFloat("wh"));
+            // transform from relative [0,1] to image coordinates.
+            AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize.getWidth(), imgSize.getHeight());
+            // transform user coordinate area to image coordinate area
+            userImgArea = imgTrafo.createTransformedShape(relUserArea).getBounds2D();
+        } else {
+            // ugly: user area in pixel coordinates
+            userImgArea = new Rectangle2D.Float(getAsFloat("wx"), getAsFloat("wy"), getAsFloat("ww"), getAsFloat("wh"));
+        }
 
-		ImageSize hiresSize = null;
-		ImageSet fileset = getImageSet();
-		if (isAbsoluteScale()) {
-			ImageInput hiresFile = fileset.getBiggest();
-			hiresSize = hiresFile.getSize();
-		}
-		return hiresSize;
-	}
-	
-	/** Returns image scaling factor.
-	 * Uses image size and user parameters.
-	 * Modifies scaleXY, userImgArea. 
-	 * @return
-	 * @throws IOException
-	 * @throws ImageOpException
-	 */
-	public float getScaleXY() throws IOException, ImageOpException {
-		//logger.debug("get_scaleXY()");
-		if(scaleXY == null){
-			// coordinates and scaling
-			float areaWidth;
-			float areaHeight;
-			float ws = getAsFloat("ws");
-			ImageSize imgSize = getInput().getSize();
-			// user window area in [0,1] coordinates
-			Rectangle2D relUserArea = new Rectangle2D.Float(getAsFloat("wx"), getAsFloat("wy"),
-					getAsFloat("ww"), getAsFloat("wh"));
-			// transform from relative [0,1] to image coordinates.
-			AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize
-					.getWidth(), imgSize.getHeight());
-			// transform user coordinate area to image coordinate area
-			userImgArea = imgTrafo.createTransformedShape(
-					relUserArea).getBounds2D();
-	
-			if (isScaleToFit()) {
-				// calculate scaling factors based on inner user area
-				areaWidth = (float) userImgArea.getWidth();
-				areaHeight = (float) userImgArea.getHeight();
-				float scaleX = getDw() / areaWidth * ws;
-				float scaleY = getDh() / areaHeight * ws;
-				scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
-			} else if (isAbsoluteScale()) {
-				// absolute scaling factor
-				if (hasOption("osize")) {
-					// get original resolution from metadata
-					imageSet.checkMeta();
-					float origResX = imageSet.getResX();
-					float origResY = imageSet.getResY();
-					if ((origResX == 0) || (origResY == 0)) {
-						throw new ImageOpException("Missing image DPI information!");
-					}
-					float ddpix = getAsFloat("ddpix");
-                    float ddpiy = getAsFloat("ddpiy");
-					if (ddpix == 0 || ddpiy == 0) {
-					    float ddpi = getAsFloat("ddpi");
-					    if (ddpi == 0) {
-					        throw new ImageOpException("Missing display DPI information!");
-					    } else {
-					        ddpix = ddpi;
-					        ddpiy = ddpi;
-					    }
-					}
-					// calculate absolute scale factor
-					float sx = ddpix / origResX;
-					float sy = ddpiy / origResY;
-					// currently only same scale -- mean value
-					scaleXY = (sx + sy) / 2f;
-				} else {
-				    // absolute scale factor
-					scaleXY = getAsFloat("scale");
-					// use original size if no destination size given
-					if (getDw() == 0 && getDh() == 0) {
-					    paramDW = (int) userImgArea.getWidth();
-                        paramDH = (int) userImgArea.getHeight();
-					}
-				}
-				// we need to correct the factor if we use a pre-scaled image
-				ImageSize hiresSize = getHiresSize();
-				if (imgSize.getWidth() != hiresSize.getWidth()) {
-					scaleXY *= (float)hiresSize.getWidth() / (float)imgSize.getWidth();
-				}
-				areaWidth = getDw() / scaleXY * ws;
-				areaHeight = getDh() / scaleXY * ws;
-				// reset user area size
-				userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
-						areaWidth, areaHeight);
-			} else {
-				// crop to fit -- don't scale
-				areaWidth = getDw() * ws;
-				areaHeight = getDh() * ws;
-				// reset user area size
-				userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
-						areaWidth, areaHeight);
-				scaleXY = 1f;
-			}
-		}
-		return (float) scaleXY;
-	}
-	
-	/** Returns the width of the destination image.
-	 * Uses dh parameter and aspect ratio if dw parameter is empty. 
-	 * @return
-	 * @throws IOException
-	 */
-	public int getDw() throws IOException {
-		logger.debug("get_paramDW()");
-		if (paramDW == null) {
+        /*
+         * calculate scaling factor
+         */
+        float ws = getAsFloat("ws");
+        if (isScaleToFit()) {
+            /*
+             * scale to fit -- scaling factor based on destination size and user area
+             */
+            areaWidth = (float) userImgArea.getWidth();
+            areaHeight = (float) userImgArea.getHeight();
+            float scaleX = getDw() / areaWidth * ws;
+            float scaleY = getDh() / areaHeight * ws;
+            scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
+        } else if (isAbsoluteScale()) {
+            /*
+             * absolute scaling factor -- either original size, based on dpi, or absolute 
+             */
+            if (hasOption("osize")) {
+                // get original resolution from metadata
+                imageSet.checkMeta();
+                float origResX = imageSet.getResX();
+                float origResY = imageSet.getResY();
+                if ((origResX == 0) || (origResY == 0)) {
+                    throw new ImageOpException("Missing image DPI information!");
+                }
+                float ddpix = getAsFloat("ddpix");
+                float ddpiy = getAsFloat("ddpiy");
+                if (ddpix == 0 || ddpiy == 0) {
+                    float ddpi = getAsFloat("ddpi");
+                    if (ddpi == 0) {
+                        throw new ImageOpException("Missing display DPI information!");
+                    } else {
+                        ddpix = ddpi;
+                        ddpiy = ddpi;
+                    }
+                }
+                // calculate absolute scale factor
+                float sx = ddpix / origResX;
+                float sy = ddpiy / origResY;
+                // currently only same scale -- mean value
+                scaleXY = (sx + sy) / 2f;
+            } else {
+                // absolute scale factor
+                scaleXY = getAsFloat("scale");
+                // use original size if no destination size given
+                if (getDw() == 0 && getDh() == 0) {
+                    paramDW = (int) userImgArea.getWidth();
+                    paramDH = (int) userImgArea.getHeight();
+                }
+            }
+            // we need to correct the factor if we use a pre-scaled image
+            ImageSize hiresSize = getHiresSize();
+            if (imgSize.getWidth() != hiresSize.getWidth()) {
+                scaleXY *= (float) hiresSize.getWidth() / (float) imgSize.getWidth();
+            }
+            areaWidth = getDw() / scaleXY * ws;
+            areaHeight = getDh() / scaleXY * ws;
+            // reset user area size
+            userImgArea.setRect(userImgArea.getX(), userImgArea.getY(), areaWidth, areaHeight);
+        } else {
+            /*
+             * crop to fit -- don't scale
+             */
+            areaWidth = getDw() * ws;
+            areaHeight = getDh() * ws;
+            // reset user area size
+            userImgArea.setRect(userImgArea.getX(), userImgArea.getY(), areaWidth, areaHeight);
+            scaleXY = 1f;
+        }
+        return (float) scaleXY;
+    }
 
-			paramDW = getAsInt("dw");
-			paramDH = getAsInt("dh");
+    /**
+     * Returns the width of the destination image.
+     * Uses dh parameter and aspect ratio if dw parameter is empty.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public int getDw() throws IOException {
+        logger.debug("get_paramDW()");
+        if (paramDW == null) {
+
+            paramDW = getAsInt("dw");
+            paramDH = getAsInt("dh");
+
+            float imgAspect = getInput().getAspect();
+            if (paramDW == 0) {
+                // calculate dw
+                paramDW = Math.round(paramDH * imgAspect);
+                setValue("dw", paramDW);
+            } else if (paramDH == 0) {
+                // calculate dh
+                paramDH = Math.round(paramDW / imgAspect);
+                setValue("dh", paramDH);
+            }
+        }
+        return paramDW;
+    }
+
+    /**
+     * Returns the height of the destination image.
+     * Uses dw parameter and aspect ratio if dh parameter is empty.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public int getDh() throws IOException {
+        logger.debug("get_paramDH()");
+        if (paramDH == null) {
+
+            paramDW = getAsInt("dw");
+            paramDH = getAsInt("dh");
 
-			float imgAspect = getInput().getAspect();
-			if (paramDW == 0) {
-				// calculate dw
-				paramDW = Math.round(paramDH * imgAspect);
-				setValue("dw", paramDW);
-			} else if (paramDH == 0) {
-				// calculate dh
-				paramDH = Math.round(paramDW / imgAspect);
-				setValue("dh", paramDH);
-			}
-		}
-		return paramDW;
-	}
-	
-	/** Returns the height of the destination image.
-	 * Uses dw parameter and aspect ratio if dh parameter is empty. 
-	 * @return
-	 * @throws IOException
-	 */
-	public int getDh() throws IOException {
-		logger.debug("get_paramDH()");
-		if (paramDH == null) {
-			
-			paramDW = getAsInt("dw");
-			paramDH = getAsInt("dh");
+            float imgAspect = getInput().getAspect();
+            if (paramDW == 0) {
+                // calculate dw
+                paramDW = Math.round(paramDH * imgAspect);
+                setValue("dw", paramDW);
+            } else if (paramDH == 0) {
+                // calculate dh
+                paramDH = Math.round(paramDW / imgAspect);
+                setValue("dh", paramDH);
+            }
+        }
+        return paramDH;
+    }
 
-			float imgAspect = getInput().getAspect();
-			if (paramDW == 0) {
-				// calculate dw
-				paramDW = Math.round(paramDH * imgAspect);
-				setValue("dw", paramDW);
-			} else if (paramDH == 0) {
-				// calculate dh
-				paramDH = Math.round(paramDW / imgAspect);
-				setValue("dh", paramDH);
-			}
-		}
-		return paramDH;
-	}
-	
-	/** Returns image quality as an integer.
-	 * @return
-	 */
-	public int getScaleQual(){
-		logger.debug("get_scaleQual()");
-		int qual = dlConfig.getAsInt("default-quality");
-		if(hasOption("q0"))
-			qual = 0;
-		else if(hasOption("q1"))
-			qual = 1;
-		else if(hasOption("q2"))
-			qual = 2;
-		return qual;
-	}
+    /**
+     * Returns image quality as an integer.
+     * 
+     * @return
+     */
+    public int getScaleQual() {
+        logger.debug("get_scaleQual()");
+        int qual = dlConfig.getAsInt("default-quality");
+        if (hasOption("q0"))
+            qual = 0;
+        else if (hasOption("q1"))
+            qual = 1;
+        else if (hasOption("q2")) qual = 2;
+        return qual;
+    }
+
+    public ColorOp getColOp() {
+        String op = getAsString("colop");
+        if (op == null || op.length() == 0) {
+            return null;
+        }
+        try {
+            return ColorOp.valueOf(op.toUpperCase());
+        } catch (Exception e) {
+            logger.error("Invalid color op: " + op);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the area of the source image that will be transformed into the
+     * destination image.
+     * 
+     * @return
+     * @throws IOException
+     * @throws ImageOpException
+     */
+    public Rectangle2D getUserImgArea() throws IOException, ImageOpException {
+        if (userImgArea == null) {
+            // getScaleXY sets userImgArea
+            getScaleXY();
+        }
+        return userImgArea;
+    }
 
-	public ColorOp getColOp() {
-		String op = getAsString("colop");
-		if (op == null || op.length() == 0) {
-			return null;
-		}
-		try {
-			return ColorOp.valueOf(op.toUpperCase());
-		} catch (Exception e) {
-			logger.error("Invalid color op: " + op);
-		}
-		return null;
-	}
-	
-	/**
-	 * Returns the area of the source image that will be transformed into the
-	 * destination image.
-	 * 
-	 * @return
-	 * @throws IOException
-	 * @throws ImageOpException
-	 */
-	public Rectangle2D getUserImgArea() throws IOException, ImageOpException{
-		if(userImgArea == null) {
-			// getScaleXY sets userImgArea
-			getScaleXY();
-		}
-		return userImgArea;		
-	}
-	
-	/** Returns the maximal area of the source image that will be used.
-	 * @return
-	 * @throws IOException
-	 * @throws ImageOpException
-	 */
-	public Rectangle2D getOuterUserImgArea() throws IOException, ImageOpException {
-		if(outerUserImgArea == null){
-			outerUserImgArea = getUserImgArea();
-			
-			// image size in pixels
-			ImageSize imgSize = getInput().getSize();
-			Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(), 
-					imgSize.getHeight());
-			
-			// clip area at the image border
-			outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
-	
-			// check image parameters sanity
-			scaleXY = getScaleXY();
-			logger.debug("outerUserImgArea.getWidth()=" + outerUserImgArea.getWidth());
-			logger.debug("get_scaleXY() * outerUserImgArea.getWidth() = " + (scaleXY * outerUserImgArea.getWidth()));
-			
-			if ((outerUserImgArea.getWidth() < 1)
-					|| (outerUserImgArea.getHeight() < 1)
-					|| (scaleXY * outerUserImgArea.getWidth() < 2)
-					|| (scaleXY * outerUserImgArea.getHeight() < 2)) {
-				logger.error("ERROR: invalid scale parameter set!");
-				throw new ImageOpException("Invalid scale parameter set!");
-			}
-		}
-		return outerUserImgArea;
-	}
-	
-	
-	public float[] getRGBM(){
-		float[] paramRGBM = null;//{0f,0f,0f};
-		Parameter p = params.get("rgbm");
-		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
-			return p.parseAsFloatArray("/");
-		}	
-		return paramRGBM;
-	}
-	
-	public float[] getRGBA(){
-		float[] paramRGBA =  null;//{0f,0f,0f};
-		Parameter p = params.get("rgba");
-		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
-			paramRGBA = p.parseAsFloatArray("/");
-		}
-		return paramRGBA;
-	}
-	
-	/** Has send-as-file been requested?
-	 * @return
-	 */
-	public boolean getSendAsFile(){
-		return hasOption("file")
-		|| hasOption("rawfile");
-	}
-	
+    /**
+     * Returns the maximal area of the source image that will be used.
+     * 
+     * @return
+     * @throws IOException
+     * @throws ImageOpException
+     */
+    public Rectangle2D getOuterUserImgArea() throws IOException, ImageOpException {
+        if (outerUserImgArea == null) {
+            outerUserImgArea = getUserImgArea();
+
+            // image size in pixels
+            ImageSize imgSize = getInput().getSize();
+            Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(), imgSize.getHeight());
+
+            // clip area at the image border
+            outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
+
+            // check image parameters sanity
+            scaleXY = getScaleXY();
+            logger.debug("outerUserImgArea.getWidth()=" + outerUserImgArea.getWidth());
+            logger.debug("get_scaleXY() * outerUserImgArea.getWidth() = " + (scaleXY * outerUserImgArea.getWidth()));
+
+            if ((outerUserImgArea.getWidth() < 1) || (outerUserImgArea.getHeight() < 1)
+                    || (scaleXY * outerUserImgArea.getWidth() < 2) || (scaleXY * outerUserImgArea.getHeight() < 2)) {
+                logger.error("ERROR: invalid scale parameter set!");
+                throw new ImageOpException("Invalid scale parameter set!");
+            }
+        }
+        return outerUserImgArea;
+    }
+
+    public float[] getRGBM() {
+        float[] paramRGBM = null;// {0f,0f,0f};
+        Parameter p = params.get("rgbm");
+        if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
+            return p.parseAsFloatArray("/");
+        }
+        return paramRGBM;
+    }
+
+    public float[] getRGBA() {
+        float[] paramRGBA = null;// {0f,0f,0f};
+        Parameter p = params.get("rgba");
+        if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
+            paramRGBA = p.parseAsFloatArray("/");
+        }
+        return paramRGBA;
+    }
+
+    /**
+     * Has send-as-file been requested?
+     * 
+     * @return
+     */
+    public boolean getSendAsFile() {
+        return hasOption("file") || hasOption("rawfile");
+    }
+
     /**
      * Returns if the image can be sent without processing. Takes image type and
      * additional image operations into account. Does not check requested size
@@ -587,36 +628,34 @@
         if (imageSendable == null) {
             String mimeType = getMimeType();
             imageSendable = (mimeType != null
-                    && (mimeType.equals("image/jpeg") || mimeType.equals("image/png") 
-                            || mimeType.equals("image/gif"))
-                    && !(hasOption("hmir")
-                    || hasOption("vmir") || (getAsFloat("rot") != 0.0)
-                    || (getRGBM() != null) || (getRGBA() != null) || (this.getColOp() != null)
-                    || (getAsFloat("cont") != 0.0) || (getAsFloat("brgt") != 0.0)));
+                    && (mimeType.equals("image/jpeg") || mimeType.equals("image/png") || mimeType.equals("image/gif")) && !(hasOption("hmir")
+                    || hasOption("vmir")
+                    || (getAsFloat("rot") != 0.0)
+                    || (getRGBM() != null)
+                    || (getRGBA() != null)
+                    || (this.getColOp() != null) || (getAsFloat("cont") != 0.0) || (getAsFloat("brgt") != 0.0)));
         }
         return imageSendable;
     }
-	
-	
-	/**
-	 * Returns if any transformation of the source image (image manipulation or
-	 * format conversion) is required.
-	 * 
-	 * @return
-	 * @throws IOException
-	 */
-	public boolean isTransformRequired() throws IOException {
-		ImageSize is = getInput().getSize();
-		ImageSize ess = getExpectedSourceSize();
-		// nt = no transform required
-		boolean nt = isImageSendable() && (
-			// lores: send if smaller
-			(isLoresOnly() && is.isSmallerThan(ess))
-			// else send if it fits
-			|| (!(isLoresOnly() || isHiresOnly()) && is.fitsIn(ess)));
-		return ! nt;
-	}
 
+    /**
+     * Returns if any transformation of the source image (image manipulation or
+     * format conversion) is required.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public boolean isTransformRequired() throws IOException {
+        ImageSize is = getInput().getSize();
+        ImageSize ess = getExpectedSourceSize();
+        // nt = no transform required
+        boolean nt = isImageSendable() && (
+        // lores: send if smaller
+                (isLoresOnly() && is.isSmallerThan(ess))
+                // else send if it fits
+                || (!(isLoresOnly() || isHiresOnly()) && is.fitsIn(ess)));
+        return !nt;
+    }
 
     /**
      * @return the docuImage
@@ -625,9 +664,9 @@
         return docuImage;
     }
 
-
     /**
-     * @param docuImage the docuImage to set
+     * @param docuImage
+     *            the docuImage to set
      */
     public void setDocuImage(DocuImage docuImage) {
         this.docuImage = docuImage;
--- a/common/src/main/java/digilib/image/ImageWorker.java	Fri Jul 19 14:08:29 2013 +0200
+++ b/common/src/main/java/digilib/image/ImageWorker.java	Fri Jul 19 19:08:39 2013 +0200
@@ -94,10 +94,7 @@
                 subf = 1 / scaleXY;
                 // for higher quality reduce subsample factor by minSubsample
                 if (jobinfo.getScaleQual() > 0) {
-                    subsamp = (float) Math
-                            .max(Math.floor(subf
-                                    / dlConfig.getAsFloat("subsample-minimum")),
-                                    1d);
+                    subsamp = (float) Math.max(Math.floor(subf / dlConfig.getAsFloat("subsample-minimum")), 1d);
                 } else {
                     subsamp = (float) Math.floor(subf);
                 }
--- a/servlet/src/main/java/digilib/conf/DigilibServletRequest.java	Fri Jul 19 14:08:29 2013 +0200
+++ b/servlet/src/main/java/digilib/conf/DigilibServletRequest.java	Fri Jul 19 19:08:39 2013 +0200
@@ -54,16 +54,18 @@
  * mk: list of marks. <br>
  * pt: total number of pages (generated by sevlet). <br>
  * baseURL: base URL (from http:// to below /servlet). <br>
- * ...et alii
+ * ...et al
  * 
  * @author casties
  * 
  */
 public class DigilibServletRequest extends DigilibRequest {
 
-    protected DocuImage image; // internal DocuImage instance for this request
+    /** internal DocuImage instance for this request */
+    protected DocuImage image; 
 
-    protected HttpServletRequest servletRequest; // internal ServletRequest
+    /** internal ServletRequest */
+    protected HttpServletRequest servletRequest; 
 
     /**
      * Creates a new instance of DigilibRequest with parameters from a
@@ -88,10 +90,11 @@
         initOptions();
     }
 
-    /**
-     * set up parameters.
-     * 
+    /* (non-Javadoc)
+     * @see digilib.conf.DigilibRequest#initParams()
+     * Define and set up parameters with default values.
      */
+    @Override
     protected void initParams() {
         /*
          * Definition of parameters and default values. Parameter of type 's'
@@ -191,6 +194,7 @@
     /**
      * Populate the request object with data from a ServletRequest.
      * 
+     * Recognizes digilib API (old and new) and IIIF API style requests. 
      * 
      * @param request
      */
@@ -207,6 +211,7 @@
             // we try to match servlet name + iiifPrefix in the uri
             int mp = uri.indexOf(ms+"/"+iiifPrefix+"/");
             if (mp > -1) {
+                // replace path with part of uri
                 path = uri.substring(mp + ms.length());
             }
             setWithIiifPath(path.substring(1));