All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.image.ResizeOp Maven / Gradle / Ivy

The newest version!
/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.day.image;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

/**
 * The ResizeOp class implements a weighted resize filter which produces better
 * results than the scaling AffineTransformOp and is faster than a blurring
 * ConvolveOp with a following scaling AffineTransformOp.
 * 

* The RenderingHints defined at construction time are used if the * destination color model has to be adapted for the filter operation. *

* Note that the following constraints have to be met *

    *
  • The source and destination must be different. *
* * @see Implementation of Image * Resizing * @version $Revision$ * @author tripod * @author fmeschbe * @since coati * @audience wad */ public class ResizeOp extends AbstractBufferedImageOp { /** The horizontal scale factor */ private final double scaleX; /** The vertical scale factor */ private final double scaleY; /** * use faster algorithm if specified */ private boolean fast; /** * Creates a new ResizeOp object. * * @param scaleX The horizontal scale factor * @param scaleY The vertical scale factor * @param hints The rendering hints. May be null. */ public ResizeOp(double scaleX, double scaleY, RenderingHints hints) { super(hints); this.scaleX = scaleX; this.scaleY = scaleY; } /** * Creates a new ResizeOp with no rendering hints. * * @param scaleX The horizontal scale factor * @param scaleY The vertical scale factor */ public ResizeOp(double scaleX, double scaleY) { this(scaleX, scaleY, null); } /** * Creates a new ResizeOp with no rendering hints and the same * horizontal and vertical scale factor. * * @param scale The scale factor used for both horizontal and vertical * scaling. */ public ResizeOp(double scale) { this(scale, scale); } // ---------- BufferedImageOp interface // ------------------------------------- /** * Returns the bounding box of the filtered destination image. The * IllegalArgumentException may be thrown if the source image is * incompatible with the types of images allowed by the class implementing * this filter. */ public Rectangle2D getBounds2D(BufferedImage src) { int nw = (int) Math.ceil((src.getWidth() * scaleX)); int nh = (int) Math.ceil((src.getHeight() * scaleY)); return new Rectangle2D.Double(0, 0, nw, nh); } /** * Returns the location of the destination point given a point in the source * image. If dstPt is non-null, it will be used to hold the return value. */ public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { if (dstPt == null) { dstPt = new Point2D.Float(); } dstPt.setLocation(srcPt.getX() * scaleX, srcPt.getY() * scaleY); return dstPt; } public boolean isFast() { return fast; } public void setFast(boolean fast) { this.fast = fast; } // ---------- protected // ----------------------------------------------------- /** * Implements the resize operation. * * @param src The source image to be operated upon. * @param dst The destination image getting the resulting image. This must */ protected void doFilter(BufferedImage src, BufferedImage dst) { if (fast) { doFilter_progressive(src, dst); } else { doFilter_weighted(src, dst); } } /** * Implements the resizing using Java2D with bicubic interpolation. note * that this is only supported in some jdk 1.5 JVMs * * @param src The source image to be operated upon. * @param dst The destination image getting the resulting image. This must */ private static void doFilter_bicubic(BufferedImage src, BufferedImage dst) { Graphics2D g = dst.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.drawImage(src, 0, 0, dst.getWidth(), dst.getHeight(), null); g.dispose(); } /** * Implements the resizing using Java2D with bilinear interpolation. * * @param src The source image to be operated upon. * @param dst The destination image getting the resulting image. This must */ private static void doFilter_bilinear(BufferedImage src, BufferedImage dst) { Graphics2D g = dst.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(src, 0, 0, dst.getWidth(), dst.getHeight(), null); g.dispose(); } /** * Implements the resizing using Java2D with a progressive bilinear * algorithm. * * @param src The source image to be operated upon. * @param dst The destination image getting the resulting image. This must */ static void doFilter_progressive(BufferedImage src, BufferedImage dst) { int dw = dst.getWidth() * 2; int dh = dst.getHeight() * 2; BufferedImage ori = src; while (src.getWidth() > dw && src.getHeight() > dh) { int type = src.getType(); BufferedImage tmp = new BufferedImage(src.getWidth() / 2, src.getHeight() / 2, ((type == 0) ? Layer.IMAGE_TYPE : type)); doFilter_bilinear(src, tmp); if (src != ori) { src.flush(); } src = tmp; } doFilter_bilinear(src, dst); if (src != ori) { src.flush(); } } /** * Implements the weighted filter algorithm. This method does not depend on * the source and destination to have the same color model, as it operates * on the RGB color value extracted with the getRGB() method * and sets the destination with setRGB() which internally * convert to the real color model. *

* The source and destination must not be the same and not null. * * @param src The source image which is to be scaled. * @param dst The destination image getting the scaled image. This must not * be null. */ private static void doFilter_weighted(BufferedImage src, BufferedImage dst) { int ow = src.getWidth(); int oh = src.getHeight(); int nw = dst.getWidth(); int nh = dst.getHeight(); int srcChunkHeight; // height of a pixel block from source int dstChunkHeight; // height of a pixel block to destination int blockHeight; // max(srcChunkHeight, dstChunkHeight) int init_s; // scaling factor for vertical scaling int blockWidth = (ow > nw) ? ow : nw; int srcChunkStep; // scan stepping for source block data int dstChunkStep; // scan stepping for destination block data int scaleCacheSize = 0; // cacheline size for vertical down-scaling if (oh > nh) { // scale down vertically - need more input for less output srcChunkHeight = divideAndRoundUp(oh,nh); // input lines srcChunkStep = srcChunkHeight; dstChunkHeight = dstChunkStep = 1; // output lines blockHeight = srcChunkHeight; init_s = nh; scaleCacheSize = nw; } else if (oh < nh) { // scale up vertically - need less input for more output srcChunkHeight = 1; // input lines srcChunkStep = 1; dstChunkHeight = dstChunkStep = divideAndRoundUp(nh,oh); // output lines blockHeight = dstChunkHeight; init_s = oh; scaleCacheSize = nw; } else { // no vertical scaling srcChunkHeight = srcChunkStep = dstChunkHeight = dstChunkStep = 1; blockHeight = srcChunkHeight; init_s = nh; } // memory buffer for source data - destination of getSample() int[] sr = new int[blockWidth * blockHeight]; int[] sg = new int[blockWidth * blockHeight]; int[] sb = new int[blockWidth * blockHeight]; int[] sa = new int[blockWidth * blockHeight]; // memory buffer for destination data - source of setSample() int[] dr = new int[blockWidth * blockHeight]; int[] dg = new int[blockWidth * blockHeight]; int[] db = new int[blockWidth * blockHeight]; int[] da = new int[blockWidth * blockHeight]; // crgba cache for previous parts in vertical down-scaling int[] ccr = new int[scaleCacheSize]; int[] ccg = new int[scaleCacheSize]; int[] ccb = new int[scaleCacheSize]; int[] cca = new int[scaleCacheSize]; // the rasters Raster srcRas = src.getRaster(); WritableRaster dstRas = dst.getRaster(); for (int srcChunkTop = 0, dstChunkTop = 0; srcChunkTop < oh; srcChunkTop += srcChunkStep, dstChunkTop += dstChunkStep) { // ensure input chunk does not overlap end of the image if (srcChunkTop + srcChunkHeight > oh) { srcChunkHeight = oh - srcChunkTop; } // get a chunk srcRas.getSamples(0, srcChunkTop, ow, srcChunkHeight, 0, sr); srcRas.getSamples(0, srcChunkTop, ow, srcChunkHeight, 1, sg); srcRas.getSamples(0, srcChunkTop, ow, srcChunkHeight, 2, sb); srcRas.getSamples(0, srcChunkTop, ow, srcChunkHeight, 3, sa); /** * Horizontal scaling takes pixel values from the source buffer and * writes the scaled results into the destination buffer. */ // horizontal scaling if (nw < ow) { preProcessTransparentPixels(sr, sg, sb, sa); // scale < 1 --> reduce horizontal size for (int oy = 0, oi = 0, ni = 0; oy < srcChunkHeight; oy++) { for (int ox = 0, s = nw, cr = 0, cg = 0, cb = 0, ca = 0; ox < ow; ox++, s += nw, oi++) { if (s >= ow) { int a = ow - s + nw; cr += sr[oi] * a; cg += sg[oi] * a; cb += sb[oi] * a; ca += sa[oi] * a; dr[ni] = cr / ow; dg[ni] = cg / ow; db[ni] = cb / ow; da[ni] = ca / ow; s -= ow; ni++; cr = sr[oi] * s; cg = sg[oi] * s; cb = sb[oi] * s; ca = sa[oi] * s; } else { cr += sr[oi] * nw; cg += sg[oi] * nw; cb += sb[oi] * nw; ca += sa[oi] * nw; } } } } else if (nw > ow) { // scale > 1 --> enlarge horizontal size for (int oy = 0, oi = 0, ni = 0; oy < srcChunkHeight; oy++) { for (int nx = 0, ox = 0, s = ow; nx < nw; nx++, s += ow, ni++) { if (s >= nw) { int a = nw - s + ow; int oi1 = (ox < ow - 1) ? oi + 1 : oi; s -= nw; dr[ni] = (sr[oi] * a + sr[oi1] * s) / ow; dg[ni] = (sg[oi] * a + sg[oi1] * s) / ow; db[ni] = (sb[oi] * a + sb[oi1] * s) / ow; da[ni] = (sa[oi] * a + sa[oi1] * s) / ow; oi++; ox++; } else { dr[ni] = sr[oi]; dg[ni] = sg[oi]; db[ni] = sb[oi]; da[ni] = sa[oi]; } } } } /** * At this point, the destination buffer contains the data if and * only if horizontal scaling took place, else the data to work on * is still in the source buffer. */ /** * Exchange source and destination buffers, if either both * dimensions get adapted or none. If only vertical, the source * already contains the pixel source and if only horizontal, the * destination already contains the destination pixels. */ if ((nw == ow && nh == oh) || (nw != ow && nh != oh)) { int[] tr = sr, tg = sg, tb = sb, ta = sa; sr = dr; sg = dg; sb = db; sa = da; dr = tr; dg = tg; db = tb; da = ta; } /** * Vertical scaling takes pixel values from the source buffer and * writes the scaled results into the destination buffer. */ // vertical scaling if (nh < oh) { // scale < 1 --> reduce preProcessTransparentPixels(sr, sg, sb, sa); // might need to fill with first row if (srcChunkTop == 0) { for (int i = 0; i < nw; i++) { ccr[i] = sr[i] * nh; ccg[i] = sg[i] * nh; ccb[i] = sb[i] * nh; cca[i] = sa[i] * nh; init_s = 2 * nh; } } int start_s = init_s; for (int ox = 0; ox < nw; ox++) { // prefill crgba with cached previous values int cr = ccr[ox]; int cg = ccg[ox]; int cb = ccb[ox]; int ca = cca[ox]; int s = start_s; for (int oy = 0, oi = ox, ni = ox; oy < dstChunkHeight; s += nh, oi += nw) { if (s >= oh) { int a = oh - s + nh; cr += sr[oi] * a; cg += sg[oi] * a; cb += sb[oi] * a; ca += sa[oi] * a; dr[ni] = cr / oh; dg[ni] = cg / oh; db[ni] = cb / oh; da[ni] = ca / oh; oy++; ni += nw; s -= oh; cr = sr[oi] * s; cg = sg[oi] * s; cb = sb[oi] * s; ca = sa[oi] * s; } else { cr += sr[oi] * nh; cg += sg[oi] * nh; cb += sb[oi] * nh; ca += sa[oi] * nh; } } // cache current crgba values and s ccr[ox] = cr; ccg[ox] = cg; ccb[ox] = cb; cca[ox] = ca; init_s = s; } // calculate number of lines needed srcChunkStep = srcChunkHeight; int sum = oh - init_s + nh; srcChunkHeight = sum / nh; if ((srcChunkHeight * nh) != sum) { srcChunkHeight++; }; } else if (nh > oh) { /** * The first block is used to initialize the cache row. The real * data copying starts with the second block, where the cache is * first copied and after having copied the cache enough the * newly read line is copied. For each of the destination rows, * except the last, the cache row is copied into the * destination, the last row is a mixture of the cache row and * the next row. At the end the next row is copied into the * cache for the next block. At the end the last input line has * only just been copied to the cache has not been used yet, we * have to force another loop. This is not optimal as the last * line is read and horizontally scaled twice. */ if (srcChunkTop == 0) { // copy the first row to the cache System.arraycopy(sr, 0, ccr, 0, nw); System.arraycopy(sg, 0, ccg, 0, nw); System.arraycopy(sb, 0, ccb, 0, nw); System.arraycopy(sa, 0, cca, 0, nw); // force reading the first line a second time dstChunkTop -= dstChunkStep; // go reading next line continue; } // scale > 1 --> enlarge for (int s = init_s, ny = 0, ni = 0; ny < dstChunkHeight; ny++, s += oh, ni += nw) { if (s < nh) { // copy cache row System.arraycopy(ccr, 0, dr, ni, nw); System.arraycopy(ccg, 0, dg, ni, nw); System.arraycopy(ccb, 0, db, ni, nw); System.arraycopy(cca, 0, da, ni, nw); } else { // scale factors int a = nh - s + oh; s -= nh; for (int ox = 0; ox < nw; ox++) { // mix current (cached) row and next row dr[ni + ox] = (ccr[ox] * a + sr[ox] * s) / oh; dg[ni + ox] = (ccg[ox] * a + sg[ox] * s) / oh; db[ni + ox] = (ccb[ox] * a + sb[ox] * s) / oh; da[ni + ox] = (cca[ox] * a + sa[ox] * s) / oh; } // copy the next row to the cache System.arraycopy(sr, 0, ccr, 0, nw); System.arraycopy(sg, 0, ccg, 0, nw); System.arraycopy(sb, 0, ccb, 0, nw); System.arraycopy(sa, 0, cca, 0, nw); // keep scale factor init_s = s + oh; // how many rows to copy dstChunkStep = ny + 1; // step out here break; } } // if src ran out of lines, but dst still has space, // do last source line again if (srcChunkTop + srcChunkStep >= oh && dstChunkTop + dstChunkStep < nh) { srcChunkTop -= srcChunkStep; } } /** * At this point we consider the resulting pixel data to be stored * in the destination buffer, either due to vertical scaling or by * the correct working of buffer exchanges between the scaling * steps. */ // make sure only the part of the band fitting is written to the // image if (dstChunkTop + dstChunkStep > nh) { dstChunkStep = nh - dstChunkTop; } dstRas.setSamples(0, dstChunkTop, nw, dstChunkStep, 0, dr); dstRas.setSamples(0, dstChunkTop, nw, dstChunkStep, 1, dg); dstRas.setSamples(0, dstChunkTop, nw, dstChunkStep, 2, db); dstRas.setSamples(0, dstChunkTop, nw, dstChunkStep, 3, da); } } /** * Divides 2 integers and rounds up the result to the next integer. * * Examples: * - a/b = 2.32: the result is 3 * - a/b = 2.00: the result is 2 * * @param a The dividend * @param b The divisor * @return The result of a/b rounded up to the next integer */ private static int divideAndRoundUp(int a, int b) { return (a + b - 1) / b; } /** * Converts black and transparent pixels into white and transparent ones. * * This processing avoids that scaling down the image creates a visible border line between a transparent area * and a non transparent one. * * Explanation: * Let's assume that we have two pixels p1 and p2 close to each other in the source image: * - p1 is black and transparent. Its RGBA values are (0; 0; 0; 0) * - p2 is light grey and visible. Its RGBA values are (246; 246; 246; 1) * * When scaling down the image by a factor 2, the destination pixel will be dark grey and half-visible as the * RGBA values of p1 and p2 are averaged. * This results in a dark grey dotted line between the transparent area and the visible one. * * After the processing, p1 and p2 are as follows: * - p1 becomes white and transparent. Its RGBA values are (255; 255; 255; 0) * - p2 stays light grey and visible. Its RGBA values are (246; 246; 246; 1) * * When scaling down the image, the destination pixel will be a bit more light grey and half-visible * and is not visible any more by human eyes. * * @param sr int array containing the R values of a row of pixels of the source image * @param sg int array containing the G values of a row of pixels of the source image * @param sb int array containing the B values of a row of pixels of the source image * @param sa int array containing the A values of a row of pixels of the source image * */ private static void preProcessTransparentPixels(int[] sr, int[] sg, int[] sb, int[] sa) { if (sr == null || sg == null || sb == null || sa == null) { return; } for (int i = 0; i < sr.length; i++) { if (sr[i] == 0 && sg[i] == 0 && sb[i] == 0 && sa[i] == 0) { sr[i] = 255; sg[i] = 255; sb[i] = 255; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy