Mercurial > hg > digilib
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; }