changeset 1411:67134a81061b new_scaling

fixed black-last-pixel bug by rounding up scale factor in ImageLoaderDocuImage.scale(). added comments and formatting.
author robcast
date Tue, 20 Oct 2015 19:31:10 +0200
parents 9fc1a281575f
children 52d0730b1f32 e12398d83238
files common/src/main/java/digilib/image/ImageJobDescription.java common/src/main/java/digilib/image/ImageLoaderDocuImage.java common/src/main/java/digilib/image/ImageWorker.java
diffstat 3 files changed, 166 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/common/src/main/java/digilib/image/ImageJobDescription.java	Tue Oct 20 17:10:28 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageJobDescription.java	Tue Oct 20 19:31:10 2015 +0200
@@ -375,7 +375,6 @@
     /**
      * Returns image scaling factor.
      * Uses image size and user parameters.
-     * Modifies scaleXY, userImgArea.
      * 
      * @return
      * @throws IOException
@@ -404,6 +403,7 @@
              */
             double scaleX = getDw() / areaWidth * ws;
             double scaleY = getDh() / areaHeight * ws;
+            // use the smaller factor to get fit-in-box
             scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
         } else if (isAbsoluteScale()) {
             /*
@@ -473,21 +473,25 @@
      * @throws IOException
      */
     public int getDw() throws IOException {
-        logger.debug("get_paramDW()");
+        //logger.debug("get_paramDW()");
         if (paramDW == null) {
 
             paramDW = getAsInt("dw");
             paramDH = getAsInt("dh");
 
             if (paramDW == 0) {
-                // calculate dw using aspect ratio of image area
+                /*
+                 * calculate dw using aspect ratio of image area
+                 */
                 userImgArea = getUserImgArea();
                 double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
                 // round up to make sure we don't squeeze dh
                 paramDW = (int) Math.ceil(paramDH * imgAspect);
                 setValue("dw", paramDW);
             } else if (paramDH == 0) {
-                // calculate dh using aspect ratio of image area
+                /*
+                 * calculate dh using aspect ratio of image area
+                 */
             	userImgArea = getUserImgArea();
                 double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
                 // round up to make sure we don't squeeze dw
@@ -506,24 +510,28 @@
      * @throws IOException
      */
     public int getDh() throws IOException {
-        logger.debug("get_paramDH()");
+        //logger.debug("get_paramDH()");
         if (paramDH == null) {
 
             paramDW = getAsInt("dw");
             paramDH = getAsInt("dh");
 
             if (paramDW == 0) {
-                // calculate dw using aspect ratio of image area
+                /*
+                 * calculate dw using aspect ratio of image area
+                 */
                 userImgArea = getUserImgArea();
                 double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
                 // round up to make sure we don't squeeze dh
                 paramDW = (int) Math.ceil(paramDH * imgAspect);
                 setValue("dw", paramDW);
             } else if (paramDH == 0) {
-                // calculate dh using aspect ratio of image area
+                /*
+                 * calculate dh using aspect ratio of image area
+                 */
                 userImgArea = getUserImgArea();
                 double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
-                // round up to make sure we don't squeeze dh
+                // round up to make sure we don't squeeze dw
                 paramDH = (int) Math.ceil(paramDW / imgAspect);
                 setValue("dh", paramDH);
             }
@@ -539,7 +547,7 @@
      * @throws IOException
      */
     public Float getWw() throws IOException {
-        logger.debug("get_paramWW()");
+        //logger.debug("get_paramWW()");
         if (paramWW == null) {
         	paramWW = getAsFloat("ww");
         	if (hasOption("pxarea")) {
@@ -559,7 +567,7 @@
      * @throws IOException
      */
     public Float getWh() throws IOException {
-        logger.debug("get_paramWH()");
+        //logger.debug("get_paramWH()");
         if (paramWH == null) {
         	paramWH = getAsFloat("wh");
         	if (hasOption("pxarea")) {
@@ -579,7 +587,7 @@
      * @throws IOException
      */
     public Float getWx() throws IOException {
-        logger.debug("get_paramWX()");
+        //logger.debug("get_paramWX()");
         if (paramWX == null) {
         	paramWX = getAsFloat("wx");
         	if (hasOption("pxarea")) {
@@ -599,7 +607,7 @@
      * @throws IOException
      */
     public Float getWy() throws IOException {
-        logger.debug("get_paramWY()");
+        //logger.debug("get_paramWY()");
         if (paramWY == null) {
         	paramWY = getAsFloat("wy");
         	if (hasOption("pxarea")) {
@@ -617,7 +625,7 @@
      * @return
      */
     public int getScaleQual() {
-        logger.debug("get_scaleQual()");
+        //logger.debug("get_scaleQual()");
         int qual = dlConfig.getAsInt("default-quality");
         if (hasOption("q0"))
             qual = 0;
@@ -658,16 +666,16 @@
             // size of the currently selected input image
             ImageSize imgSize = getInput().getSize();
             // transform from relative [0,1] to image coordinates.
-            float areaXf = getWx() * imgSize.getWidth();
-            float areaYf = getWy() * imgSize.getHeight();
-            float areaWidthF = getWw() * imgSize.getWidth();
-            float areaHeightF = getWh() * imgSize.getHeight();
+            double areaXf = getWx() * imgSize.getWidth();
+            double areaYf = getWy() * imgSize.getHeight();
+            double areaWidthF = getWw() * imgSize.getWidth();
+            double areaHeightF = getWh() * imgSize.getHeight();
             // round to pixels
-    		int areaX = Math.round(areaXf);
-    		int areaY = Math.round(areaYf);
-    		int areaHeight = Math.round(areaHeightF);
-    		int areaWidth = Math.round(areaWidthF);
-            userImgArea = new Rectangle2D.Float(areaX, areaY, areaWidth, areaHeight);
+    		long areaX = Math.round(areaXf);
+    		long areaY = Math.round(areaYf);
+    		long areaHeight = Math.round(areaHeightF);
+    		long areaWidth = Math.round(areaWidthF);
+            userImgArea = new Rectangle2D.Double(areaX, areaY, areaWidth, areaHeight);
         }
         return userImgArea;
     }
@@ -675,6 +683,10 @@
     /**
      * Returns the maximal area of the source image that will be used.
      * 
+     * This was meant to correct for missing pixels outside the 
+     * userImgArea when rotating oblique angles but is not yet implemented.
+     * Currently returns userImgArea.
+     * 
      * @return
      * @throws IOException
      * @throws ImageOpException
@@ -692,18 +704,21 @@
 
             // 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!");
+            	logger.debug("scaleXY="+scaleXY+" outerUserImgArea="+outerUserImgArea);
                 throw new ImageOpException("Invalid scale parameter set!");
             }
         }
         return outerUserImgArea;
     }
 
+    /**
+     * Get the RGBM parameter set.
+     * 
+     * @return
+     */
     public float[] getRGBM() {
         float[] paramRGBM = null;// {0f,0f,0f};
         Parameter p = params.get("rgbm");
@@ -713,6 +728,11 @@
         return paramRGBM;
     }
 
+    /**
+     * Get the RGBA parameter set.
+     * 
+     * @return
+     */
     public float[] getRGBA() {
         float[] paramRGBA = null;// {0f,0f,0f};
         Parameter p = params.get("rgba");
--- a/common/src/main/java/digilib/image/ImageLoaderDocuImage.java	Tue Oct 20 17:10:28 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageLoaderDocuImage.java	Tue Oct 20 19:31:10 2015 +0200
@@ -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.7";
 
     /** image object */
     protected BufferedImage img;
@@ -167,18 +167,24 @@
     /** 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 +200,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 +227,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) {
@@ -266,7 +281,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,7 +353,11 @@
         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;
@@ -391,7 +413,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
@@ -446,36 +471,43 @@
         // 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);
-        logger.debug("scaled from " + img.getWidth() + "x" + img.getHeight() + " img=" + img);
         /* 
-         * 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 oldWidth = img.getWidth();
-        int oldHeight = img.getHeight();
-        double targetWidth = oldWidth * scaleX;
-        double targetHeight = oldHeight * scaleY;
-        double deltaX = targetWidth - Math.floor(targetWidth);
-        double deltaY = targetHeight - Math.floor(targetHeight);
-        logger.debug("dw="+targetWidth+" dh="+targetHeight);
-        logger.debug("dx="+deltaX+" dy="+deltaY);
+        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) {
-            scaleX += (1 - deltaX) / oldWidth;
+        	// round up
+        	logger.debug("rounding up x scale factor");
+            scaleX += (1 - deltaX) / imgW;
         }
         if (deltaY > epsilon) {
-            scaleY += (1 - deltaY) / oldHeight;
+        	// round up
+        	logger.debug("rounding up y scale factor");
+            scaleY += (1 - deltaY) / imgH;
         }
-        double targetWidth2 = oldWidth * scaleX;
-        double targetHeight2 = oldHeight * scaleY;
-        logger.debug("dw="+targetWidth2+" dh="+targetHeight2);
+        // 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);
@@ -483,6 +515,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
@@ -514,6 +552,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);
@@ -522,6 +564,10 @@
         imageSize = null;
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#rotate(double)
+     */
     public void rotate(double angle) throws ImageOpException {
         logger.debug("rotate: " + angle);
         // setup rotation
@@ -550,6 +596,10 @@
         imageSize = null;
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#mirror(double)
+     */
     public void mirror(double angle) throws ImageOpException {
         logger.debug("mirror: " + angle);
         // setup mirror
@@ -582,6 +632,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);
@@ -613,6 +667,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);
         /*
@@ -724,6 +783,10 @@
         }
     }
 
+    /* 
+     * (non-Javadoc)
+     * @see digilib.image.DocuImageImpl#dispose()
+     */
     public void dispose() {
         if (reader != null) {
             reader.dispose();
@@ -732,6 +795,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	Tue Oct 20 17:10:28 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageWorker.java	Tue Oct 20 19:31:10 2015 +0200
@@ -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,6 +77,7 @@
         // set interpolation quality
         docuImage.setQuality(jobinfo.getScaleQual());
 
+        // get area of interest and scale factor
         Rectangle loadRect = jobinfo.getOuterUserImgArea().getBounds();
         double scaleXY = jobinfo.getScaleXY();
 
@@ -85,8 +85,11 @@
             logger.debug("ImageWorker stopping (after setup)");
             return null;
         }
-        // use subimage loading if possible
+        /*
+         * load, crop and scale the image 
+         */
         if (docuImage.isSubimageSupported()) {
+        	// use subimage loading if possible
             logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
             double subf = 1d;
             double subsamp = 1d;
@@ -126,26 +129,29 @@
             }
             docuImage.scale(scaleXY, scaleXY);
         }
-
         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 +168,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 +193,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;
     }