Path: blob/master/src/java.desktop/share/classes/sun/java2d/pipe/DrawImage.java
41159 views
/*1* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.pipe;2627import java.awt.AlphaComposite;28import java.awt.Color;29import java.awt.Image;30import java.awt.Rectangle;31import java.awt.Transparency;32import java.awt.geom.AffineTransform;33import java.awt.geom.NoninvertibleTransformException;34import java.awt.image.AffineTransformOp;35import java.awt.image.BufferedImage;36import java.awt.image.BufferedImageOp;37import java.awt.image.ColorModel;38import java.awt.image.DataBuffer;39import java.awt.image.ImageObserver;40import java.awt.image.IndexColorModel;41import java.awt.image.Raster;42import java.awt.image.VolatileImage;4344import sun.awt.SunHints;45import sun.awt.image.ImageRepresentation;46import sun.awt.image.SurfaceManager;47import sun.awt.image.ToolkitImage;48import sun.java2d.InvalidPipeException;49import sun.java2d.SunGraphics2D;50import sun.java2d.SurfaceData;51import sun.java2d.loops.Blit;52import sun.java2d.loops.BlitBg;53import sun.java2d.loops.CompositeType;54import sun.java2d.loops.MaskBlit;55import sun.java2d.loops.ScaledBlit;56import sun.java2d.loops.SurfaceType;57import sun.java2d.loops.TransformHelper;5859public class DrawImage implements DrawImagePipe60{61public boolean copyImage(SunGraphics2D sg, Image img,62int x, int y,63Color bgColor)64{65int imgw = img.getWidth(null);66int imgh = img.getHeight(null);67if (isSimpleTranslate(sg)) {68return renderImageCopy(sg, img, bgColor,69x + sg.transX, y + sg.transY,700, 0, imgw, imgh);71}72AffineTransform atfm = sg.transform;73if ((x | y) != 0) {74atfm = new AffineTransform(atfm);75atfm.translate(x, y);76}77transformImage(sg, img, atfm, sg.interpolationType,780, 0, imgw, imgh, bgColor);79return true;80}8182public boolean copyImage(SunGraphics2D sg, Image img,83int dx, int dy, int sx, int sy, int w, int h,84Color bgColor)85{86if (isSimpleTranslate(sg)) {87return renderImageCopy(sg, img, bgColor,88dx + sg.transX, dy + sg.transY,89sx, sy, w, h);90}91scaleImage(sg, img, dx, dy, (dx + w), (dy + h),92sx, sy, (sx + w), (sy + h), bgColor);93return true;94}9596public boolean scaleImage(SunGraphics2D sg, Image img, int x, int y,97int width, int height,98Color bgColor)99{100int imgw = img.getWidth(null);101int imgh = img.getHeight(null);102// Only accelerate scale if:103// - w/h positive values104// - sg transform integer translate/identity only105// - no bgColor in operation106if ((width > 0) && (height > 0) && isSimpleTranslate(sg)) {107double dx1 = x + sg.transX;108double dy1 = y + sg.transY;109double dx2 = dx1 + width;110double dy2 = dy1 + height;111if (renderImageScale(sg, img, bgColor, sg.interpolationType,1120, 0, imgw, imgh,113dx1, dy1, dx2, dy2))114{115return true;116}117}118119AffineTransform atfm = sg.transform;120if ((x | y) != 0 || width != imgw || height != imgh) {121atfm = new AffineTransform(atfm);122atfm.translate(x, y);123atfm.scale(((double)width)/imgw, ((double)height)/imgh);124}125transformImage(sg, img, atfm, sg.interpolationType,1260, 0, imgw, imgh, bgColor);127return true;128}129130/*131* This method is only called in those circumstances where the132* operation has a non-null secondary transform specified. Its133* role is to check for various optimizations based on the types134* of both the secondary and SG2D transforms and to do some135* quick calculations to avoid having to combine the transforms136* and/or to call a more generalized method.137*/138protected void transformImage(SunGraphics2D sg, Image img, int x, int y,139AffineTransform extraAT, int interpType)140{141int txtype = extraAT.getType();142int imgw = img.getWidth(null);143int imgh = img.getHeight(null);144boolean checkfinalxform;145146if (sg.transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE &&147(txtype == AffineTransform.TYPE_IDENTITY ||148txtype == AffineTransform.TYPE_TRANSLATION))149{150// First optimization - both are some kind of translate151152// Combine the translations and check if interpolation is necessary.153double tx = extraAT.getTranslateX();154double ty = extraAT.getTranslateY();155tx += sg.transform.getTranslateX();156ty += sg.transform.getTranslateY();157int itx = (int) Math.floor(tx + 0.5);158int ity = (int) Math.floor(ty + 0.5);159if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR ||160(closeToInteger(itx, tx) && closeToInteger(ity, ty)))161{162renderImageCopy(sg, img, null, x+itx, y+ity, 0, 0, imgw, imgh);163return;164}165checkfinalxform = false;166} else if (sg.transformState <= SunGraphics2D.TRANSFORM_TRANSLATESCALE &&167((txtype & (AffineTransform.TYPE_FLIP |168AffineTransform.TYPE_MASK_ROTATION |169AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0))170{171// Second optimization - both are some kind of translate or scale172173// Combine the scales and check if interpolation is necessary.174175// Transform source bounds by extraAT,176// then translate the bounds again by x, y177// then transform the bounds again by sg.transform178double[] coords = new double[] {1790, 0, imgw, imgh,180};181extraAT.transform(coords, 0, coords, 0, 2);182coords[0] += x;183coords[1] += y;184coords[2] += x;185coords[3] += y;186sg.transform.transform(coords, 0, coords, 0, 2);187188if (tryCopyOrScale(sg, img, 0, 0, imgw, imgh,189null, interpType, coords))190{191return;192}193checkfinalxform = false;194} else {195checkfinalxform = true;196}197198// Begin Transform199AffineTransform tx = new AffineTransform(sg.transform);200tx.translate(x, y);201tx.concatenate(extraAT);202203// Do not try any more optimizations if either of the cases204// above was tried as we have already verified that the205// resulting transform will not simplify.206if (checkfinalxform) {207// In this case neither of the above simple transform208// pairs was found so we will do some final tests on209// the final rendering transform which may be the210// simple product of two complex transforms.211transformImage(sg, img, tx, interpType, 0, 0, imgw, imgh, null);212} else {213renderImageXform(sg, img, tx, interpType, 0, 0, imgw, imgh, null);214}215}216217/*218* This method is called with a final rendering transform that219* has combined all of the information about the Graphics2D220* transform attribute with the transformations specified by221* the arguments to the drawImage call.222* Its role is to see if the combined transform ends up being223* acceleratable by either a renderImageCopy or renderImageScale224* once all of the math is done.225*226* Note: The transform supplied here has an origin that is227* already adjusted to point to the device location where228* the (sx1, sy1) location of the source image should be placed.229*/230protected void transformImage(SunGraphics2D sg, Image img,231AffineTransform tx, int interpType,232int sx1, int sy1, int sx2, int sy2,233Color bgColor)234{235// Transform 3 source corners by tx and analyze them236// for simplified operations (Copy or Scale). Using237// 3 points lets us analyze any kind of transform,238// even transforms that involve very tiny amounts of239// rotation or skew to see if they degenerate to a240// simple scale or copy operation within the allowable241// error bounds.242// Note that we use (0,0,w,h) instead of (sx1,sy1,sx2,sy2)243// because the transform is already translated such that244// the origin is where sx1, sy1 should go.245double[] coords = new double[6];246/* index: 0 1 2 3 4 5 */247/* coord: (0, 0), (w, h), (0, h) */248coords[2] = sx2 - sx1;249coords[3] = coords[5] = sy2 - sy1;250tx.transform(coords, 0, coords, 0, 3);251// First test if the X coords of the transformed UL252// and LL points match and that the Y coords of the253// transformed LR and LL points also match.254// If they do then it is a "rectilinear" transform and255// tryCopyOrScale will make sure it is upright and256// integer-based.257if (Math.abs(coords[0] - coords[4]) < MAX_TX_ERROR &&258Math.abs(coords[3] - coords[5]) < MAX_TX_ERROR &&259tryCopyOrScale(sg, img, sx1, sy1, sx2, sy2,260bgColor, interpType, coords))261{262return;263}264265renderImageXform(sg, img, tx, interpType, sx1, sy1, sx2, sy2, bgColor);266}267268/*269* Check the bounding coordinates of the transformed source270* image to see if they fall on integer coordinates such271* that they will cause no interpolation anomalies if we272* use our simplified Blit or ScaledBlit operations instead273* of a full transform operation.274*/275protected boolean tryCopyOrScale(SunGraphics2D sg,276Image img,277int sx1, int sy1,278int sx2, int sy2,279Color bgColor, int interpType,280double[] coords)281{282double dx1 = coords[0];283double dy1 = coords[1];284double dx2 = coords[2];285double dy2 = coords[3];286double dw = dx2 - dx1;287double dh = dy2 - dy1;288289/* If any of the destination coordinates exceed the integer range,290* then the calculations performed in calls made here cannot be291* guaranteed to be correct, or to converge (terminate).292* So return out of here, deferring to code that can handle this.293*/294if (dx1 < Integer.MIN_VALUE || dx1 > Integer.MAX_VALUE ||295dy1 < Integer.MIN_VALUE || dy1 > Integer.MAX_VALUE ||296dx2 < Integer.MIN_VALUE || dx2 > Integer.MAX_VALUE ||297dy2 < Integer.MIN_VALUE || dy2 > Integer.MAX_VALUE)298{299return false;300}301302// First check if width and height are very close to img w&h.303if (closeToInteger(sx2-sx1, dw) && closeToInteger(sy2-sy1, dh)) {304// Round location to nearest pixel and then test305// if it will cause interpolation anomalies.306int idx = (int) Math.floor(dx1 + 0.5);307int idy = (int) Math.floor(dy1 + 0.5);308if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR ||309(closeToInteger(idx, dx1) && closeToInteger(idy, dy1)))310{311renderImageCopy(sg, img, bgColor,312idx, idy,313sx1, sy1, sx2-sx1, sy2-sy1);314return true;315}316}317// (For now) We can only use our ScaledBlits if the image318// is upright (i.e. dw & dh both > 0)319if (dw > 0 && dh > 0) {320if (renderImageScale(sg, img, bgColor, interpType,321sx1, sy1, sx2, sy2,322dx1, dy1, dx2, dy2))323{324return true;325}326}327return false;328}329330/**331* Return a non-accelerated BufferedImage of the requested type with the332* indicated subimage of the original image located at 0,0 in the new image.333* If a bgColor is supplied, composite the original image over that color334* with a SrcOver operation, otherwise make a SrcNoEa copy.335* <p>336* Returned BufferedImage is not accelerated for two reasons:337* <ul>338* <li> Types of the image and surface are predefined, because these types339* correspond to the TransformHelpers, which we know we have. And340* acceleration can change the type of the surface341* <li> Image will be used only once and acceleration caching wouldn't help342* </ul>343*/344private BufferedImage makeBufferedImage(Image img, Color bgColor, int type,345int sx1, int sy1, int sx2, int sy2)346{347final int width = sx2 - sx1;348final int height = sy2 - sy1;349final BufferedImage bimg = new BufferedImage(width, height, type);350final SunGraphics2D g2d = (SunGraphics2D) bimg.createGraphics();351g2d.setComposite(AlphaComposite.Src);352bimg.setAccelerationPriority(0);353if (bgColor != null) {354g2d.setColor(bgColor);355g2d.fillRect(0, 0, width, height);356g2d.setComposite(AlphaComposite.SrcOver);357}358g2d.copyImage(img, 0, 0, sx1, sy1, width, height, null, null);359g2d.dispose();360return bimg;361}362363protected void renderImageXform(SunGraphics2D sg, Image img,364AffineTransform tx, int interpType,365int sx1, int sy1, int sx2, int sy2,366Color bgColor)367{368final AffineTransform itx;369try {370itx = tx.createInverse();371} catch (final NoninvertibleTransformException ignored) {372// Non-invertible transform means no output373return;374}375376/*377* Find the maximum bounds on the destination that will be378* affected by the transformed source. First, transform all379* four corners of the source and then min and max the resulting380* destination coordinates of the transformed corners.381* Note that tx already has the offset to sx1,sy1 accounted382* for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the383* source coordinates.384*/385final double[] coords = new double[8];386/* corner: UL UR LL LR */387/* index: 0 1 2 3 4 5 6 7 */388/* coord: (0, 0), (w, 0), (0, h), (w, h) */389coords[2] = coords[6] = sx2 - sx1;390coords[5] = coords[7] = sy2 - sy1;391tx.transform(coords, 0, coords, 0, 4);392double ddx1, ddy1, ddx2, ddy2;393ddx1 = ddx2 = coords[0];394ddy1 = ddy2 = coords[1];395for (int i = 2; i < coords.length; i += 2) {396double d = coords[i];397if (ddx1 > d) ddx1 = d;398else if (ddx2 < d) ddx2 = d;399d = coords[i+1];400if (ddy1 > d) ddy1 = d;401else if (ddy2 < d) ddy2 = d;402}403404Region clip = sg.getCompClip();405final int dx1 = Math.max((int) Math.floor(ddx1), clip.getLoX());406final int dy1 = Math.max((int) Math.floor(ddy1), clip.getLoY());407final int dx2 = Math.min((int) Math.ceil(ddx2), clip.getHiX());408final int dy2 = Math.min((int) Math.ceil(ddy2), clip.getHiY());409if (dx2 <= dx1 || dy2 <= dy1) {410// empty destination means no output411return;412}413414final SurfaceData dstData = sg.surfaceData;415SurfaceData srcData = dstData.getSourceSurfaceData(img,416SunGraphics2D.TRANSFORM_GENERIC,417sg.imageComp,418bgColor);419420if (srcData == null) {421img = getBufferedImage(img);422srcData = dstData.getSourceSurfaceData(img,423SunGraphics2D.TRANSFORM_GENERIC,424sg.imageComp,425bgColor);426if (srcData == null) {427// REMIND: Is this correct? Can this happen?428return;429}430}431432if (isBgOperation(srcData, bgColor)) {433// We cannot perform bg operations during transform so make434// a temp image with the appropriate background based on435// background alpha value and work from there. If background436// alpha is opaque use INT_RGB else use INT_ARGB so that we437// will not lose translucency of background.438439int bgAlpha = bgColor.getAlpha();440int type = ((bgAlpha == 255)441? BufferedImage.TYPE_INT_RGB442: BufferedImage.TYPE_INT_ARGB);443img = makeBufferedImage(img, bgColor, type, sx1, sy1, sx2, sy2);444// Temp image has appropriate subimage at 0,0 now.445sx2 -= sx1;446sy2 -= sy1;447sx1 = sy1 = 0;448449srcData = dstData.getSourceSurfaceData(img,450SunGraphics2D.TRANSFORM_GENERIC,451sg.imageComp,452bgColor);453}454455SurfaceType srcType = srcData.getSurfaceType();456TransformHelper helper = TransformHelper.getFromCache(srcType);457458if (helper == null) {459/* We have no helper for this source image type.460* But we know that we do have helpers for both RGB and ARGB,461* so convert to one of those types depending on transparency.462* ARGB_PRE might be a better choice if the source image has463* alpha, but it may cause some recursion here since we only464* tend to have converters that convert to ARGB.465*/466int type = ((srcData.getTransparency() == Transparency.OPAQUE)467? BufferedImage.TYPE_INT_RGB468: BufferedImage.TYPE_INT_ARGB);469img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2);470// Temp image has appropriate subimage at 0,0 now.471sx2 -= sx1;472sy2 -= sy1;473sx1 = sy1 = 0;474475srcData = dstData.getSourceSurfaceData(img,476SunGraphics2D.TRANSFORM_GENERIC,477sg.imageComp,478null);479srcType = srcData.getSurfaceType();480helper = TransformHelper.getFromCache(srcType);481// assert(helper != null);482}483484SurfaceType dstType = dstData.getSurfaceType();485if (sg.compositeState <= SunGraphics2D.COMP_ALPHA) {486/* NOTE: We either have, or we can make,487* a MaskBlit for any alpha composite type488*/489MaskBlit maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre,490sg.imageComp, dstType);491492/* NOTE: We can only use the native TransformHelper493* func to go directly to the dest if both the helper494* and the MaskBlit are native.495* All helpers are native at this point, but some MaskBlit496* objects are implemented in Java, so we need to check.497*/498if (maskblit.getNativePrim() != 0) {499// We can render directly.500helper.Transform(maskblit, srcData, dstData,501sg.composite, clip,502itx, interpType,503sx1, sy1, sx2, sy2,504dx1, dy1, dx2, dy2,505null, 0, 0);506return;507}508}509510// We need to transform to a temp image and then copy511// just the pieces that are valid data to the dest.512final int w = dx2 - dx1;513final int h = dy2 - dy1;514BufferedImage tmpimg = new BufferedImage(w, h,515BufferedImage.TYPE_INT_ARGB_PRE);516SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg);517SurfaceType tmpType = tmpData.getSurfaceType();518MaskBlit tmpmaskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre,519CompositeType.SrcNoEa,520tmpType);521/*522* The helper function fills a temporary edges buffer523* for us with the bounding coordinates of each scanline524* in the following format:525*526* edges[0, 1] = [top y, bottom y)527* edges[2, 3] = [left x, right x) of top row528* ...529* edges[h*2, h*2+1] = [left x, right x) of bottom row530*531* all coordinates in the edges array will be relative to dx1, dy1532*533* edges thus has to be h*2+2 in length534*/535final int[] edges = new int[h * 2 + 2];536// It is important that edges[0]=edges[1]=0 when we call537// Transform in case it must return early and we would538// not want to render anything on an error condition.539helper.Transform(tmpmaskblit, srcData, tmpData,540AlphaComposite.Src, null,541itx, interpType,542sx1, sy1, sx2, sy2,5430, 0, w, h,544edges, dx1, dy1);545546final Region region = Region.getInstance(dx1, dy1, dx2, dy2, edges);547clip = clip.getIntersection(region);548549/* NOTE: We either have, or we can make,550* a Blit for any composite type, even Custom551*/552final Blit blit = Blit.getFromCache(tmpType, sg.imageComp, dstType);553blit.Blit(tmpData, dstData, sg.composite, clip, 0, 0, dx1, dy1, w, h);554}555556// Render an image using only integer translation557// (no scale or transform or sub-pixel interpolated translations).558protected boolean renderImageCopy(SunGraphics2D sg, Image img,559Color bgColor,560int dx, int dy,561int sx, int sy,562int w, int h)563{564Region clip = sg.getCompClip();565SurfaceData dstData = sg.surfaceData;566567int attempts = 0;568// Loop up to twice through; this gives us a chance to569// revalidate the surfaceData objects in case of an exception570// and try it once more571while (true) {572SurfaceData srcData =573dstData.getSourceSurfaceData(img,574SunGraphics2D.TRANSFORM_ISIDENT,575sg.imageComp,576bgColor);577if (srcData == null) {578return false;579}580581try {582blitSurfaceData(sg, clip, srcData, dstData,583sx, sy, dx, dy, w, h, bgColor);584return true;585} catch (NullPointerException e) {586if (!(SurfaceData.isNull(dstData) ||587SurfaceData.isNull(srcData)))588{589// Something else caused the exception, throw it...590throw e;591}592return false;593// NOP if we have been disposed594} catch (InvalidPipeException e) {595// Always catch the exception; try this a couple of times596// and fail silently if the system is not yet ready to597// revalidate the source or dest surfaceData objects.598++attempts;599clip = sg.getCompClip(); // ensures sg.surfaceData is valid600dstData = sg.surfaceData;601if (SurfaceData.isNull(dstData) ||602SurfaceData.isNull(srcData) || (attempts > 1))603{604return false;605}606}607}608}609610// Render an image using only integer scaling (no transform).611protected boolean renderImageScale(SunGraphics2D sg, Image img,612Color bgColor, int interpType,613int sx1, int sy1,614int sx2, int sy2,615double dx1, double dy1,616double dx2, double dy2)617{618// Currently only NEAREST_NEIGHBOR interpolation is implemented619// for ScaledBlit operations.620if (interpType != AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {621return false;622}623624Region clip = sg.getCompClip();625SurfaceData dstData = sg.surfaceData;626627int attempts = 0;628// Loop up to twice through; this gives us a chance to629// revalidate the surfaceData objects in case of an exception630// and try it once more631while (true) {632SurfaceData srcData =633dstData.getSourceSurfaceData(img,634SunGraphics2D.TRANSFORM_TRANSLATESCALE,635sg.imageComp,636bgColor);637638if (srcData == null || isBgOperation(srcData, bgColor)) {639return false;640}641642try {643SurfaceType srcType = srcData.getSurfaceType();644SurfaceType dstType = dstData.getSurfaceType();645return scaleSurfaceData(sg, clip,646srcData, dstData, srcType, dstType,647sx1, sy1, sx2, sy2,648dx1, dy1, dx2, dy2);649} catch (NullPointerException e) {650if (!SurfaceData.isNull(dstData)) {651// Something else caused the exception, throw it...652throw e;653}654return false;655// NOP if we have been disposed656} catch (InvalidPipeException e) {657// Always catch the exception; try this a couple of times658// and fail silently if the system is not yet ready to659// revalidate the source or dest surfaceData objects.660++attempts;661clip = sg.getCompClip(); // ensures sg.surfaceData is valid662dstData = sg.surfaceData;663if (SurfaceData.isNull(dstData) ||664SurfaceData.isNull(srcData) || (attempts > 1))665{666return false;667}668}669}670}671672public boolean scaleImage(SunGraphics2D sg, Image img,673int dx1, int dy1, int dx2, int dy2,674int sx1, int sy1, int sx2, int sy2,675Color bgColor)676{677int srcW, srcH, dstW, dstH;678int srcX, srcY, dstX, dstY;679boolean srcWidthFlip = false;680boolean srcHeightFlip = false;681boolean dstWidthFlip = false;682boolean dstHeightFlip = false;683684if (sx2 > sx1) {685srcW = sx2 - sx1;686srcX = sx1;687} else {688srcWidthFlip = true;689srcW = sx1 - sx2;690srcX = sx2;691}692if (sy2 > sy1) {693srcH = sy2-sy1;694srcY = sy1;695} else {696srcHeightFlip = true;697srcH = sy1-sy2;698srcY = sy2;699}700if (dx2 > dx1) {701dstW = dx2 - dx1;702dstX = dx1;703} else {704dstW = dx1 - dx2;705dstWidthFlip = true;706dstX = dx2;707}708if (dy2 > dy1) {709dstH = dy2 - dy1;710dstY = dy1;711} else {712dstH = dy1 - dy2;713dstHeightFlip = true;714dstY = dy2;715}716if (srcW <= 0 || srcH <= 0) {717return true;718}719// Only accelerate scale if it does not involve a flip or transform720if ((srcWidthFlip == dstWidthFlip) &&721(srcHeightFlip == dstHeightFlip) &&722isSimpleTranslate(sg))723{724double ddx1 = dstX + sg.transX;725double ddy1 = dstY + sg.transY;726double ddx2 = ddx1 + dstW;727double ddy2 = ddy1 + dstH;728if (renderImageScale(sg, img, bgColor, sg.interpolationType,729srcX, srcY, srcX+srcW, srcY+srcH,730ddx1, ddy1, ddx2, ddy2))731{732return true;733}734}735736AffineTransform atfm = new AffineTransform(sg.transform);737atfm.translate(dx1, dy1);738double m00 = (double)(dx2-dx1)/(sx2-sx1);739double m11 = (double)(dy2-dy1)/(sy2-sy1);740atfm.scale(m00, m11);741atfm.translate(srcX-sx1, srcY-sy1);742743final double scaleX = SurfaceManager.getImageScaleX(img);744final double scaleY = SurfaceManager.getImageScaleY(img);745final int imgW = (int) Math.ceil(img.getWidth(null) * scaleX);746final int imgH = (int) Math.ceil(img.getHeight(null) * scaleY);747srcW += srcX;748srcH += srcY;749// Make sure we are not out of bounds750if (srcW > imgW) {751srcW = imgW;752}753if (srcH > imgH) {754srcH = imgH;755}756if (srcX < 0) {757atfm.translate(-srcX, 0);758srcX = 0;759}760if (srcY < 0) {761atfm.translate(0, -srcY);762srcY = 0;763}764if (srcX >= srcW || srcY >= srcH) {765return true;766}767// Note: src[WH] are currently the right and bottom coordinates.768// The following two lines would adjust src[WH] back to being769// dimensions.770// srcW -= srcX;771// srcH -= srcY;772// Since transformImage needs right and bottom coords we will773// omit this adjustment.774775transformImage(sg, img, atfm, sg.interpolationType,776srcX, srcY, srcW, srcH, bgColor);777return true;778}779780/**781** Utilities782** The following methods are used by the public methods above783** for performing various operations784**/785786/*787* This constant represents a tradeoff between the788* need to make sure that image transformations are789* "very close" to integer device coordinates before790* we decide to use an integer scale or copy operation791* as a substitute and the fact that roundoff errors792* in AffineTransforms are frequently introduced by793* performing multiple sequential operations on them.794*795* The evaluation of bug 4990624 details the potential796* for this error cutoff to result in display anomalies797* in different types of image operations and how this798* value represents a good compromise here.799*/800private static final double MAX_TX_ERROR = .0001;801802public static boolean closeToInteger(int i, double d) {803return (Math.abs(d-i) < MAX_TX_ERROR);804}805806public static boolean isSimpleTranslate(SunGraphics2D sg) {807int ts = sg.transformState;808if (ts <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) {809// Integer translates are always "simple"810return true;811}812if (ts >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {813// Scales and beyond are always "not simple"814return false;815}816// non-integer translates are only simple when not interpolating817if (sg.interpolationType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {818return true;819}820return false;821}822823protected static boolean isBgOperation(SurfaceData srcData, Color bgColor) {824// If we cannot get the srcData, then cannot assume anything about825// the image826return ((srcData == null) ||827((bgColor != null) &&828(srcData.getTransparency() != Transparency.OPAQUE)));829}830831protected BufferedImage getBufferedImage(Image img) {832if (img instanceof BufferedImage) {833return (BufferedImage)img;834}835// Must be VolatileImage; get BufferedImage representation836return ((VolatileImage)img).getSnapshot();837}838839/*840* Return the color model to be used with this BufferedImage and841* transform.842*/843private ColorModel getTransformColorModel(SunGraphics2D sg,844BufferedImage bImg,845AffineTransform tx) {846ColorModel cm = bImg.getColorModel();847ColorModel dstCM = cm;848849if (tx.isIdentity()) {850return dstCM;851}852int type = tx.getType();853boolean needTrans =854((type & (AffineTransform.TYPE_MASK_ROTATION |855AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);856if (! needTrans &&857type != AffineTransform.TYPE_TRANSLATION &&858type != AffineTransform.TYPE_IDENTITY)859{860double[] mtx = new double[4];861tx.getMatrix(mtx);862// Check out the matrix. A non-integral scale will force ARGB863// since the edge conditions cannot be guaranteed.864needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]);865}866867if (sg.renderHint != SunHints.INTVAL_RENDER_QUALITY) {868if (cm instanceof IndexColorModel) {869Raster raster = bImg.getRaster();870IndexColorModel icm = (IndexColorModel) cm;871// Just need to make sure that we have a transparent pixel872if (needTrans && cm.getTransparency() == Transparency.OPAQUE) {873// Fix 4221407874if (raster instanceof sun.awt.image.BytePackedRaster) {875dstCM = ColorModel.getRGBdefault();876}877else {878double[] matrix = new double[6];879tx.getMatrix(matrix);880if (matrix[1] == 0. && matrix[2] ==0.881&& matrix[4] == 0. && matrix[5] == 0.) {882// Only scaling so do not need to create883}884else {885int mapSize = icm.getMapSize();886if (mapSize < 256) {887int[] cmap = new int[mapSize+1];888icm.getRGBs(cmap);889cmap[mapSize] = 0x0000;890dstCM = new891IndexColorModel(icm.getPixelSize(),892mapSize+1,893cmap, 0, true, mapSize,894DataBuffer.TYPE_BYTE);895}896else {897dstCM = ColorModel.getRGBdefault();898}899} /* if (matrix[0] < 1.f ...) */900} /* raster instanceof sun.awt.image.BytePackedRaster */901} /* if (cm.getTransparency() == cm.OPAQUE) */902} /* if (cm instanceof IndexColorModel) */903else if (needTrans && cm.getTransparency() == Transparency.OPAQUE) {904// Need a bitmask transparency905// REMIND: for now, use full transparency since no loops906// for bitmask907dstCM = ColorModel.getRGBdefault();908}909} /* if (sg.renderHint == RENDER_QUALITY) */910else {911912if (cm instanceof IndexColorModel ||913(needTrans && cm.getTransparency() == Transparency.OPAQUE))914{915// Need a bitmask transparency916// REMIND: for now, use full transparency since no loops917// for bitmask918dstCM = ColorModel.getRGBdefault();919}920}921922return dstCM;923}924925private static void blitSurfaceData(SunGraphics2D sg, Region clip,926SurfaceData srcData,927SurfaceData dstData,928int sx, int sy, int dx, int dy,929int w, int h, Color bgColor)930{931CompositeType comp = sg.imageComp;932if (CompositeType.SrcOverNoEa.equals(comp) &&933(srcData.getTransparency() == Transparency.OPAQUE ||934(bgColor != null &&935bgColor.getTransparency() == Transparency.OPAQUE)))936{937comp = CompositeType.SrcNoEa;938}939if (srcData == dstData && sx == dx && sy == dy940&& CompositeType.SrcNoEa.equals(comp)) {941// Performance optimization. We skip the Blit/BlitBG if we know that942// it will be noop.943return;944}945// The next optimization should be used by all our pipelines but for now946// some of the native pipelines "ogl", "d3d", "gdi", "xrender" relies to947// much on the native driver, which does not apply it automatically.948// At some point, we should remove it from here, since it affects the949// performance of the software loops, and move to the appropriate place.950Rectangle dst =951new Rectangle(dx, dy, w, h).intersection(dstData.getBounds());952if (dst.isEmpty()) {953// The check above also includes:954// if (w <= 0 || h <= 0) {955/*956* Fix for bugid 4783274 - BlitBg throws an exception for957* a particular set of anomalous parameters.958* REMIND: The native loops do proper clipping and would959* detect this situation themselves, but the Java loops960* all seem to trust their parameters a little too well961* to the point where they will try to process a negative962* area of pixels and throw exceptions. The real fix is963* to modify the Java loops to do proper clipping so that964* they can deal with negative dimensions as well as965* improperly large dimensions, but that fix is too risky966* to integrate for Mantis at this point. In the meantime967* eliminating the negative or zero dimensions here is968* "correct" and saves them from some nasty exceptional969* conditions, one of which is the test case of 4783274.970*/971// return;972// }973return;974}975// Adjust final src(x,y) based on the dst. The logic is that, when dst976// limits drawing on the destination, corresponding pixels from the src977// should be skipped.978sx += dst.x - dx;979sy += dst.y - dy;980981SurfaceType srcType = srcData.getSurfaceType();982SurfaceType dstType = dstData.getSurfaceType();983if (!isBgOperation(srcData, bgColor)) {984Blit blit = Blit.getFromCache(srcType, comp, dstType);985blit.Blit(srcData, dstData, sg.composite, clip,986sx, sy, dst.x, dst.y, dst.width, dst.height);987} else {988BlitBg blit = BlitBg.getFromCache(srcType, comp, dstType);989blit.BlitBg(srcData, dstData, sg.composite, clip, bgColor.getRGB(),990sx, sy, dst.x, dst.y, dst.width, dst.height);991}992}993994protected boolean scaleSurfaceData(SunGraphics2D sg,995Region clipRegion,996SurfaceData srcData,997SurfaceData dstData,998SurfaceType srcType,999SurfaceType dstType,1000int sx1, int sy1,1001int sx2, int sy2,1002double dx1, double dy1,1003double dx2, double dy2)1004{1005CompositeType comp = sg.imageComp;1006if (CompositeType.SrcOverNoEa.equals(comp) &&1007(srcData.getTransparency() == Transparency.OPAQUE))1008{1009comp = CompositeType.SrcNoEa;1010}10111012ScaledBlit blit = ScaledBlit.getFromCache(srcType, comp, dstType);1013if (blit != null) {1014blit.Scale(srcData, dstData, sg.composite, clipRegion,1015sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);1016return true;1017}1018return false;1019}10201021protected static boolean imageReady(ToolkitImage sunimg,1022ImageObserver observer)1023{1024if (sunimg.hasError()) {1025if (observer != null) {1026observer.imageUpdate(sunimg,1027ImageObserver.ERROR|ImageObserver.ABORT,1028-1, -1, -1, -1);1029}1030return false;1031}1032return true;1033}10341035public boolean copyImage(SunGraphics2D sg, Image img,1036int x, int y,1037Color bgColor,1038ImageObserver observer) {1039if (!(img instanceof ToolkitImage)) {1040return copyImage(sg, img, x, y, bgColor);1041} else {1042ToolkitImage sunimg = (ToolkitImage)img;1043if (!imageReady(sunimg, observer)) {1044return false;1045}1046ImageRepresentation ir = sunimg.getImageRep();1047return ir.drawToBufImage(sg, sunimg, x, y, bgColor, observer);1048}1049}10501051public boolean copyImage(SunGraphics2D sg, Image img,1052int dx, int dy, int sx, int sy, int w, int h,1053Color bgColor,1054ImageObserver observer) {1055if (!(img instanceof ToolkitImage)) {1056return copyImage(sg, img, dx, dy, sx, sy, w, h, bgColor);1057} else {1058ToolkitImage sunimg = (ToolkitImage)img;1059if (!imageReady(sunimg, observer)) {1060return false;1061}1062ImageRepresentation ir = sunimg.getImageRep();1063return ir.drawToBufImage(sg, sunimg,1064dx, dy, (dx + w), (dy + h),1065sx, sy, (sx + w), (sy + h),1066bgColor, observer);1067}1068}10691070public boolean scaleImage(SunGraphics2D sg, Image img,1071int x, int y,1072int width, int height,1073Color bgColor,1074ImageObserver observer) {1075if (!(img instanceof ToolkitImage)) {1076return scaleImage(sg, img, x, y, width, height, bgColor);1077} else {1078ToolkitImage sunimg = (ToolkitImage)img;1079if (!imageReady(sunimg, observer)) {1080return false;1081}1082ImageRepresentation ir = sunimg.getImageRep();1083return ir.drawToBufImage(sg, sunimg, x, y, width, height, bgColor,1084observer);1085}1086}10871088public boolean scaleImage(SunGraphics2D sg, Image img,1089int dx1, int dy1, int dx2, int dy2,1090int sx1, int sy1, int sx2, int sy2,1091Color bgColor,1092ImageObserver observer) {1093if (!(img instanceof ToolkitImage)) {1094return scaleImage(sg, img, dx1, dy1, dx2, dy2,1095sx1, sy1, sx2, sy2, bgColor);1096} else {1097ToolkitImage sunimg = (ToolkitImage)img;1098if (!imageReady(sunimg, observer)) {1099return false;1100}1101ImageRepresentation ir = sunimg.getImageRep();1102return ir.drawToBufImage(sg, sunimg, dx1, dy1, dx2, dy2,1103sx1, sy1, sx2, sy2, bgColor, observer);1104}1105}11061107public boolean transformImage(SunGraphics2D sg, Image img,1108AffineTransform atfm,1109ImageObserver observer) {1110if (!(img instanceof ToolkitImage)) {1111transformImage(sg, img, 0, 0, atfm, sg.interpolationType);1112return true;1113} else {1114ToolkitImage sunimg = (ToolkitImage)img;1115if (!imageReady(sunimg, observer)) {1116return false;1117}1118ImageRepresentation ir = sunimg.getImageRep();1119return ir.drawToBufImage(sg, sunimg, atfm, observer);1120}1121}11221123public void transformImage(SunGraphics2D sg, BufferedImage img,1124BufferedImageOp op, int x, int y)1125{1126if (op != null) {1127if (op instanceof AffineTransformOp) {1128AffineTransformOp atop = (AffineTransformOp) op;1129transformImage(sg, img, x, y,1130atop.getTransform(),1131atop.getInterpolationType());1132return;1133} else {1134img = op.filter(img, null);1135}1136}1137copyImage(sg, img, x, y, null);1138}1139}114011411142