changeset 1438:b8f2fde0f034 release-2.3

Merge with 1edc05d955cba1bd06c340b60650f507be4af755
author robcast
date Tue, 10 Nov 2015 14:30:43 +0100
parents d5193f15619b (current diff) 1edc05d955cb (diff)
children 2a248500ede2
files common/pom.xml doc/pom.xml pdf/pom.xml pom.xml servlet/pom.xml servlet3/pom.xml text/pom.xml webapp/pom.xml webapp/src/main/webapp/WEB-INF/web-2.3.xml webapp/src/main/webapp/jquery/annotator-dl.js webapp/src/main/webapp/jquery/annotator-dl.min.js webapp/src/main/webapp/jquery/digilib-ann.html webapp/src/main/webapp/jquery/digilib-vector.html webapp/src/main/webapp/jquery/img/fullscreen/32/annotation-polygon.png webapp/src/main/webapp/jquery/img/fullscreen/32/annotation-polyline.png webapp/src/main/webapp/jquery/img/info.png webapp/src/main/webapp/jquery/img/move.png webapp/src/main/webapp/jquery/jquery.colorPicker.js webapp/src/main/webapp/jquery/jquery.digilib.annotator.js webapp/src/main/webapp/jquery/jquery.digilib.geometry.js webapp/src/main/webapp/jquery/jquery.digilib.vector.js
diffstat 33 files changed, 836 insertions(+), 515 deletions(-) [+]
line wrap: on
line diff
--- a/common/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -1,61 +1,87 @@
 <?xml version="1.0"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <artifactId>digilib</artifactId>
-    <groupId>digilib</groupId>
-    <version>2.3.1</version>
-  </parent>
-  <artifactId>digilib-common</artifactId>
-  <name>digilib-common</name>
-  <description>The Digital Image Library - common library</description>
-  <url>http://digilib.sourceforge.net</url>
-  <packaging>jar</packaging>
-  <build>
-  	<pluginManagement>
-  		<plugins>
-  			<plugin>
-  				<groupId>org.apache.maven.plugins</groupId>
-  				<artifactId>maven-compiler-plugin</artifactId>
-                <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target> 
-                </configuration>
-  			</plugin>
-   		</plugins>
-  	</pluginManagement>
-  </build>
+<project
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>digilib</artifactId>
+		<groupId>digilib</groupId>
+		<version>2.3.1</version>
+	</parent>
+	
+	<artifactId>digilib-common</artifactId>
+	<name>digilib-common</name>
+	<description>The Digital Image Library - common library</description>
+	<url>http://digilib.sourceforge.net</url>
+	<packaging>jar</packaging>
+	
+	<profiles>
+		<profile>
+			<id>imageio-jai</id>
+            <!--  use JAI-ImageIO plugin -->
+			<activation>
+				<activeByDefault>true</activeByDefault>
+				<property>
+					<name>imageio</name>
+					<value>jai</value>
+				</property>
+			</activation>
+			<dependencies>
+				<dependency>
+					<groupId>com.github.jai-imageio</groupId>
+					<artifactId>jai-imageio-core</artifactId>
+					<version>1.3.0</version>
+                    <type>jar</type>
+                    <scope>compile</scope>
+				</dependency>
+				<dependency> 
+			         <groupId>com.github.jai-imageio</groupId> 
+			         <artifactId>jai-imageio-jpeg2000</artifactId> 
+				     <version>1.3.0</version> 
+				 </dependency> 
+			</dependencies>
+		</profile>
+		<profile>
+			<id>imageio-12m</id>
+            <!-- use Twelvemonkeys ImageIO plugins -->
+			<activation>
+				<property>
+					<name>imageio</name>
+					<value>12m</value>
+				</property>
+			</activation>
+			<dependencies>
+				<dependency>
+					<groupId>com.twelvemonkeys.imageio</groupId>
+					<artifactId>imageio-jpeg</artifactId>
+					<version>3.1.2</version>
+                    <type>jar</type>
+                    <scope>compile</scope>
+				</dependency>
+				<dependency>
+					<groupId>com.twelvemonkeys.imageio</groupId>
+					<artifactId>imageio-tiff</artifactId>
+					<version>3.1.2</version>
+                    <type>jar</type>
+                    <scope>compile</scope>
+				</dependency>
+			</dependencies>
+		</profile>
+	</profiles>
 
-  <repositories>
-    <!-- This provides Stian Soiland-Reyes re-packaged version of JAI-ImageIO -->
-    <repository>
-      <releases />
-       <snapshots>
-           <enabled>false</enabled>
-       </snapshots>
-       <id>mygrid-repository</id>
-       <name>myGrid Repository</name>
-       <url>http://www.mygrid.org.uk/maven/repository</url>
-    </repository>
-  </repositories>
-  <dependencies>
-	<dependency>
-	    <groupId>net.java.dev.jai-imageio</groupId> 
-	    <artifactId>jai-imageio-core-standalone</artifactId> 
-	    <version>1.2-pre-dr-b04-2011-07-04</version> 
-	</dependency>
-  	<dependency>
-  		<groupId>org.devlib.schmidt</groupId>
-  		<artifactId>imageinfo</artifactId>
-        <version>1.9</version>
-  		<type>jar</type>
-  		<scope>compile</scope>
-  	</dependency>
-  	<dependency>
-  		<groupId>log4j</groupId>
-  		<artifactId>log4j</artifactId>
-  		<type>jar</type>
-  		<scope>compile</scope>
-  	</dependency>
-  </dependencies>
+	<dependencies>
+		<dependency>
+			<groupId>org.devlib.schmidt</groupId>
+			<artifactId>imageinfo</artifactId>
+			<version>1.9</version>
+			<type>jar</type>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<type>jar</type>
+			<scope>compile</scope>
+		</dependency>
+	</dependencies>
 </project>
--- a/common/src/main/java/digilib/conf/DigilibConfiguration.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/src/main/java/digilib/conf/DigilibConfiguration.java	Tue Nov 10 14:30:43 2015 +0100
@@ -57,7 +57,7 @@
 
     /** digilib version */
     public static String getClassVersion() {
-        return "2.3.1";
+        return "2.3.4";
     }
 
     /* non-static getVersion for Java inheritance */
--- a/common/src/main/java/digilib/conf/DigilibRequest.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/src/main/java/digilib/conf/DigilibRequest.java	Tue Nov 10 14:30:43 2015 +0100
@@ -463,8 +463,8 @@
                     float h = Float.parseFloat(parms[3]);
                     setValue("wh", h / 100f);
                 } catch (Exception e) {
-                    errorMessage = "Error parsing range parameter in IIIF path!";
-                    logger.error(errorMessage, e);
+                    errorMessage = "Error parsing range parameter in IIIF path! ";
+                    logger.error(errorMessage+e);
                     return false;
                 }
             } else {
@@ -503,8 +503,8 @@
                     options.setOption("ascale");
                     setValue("scale", pct / 100);
                 } catch (NumberFormatException e) {
-                    errorMessage = "Error parsing size parameter in IIIF path!";
-                    logger.error(errorMessage, e);
+                    errorMessage = "Error parsing size parameter in IIIF path! ";
+                    logger.error(errorMessage+e);
                     return false;
                 }
             } else {
@@ -520,11 +520,9 @@
                             // width only
                             setValueFromString("dw", parms[0]);
                         } else {
-                            // w,h -- according to spec, we should distort the
-                            // image to match ;-(
-                            errorMessage = "Non-uniform-scale size parameter in IIIF path not supported!";
-                            logger.error(errorMessage);
-                            return false;
+                            // w,h -- according to spec, we should distort the image to match ;-(
+                        	options.setOption("squeeze");
+                            setValueFromString("dw", parms[0]);
                         }
                     }
                     if (parms[1].length() > 0) {
@@ -532,8 +530,8 @@
                         setValueFromString("dh", parms[1]);
                     }
                 } catch (Exception e) {
-                    errorMessage = "Error parsing size parameter in IIIF path!";
-                    logger.error(errorMessage, e);
+                    errorMessage = "Error parsing size parameter in IIIF path! ";
+                    logger.error(errorMessage+e);
                     return false;
                 }
             }
@@ -552,8 +550,8 @@
                 float rot = Float.parseFloat(rotation);
                 setValue("rot", rot);
             } catch (NumberFormatException e) {
-                errorMessage = "Error parsing rotation parameter in IIIF path!";
-                logger.error(errorMessage, e);
+                errorMessage = "Error parsing rotation parameter in IIIF path! ";
+                logger.error(errorMessage+e);
                 return false;
             }
         }
--- a/common/src/main/java/digilib/image/ImageJobDescription.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageJobDescription.java	Tue Nov 10 14:30:43 2015 +0100
@@ -1,31 +1,5 @@
 package digilib.image;
 
-/*
- * #%L
- * A class for storing the set of parameters necessary for scaling images 
- * with an ImageWorker.
- * 
- * %%
- * Copyright (C) 2001 - 2013 MPIWG Berlin
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
- * published by the Free Software Foundation, either version 3 of the 
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Lesser Public License for more details.
- * 
- * You should have received a copy of the GNU General Lesser Public 
- * License along with this program.  If not, see
- * <http://www.gnu.org/licenses/lgpl-3.0.html>.
- * #L%
- * Author: Robert Casties (robcast@berlios.de)
- */
-
-import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
 
@@ -69,10 +43,11 @@
     protected DocuDirectory fileDir = null;
     protected DocuImage docuImage = null;
     protected String filePath = null;
-    protected ImageSize expectedSourceSize = null;
-    protected Double scaleXY = null;
-    protected Rectangle2D userImgArea = null;
-    protected Rectangle2D outerUserImgArea = null;
+    protected ImageSize minSourceSize = null;
+    protected Double scaleX = null;
+    protected Double scaleY = null;
+    protected Rectangle2D imgArea = null;
+    protected Rectangle2D outerImgArea = null;
     protected Boolean imageSendable = null;
     protected String mimeType = null;
     protected Integer paramDW = null;
@@ -83,6 +58,7 @@
     protected Float paramWH = null;
     protected DocuDirCache dirCache = null;
 	protected ImageSize hiresSize = null;
+	protected ImageSize imgSize = null;
 
     /**
      * create empty ImageJobDescription.
@@ -165,6 +141,7 @@
      */
     public static ImageJobDescription getInstance(DigilibRequest dlReq, DigilibConfiguration dlcfg) {
         ImageJobDescription newMap = new ImageJobDescription(dlcfg);
+        newMap.initParams();
         // add all params to this map
         newMap.params.putAll(dlReq.getParams());
         newMap.initOptions();
@@ -183,19 +160,167 @@
      */
     public static ImageJobDescription getInstance(ParameterMap pm, DigilibConfiguration dlcfg) {
         ImageJobDescription newMap = new ImageJobDescription(dlcfg);
+        newMap.initParams();
         // add all params to this map
         newMap.params.putAll(pm.getParams());
         newMap.initOptions();
         return newMap;
     }
 
+    
+    /**
+     * Prepare image scaling factors and coordinates.
+     * 
+     * Uses image size and user parameters.
+     * 
+     * Sets scaleX, scaleY, imgArea.
+     * 
+     * @return
+     * @throws IOException
+     * @throws ImageOpException
+     */
+    public void prepareScaleParams() throws IOException, ImageOpException {
+        // logger.debug("get_scaleXY()");
+
+    	/*
+         * get image region of interest
+         */
+        // size of the currently selected input image
+        imgSize  = getImgSize();
+        // transform from relative [0,1] to image coordinates.
+        double areaXf = getWx() * imgSize.getWidth();
+        double areaYf = getWy() * imgSize.getHeight();
+        double areaWidthF = getWw() * imgSize.getWidth();
+        double areaHeightF = getWh() * imgSize.getHeight();
+        // round to pixels
+        long areaX = Math.round(areaXf);
+        long areaY = Math.round(areaYf);
+        long areaHeight = Math.round(areaHeightF);
+        long areaWidth = Math.round(areaWidthF);
+
+        /*
+         * calculate scaling factors
+         */
+		if (isScaleToFit()) {
+            /*
+             * scale to fit -- scaling factor based on destination size and user area
+             */
+            scaleX = getDw() / (double) areaWidth;
+            scaleY = getDh() / (double) areaHeight;
+            if (scaleX == 0) {
+            	// dw undefined
+            	scaleX = scaleY;
+            } else if (scaleY == 0) {
+            	// dh undefined
+            	scaleY = scaleX;
+            } else {
+                // use the smaller factor to get fit-in-box
+            	if (scaleX > scaleY) {
+            		scaleX = scaleY;
+            		if (hasOption("fill")) {
+            			// fill mode uses whole destination rect
+            			// TODO: should we center, clip or shift the area?
+            			areaWidth = (long) (getDw() / scaleX);
+            		}
+            	} else {
+            		scaleY = scaleX;
+            		if (hasOption("fill")) {
+            			// fill mode uses whole destination rect
+            			// TODO: should we center, clip or shift the area?
+            			areaHeight = (long) (getDh() / scaleY);
+            		}
+            	}
+            }
+            
+        } else if (isSqueezeToFit()) {
+            /*
+             * squeeze to fit -- scaling factor based on destination size and user area
+             */
+            scaleX = getDw() / (double) areaWidth;
+            scaleY = getDh() / (double) areaHeight;
+            
+        } else if (isCropToFit()){
+            /*
+             * crop to fit -- don't scale
+             */
+            areaWidth = getDw();
+            areaHeight = getDh();
+            scaleX = 1d;
+            scaleY = 1d;
+
+        } else if (isAbsoluteScale()) {
+            /*
+             * absolute scaling factor -- either original size, based on dpi, or absolute 
+             */
+            if (hasOption("osize")) {
+                /*
+                 * get original resolution from metadata
+                 */
+                imageSet.checkMeta();
+                double origResX = imageSet.getResX();
+                double origResY = imageSet.getResY();
+                if ((origResX == 0) || (origResY == 0)) {
+                    throw new ImageOpException("Missing image DPI information!");
+                }
+                double ddpix = getAsFloat("ddpix");
+                double ddpiy = getAsFloat("ddpiy");
+                if (ddpix == 0 || ddpiy == 0) {
+                    double ddpi = getAsFloat("ddpi");
+                    if (ddpi == 0) {
+                        throw new ImageOpException("Missing display DPI information!");
+                    } else {
+                        ddpix = ddpi;
+                        ddpiy = ddpi;
+                    }
+                }
+                // calculate absolute scale factor
+                scaleX = ddpix / origResX;
+                scaleY = ddpiy / origResY;
+                
+            } else {
+                /*
+                 * explicit absolute scale factor
+                 */
+                double scaleXY = (double) getAsFloat("scale");
+                scaleX = scaleXY;
+                scaleY = scaleXY;
+                // use original size if no destination size given
+                if (getDw() == 0 && getDh() == 0) {
+                    paramDW = (int) areaWidth;
+                    paramDH = (int) areaHeight;
+                }
+            }
+            /*
+             * correct scaling factor if we use a pre-scaled image
+             */
+            hiresSize = getHiresSize();
+            if (imgSize.getWidth() != hiresSize.getWidth()) {
+                double preScale = (double) hiresSize.getWidth() / (double) imgSize.getWidth();
+				scaleX *= preScale;
+				scaleY *= preScale;
+            }
+            areaWidth = Math.round(getDw() / scaleX);
+            areaHeight = Math.round(getDh() / scaleY);
+            
+        } else {
+            throw new ImageOpException("Unknown scaling mode!");
+        }
+
+		/*
+		 * set image area
+		 */
+		imgArea = new Rectangle2D.Double(areaX, areaY, areaWidth, areaHeight);
+    
+    }
+    
+
     /**
      * Returns the mime-type of the input.
      * 
      * @return
      * @throws IOException
      */
-    public String getMimeType() throws IOException {
+    public String getInputMimeType() throws IOException {
         if (mimeType == null) {
             input = getInput();
             mimeType = input.getMimetype();
@@ -218,7 +343,7 @@
         }
         // use input image type
         try {
-            String mt = getMimeType();
+            String mt = getInputMimeType();
             if ((mt.equals("image/jpeg") || mt.equals("image/jp2") || mt.equals("image/fpx"))) {
                 return "image/jpeg";
             } else {
@@ -261,14 +386,14 @@
                 input = imageSet.getBiggest();
             } else if (isLoresOnly()) {
                 // enforced lores uses next smaller resolution
-                input = imageSet.getNextSmaller(getExpectedSourceSize());
+                input = imageSet.getNextSmaller(getMinSourceSize());
                 if (input == null) {
                     // this is the smallest we have
                     input = imageSet.getSmallest();
                 }
             } else {
                 // autores: use next higher resolution
-                input = imageSet.getNextBigger(getExpectedSourceSize());
+                input = imageSet.getNextBigger(getMinSourceSize());
                 if (input == null) {
                     // this is the highest we have
                     input = imageSet.getBiggest();
@@ -342,18 +467,57 @@
         return filePath;
     }
 
+    /**
+     * Only use the highest resolution image.
+     * 
+     * @return
+     */
     public boolean isHiresOnly() {
         return hasOption("clip") || hasOption("hires");
     }
 
+    /**
+     * Prefer a prescaled lower resolution image.
+     * 
+     * @return
+     */
     public boolean isLoresOnly() {
         return hasOption("lores");
     }
 
+    /**
+     * Scale according to zoom area and destination size preserving aspect ratio.
+     * 
+     * @return
+     */
     public boolean isScaleToFit() {
-        return !(hasOption("clip") || hasOption("osize") || hasOption("ascale"));
+        return hasOption("fit") || 
+        		!(hasOption("clip") || hasOption("osize") || hasOption("ascale") || hasOption("squeeze"));
     }
 
+    /**
+     * Do not scale, just crop.
+     * 
+     * @return
+     */
+    public boolean isCropToFit() {
+        return hasOption("clip");
+    }
+
+    /**
+     * Scale according to zoom area and destination size violating aspect ratio.
+     * 
+     * @return
+     */
+    public boolean isSqueezeToFit() {
+        return hasOption("squeeze");
+    }
+
+    /**
+     * Scale according to fixed factor independent of destination size.
+     * 
+     * @return
+     */
     public boolean isAbsoluteScale() {
         return hasOption("osize") || hasOption("ascale");
     }
@@ -361,25 +525,54 @@
     /**
      * Returns the minimum size the source image should have for scaling.
      * 
+     * This function is called by getInput(). It must not assume a selected input image!
+     * 
      * @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(getWw(), getWh())) * 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")));
-            }
+    public ImageSize getMinSourceSize() throws IOException {
+        //logger.debug("getMinSourceSize()");
+        if (minSourceSize != null) {
+        	return minSourceSize;
         }
-        return expectedSourceSize;
+        
+        minSourceSize = new ImageSize();
+        if (isScaleToFit()) {
+        	/*
+        	 * scale to fit -- calculate minimum source size
+        	 * 
+        	 * roughly: w_min = dw * 1/ww
+        	 * 
+        	 * Note: dw or dh can be empty (=0) 
+        	 */
+        	float scale = (1 / Math.min(getWw(), getWh()));
+        	minSourceSize.setSize(
+        			Math.round(getAsInt("dw") * scale), 
+        			Math.round(getAsInt("dh") * scale));
+        	
+        } else if (isSqueezeToFit()) {
+        	/*
+        	 * squeeze to fit -- calculate minimum source size
+        	 * 
+        	 * w_min = dw * 1/ww
+        	 */
+        	minSourceSize.setSize(
+        			Math.round(getAsInt("dw") / getWw()), 
+        			Math.round(getAsInt("dh") / getWh()));
+        	
+        } else if (isAbsoluteScale() && hasOption("ascale")) {
+        	/*
+        	 * absolute scale -- apply scale to hires size
+        	 */
+        	minSourceSize = getHiresSize().getScaled(getAsFloat("scale"));
+        	
+        } else {
+        	/*
+        	 * clip or other -- source = hires size
+        	 */
+        	minSourceSize = getHiresSize();
+        }
+        return minSourceSize;
     }
 
     /**
@@ -389,114 +582,59 @@
      * @throws IOException
      */
     public ImageSize getHiresSize() throws IOException {
-        logger.debug("get_hiresSize()");
+        //logger.debug("get_hiresSize()");
         if (hiresSize == null) {
 	        ImageSet fileset = getImageSet();
 	        ImageInput hiresFile = fileset.getBiggest();
 	        hiresSize = hiresFile.getSize();
+	        if (hiresSize == null) {
+	        	throw new FileOpException("Can't get size from hires image file!");
+	        }
         }
         return hiresSize;
     }
 
     /**
-     * Returns image scaling factor.
-     * Uses image size and user parameters.
-     * Modifies scaleXY, userImgArea.
+     * Returns the size of the selected input image.
+     * 
+     * @return
+     * @throws IOException
+     */
+    public ImageSize getImgSize() throws IOException {
+        //logger.debug("get_hiresSize()");
+        if (imgSize == null) {
+        	imgSize = getInput().getSize();
+        }
+        return imgSize;
+    }
+
+      
+    /**
+     * Return the X scale factor.
      * 
      * @return
      * @throws IOException
      * @throws ImageOpException
      */
-    public double getScaleXY() throws IOException, ImageOpException {
-        // logger.debug("get_scaleXY()");
-        if (scaleXY != null) {
-            return scaleXY.doubleValue();
-        }
-
-        /*
-         * calculate region of interest
-         */
-        double areaWidth;
-        double areaHeight;
-        // size of the currently selected input image
-        ImageSize imgSize = getInput().getSize();
-        // user area is in [0,1] coordinates
-        Rectangle2D relUserArea = new Rectangle2D.Float(getWx(), getWy(), getWw(), getWh());
-        // 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();
+    public double getScaleX() throws IOException, ImageOpException {
+    	if (scaleX == null) {
+    		prepareScaleParams();
+    	}
+    	return scaleX.doubleValue();
+    }
 
-        /*
-         * calculate scaling factor
-         */
-        float ws = getAsFloat("ws");
-        if (isScaleToFit()) {
-            /*
-             * scale to fit -- scaling factor based on destination size and user area
-             */
-            areaWidth = (double) userImgArea.getWidth();
-            areaHeight = (double) userImgArea.getHeight();
-            double scaleX = getDw() / areaWidth * ws;
-            double 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();
-                double origResX = imageSet.getResX();
-                double origResY = imageSet.getResY();
-                if ((origResX == 0) || (origResY == 0)) {
-                    throw new ImageOpException("Missing image DPI information!");
-                }
-                double ddpix = getAsFloat("ddpix");
-                double ddpiy = getAsFloat("ddpiy");
-                if (ddpix == 0 || ddpiy == 0) {
-                    double ddpi = getAsFloat("ddpi");
-                    if (ddpi == 0) {
-                        throw new ImageOpException("Missing display DPI information!");
-                    } else {
-                        ddpix = ddpi;
-                        ddpiy = ddpi;
-                    }
-                }
-                // calculate absolute scale factor
-                double sx = ddpix / origResX;
-                double sy = ddpiy / origResY;
-                // currently only same scale -- mean value
-                scaleXY = (sx + sy) / 2f;
-            } else {
-                // absolute scale factor
-                scaleXY = (double) 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 *= (double) hiresSize.getWidth() / (double) 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 = 1d;
-        }
-        return scaleXY.doubleValue();
+    /**
+     * Return the Y scale factor.
+     * 
+     * @return
+     * @throws IOException
+     * @throws ImageOpException
+     */
+    public double getScaleY() throws IOException, ImageOpException {
+    	if (scaleY == null) {
+    		prepareScaleParams();
+    	}
+    	return scaleY.doubleValue();
     }
 
     /**
@@ -507,22 +645,10 @@
      * @throws IOException
      */
     public int getDw() throws IOException {
-        logger.debug("get_paramDW()");
+        //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;
     }
@@ -535,75 +661,63 @@
      * @throws IOException
      */
     public int getDh() throws IOException {
-        logger.debug("get_paramDH()");
+        //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;
     }
 
     /**
-     * Returns the width of the image area.
+     * Returns the relative width of the image area.
      * Uses ww parameter.
      * 
      * @return
      * @throws IOException
      */
     public Float getWw() throws IOException {
-        logger.debug("get_paramWW()");
+        //logger.debug("get_paramWW()");
         if (paramWW == null) {
         	paramWW = getAsFloat("ww");
         	if (hasOption("pxarea")) {
         		// area in absolute pixels - convert to relative
-        		ImageSize imgSize = getHiresSize();
-        		paramWW = paramWW / imgSize.getWidth(); 
+        		hiresSize = getHiresSize();
+        		paramWW = paramWW / hiresSize.getWidth(); 
         	}
         }
         return paramWW;
     }
 
     /**
-     * Returns the height of the image area.
+     * Returns the relative height of the image area.
      * Uses wh parameter.
      * 
      * @return
      * @throws IOException
      */
     public Float getWh() throws IOException {
-        logger.debug("get_paramWH()");
+        //logger.debug("get_paramWH()");
         if (paramWH == null) {
         	paramWH = getAsFloat("wh");
         	if (hasOption("pxarea")) {
         		// area in absolute pixels - convert to relative
-        		ImageSize imgSize = getHiresSize();
-        		paramWH = paramWH / imgSize.getHeight(); 
+        		hiresSize = getHiresSize();
+        		paramWH = paramWH / hiresSize.getHeight(); 
         	}
         }
         return paramWH;
     }
 
     /**
-     * Returns the x-offset of the image area.
+     * Returns the relative x-offset of the image area.
      * Uses wx parameter.
      * 
      * @return
      * @throws IOException
      */
     public Float getWx() throws IOException {
-        logger.debug("get_paramWX()");
+        //logger.debug("get_paramWX()");
         if (paramWX == null) {
         	paramWX = getAsFloat("wx");
         	if (hasOption("pxarea")) {
@@ -616,20 +730,20 @@
     }
 
     /**
-     * Returns the y-offset of the image area.
+     * Returns the relative y-offset of the image area.
      * Uses wy parameter.
      * 
      * @return
      * @throws IOException
      */
     public Float getWy() throws IOException {
-        logger.debug("get_paramWY()");
+        //logger.debug("get_paramWY()");
         if (paramWY == null) {
         	paramWY = getAsFloat("wy");
         	if (hasOption("pxarea")) {
         		// area in absolute pixels - convert to relative
         		ImageSize imgSize = getHiresSize();
-        		paramWY = paramWY / imgSize.getWidth(); 
+        		paramWY = paramWY / imgSize.getHeight(); 
         	}
         }
         return paramWY;
@@ -641,7 +755,7 @@
      * @return
      */
     public int getScaleQual() {
-        logger.debug("get_scaleQual()");
+        //logger.debug("get_scaleQual()");
         int qual = dlConfig.getAsInt("default-quality");
         if (hasOption("q0"))
             qual = 0;
@@ -671,53 +785,48 @@
     }
 
     /**
-     * Returns the area of the source image that will be transformed into the
-     * destination image.
+     * Returns the maximal area of the source image that will be used.
      * 
-     * @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.
+     * This was meant to include extra pixels outside the 
+     * imgArea when rotating by oblique angles but is not yet implemented.
+     * Currently returns imgArea.
      * 
      * @return
      * @throws IOException
      * @throws ImageOpException
      */
-    public Rectangle2D getOuterUserImgArea() throws IOException, ImageOpException {
-        if (outerUserImgArea == null) {
-            outerUserImgArea = getUserImgArea();
+    public Rectangle2D getOuterImgArea() throws IOException, ImageOpException {
+        if (outerImgArea == null) {
+        	// calculate scale parameters
+        	if (imgArea == null) {
+        		prepareScaleParams();
+        	}
+            // start with imgArea
+            outerImgArea = imgArea;
 
             // image size in pixels
             ImageSize imgSize = getInput().getSize();
-            Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(), imgSize.getHeight());
+            Rectangle2D imgBounds = new Rectangle2D.Double(0, 0, imgSize.getWidth(), imgSize.getHeight());
 
             // clip area at the image border
-            outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
+            outerImgArea = outerImgArea.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)) {
+            if ((outerImgArea.getWidth() < 1) || (outerImgArea.getHeight() < 1)
+                    || (scaleX * outerImgArea.getWidth() < 2) || (scaleY * outerImgArea.getHeight() < 2)) {
                 logger.error("ERROR: invalid scale parameter set!");
+            	logger.debug("scaleX="+scaleX+" scaleY="+scaleY+" outerImgArea="+outerImgArea);
                 throw new ImageOpException("Invalid scale parameter set!");
             }
         }
-        return outerUserImgArea;
+        return outerImgArea;
     }
 
+    /**
+     * Get the RGBM parameter set.
+     * 
+     * @return
+     */
     public float[] getRGBM() {
         float[] paramRGBM = null;// {0f,0f,0f};
         Parameter p = params.get("rgbm");
@@ -727,6 +836,11 @@
         return paramRGBM;
     }
 
+    /**
+     * Get the RGBA parameter set.
+     * 
+     * @return
+     */
     public float[] getRGBA() {
         float[] paramRGBA = null;// {0f,0f,0f};
         Parameter p = params.get("rgba");
@@ -755,10 +869,12 @@
      */
     public boolean isImageSendable() throws IOException {
         if (imageSendable == null) {
-            String mimeType = getMimeType();
+            String mimeType = getInputMimeType();
             imageSendable = (mimeType != null
             		// input image is browser compatible
                     && (mimeType.equals("image/jpeg") || mimeType.equals("image/png") || mimeType.equals("image/gif"))
+                    // no forced type conversion
+                    && !(hasOption("jpg") || hasOption("png"))
                     // no zooming
                     && !(getWx() > 0f || getWy() > 0f || getWw() < 1f || getWh() < 1f
                     // no other image operations
@@ -781,7 +897,7 @@
      */
     public boolean isTransformRequired() throws IOException {
         ImageSize is = getInput().getSize();
-        ImageSize ess = getExpectedSourceSize();
+        ImageSize ess = getMinSourceSize();
         // does the image require processing?
         if (isImageSendable()) {
         	// does the image require rescaling?
--- a/common/src/main/java/digilib/image/ImageLoaderDocuImage.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageLoaderDocuImage.java	Tue Nov 10 14:30:43 2015 +0100
@@ -72,7 +72,7 @@
 public class ImageLoaderDocuImage extends ImageInfoDocuImage {
 
     /** DocuImage version */
-    public static final String version = "ImageLoaderDocuImage 2.1.6a";
+    public static final String version = "ImageLoaderDocuImage 2.1.8";
 
     /** image object */
     protected BufferedImage img;
@@ -104,8 +104,10 @@
     /* lookup table for false-color */
     protected static LookupTable mapBgrByteTable;
     protected static boolean needsMapBgr = false;
+    /* set destination type to sRGB if available */
+    protected static boolean setDestSrgb = true;
     /* set destination type to sRGB if available, even for non-RGB images */
-    protected static boolean alwaysSetDestSrgb = false;
+    protected static boolean setDestSrgbForNonRgb = false;
 
     static {
         /*
@@ -161,24 +163,30 @@
         // this hopefully works for all
         mapBgrByteTable = new ByteLookupTable(0, new byte[][] { mapR, mapG, mapB });
         logger.debug("ImageIO Hacks: needsRescaleRgba="+needsRescaleRgba+" needsInvertRgba="+needsInvertRgba+
-                " needsMapBgr="+needsMapBgr);
+                " needsMapBgr="+needsMapBgr+" setDestSrgb="+setDestSrgb+" setDestSrgbForNonRgb="+setDestSrgbForNonRgb);
     }
 
     /** the size of the current image */
     protected ImageSize imageSize;
 
-    /**
-     * @return the version
+    /* (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#getVersion()
      */
     public String getVersion() {
         return version;
     }
 
-    /* loadSubimage is supported. */
+    /* 
+     * loadSubimage is supported.
+     * @see digilib.image.DocuImageImpl#isSubimageSupported()
+     */
     public boolean isSubimageSupported() {
         return true;
     }
 
+    /* (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#setQuality(int)
+     */
     public void setQuality(int qual) {
         quality = qual;
         renderHint = new RenderingHints(null);
@@ -194,7 +202,10 @@
         }
     }
 
-    /* returns the size of the current image */
+    /* 
+     * returns the size of the current image
+     * @see digilib.image.DocuImageImpl#getSize()
+     */
     public ImageSize getSize() {
         if (imageSize == null) {
             int h = 0;
@@ -218,13 +229,19 @@
         return imageSize;
     }
 
-    /* returns a list of supported image formats */
+    /* 
+     * returns a list of supported image formats
+     * @see digilib.image.DocuImageImpl#getSupportedFormats()
+     */
     public Iterator<String> getSupportedFormats() {
         String[] formats = ImageIO.getReaderFormatNames();
         return Arrays.asList(formats).iterator();
     }
 
-    /* Check image size and type and store in ImageInput */
+    /* 
+     * Check image size and type and store in ImageInput
+     * @see digilib.image.ImageInfoDocuImage#identify(digilib.io.ImageInput)
+     */
     public ImageInput identify(ImageInput input) throws IOException {
         ImageInput ii = null;
         if (!reuseReader) {
@@ -257,7 +274,7 @@
             return input;
         } catch (FileOpException e) {
             // maybe just our class doesn't know what to do
-            logger.error("ImageLoaderDocuimage unable to identify:", e);
+            logger.error("ImageLoaderDocuimage unable to identify: "+e);
             return null;
         } finally {
             if (!reuseReader && reader != null) {
@@ -266,7 +283,10 @@
         }
     }
 
-    /* load image file */
+    /* 
+     * load image file
+     * @see digilib.image.DocuImageImpl#loadImage(digilib.io.ImageInput)
+     */
     public void loadImage(ImageInput ii) throws FileOpException {
         logger.debug("loadImage: " + ii);
         this.input = ii;
@@ -335,43 +355,59 @@
         return reader;
     }
 
-    /* Load an image file into the Object. */
+    /* 
+     * Load an image file into the Object.
+     * 
+     * @see digilib.image.DocuImageImpl#loadSubimage(digilib.io.ImageInput, java.awt.Rectangle, int)
+     */
     public void loadSubimage(ImageInput ii, Rectangle region, int prescale) throws FileOpException {
         logger.debug("loadSubimage");
         this.input = ii;
         // ImageReader reader = null;
         try {
             reader = getReader(ii);
-            // set up reader parameters
+            /*
+             * set up reader parameters
+             */
             ImageReadParam readParam = reader.getDefaultReadParam();
             readParam.setSourceRegion(region);
             if (prescale > 1) {
                 readParam.setSourceSubsampling(prescale, prescale, 0, 0);
             }
-            // try to set target color space to sRGB
-            for (Iterator<ImageTypeSpecifier> i = reader.getImageTypes(0); i.hasNext();) {
-                ImageTypeSpecifier type = (ImageTypeSpecifier) i.next();
-                ColorModel cm = type.getColorModel();
-                ColorSpace cs = cm.getColorSpace();
-                logger.debug("loadSubimage: possible color model:"+cm+" color space:"+cs);
-                if (cs.getNumComponents() < 3 && ! ImageLoaderDocuImage.alwaysSetDestSrgb) {
-                    // if the first type is not RGB do nothing
-                    logger.debug("loadSubimage: image is not RGB " + type);
-                    break;
-                }
-                if (cs.isCS_sRGB()) {
-                    logger.debug("loadSubimage: substituted sRGB destination type " + type);
-                    readParam.setDestinationType(type);
-                    break;
-                }
-            }
-            // read image
+			if (ImageLoaderDocuImage.setDestSrgb) {
+				/*
+				 * try to set target color space to sRGB
+				 */
+				for (Iterator<ImageTypeSpecifier> i = reader.getImageTypes(0); i.hasNext();) {
+					ImageTypeSpecifier type = (ImageTypeSpecifier) i.next();
+					ColorModel cm = type.getColorModel();
+					ColorSpace cs = cm.getColorSpace();
+					logger.debug("loadSubimage: possible color model:" + cm + " color space:" + cs);
+					if (cs.getNumComponents() < 3 && !ImageLoaderDocuImage.setDestSrgbForNonRgb) {
+						// if the first type is not RGB do nothing
+						logger.debug("loadSubimage: image is not RGB " + type);
+						break;
+					}
+					if (cs.isCS_sRGB()) {
+						logger.debug("loadSubimage: substituted sRGB destination type " + type);
+						readParam.setDestinationType(type);
+						break;
+					}
+				}
+			}
+			
+            /*
+             * read image
+             */
             logger.debug("loadSubimage: loading..");
             img = reader.read(0, readParam);
             logger.debug("loadSubimage: loaded");
             // invalidate image size if it was set
             imageSize = null;
-            // downconvert highcolor images
+            
+            /*
+             * downconvert highcolor images
+             */
             if (img.getColorModel().getComponentSize(0) > 8) {
                 logger.debug("loadSubimage: converting to 8bit");
                 int type = BufferedImage.TYPE_INT_RGB;
@@ -391,7 +427,10 @@
         }
     }
 
-    /* write image of type mt to Stream */
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#writeImage(java.lang.String, java.io.OutputStream)
+     */
     public void writeImage(String mt, OutputStream ostream) throws ImageOpException, FileOpException {
         logger.debug("writeImage");
         // setup output
@@ -442,18 +481,71 @@
         } catch (IOException e) {
             logger.error("Error writing image:", e);
             throw new FileOpException("Error writing image!", e);
+        } finally {
+        	if (writer != null) {
+        		writer.dispose();
+        	}
+        	if (imgout != null) {
+        		/* 
+        		 * ImageOutputStream likes to keep ServletOutputStream and close it when disposed.
+        		 * Thanks to Tom Van Wietmarschen's mail to tomcat-users on July 4, 2008!
+        		 */
+        		try {
+					imgout.close();
+				} catch (IOException e) {
+					logger.error("Error closing ImageOutputStream!", e);
+				}
+        	}
         }
-        // TODO: should we: finally { writer.dispose(); }
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#scale(double, double)
+     */
     public void scale(double scaleX, double scaleY) throws ImageOpException {
         logger.debug("scale: " + scaleX);
-        /* for downscaling in high quality the image is blurred first */
+        /* 
+         * for downscaling in high quality the image is blurred first ...
+         */
         if ((scaleX <= 0.5) && (quality > 1)) {
             int bl = (int) Math.floor(1 / scaleX);
             blur(bl);
         }
-        /* then scaled */
+        /* 
+         * ... then scaled.
+         * 
+         * We need to correct the scale factors to round to whole pixels 
+         * or else we get a 1px black (or transparent) border.
+         */
+        int imgW = img.getWidth();
+        int imgH = img.getHeight();
+        double targetW = imgW * scaleX;
+        double targetH = imgH * scaleY;
+        double deltaX = targetW - Math.floor(targetW);
+        double deltaY = targetH - Math.floor(targetH);
+        if (deltaX > epsilon) {
+        	// round x
+            if (deltaX > 0.5d) {
+                logger.debug("rounding up x scale factor");
+                scaleX += (1 - deltaX) / imgW;
+            } else {
+                logger.debug("rounding down x scale factor");
+                scaleX -= deltaX / imgW;
+            }
+        }
+        if (deltaY > epsilon) {
+            // round y
+            if (deltaY > 0.5d) {
+                logger.debug("rounding up y scale factor");
+                scaleY += (1 - deltaY) / imgH;
+            } else {
+                logger.debug("rounding down y scale factor");
+                scaleY -= deltaY / imgH;
+            }
+        }
+        // scale with AffineTransformOp
+        logger.debug("scaled from " + imgW + "x" + imgH + " img=" + img);
         AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform.getScaleInstance(scaleX, scaleY), renderHint);
         img = scaleOp.filter(img, null);
         logger.debug("scaled to " + img.getWidth() + "x" + img.getHeight() + " img=" + img);
@@ -461,6 +553,12 @@
         imageSize = null;
     }
 
+    /**
+     * Blur the image with a convolution using the given radius.
+     * 
+     * @param radius
+     * @throws ImageOpException
+     */
     public void blur(int radius) throws ImageOpException {
         logger.debug("blur: " + radius);
         // minimum radius is 2
@@ -492,6 +590,10 @@
         logger.debug("blurred: " + img);
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#crop(int, int, int, int)
+     */
     public void crop(int x_off, int y_off, int width, int height) throws ImageOpException {
         // setup Crop
         img = img.getSubimage(x_off, y_off, width, height);
@@ -500,6 +602,10 @@
         imageSize = null;
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#rotate(double)
+     */
     public void rotate(double angle) throws ImageOpException {
         logger.debug("rotate: " + angle);
         // setup rotation
@@ -528,6 +634,10 @@
         imageSize = null;
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#mirror(double)
+     */
     public void mirror(double angle) throws ImageOpException {
         logger.debug("mirror: " + angle);
         // setup mirror
@@ -560,6 +670,10 @@
         imageSize = null;
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#enhance(float, float)
+     */
     public void enhance(float mult, float add) throws ImageOpException {
         RescaleOp op = null;
         logger.debug("enhance: img=" + img);
@@ -591,6 +705,11 @@
         op.filter(img, img);
     }
 
+    /* 
+     * (non-Javadoc)
+     * 
+     * @see digilib.image.DocuImageImpl#enhanceRGB(float[], float[])
+     */
     public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException {
         logger.debug("enhanceRGB: rgbm=" + rgbm + " rgba=" + rgba);
         /*
@@ -702,6 +821,10 @@
         }
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#dispose()
+     */
     public void dispose() {
         if (reader != null) {
             reader.dispose();
@@ -710,6 +833,10 @@
         img = null;
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#getAwtImage()
+     */
     public Image getAwtImage() {
         return (Image) img;
     }
--- a/common/src/main/java/digilib/image/ImageWorker.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageWorker.java	Tue Nov 10 14:30:43 2015 +0100
@@ -57,8 +57,7 @@
     /**
      * render and return the image
      */
-    public DocuImage call() throws FileOpException, IOException,
-            ImageOpException {
+    public DocuImage call() throws FileOpException, IOException, ImageOpException {
 
         logger.debug("ImageWorker starting");
         long startTime = System.currentTimeMillis();
@@ -78,16 +77,25 @@
         // set interpolation quality
         docuImage.setQuality(jobinfo.getScaleQual());
 
-        Rectangle loadRect = jobinfo.getOuterUserImgArea().getBounds();
-        double scaleXY = jobinfo.getScaleXY();
+        // get area of interest and scale factor
+        jobinfo.prepareScaleParams();
+        Rectangle loadRect = jobinfo.getOuterImgArea().getBounds();
+        double scaleX = jobinfo.getScaleX();
+        double scaleY = jobinfo.getScaleY();
 
         if (stopNow) {
             logger.debug("ImageWorker stopping (after setup)");
             return null;
         }
-        // use subimage loading if possible
+		/*
+         * load, crop and scale the image 
+         */
         if (docuImage.isSubimageSupported()) {
-            logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
+        	/*
+        	 * use subimage loading with subsampling
+        	 */
+        	double scaleXY = scaleX + scaleY / 2d;
+            logger.debug("Subimage: scale " + scaleX + ", " + scaleY);
             double subf = 1d;
             double subsamp = 1d;
             if (scaleXY < 1) {
@@ -99,8 +107,9 @@
                     subsamp = Math.floor(subf);
                 }
                 // correct scaling factor by subsampling factor
-                scaleXY *= subsamp;
-                logger.debug("Using subsampling: " + subsamp + " rest " + scaleXY);
+                scaleX *= subsamp;
+                scaleY *= subsamp;
+                logger.debug("Using subsampling: " + subsamp + " rest " + scaleX + ", " + scaleY);
             }
             // load region with subsampling
             docuImage.loadSubimage(jobinfo.getInput(), loadRect, (int) subsamp);
@@ -110,9 +119,12 @@
                 return null;
             }
             // and scale
-            docuImage.scale(scaleXY, scaleXY);
+            docuImage.scale(scaleX, scaleY);
+            
         } else {
-            // else load and crop the whole file
+            /*
+             * else load and crop the whole file
+             */
             docuImage.loadImage(jobinfo.getInput());
             if (stopNow) {
                 logger.debug("ImageWorker stopping (after loading)");
@@ -124,28 +136,32 @@
                 logger.debug("ImageWorker stopping (after cropping)");
                 return null;
             }
-            docuImage.scale(scaleXY, scaleXY);
+            docuImage.scale(scaleX, scaleY);
         }
-
+        
         if (stopNow) {
             logger.debug("ImageWorker stopping (after scaling)");
             return null;
         }
-        // mirror image
-        // operation mode: "hmir": mirror horizontally, "vmir": mirror
-        // vertically
+
+        /* 
+         * mirror image
+         * operation mode: "hmir": mirror horizontally, "vmir": mirror vertically
+         */
         if (jobinfo.hasOption("hmir")) {
             docuImage.mirror(0);
         }
         if (jobinfo.hasOption("vmir")) {
             docuImage.mirror(90);
         }
-
         if (stopNow) {
             logger.debug("ImageWorker stopping (after mirroring)");
             return null;
         }
-        // rotate image
+        
+        /*
+         * rotate image
+         */
         if (jobinfo.getAsFloat("rot") != 0d) {
             docuImage.rotate(jobinfo.getAsFloat("rot"));
             /*
@@ -162,12 +178,14 @@
              */
 
         }
-
         if (stopNow) {
             logger.debug("ImageWorker stopping (after rotating)");
             return null;
         }
-        // color modification
+        
+        /*
+         * color modification
+         */
         float[] paramRGBM = jobinfo.getRGBM();
         float[] paramRGBA = jobinfo.getRGBA();
         if ((paramRGBM != null) || (paramRGBA != null)) {
@@ -185,31 +203,34 @@
             }
             docuImage.enhanceRGB(mult, paramRGBA);
         }
-
         if (stopNow) {
             logger.debug("ImageWorker stopping (after enhanceRGB)");
             return null;
         }
-        // contrast and brightness enhancement
+
+        /*
+         * contrast and brightness enhancement
+         */
         float paramCONT = jobinfo.getAsFloat("cont");
         float paramBRGT = jobinfo.getAsFloat("brgt");
         if ((paramCONT != 0f) || (paramBRGT != 0f)) {
             float mult = (float) Math.pow(2, paramCONT);
             docuImage.enhance(mult, paramBRGT);
         }
-
         if (stopNow) {
             logger.debug("ImageWorker stopping (after enhance)");
             return null;
         }
-        // color operation
+        
+        /*
+         * color operation
+         */
         DocuImage.ColorOp colop = jobinfo.getColOp();
         if (colop != null) {
             docuImage.colorOp(colop);
         }
 
-        logger.debug("rendered in " + (System.currentTimeMillis() - startTime)
-                + "ms");
+        logger.debug("rendered in " + (System.currentTimeMillis() - startTime) + "ms");
 
         return docuImage;
     }
--- a/common/src/main/java/digilib/meta/IndexMetaDirMeta.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/common/src/main/java/digilib/meta/IndexMetaDirMeta.java	Tue Nov 10 14:30:43 2015 +0100
@@ -32,7 +32,6 @@
 import digilib.io.Directory;
 import digilib.io.DocuDirectory;
 import digilib.io.DocuDirent;
-import digilib.io.FileOps.FileClass;
 
 /**
  * DirMeta implementation reading index.meta files.
--- a/doc/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/doc/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -15,12 +15,12 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-site-plugin</artifactId>
-        <version>3.3</version>
+        <version>3.4</version>
         <dependencies>
           <dependency>
             <groupId>org.apache.maven.doxia</groupId>
             <artifactId>doxia-module-markdown</artifactId>
-            <version>1.4</version>
+            <version>1.6</version>
           </dependency>
         </dependencies>
       </plugin>
@@ -31,7 +31,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-project-info-reports-plugin</artifactId>
-        <version>2.7</version>
+        <version>2.8.1</version>
         <reportSets>
           <reportSet>
             <reports>
--- a/doc/src/site/markdown/digilib-config.md	Mon Oct 12 11:36:05 2015 +0200
+++ b/doc/src/site/markdown/digilib-config.md	Tue Nov 10 14:30:43 2015 +0100
@@ -54,9 +54,6 @@
 	       is first in list. -->
 	  <parameter name="basedir-list" value="/docuserver/images:/docuserver/scaled/small" />
 	
-	  <!-- mimimum amount of scaling done with antialiasing -->
-	  <parameter name="subsample-minimum" value="2"/>
-	
 	  <!-- default interpolation quality (0=worst) -->
 	  <parameter name="default-quality" value="2"/>
 	
@@ -88,7 +85,7 @@
 	  <!-- use mapping of "virtual directories" to real directories on the server -->
 	  <parameter name="use-mapping" value="false"/>
 	
-	  <!-- location of XML mapping file -->
+	  <!-- location of XML name mapping file -->
 	  <parameter name="mapping-file" value="digilib-map.xml"/>
 	
 	  <!-- location of logger config file -->
--- a/doc/src/site/markdown/digilib-short.md	Mon Oct 12 11:36:05 2015 +0200
+++ b/doc/src/site/markdown/digilib-short.md	Tue Nov 10 14:30:43 2015 +0100
@@ -48,15 +48,12 @@
 pages. Here is a minimal version:
 
 The Scaler servlet takes parameters in the HTTP request format:
-`Scaler/request\_path/?param1=value1&param2=value2&...` Unknown parameters
+`Scaler?param1=value1&param2=value2&...` Unknown parameters
 will be silently ignored.
 
 Recognised parameters:
 
-- `request_path` path to file or directory.
-- `fn` path to file or directory. This path will be added to the
-    `request_path` behind the servlet name. Either parameter can be
-    empty. All paths are relative to the configured base directory 
+- `fn` path to file or directory. All paths are relative to the configured base directory 
     (digilib-config parameter `basedir-list`). 
 - `pn` page number. Index into the (alphabetically sorted)
     directory given by the path. Starts with 1. Ignored if the path
@@ -73,8 +70,7 @@
     <= 1). Default: 1.
 - `wh` relative height of the image area to be sent (0 <= `wh`
     <= 1). Default: 1.
-- The image to be loaded can be specified by the `request_path`
-    (deprecated) or the `fn` (preferred) parameter and the optional
+- The image to be loaded can be specified by  `fn` parameter and the optional
     index `pn`
     - if `fn` points to a directory then the file with the index `pn`
         (in alphabetical order according to ASCII) will be loaded
@@ -88,7 +84,7 @@
 parameter is determined by the aspect ratio of the image.
 
 An example for a Scaler URL is:
-`http://digilib.mpiwg-berlin.mpg.de/digitallibrary/servlet/Scaler?fn=experimental/digilib-test/images&wh=0.1712&ww=0.1282&wy=0.1681&wx=0.6895&dw=862&dh=904`
+`http://digilib.mpiwg-berlin.mpg.de/digitallibrary/Scaler?fn=experimental/digilib-test/images&wh=0.1712&ww=0.1282&wy=0.1681&wx=0.6895&dw=862&dh=904`
 such a URL can be used as src attribute to an img element in the
 frontend HTML.
 
--- a/doc/src/site/markdown/iiif-api.md	Mon Oct 12 11:36:05 2015 +0200
+++ b/doc/src/site/markdown/iiif-api.md	Tue Nov 10 14:30:43 2015 +0100
@@ -2,7 +2,7 @@
 
 The Scaler servlet provides not only its native [Scaler API](scaler-api.html) but also an API compliant to the standards of the International Image Interoperability Framework http://iiif.io.
 
-As of version 2.3 digilib supports the [IIIF Image API version 1.1](http://iiif.io/api/image/1.1/) at [compliance level 2](http://iiif.io/api/image/1.1/compliance.html) (except for forced w,h sizes where the image would be distorted).
+As of version 2.3 digilib supports the [IIIF Image API version 1.1](http://iiif.io/api/image/1.1/) at [compliance level 2](http://iiif.io/api/image/1.1/compliance.html) (since V2.3.3 even for forced w,h sizes where the image will be distorted).
 
 IIIF Image API URLs for an image request have the form:
 
--- a/doc/src/site/markdown/java-settings.md	Mon Oct 12 11:36:05 2015 +0200
+++ b/doc/src/site/markdown/java-settings.md	Tue Nov 10 14:30:43 2015 +0100
@@ -36,12 +36,17 @@
 [`/server/dlConfig.jsp`](http://localhost:8080/digilib/server/dlConfig.jsp)
 status page.
 
-Sometimes there are memory issues. Newer versions of Tomcat refuse to load
-the libraries and I found that in some cases digilib stopped reading TIFF files
+Sometimes there are problems with leaking memory. Newer versions of Tomcat refuse to load
+the libraries (see JREMemoryLeakPreventionListener) and I found that in some 
+cases digilib stopped reading TIFF files
 after a period of running. In these cases it helped to install the JAI files in 
 Tomcats `lib/` directory or globally in the local Java JDK
 installation (i.e. in the Java's 'jre/lib/ext/' directory on linux).
 
+If you really need to have the imageio-plugins JAR inside the web app, please consider 
+using Harald Kuhrs [IIOProviderContextListener](https://github.com/haraldk/TwelveMonkeys#deploying-the-plugins-in-a-web-app).
+ 
+
 # Codec availability and Performance
 
 (Ubbo Veentjer, Oct 2015)
@@ -61,21 +66,27 @@
 
     1564059 [http-apr-9092-exec-4] DEBUG digilib.image.DocuImage  - ImageIO: this reader: class com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader
 
+(Robert Casties, Oct 2015)
+
+You can now use the TwelveMonkeys codecs instead of the default JAI-ImageIO by just [building digilib](build-maven.html) with the Maven-Parameter `imageio=12m`:
+
+    mvn -Dimageio=12m package
+
 # Codec performance
 
 (Ubbo Veentjer, Oct 2015)
 
-In our tests comparing the performance of OpenJDK7, OpenJDK8, imageio-ext and TwelveMonkeys codecs we experienced the following numbers for decoding, encoding and scaling a 4968px*5968px to 50% size: 
+In our tests comparing the performance of OpenJDK7, OpenJDK8, imageio-ext and TwelveMonkeys codecs we experienced the following numbers for decoding, encoding and scaling a 4968px*5968px JPEG file with a color profile to 50% size: 
 
-24801 ms - OpenJDK7
-11507 ms - OpenJDK7 with com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader
-4216 ms - OpenJDK7 with imageio-ext using libjpeg-turbo
-3635 ms - OpenJDK8 
+    24801 ms - OpenJDK7
+    11507 ms - OpenJDK7 with com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader
+    4216 ms - OpenJDK7 with imageio-ext using libjpeg-turbo
+    3635 ms - OpenJDK8 
 
-This numbers may depend on the actual implementation used, the processing power of the CPU and many other factors, to this are just meant to be a rough hint.
+This numbers may depend on the actual implementation used, the processing power of the CPU and many other factors, to this is just meant to be a rough hint.
 
 For using imageio-ext, the native library needs to be 
-available with the LD_LIBRARY_PATH environment variable (compare: https://github.com/geosolutions-it/imageio-ext/wiki/TurboJPEG-plugin), also the .jar archives need to be on the classpath.
+available with the `LD_LIBRARY_PATH` environment variable (compare: https://github.com/geosolutions-it/imageio-ext/wiki/TurboJPEG-plugin), also the .jar archives need to be on the classpath.
 
 For using the TwelveMonkey Codecs we added the following jars to the tomcat lib directory, which were retrieved by maven (dependency on imageio-jpeg-3.1.2):
 
@@ -87,7 +98,7 @@
 * imageio-metadata-3.1.2.jar
 
 
-# Codec availability
+# Available image formats
 
 (Ubbo Veentjer, Oct 2015)
 
--- a/pdf/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/pdf/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -11,11 +11,11 @@
 	<description>The Digital Image Library - PDF generation servlet</description>
 	<url>http://digilib.sourceforge.net</url>
 	<repositories>
-    <repository>
+    <!-- <repository>
       <id>itextpdf.com</id>
       <name>Maven Repository for iText</name>
       <url>http://maven.itextpdf.com/</url>
-    </repository>
+    </repository> -->
     </repositories>
 	<dependencies>
 		<dependency>
@@ -27,7 +27,7 @@
 		<dependency>
 			<groupId>com.itextpdf</groupId>
 			<artifactId>itextpdf</artifactId>
-	        <version>5.5.5</version>
+	        <version>5.5.7</version>
 			<type>jar</type>
 			<scope>compile</scope>
 		</dependency>
@@ -57,7 +57,7 @@
 				<dependency>
 					<groupId>javax.servlet</groupId>
 					<artifactId>servlet-api</artifactId>
-					<version>2.3</version>
+					<version>2.4</version>
 					<type>jar</type>
 					<scope>provided</scope>
 				</dependency>
--- a/pdf/src/main/java/digilib/conf/PDFRequest.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/pdf/src/main/java/digilib/conf/PDFRequest.java	Tue Nov 10 14:30:43 2015 +0100
@@ -32,11 +32,9 @@
 
 import org.apache.log4j.Logger;
 
-import digilib.conf.DigilibConfiguration;
 import digilib.image.ImageJobDescription;
 import digilib.io.DocuDirectory;
 import digilib.io.FileOpException;
-import digilib.io.FileOps.FileClass;
 import digilib.util.NumRange;
 import digilib.util.OptionsSet;
 import digilib.util.ParameterMap;
@@ -59,18 +57,18 @@
 
 	
 	/**
-	 * Initialize the PDFJobInformation
+	 * Initialize the PDFRequest
 	 * 
-	 * @param dlcfg			
-	 * 						The DigilibConfiguration. 
+	 * @param dlcfg	The DigilibConfiguration. 
 	 */
 	public PDFRequest(DigilibConfiguration dlcfg) {
 		super(30);
 		dlConfig = dlcfg;
+		initParams();
 	}
 
 	/**
-	 * Initialize the PDFJobInformation with a request.
+	 * Initialize the PDFRequest with a request.
 	 * 
 	 * @param dlcfg		The DigilibConfiguration. 		
 	 * @param request
@@ -79,7 +77,8 @@
 	public PDFRequest(HttpServletRequest request, DigilibConfiguration dlcfg) throws FileOpException {
 		super(30);
 		dlConfig = dlcfg;
-		this.setWithRequest(request);
+		initParams();
+		setWithRequest(request);
 	}
 
 	
@@ -119,7 +118,7 @@
 		pages = new NumRange(getAsString("pgs"));
         ImageJobDescription ij = ImageJobDescription.getInstance(this, dlConfig);
         DocuDirectory dir = ij.getFileDirectory();
-        int dirsize = dir.size(FileClass.IMAGE);
+        int dirsize = dir.size();
         pages.setMaxnum(dirsize);
 	}
 	
--- a/pdf/src/main/java/digilib/conf/PDFServletConfiguration.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/pdf/src/main/java/digilib/conf/PDFServletConfiguration.java	Tue Nov 10 14:30:43 2015 +0100
@@ -55,7 +55,7 @@
     public static final String PDF_CACHEDIR_KEY = "pdf.servlet.cache.dir";
 
     public static String getClassVersion() {
-        return "2.3.0 pdf";
+        return DigilibServletConfiguration.getClassVersion() + " pdf";
     }
 
     /* non-static getVersion for Java inheritance */
--- a/pdf/src/main/java/digilib/pdf/PDFStreamWorker.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/pdf/src/main/java/digilib/pdf/PDFStreamWorker.java	Tue Nov 10 14:30:43 2015 +0100
@@ -176,7 +176,7 @@
 		Image pdfimg = Image.getInstance(img.getAwtImage(), null);
 		float docW = PageSize.A4.getWidth() - 2 * PageSize.A4.getBorder();
 		float docH = PageSize.A4.getHeight() - 2 * PageSize.A4.getBorder();
-		// TODO: do we really scale this again?
+		// fit the image to the page
 		pdfimg.scaleToFit(docW, docH);
 		// add to PDF
 		doc.add(pdfimg);
--- a/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -2,6 +2,10 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
 
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
   <groupId>digilib</groupId>
   <artifactId>digilib</artifactId>
   <version>2.3.1</version>
@@ -96,7 +100,11 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
-          <version>2.3.2</version>
+          <version>3.3</version>
+            <configuration>
+                <source>1.7</source>
+                <target>1.7</target>
+            </configuration>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -104,7 +112,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-scm-plugin</artifactId>
-        <version>1.5</version>
+        <version>1.9.4</version>
         <configuration>
           <goals>install</goals>
         </configuration>
@@ -124,7 +132,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.9.1</version>
+        <version>2.10.3</version>
         <configuration>
           <show>package</show>
           <nohelp>true</nohelp>
@@ -242,9 +250,6 @@
       </dependency>
     </dependencies>
   </dependencyManagement>
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-  </properties>
 
   <dependencies>
   </dependencies>
--- a/servlet/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -21,7 +21,7 @@
   	<dependency>
   		<groupId>javax.servlet</groupId>
   		<artifactId>servlet-api</artifactId>
-  		<version>2.3</version>
+  		<version>2.4</version>
   		<type>jar</type>
   		<scope>provided</scope>
   	</dependency>
--- a/servlet/src/main/java/digilib/conf/DigilibServletConfiguration.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet/src/main/java/digilib/conf/DigilibServletConfiguration.java	Tue Nov 10 14:30:43 2015 +0100
@@ -88,7 +88,7 @@
     public final Long webappStartTime = System.currentTimeMillis();
 
     public static String getClassVersion() {
-        return "2.3.0 srv";
+        return DigilibConfiguration.getClassVersion() + " srv";
     }
     
     /* non-static getVersion for Java inheritance */
--- a/servlet/src/main/java/digilib/servlet/ServletOps.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet/src/main/java/digilib/servlet/ServletOps.java	Tue Nov 10 14:30:43 2015 +0100
@@ -301,7 +301,9 @@
     }
 
     /**
-     * Write image img to ServletResponse response.
+     * Write image img to ServletResponse response as data of mimeType.
+     * 
+     * If mimeType is null, use heuristics for type.
      * 
      * @param img
      * @param mimeType
@@ -317,13 +319,12 @@
     		logger.error("No response!");
     		return;
     	}
-        //logger.debug("sending to response: ("+ headersToString(response) + ") committed=" + response.isCommitted());
         logger.debug("sending to response. committed=" + response.isCommitted());
-        // TODO: should we erase or replace old last-modified header?
         try {
-            OutputStream outstream = response.getOutputStream();
-            // setup output -- if mime type is set use that otherwise
-            // if source is JPG then dest will be JPG else it's PNG
+            /*
+             * determine the content-type: if mime type is set use that 
+             * otherwise if source is JPG then dest will be JPG else it's PNG
+             */
             if (mimeType == null) {
                 mimeType = img.getMimetype();
                 if (mimeType == null) {
@@ -338,13 +339,28 @@
             } else {
                 mimeType = "image/png";
             }
-            // write the image
+            // set the content type
             response.setContentType(mimeType);
+            String respType = response.getContentType();
+            if (! mimeType.equals(respType)) {
+            	// this shouldn't happen
+            	logger.error("Crap! ServletResponse lost content type! ["+respType+"] Aborting!");
+            	// TODO: would this even help?
+            	response.getOutputStream().close();
+            	return;
+            }
+            
+            /*
+             * write the image
+             */
+            OutputStream outstream = response.getOutputStream();
             img.writeImage(mimeType, outstream);
+            
         } catch (IOException e) {
             throw new ServletException("Error sending image:", e);
-        }
-        // TODO: should we: finally { img.dispose(); }
+        } finally { 
+        	img.dispose(); 
+    	}
     }
 
 
@@ -383,7 +399,8 @@
         } else if (url.endsWith("/")) {
             url = url.substring(0, url.lastIndexOf("/"));
         }
-        response.setContentType("application/json;charset=UTF-8");
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType("application/json,application/ld+json");
         PrintWriter writer;
         try {
             writer = response.getWriter();
--- a/servlet2/src/main/java/digilib/servlet/Scaler.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet2/src/main/java/digilib/servlet/Scaler.java	Tue Nov 10 14:30:43 2015 +0100
@@ -61,7 +61,7 @@
     private static final long serialVersionUID = -5439198888139362735L;
 
     /** digilib servlet version (for all components) */
-    public static final String version = "2.3.1 noasync";
+    public static final String version = DigilibServletConfiguration.getClassVersion() + " noasync";
 
     /** servlet error codes */
     public static enum Error {
--- a/servlet3/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet3/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -7,21 +7,7 @@
   </parent>
   <artifactId>digilib-servlet3</artifactId>
   <name>digilib-servlet3</name>
-  <build>
-  	<pluginManagement>
-  		<plugins>
-  			<plugin>
-  				<groupId>org.apache.maven.plugins</groupId>
-  				<artifactId>maven-compiler-plugin</artifactId>
-                <version>3.3</version>
-                <configuration>
-                    <source>1.7</source>
-                    <target>1.7</target> 
-                </configuration>
-  			</plugin>
-  		</plugins>
-  	</pluginManagement>
-  </build>
+
   <dependencies>
   	<dependency>
   		<groupId>org.mortbay.jetty</groupId>
--- a/servlet3/src/main/java/digilib/conf/DigilibServlet3Configuration.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet3/src/main/java/digilib/conf/DigilibServlet3Configuration.java	Tue Nov 10 14:30:43 2015 +0100
@@ -42,7 +42,7 @@
 public class DigilibServlet3Configuration extends DigilibServletConfiguration {
 
     public static String getClassVersion() {
-        return "2.3.0 async";
+        return DigilibServletConfiguration.getClassVersion() + " async";
     }
 
     /* non-static getVersion for Java inheritance */
--- a/servlet3/src/main/java/digilib/servlet/AsyncServletWorker.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet3/src/main/java/digilib/servlet/AsyncServletWorker.java	Tue Nov 10 14:30:43 2015 +0100
@@ -54,10 +54,10 @@
 public class AsyncServletWorker implements Runnable, AsyncListener {
 
     /** the AsyncServlet context */
-    private AsyncContext asyncContext;
+    private AsyncContext asyncContext = null;
 
     /** the ImageWorker we use */
-    private ImageWorker imageWorker;
+    private ImageWorker imageWorker = null;
 
     protected static Logger logger = Logger.getLogger(AsyncServletWorker.class);
     private long startTime;
@@ -92,22 +92,29 @@
     @Override
     public void run() {
         try {
-            // render the image
+            /*
+             * render the image
+             */
             DocuImage img = imageWorker.call();
             if (completed) {
                 logger.debug("AsyncServletWorker already completed (after scaling)!");
                 return;
             }
-            // forced destination image type
+            /*
+             * set forced destination image type
+             */
             String mt = null;
             if (jobinfo.hasOption("jpg")) {
                 mt = "image/jpeg";
             } else if (jobinfo.hasOption("png")) {
                 mt = "image/png";
             }
-            // send image
-            ServletOps.sendImage(img, mt,
-                    (HttpServletResponse) asyncContext.getResponse(), logger);
+            /*
+             *  send the image
+             */
+            HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
+            ServletOps.sendImage(img, mt, response, logger);
+            
             logger.debug("Job done in: "
                     + (System.currentTimeMillis() - startTime) + "ms");
         } catch (ImageOpException e) {
@@ -128,7 +135,7 @@
             } else {
                 // submit response
                 logger.debug("context complete.");
-                this.completed = true;
+                completed = true;
                 asyncContext.complete();
             }
         }
@@ -144,7 +151,7 @@
     public void onComplete(AsyncEvent event) throws IOException {
         logger.debug("AsyncServletWorker onComplete");
         // make sure complete isn't called twice
-        this.completed = true;
+        completed = true;
     }
 
     @Override
@@ -155,7 +162,7 @@
             return;
         }
         imageWorker.stopNow();
-        this.completed = true;
+        completed = true;
         Scaler.digilibError(errMsgType, Error.UNKNOWN, null,
                 (HttpServletResponse) asyncContext.getResponse());
         asyncContext.complete();
@@ -171,7 +178,7 @@
             return;
         }
         imageWorker.stopNow();
-        this.completed = true;
+        completed = true;
         Scaler.digilibError(errMsgType, Error.UNKNOWN, "ERROR: timeout rendering image!",
                 (HttpServletResponse) asyncContext.getResponse());
         asyncContext.complete();
--- a/servlet3/src/main/java/digilib/servlet/Scaler.java	Mon Oct 12 11:36:05 2015 +0200
+++ b/servlet3/src/main/java/digilib/servlet/Scaler.java	Tue Nov 10 14:30:43 2015 +0100
@@ -228,9 +228,11 @@
 
         accountlog.debug("request: " + request.getQueryString());
         logger.debug("request: " + request.getQueryString());
-        logger.debug("headers: " + ServletOps.headersToString(request));
-        // logger.debug("response:"+ response + " committed=" +
-        // response.isCommitted());
+        //logger.debug("headers: " + ServletOps.headersToString(request));
+        //logger.debug("processRequest response committed=" + response.isCommitted());
+        if (response.isCommitted()) {
+        	logger.error("Crap: response committed before we got a chance!");
+        }
         final long startTime = System.currentTimeMillis();
 
         // parse request
@@ -262,11 +264,13 @@
         
         try {
             /*
-             * check if we can fast-track without scaling
+             * get the input file
              */
             ImageInput fileToLoad = (ImageInput) jobTicket.getInput();
 
-            // check permissions
+            /*
+             * check permissions
+             */
             if (useAuthorization) {
                 // is the current request/user authorized?
                 if (!authOp.isAuthorized(dlRequest)) {
@@ -275,10 +279,13 @@
                 }
             }
 
-            // if requested, send image as a file
+            /*
+             * if requested, send image as a file
+             */
             if (sendFileAllowed && jobTicket.getSendAsFile()) {
                 String mt = null;
                 if (jobTicket.hasOption("rawfile")) {
+                	// mo=rawfile sends as octet-stream
                     mt = "application/octet-stream";
                 }
                 logger.debug("Sending RAW File as is.");
@@ -287,7 +294,9 @@
                 return;
             }
 
-            // if possible, send the image without actually having to transform it
+            /*
+             * send the image if it's possible without having to transform it
+             */
             if (!jobTicket.isTransformRequired()) {
                 logger.debug("Sending File as is.");
                 ServletOps.sendFile(fileToLoad.getFile(), null, null, response, logger);
@@ -295,15 +304,19 @@
                 return;
             }
 
-            // check load of workers
+            /*
+             * check load of workers
+             */
             if (imageJobCenter.isBusy()) {
                 logger.error("Servlet overloaded!");
                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                 return;
             }
 
-            // worker job is done asynchronously
-            AsyncContext asyncCtx = request.startAsync(request, response);
+            /*
+             * dispatch worker job to be done asynchronously
+             */
+            AsyncContext asyncCtx = request.startAsync();
             // create job
             AsyncServletWorker job = new AsyncServletWorker(dlConfig, jobTicket, asyncCtx, errMsgType, startTime);
             // AsyncServletWorker is its own AsyncListener
@@ -348,7 +361,7 @@
                 status = HttpServletResponse.SC_FORBIDDEN;
             } else if (error == Error.FILE) {
                 if (msg == null) {
-                    msg = "ERROR: Image file not found!";
+                    msg = "ERROR: Image file not found or image not readable!";
                 }
                 img = notfoundImgFile;
                 status = HttpServletResponse.SC_NOT_FOUND;
--- a/text/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/text/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -38,7 +38,7 @@
 				<dependency>
 					<groupId>javax.servlet</groupId>
 					<artifactId>servlet-api</artifactId>
-					<version>2.3</version>
+					<version>2.4</version>
 					<type>jar</type>
 					<scope>provided</scope>
 				</dependency>
--- a/webapp/pom.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/webapp/pom.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -12,17 +12,13 @@
 	<url>http://digilib.sourceforge.net</url>
 	<packaging>war</packaging>
 
-	<properties>
-		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-	</properties>
-
 	<build>
 		<pluginManagement>
 			<plugins>
 				<plugin>
 					<groupId>org.apache.maven.plugins</groupId>
 					<artifactId>maven-war-plugin</artifactId>
-					<version>2.1.1</version>
+					<version>2.6</version>
 				</plugin>
 				<plugin>
 					<groupId>org.codehaus.mojo</groupId>
@@ -61,7 +57,7 @@
 						<groupId>org.apache.maven.plugins</groupId>
 						<artifactId>maven-war-plugin</artifactId>
 						<configuration>
-							<webXml>${basedir}/src/main/webapp/WEB-INF/web-2.3.xml</webXml>
+							<webXml>${basedir}/src/main/webapp/WEB-INF/web-2.4.xml</webXml>
 							<classifier>srv2</classifier>
 						</configuration>
 					</plugin>
@@ -99,8 +95,8 @@
 						<groupId>org.apache.maven.plugins</groupId>
 						<artifactId>maven-compiler-plugin</artifactId>
 						<configuration>
-							<source>1.6</source>
-							<target>1.6</target>
+							<source>1.7</source>
+							<target>1.7</target>
 						</configuration>
 					</plugin>
 				</plugins>
--- a/webapp/src/main/webapp/WEB-INF/web-2.3.xml	Mon Oct 12 11:36:05 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
- 
-<web-app>
-  <!-- General description of your web application -->
-  <display-name>
-        digilib
-  </display-name>
-  <description>
-        This is the web frontend of the Digital Document Library.
-  </description>
-  <!-- The Intialisation Listener -->
-  <listener>
-        <listener-class>
-            digilib.conf.DigilibServletConfiguration
-        </listener-class>
-  </listener>
-  <!-- The Scaler servlet -->
-  <servlet>
-        <servlet-name>
-            Scaler
-        </servlet-name>
-        <servlet-class>
-            digilib.servlet.Scaler
-        </servlet-class>
-        <!-- Load this servlet at server startup time -->
-        <load-on-startup>
-            5
-        </load-on-startup>
-  </servlet>
-  <!-- The mapping for the Scaler servlet -->
-  <servlet-mapping>
-        <servlet-name>
-            Scaler
-        </servlet-name>
-        <url-pattern>
-            /servlet/Scaler/*
-        </url-pattern>
-  </servlet-mapping>
-  <servlet-mapping>
-        <servlet-name>
-            Scaler
-        </servlet-name>
-        <url-pattern>
-            /Scaler/*
-        </url-pattern>
-  </servlet-mapping>
-</web-app>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/src/main/webapp/WEB-INF/web-2.4.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+     version="2.4">
+     
+  <!-- General description of your web application -->
+  <display-name>
+        digilib
+  </display-name>
+  <description>
+        This is the web frontend of the Digital Document Library.
+  </description>
+  <!-- The Intialisation Listener -->
+  <listener>
+        <listener-class>
+            digilib.conf.DigilibServletConfiguration
+        </listener-class>
+  </listener>
+  <!-- The Scaler servlet -->
+  <servlet>
+        <servlet-name>
+            Scaler
+        </servlet-name>
+        <servlet-class>
+            digilib.servlet.Scaler
+        </servlet-class>
+        <!-- Load this servlet at server startup time -->
+        <load-on-startup>
+            5
+        </load-on-startup>
+  </servlet>
+  <!-- The mapping for the Scaler servlet -->
+  <servlet-mapping>
+        <servlet-name>
+            Scaler
+        </servlet-name>
+        <url-pattern>
+            /servlet/Scaler/*
+        </url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+        <servlet-name>
+            Scaler
+        </servlet-name>
+        <url-pattern>
+            /Scaler/*
+        </url-pattern>
+  </servlet-mapping>
+</web-app>
--- a/webapp/src/main/webapp/WEB-INF/web-3.0.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/webapp/src/main/webapp/WEB-INF/web-3.0.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <web-app
-        version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+        version="3.0">
         
   <!-- General description of your web application -->
   <display-name>
--- a/webapp/src/main/webapp/WEB-INF/web-additional.xml	Mon Oct 12 11:36:05 2015 +0200
+++ b/webapp/src/main/webapp/WEB-INF/web-additional.xml	Tue Nov 10 14:30:43 2015 +0100
@@ -40,6 +40,10 @@
     <!-- The mapping for the PDFCache servlet -->
     <servlet-mapping>
         <servlet-name>PDFCache</servlet-name>
+        <url-pattern>/PDFCache/*</url-pattern>
+    </servlet-mapping>
+    <servlet-mapping>
+        <servlet-name>PDFCache</servlet-name>
         <url-pattern>/servlet/PDFCache/*</url-pattern>
     </servlet-mapping>
     <servlet-mapping>
--- a/webapp/src/main/webapp/jquery/jquery.digilib.measure.js	Mon Oct 12 11:36:05 2015 +0200
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.measure.js	Tue Nov 10 14:30:43 2015 +0100
@@ -1393,7 +1393,7 @@
                     shape.properties.maxvtx = 2;
                 },
                 'svg' : function (shape) {
-                    var $s = factory['Line'](shape);
+                    var $s = factory['Line'].svg(shape);
                     var place = $s.place;
                     var gridID = shape.id + '-grid';
                     var props = shape.properties;
--- a/webapp/src/main/webapp/jquery/jquery.digilib.sliders.js	Mon Oct 12 11:36:05 2015 +0200
+++ b/webapp/src/main/webapp/jquery/jquery.digilib.sliders.js	Tue Nov 10 14:30:43 2015 +0100
@@ -151,13 +151,14 @@
 
     // update preview values for a given slider
     var updatePreview = function ($slider) {
-        if ($slider == null) return;
-        var cls = $slider.data('cls');
+        if ($slider == null) {
+            return;
+            }
         var $preview = $slider.data('preview');
         if ($preview == null) {
-            console.error("slider updatePreview without preview!");
             return;
-        }
+            }
+        var cls = $slider.data('cls');
         var $td2 = $preview.find('table.'+cls+'preview td');
         // account for current brgt/cont/rgbm/rgba values
         var calcRGBValue = function (code, val) {
@@ -168,7 +169,7 @@
             var brgt = colorVals.brgt;
             var resultVal = cont + brgt;
             return Math.min(Math.max(Math.round(resultVal), 0), 255);
-           };
+            };
         // color one table cell according to index position
         var setRGBValues = function (index) {
             var val = index * 32;
@@ -340,12 +341,12 @@
                 update($slider, val);
                 }
             };
-        var reset = function () {
+        var resetToStartvalue = function () {
             $text.val(startvalue);
             textChange();
             };
-        var resetdefault = function () {
-            $text.val(opts.start);
+        var resetToDefaultvalue = function () {
+            $text.val(options.start);
             textChange();
             };
         // connect slider and input
@@ -354,8 +355,8 @@
         $slider.data({
             '$text' : $text,
             '$range' : $range,
-            'reset' : reset,
-            'default' : resetdefault,
+            'reset' : resetToStartvalue,
+            'default' : resetToDefaultvalue,
             'update' : null
         });
         return $slider;
@@ -434,7 +435,7 @@
             var rgba = input['ra'].val() + '/' + input['ga'].val() + '/' + input['ba'].val();
             var rgbm = input['rm'].val() + '/' + input['gm'].val() + '/' + input['bm'].val();
             if (typeof onSubmit === 'function') {
-                (rgbm, rgba);
+                onSubmit(rgbm, rgba);
                 }
             };
         $.each(primaryColors, insertTableRow);