/* * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.java2d.pipe; import java.awt.AlphaComposite;import java.awt.Color;import java.awt.Graphics2D;import java.awt.Image;import java.awt.Rectangle;import java.awt.Transparency;import java.awt.geom.AffineTransform;import java.awt.geom.NoninvertibleTransformException;import java.awt.image.AffineTransformOp;import java.awt.image.BufferedImage;import java.awt.image.BufferedImageOp;import java.awt.image.ColorModel;import java.awt.image.DataBuffer;import java.awt.image.DirectColorModel;import java.awt.image.ImageObserver;import java.awt.image.IndexColorModel;import java.awt.image.Raster;import java.awt.image.VolatileImage;import java.awt.image.WritableRaster;import java.awt.image.ImagingOpException;import sun.awt.SunHints;import sun.awt.image.ImageRepresentation;import sun.awt.image.ToolkitImage;import sun.java2d.InvalidPipeException;import sun.java2d.SunGraphics2D;import sun.java2d.SurfaceData;import sun.java2d.loops.Blit;import sun.java2d.loops.BlitBg;import sun.java2d.loops.TransformHelper;import sun.java2d.loops.MaskBlit;import sun.java2d.loops.CompositeType;import sun.java2d.loops.ScaledBlit;import sun.java2d.loops.SurfaceType; public class DrawImage implements DrawImagePipe{ public boolean copyImage(SunGraphics2D sg, Image img, int x, int y, Color bgColor) { int imgw = img.getWidth(null); int imgh = img.getHeight(null); if (isSimpleTranslate(sg)) { return renderImageCopy(sg, img, bgColor, x + sg.transX, y + sg.transY, 0, 0, imgw, imgh); } AffineTransform atfm = sg.transform; if ((x | y) != 0) { atfm = new AffineTransform(atfm); atfm.translate(x, y); } transformImage(sg, img, atfm, sg.interpolationType, 0, 0, imgw, imgh, bgColor); return true; } public boolean copyImage(SunGraphics2D sg, Image img, int dx, int dy, int sx, int sy, int w, int h, Color bgColor) { if (isSimpleTranslate(sg)) { return renderImageCopy(sg, img, bgColor, dx + sg.transX, dy + sg.transY, sx, sy, w, h); } scaleImage(sg, img, dx, dy, (dx + w), (dy + h), sx, sy, (sx + w), (sy + h), bgColor); return true; } public boolean scaleImage(SunGraphics2D sg, Image img, int x, int y, int width, int height, Color bgColor) { int imgw = img.getWidth(null); int imgh = img.getHeight(null); // Only accelerate scale if: // - w/h positive values // - sg transform integer translate/identity only // - no bgColor in operation if ((width > 0) && (height > 0) && isSimpleTranslate(sg)) { double dx1 = x + sg.transX; double dy1 = y + sg.transY; double dx2 = dx1 + width; double dy2 = dy1 + height; if (renderImageScale(sg, img, bgColor, sg.interpolationType, 0, 0, imgw, imgh, dx1, dy1, dx2, dy2)) { return true; } } AffineTransform atfm = sg.transform; if ((x | y) != 0 || width != imgw || height != imgh) { atfm = new AffineTransform(atfm); atfm.translate(x, y); atfm.scale(((double)width)/imgw, ((double)height)/imgh); } transformImage(sg, img, atfm, sg.interpolationType, 0, 0, imgw, imgh, bgColor); return true; } /* * This method is only called in those circumstances where the * operation has a non-null secondary transform specfied. Its * role is to check for various optimizations based on the types * of both the secondary and SG2D transforms and to do some * quick calculations to avoid having to combine the transforms * and/or to call a more generalized method. */ protected void transformImage(SunGraphics2D sg, Image img, int x, int y, AffineTransform extraAT, int interpType) { int txtype = extraAT.getType(); int imgw = img.getWidth(null); int imgh = img.getHeight(null); boolean checkfinalxform; if (sg.transformState <= sg.TRANSFORM_ANY_TRANSLATE && (txtype == AffineTransform.TYPE_IDENTITY || txtype == AffineTransform.TYPE_TRANSLATION)) { // First optimization - both are some kind of translate // Combine the translations and check if interpolation is necessary. double tx = extraAT.getTranslateX(); double ty = extraAT.getTranslateY(); tx += sg.transform.getTranslateX(); ty += sg.transform.getTranslateY(); int itx = (int) Math.floor(tx + 0.5); int ity = (int) Math.floor(ty + 0.5); if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR || (closeToInteger(itx, tx) && closeToInteger(ity, ty))) { renderImageCopy(sg, img, null, x+itx, y+ity, 0, 0, imgw, imgh); return; } checkfinalxform = false; } else if (sg.transformState <= sg.TRANSFORM_TRANSLATESCALE && ((txtype & (AffineTransform.TYPE_FLIP | AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)) { // Second optimization - both are some kind of translate or scale // Combine the scales and check if interpolation is necessary. // Transform source bounds by extraAT, // then translate the bounds again by x, y // then transform the bounds again by sg.transform double coords[] = new double[] { 0, 0, imgw, imgh, }; extraAT.transform(coords, 0, coords, 0, 2); coords[0] += x; coords[1] += y; coords[2] += x; coords[3] += y; sg.transform.transform(coords, 0, coords, 0, 2); if (tryCopyOrScale(sg, img, 0, 0, imgw, imgh, null, interpType, coords)) { return; } checkfinalxform = false; } else { checkfinalxform = true; } // Begin Transform AffineTransform tx = new AffineTransform(sg.transform); tx.translate(x, y); tx.concatenate(extraAT); // Do not try any more optimizations if either of the cases // above was tried as we have already verified that the // resulting transform will not simplify. if (checkfinalxform) { // In this case neither of the above simple transform // pairs was found so we will do some final tests on // the final rendering transform which may be the // simple product of two complex transforms. transformImage(sg, img, tx, interpType, 0, 0, imgw, imgh, null); } else { renderImageXform(sg, img, tx, interpType, 0, 0, imgw, imgh, null); } } /* * This method is called with a final rendering transform that * has combined all of the information about the Graphics2D * transform attribute with the transformations specified by * the arguments to the drawImage call. * Its role is to see if the combined transform ends up being * acceleratable by either a renderImageCopy or renderImageScale * once all of the math is done. * * Note: The transform supplied here has an origin that is * already adjusted to point to the device location where * the (sx1, sy1) location of the source image should be placed. */ protected void transformImage(SunGraphics2D sg, Image img, AffineTransform tx, int interpType, int sx1, int sy1, int sx2, int sy2, Color bgColor) { // Transform 3 source corners by tx and analyze them // for simplified operations (Copy or Scale). Using // 3 points lets us analyze any kind of transform, // even transforms that involve very tiny amounts of // rotation or skew to see if they degenerate to a // simple scale or copy operation within the allowable // error bounds. // Note that we use (0,0,w,h) instead of (sx1,sy1,sx2,sy2) // because the transform is already translated such that // the origin is where sx1, sy1 should go. double coords[] = new double[6]; /* index: 0 1 2 3 4 5 */ /* coord: (0, 0), (w, h), (0, h) */ coords[2] = sx2 - sx1; coords[3] = coords[5] = sy2 - sy1; tx.transform(coords, 0, coords, 0, 3); // First test if the X coords of the transformed UL // and LL points match and that the Y coords of the // transformed LR and LL points also match. // If they do then it is a "rectilinear" transform and // tryCopyOrScale will make sure it is upright and // integer-based. if (Math.abs(coords[0] - coords[4]) < MAX_TX_ERROR && Math.abs(coords[3] - coords[5]) < MAX_TX_ERROR && tryCopyOrScale(sg, img, sx1, sy1, sx2, sy2, bgColor, interpType, coords)) { return; } renderImageXform(sg, img, tx, interpType, sx1, sy1, sx2, sy2, bgColor); } /* * Check the bounding coordinates of the transformed source * image to see if they fall on integer coordinates such * that they will cause no interpolation anomalies if we * use our simplified Blit or ScaledBlit operations instead * of a full transform operation. */ protected boolean tryCopyOrScale(SunGraphics2D sg, Image img, int sx1, int sy1, int sx2, int sy2, Color bgColor, int interpType, double coords[]) { double dx = coords[0]; double dy = coords[1]; double dw = coords[2] - dx; double dh = coords[3] - dy; // First check if width and height are very close to img w&h. if (closeToInteger(sx2-sx1, dw) && closeToInteger(sy2-sy1, dh)) { // Round location to nearest pixel and then test // if it will cause interpolation anomalies. int idx = (int) Math.floor(dx + 0.5); int idy = (int) Math.floor(dy + 0.5); if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR || (closeToInteger(idx, dx) && closeToInteger(idy, dy))) { renderImageCopy(sg, img, bgColor, idx, idy, sx1, sy1, sx2-sx1, sy2-sy1); return true; } } // (For now) We can only use our ScaledBlits if the image // is upright (i.e. dw & dh both > 0) if (dw > 0 && dh > 0) { if (renderImageScale(sg, img, bgColor, interpType, sx1, sy1, sx2, sy2, coords[0], coords[1], coords[2], coords[3])) { return true; } } return false; } /* * Return a BufferedImage of the requested type with the indicated * subimage of the original image located at 0,0 in the new image. * If a bgColor is supplied, composite the original image over that * color with a SrcOver operation, otherwise make a SrcNoEa copy. */ BufferedImage makeBufferedImage(Image img, Color bgColor, int type, int sx1, int sy1, int sx2, int sy2) { BufferedImage bimg = new BufferedImage(sx2-sx1, sy2-sy1, type); Graphics2D g2d = bimg.createGraphics(); g2d.setComposite(AlphaComposite.Src); if (bgColor != null) { g2d.setColor(bgColor); g2d.fillRect(0, 0, sx2-sx1, sy2-sy1); g2d.setComposite(AlphaComposite.SrcOver); } g2d.drawImage(img, -sx1, -sy1, null); g2d.dispose(); return bimg; } protected void renderImageXform(SunGraphics2D sg, Image img, AffineTransform tx, int interpType, int sx1, int sy1, int sx2, int sy2, Color bgColor) { Region clip = sg.getCompClip(); SurfaceData dstData = sg.surfaceData; SurfaceData srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, bgColor); if (srcData == null) { img = getBufferedImage(img); srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, bgColor); if (srcData == null) { // REMIND: Is this correct? Can this happen? return; } } if (isBgOperation(srcData, bgColor)) { // We cannot perform bg operations during transform so make // an opaque temp image with the appropriate background // and work from there. img = makeBufferedImage(img, bgColor, BufferedImage.TYPE_INT_RGB, sx1, sy1, sx2, sy2); // Temp image has appropriate subimage at 0,0 now. sx2 -= sx1; sy2 -= sy1; sx1 = sy1 = 0; srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, bgColor); } SurfaceType srcType = srcData.getSurfaceType(); TransformHelper helper = TransformHelper.getFromCache(srcType); if (helper == null) { /* We have no helper for this source image type. * But we know that we do have helpers for both RGB and ARGB, * so convert to one of those types depending on transparency. * ARGB_PRE might be a better choice if the source image has * alpha, but it may cause some recursion here since we only * tend to have converters that convert to ARGB. */ int type = ((srcData.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB); img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2); // Temp image has appropriate subimage at 0,0 now. sx2 -= sx1; sy2 -= sy1; sx1 = sy1 = 0; srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, null); srcType = srcData.getSurfaceType(); helper = TransformHelper.getFromCache(srcType); // assert(helper != null); } AffineTransform itx; try { itx = tx.createInverse(); } catch (NoninvertibleTransformException e) { // Non-invertible transform means no output return; } /* * Find the maximum bounds on the destination that will be * affected by the transformed source. First, transform all * four corners of the source and then min and max the resulting * destination coordinates of the transformed corners. * Note that tx already has the offset to sx1,sy1 accounted * for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the * source coordinates. */ double coords[] = new double[8]; /* corner: UL UR LL LR */ /* index: 0 1 2 3 4 5 6 7 */ /* coord: (0, 0), (w, 0), (0, h), (w, h) */ coords[2] = coords[6] = sx2 - sx1; coords[5] = coords[7] = sy2 - sy1; tx.transform(coords, 0, coords, 0, 4); double ddx1, ddy1, ddx2, ddy2; ddx1 = ddx2 = coords[0]; ddy1 = ddy2 = coords[1]; for (int i = 2; i < coords.length; i += 2) { double d = coords[i]; if (ddx1 > d) ddx1 = d; else if (ddx2 < d) ddx2 = d; d = coords[i+1]; if (ddy1 > d) ddy1 = d; else if (ddy2 < d) ddy2 = d; } int dx1 = (int) Math.floor(ddx1); int dy1 = (int) Math.floor(ddy1); int dx2 = (int) Math.ceil(ddx2); int dy2 = (int) Math.ceil(ddy2); SurfaceType dstType = dstData.getSurfaceType(); MaskBlit maskblit; Blit blit; if (sg.compositeState <= sg.COMP_ALPHA) { /* NOTE: We either have, or we can make, * a MaskBlit for any alpha composite type */ maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, sg.imageComp, dstType); /* NOTE: We can only use the native TransformHelper * func to go directly to the dest if both the helper * and the MaskBlit are native. * All helpers are native at this point, but some MaskBlit * objects are implemented in Java, so we need to check. */ if (maskblit.getNativePrim() != 0) { // We can render directly. helper.Transform(maskblit, srcData, dstData, sg.composite, clip, itx, interpType, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, null, 0, 0); return; } blit = null; } else { /* NOTE: We either have, or we can make, * a Blit for any composite type, even Custom */ maskblit = null; blit = Blit.getFromCache(SurfaceType.IntArgbPre, sg.imageComp, dstType); } // We need to transform to a temp image and then copy // just the pieces that are valid data to the dest. BufferedImage tmpimg = new BufferedImage(dx2-dx1, dy2-dy1, BufferedImage.TYPE_INT_ARGB); SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg); SurfaceType tmpType = tmpData.getSurfaceType(); MaskBlit tmpmaskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, CompositeType.SrcNoEa, tmpType); /* * The helper function fills a temporary edges buffer * for us with the bounding coordinates of each scanline * in the following format:Lines 1–500 of 1110. Load more
/* * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.java2d.pipe; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DirectColorModel; import java.awt.image.ImageObserver; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.VolatileImage; import java.awt.image.WritableRaster; import java.awt.image.ImagingOpException; import sun.awt.SunHints; import sun.awt.image.ImageRepresentation; import sun.awt.image.ToolkitImage; import sun.java2d.InvalidPipeException; import sun.java2d.SunGraphics2D; import sun.java2d.SurfaceData; import sun.java2d.loops.Blit; import sun.java2d.loops.BlitBg; import sun.java2d.loops.TransformHelper; import sun.java2d.loops.MaskBlit; import sun.java2d.loops.CompositeType; import sun.java2d.loops.ScaledBlit; import sun.java2d.loops.SurfaceType; public class DrawImage implements DrawImagePipe { public boolean copyImage(SunGraphics2D sg, Image img, int x, int y, Color bgColor) { int imgw = img.getWidth(null); int imgh = img.getHeight(null); if (isSimpleTranslate(sg)) { return renderImageCopy(sg, img, bgColor, x + sg.transX, y + sg.transY, 0, 0, imgw, imgh); } AffineTransform atfm = sg.transform; if ((x | y) != 0) { atfm = new AffineTransform(atfm); atfm.translate(x, y); } transformImage(sg, img, atfm, sg.interpolationType, 0, 0, imgw, imgh, bgColor); return true; } public boolean copyImage(SunGraphics2D sg, Image img, int dx, int dy, int sx, int sy, int w, int h, Color bgColor) { if (isSimpleTranslate(sg)) { return renderImageCopy(sg, img, bgColor, dx + sg.transX, dy + sg.transY, sx, sy, w, h); } scaleImage(sg, img, dx, dy, (dx + w), (dy + h), sx, sy, (sx + w), (sy + h), bgColor); return true; } public boolean scaleImage(SunGraphics2D sg, Image img, int x, int y, int width, int height, Color bgColor) { int imgw = img.getWidth(null); int imgh = img.getHeight(null); // Only accelerate scale if: // - w/h positive values // - sg transform integer translate/identity only // - no bgColor in operation if ((width > 0) && (height > 0) && isSimpleTranslate(sg)) { double dx1 = x + sg.transX; double dy1 = y + sg.transY; double dx2 = dx1 + width; double dy2 = dy1 + height; if (renderImageScale(sg, img, bgColor, sg.interpolationType, 0, 0, imgw, imgh, dx1, dy1, dx2, dy2)) { return true; } } AffineTransform atfm = sg.transform; if ((x | y) != 0 || width != imgw || height != imgh) { atfm = new AffineTransform(atfm); atfm.translate(x, y); atfm.scale(((double)width)/imgw, ((double)height)/imgh); } transformImage(sg, img, atfm, sg.interpolationType, 0, 0, imgw, imgh, bgColor); return true; } /* * This method is only called in those circumstances where the * operation has a non-null secondary transform specfied. Its * role is to check for various optimizations based on the types * of both the secondary and SG2D transforms and to do some * quick calculations to avoid having to combine the transforms * and/or to call a more generalized method. */ protected void transformImage(SunGraphics2D sg, Image img, int x, int y, AffineTransform extraAT, int interpType) { int txtype = extraAT.getType(); int imgw = img.getWidth(null); int imgh = img.getHeight(null); boolean checkfinalxform; if (sg.transformState <= sg.TRANSFORM_ANY_TRANSLATE && (txtype == AffineTransform.TYPE_IDENTITY || txtype == AffineTransform.TYPE_TRANSLATION)) { // First optimization - both are some kind of translate // Combine the translations and check if interpolation is necessary. double tx = extraAT.getTranslateX(); double ty = extraAT.getTranslateY(); tx += sg.transform.getTranslateX(); ty += sg.transform.getTranslateY(); int itx = (int) Math.floor(tx + 0.5); int ity = (int) Math.floor(ty + 0.5); if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR || (closeToInteger(itx, tx) && closeToInteger(ity, ty))) { renderImageCopy(sg, img, null, x+itx, y+ity, 0, 0, imgw, imgh); return; } checkfinalxform = false; } else if (sg.transformState <= sg.TRANSFORM_TRANSLATESCALE && ((txtype & (AffineTransform.TYPE_FLIP | AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)) { // Second optimization - both are some kind of translate or scale // Combine the scales and check if interpolation is necessary. // Transform source bounds by extraAT, // then translate the bounds again by x, y // then transform the bounds again by sg.transform double coords[] = new double[] { 0, 0, imgw, imgh, }; extraAT.transform(coords, 0, coords, 0, 2); coords[0] += x; coords[1] += y; coords[2] += x; coords[3] += y; sg.transform.transform(coords, 0, coords, 0, 2); if (tryCopyOrScale(sg, img, 0, 0, imgw, imgh, null, interpType, coords)) { return; } checkfinalxform = false; } else { checkfinalxform = true; } // Begin Transform AffineTransform tx = new AffineTransform(sg.transform); tx.translate(x, y); tx.concatenate(extraAT); // Do not try any more optimizations if either of the cases // above was tried as we have already verified that the // resulting transform will not simplify. if (checkfinalxform) { // In this case neither of the above simple transform // pairs was found so we will do some final tests on // the final rendering transform which may be the // simple product of two complex transforms. transformImage(sg, img, tx, interpType, 0, 0, imgw, imgh, null); } else { renderImageXform(sg, img, tx, interpType, 0, 0, imgw, imgh, null); } } /* * This method is called with a final rendering transform that * has combined all of the information about the Graphics2D * transform attribute with the transformations specified by * the arguments to the drawImage call. * Its role is to see if the combined transform ends up being * acceleratable by either a renderImageCopy or renderImageScale * once all of the math is done. * * Note: The transform supplied here has an origin that is * already adjusted to point to the device location where * the (sx1, sy1) location of the source image should be placed. */ protected void transformImage(SunGraphics2D sg, Image img, AffineTransform tx, int interpType, int sx1, int sy1, int sx2, int sy2, Color bgColor) { // Transform 3 source corners by tx and analyze them // for simplified operations (Copy or Scale). Using // 3 points lets us analyze any kind of transform, // even transforms that involve very tiny amounts of // rotation or skew to see if they degenerate to a // simple scale or copy operation within the allowable // error bounds. // Note that we use (0,0,w,h) instead of (sx1,sy1,sx2,sy2) // because the transform is already translated such that // the origin is where sx1, sy1 should go. double coords[] = new double[6]; /* index: 0 1 2 3 4 5 */ /* coord: (0, 0), (w, h), (0, h) */ coords[2] = sx2 - sx1; coords[3] = coords[5] = sy2 - sy1; tx.transform(coords, 0, coords, 0, 3); // First test if the X coords of the transformed UL // and LL points match and that the Y coords of the // transformed LR and LL points also match. // If they do then it is a "rectilinear" transform and // tryCopyOrScale will make sure it is upright and // integer-based. if (Math.abs(coords[0] - coords[4]) < MAX_TX_ERROR && Math.abs(coords[3] - coords[5]) < MAX_TX_ERROR && tryCopyOrScale(sg, img, sx1, sy1, sx2, sy2, bgColor, interpType, coords)) { return; } renderImageXform(sg, img, tx, interpType, sx1, sy1, sx2, sy2, bgColor); } /* * Check the bounding coordinates of the transformed source * image to see if they fall on integer coordinates such * that they will cause no interpolation anomalies if we * use our simplified Blit or ScaledBlit operations instead * of a full transform operation. */ protected boolean tryCopyOrScale(SunGraphics2D sg, Image img, int sx1, int sy1, int sx2, int sy2, Color bgColor, int interpType, double coords[]) { double dx = coords[0]; double dy = coords[1]; double dw = coords[2] - dx; double dh = coords[3] - dy; // First check if width and height are very close to img w&h. if (closeToInteger(sx2-sx1, dw) && closeToInteger(sy2-sy1, dh)) { // Round location to nearest pixel and then test // if it will cause interpolation anomalies. int idx = (int) Math.floor(dx + 0.5); int idy = (int) Math.floor(dy + 0.5); if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR || (closeToInteger(idx, dx) && closeToInteger(idy, dy))) { renderImageCopy(sg, img, bgColor, idx, idy, sx1, sy1, sx2-sx1, sy2-sy1); return true; } } // (For now) We can only use our ScaledBlits if the image // is upright (i.e. dw & dh both > 0) if (dw > 0 && dh > 0) { if (renderImageScale(sg, img, bgColor, interpType, sx1, sy1, sx2, sy2, coords[0], coords[1], coords[2], coords[3])) { return true; } } return false; } /* * Return a BufferedImage of the requested type with the indicated * subimage of the original image located at 0,0 in the new image. * If a bgColor is supplied, composite the original image over that * color with a SrcOver operation, otherwise make a SrcNoEa copy. */ BufferedImage makeBufferedImage(Image img, Color bgColor, int type, int sx1, int sy1, int sx2, int sy2) { BufferedImage bimg = new BufferedImage(sx2-sx1, sy2-sy1, type); Graphics2D g2d = bimg.createGraphics(); g2d.setComposite(AlphaComposite.Src); if (bgColor != null) { g2d.setColor(bgColor); g2d.fillRect(0, 0, sx2-sx1, sy2-sy1); g2d.setComposite(AlphaComposite.SrcOver); } g2d.drawImage(img, -sx1, -sy1, null); g2d.dispose(); return bimg; } protected void renderImageXform(SunGraphics2D sg, Image img, AffineTransform tx, int interpType, int sx1, int sy1, int sx2, int sy2, Color bgColor) { Region clip = sg.getCompClip(); SurfaceData dstData = sg.surfaceData; SurfaceData srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, bgColor); if (srcData == null) { img = getBufferedImage(img); srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, bgColor); if (srcData == null) { // REMIND: Is this correct? Can this happen? return; } } if (isBgOperation(srcData, bgColor)) { // We cannot perform bg operations during transform so make // an opaque temp image with the appropriate background // and work from there. img = makeBufferedImage(img, bgColor, BufferedImage.TYPE_INT_RGB, sx1, sy1, sx2, sy2); // Temp image has appropriate subimage at 0,0 now. sx2 -= sx1; sy2 -= sy1; sx1 = sy1 = 0; srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, bgColor); } SurfaceType srcType = srcData.getSurfaceType(); TransformHelper helper = TransformHelper.getFromCache(srcType); if (helper == null) { /* We have no helper for this source image type. * But we know that we do have helpers for both RGB and ARGB, * so convert to one of those types depending on transparency. * ARGB_PRE might be a better choice if the source image has * alpha, but it may cause some recursion here since we only * tend to have converters that convert to ARGB. */ int type = ((srcData.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB); img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2); // Temp image has appropriate subimage at 0,0 now. sx2 -= sx1; sy2 -= sy1; sx1 = sy1 = 0; srcData = dstData.getSourceSurfaceData(img, sg.TRANSFORM_GENERIC, sg.imageComp, null); srcType = srcData.getSurfaceType(); helper = TransformHelper.getFromCache(srcType); // assert(helper != null); } AffineTransform itx; try { itx = tx.createInverse(); } catch (NoninvertibleTransformException e) { // Non-invertible transform means no output return; } /* * Find the maximum bounds on the destination that will be * affected by the transformed source. First, transform all * four corners of the source and then min and max the resulting * destination coordinates of the transformed corners. * Note that tx already has the offset to sx1,sy1 accounted * for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the * source coordinates. */ double coords[] = new double[8]; /* corner: UL UR LL LR */ /* index: 0 1 2 3 4 5 6 7 */ /* coord: (0, 0), (w, 0), (0, h), (w, h) */ coords[2] = coords[6] = sx2 - sx1; coords[5] = coords[7] = sy2 - sy1; tx.transform(coords, 0, coords, 0, 4); double ddx1, ddy1, ddx2, ddy2; ddx1 = ddx2 = coords[0]; ddy1 = ddy2 = coords[1]; for (int i = 2; i < coords.length; i += 2) { double d = coords[i]; if (ddx1 > d) ddx1 = d; else if (ddx2 < d) ddx2 = d; d = coords[i+1]; if (ddy1 > d) ddy1 = d; else if (ddy2 < d) ddy2 = d; } int dx1 = (int) Math.floor(ddx1); int dy1 = (int) Math.floor(ddy1); int dx2 = (int) Math.ceil(ddx2); int dy2 = (int) Math.ceil(ddy2); SurfaceType dstType = dstData.getSurfaceType(); MaskBlit maskblit; Blit blit; if (sg.compositeState <= sg.COMP_ALPHA) { /* NOTE: We either have, or we can make, * a MaskBlit for any alpha composite type */ maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, sg.imageComp, dstType); /* NOTE: We can only use the native TransformHelper * func to go directly to the dest if both the helper * and the MaskBlit are native. * All helpers are native at this point, but some MaskBlit * objects are implemented in Java, so we need to check. */ if (maskblit.getNativePrim() != 0) { // We can render directly. helper.Transform(maskblit, srcData, dstData, sg.composite, clip, itx, interpType, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, null, 0, 0); return; } blit = null; } else { /* NOTE: We either have, or we can make, * a Blit for any composite type, even Custom */ maskblit = null; blit = Blit.getFromCache(SurfaceType.IntArgbPre, sg.imageComp, dstType); } // We need to transform to a temp image and then copy // just the pieces that are valid data to the dest. BufferedImage tmpimg = new BufferedImage(dx2-dx1, dy2-dy1, BufferedImage.TYPE_INT_ARGB); SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg); SurfaceType tmpType = tmpData.getSurfaceType(); MaskBlit tmpmaskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, CompositeType.SrcNoEa, tmpType); /* * The helper function fills a temporary edges buffer * for us with the bounding coordinates of each scanline * in the following format: