View Javadoc
1   /*
2    * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.java2d.pipe;
27  
28  import java.awt.AlphaComposite;
29  import java.awt.Color;
30  import java.awt.Image;
31  import java.awt.Transparency;
32  import java.awt.geom.AffineTransform;
33  import java.awt.geom.NoninvertibleTransformException;
34  import java.awt.image.AffineTransformOp;
35  import java.awt.image.BufferedImage;
36  import java.awt.image.BufferedImageOp;
37  import java.awt.image.ColorModel;
38  import java.awt.image.DataBuffer;
39  import java.awt.image.ImageObserver;
40  import java.awt.image.IndexColorModel;
41  import java.awt.image.Raster;
42  import java.awt.image.VolatileImage;
43  import sun.awt.SunHints;
44  import sun.awt.image.ImageRepresentation;
45  import sun.awt.image.SurfaceManager;
46  import sun.awt.image.ToolkitImage;
47  import sun.java2d.InvalidPipeException;
48  import sun.java2d.SunGraphics2D;
49  import sun.java2d.SurfaceData;
50  import sun.java2d.loops.Blit;
51  import sun.java2d.loops.BlitBg;
52  import sun.java2d.loops.TransformHelper;
53  import sun.java2d.loops.MaskBlit;
54  import sun.java2d.loops.CompositeType;
55  import sun.java2d.loops.ScaledBlit;
56  import sun.java2d.loops.SurfaceType;
57  
58  public class DrawImage implements DrawImagePipe
59  {
60      public boolean copyImage(SunGraphics2D sg, Image img,
61                               int x, int y,
62                               Color bgColor)
63      {
64          int imgw = img.getWidth(null);
65          int imgh = img.getHeight(null);
66          if (isSimpleTranslate(sg)) {
67              return renderImageCopy(sg, img, bgColor,
68                                     x + sg.transX, y + sg.transY,
69                                     0, 0, imgw, imgh);
70          }
71          AffineTransform atfm = sg.transform;
72          if ((x | y) != 0) {
73              atfm = new AffineTransform(atfm);
74              atfm.translate(x, y);
75          }
76          transformImage(sg, img, atfm, sg.interpolationType,
77                         0, 0, imgw, imgh, bgColor);
78          return true;
79      }
80  
81      public boolean copyImage(SunGraphics2D sg, Image img,
82                               int dx, int dy, int sx, int sy, int w, int h,
83                               Color bgColor)
84      {
85          if (isSimpleTranslate(sg)) {
86              return renderImageCopy(sg, img, bgColor,
87                                     dx + sg.transX, dy + sg.transY,
88                                     sx, sy, w, h);
89          }
90          scaleImage(sg, img, dx, dy, (dx + w), (dy + h),
91                     sx, sy, (sx + w), (sy + h), bgColor);
92          return true;
93      }
94  
95      public boolean scaleImage(SunGraphics2D sg, Image img, int x, int y,
96                                int width, int height,
97                                Color bgColor)
98      {
99          int imgw = img.getWidth(null);
100         int imgh = img.getHeight(null);
101         // Only accelerate scale if:
102         //          - w/h positive values
103         //          - sg transform integer translate/identity only
104         //          - no bgColor in operation
105         if ((width > 0) && (height > 0) && isSimpleTranslate(sg)) {
106             double dx1 = x + sg.transX;
107             double dy1 = y + sg.transY;
108             double dx2 = dx1 + width;
109             double dy2 = dy1 + height;
110             if (renderImageScale(sg, img, bgColor, sg.interpolationType,
111                                  0, 0, imgw, imgh,
112                                  dx1, dy1, dx2, dy2))
113             {
114                 return true;
115             }
116         }
117 
118         AffineTransform atfm = sg.transform;
119         if ((x | y) != 0 || width != imgw || height != imgh) {
120             atfm = new AffineTransform(atfm);
121             atfm.translate(x, y);
122             atfm.scale(((double)width)/imgw, ((double)height)/imgh);
123         }
124         transformImage(sg, img, atfm, sg.interpolationType,
125                        0, 0, imgw, imgh, bgColor);
126         return true;
127     }
128 
129     /*
130      * This method is only called in those circumstances where the
131      * operation has a non-null secondary transform specified.  Its
132      * role is to check for various optimizations based on the types
133      * of both the secondary and SG2D transforms and to do some
134      * quick calculations to avoid having to combine the transforms
135      * and/or to call a more generalized method.
136      */
137     protected void transformImage(SunGraphics2D sg, Image img, int x, int y,
138                                   AffineTransform extraAT, int interpType)
139     {
140         int txtype = extraAT.getType();
141         int imgw = img.getWidth(null);
142         int imgh = img.getHeight(null);
143         boolean checkfinalxform;
144 
145         if (sg.transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE &&
146             (txtype == AffineTransform.TYPE_IDENTITY ||
147              txtype == AffineTransform.TYPE_TRANSLATION))
148         {
149             // First optimization - both are some kind of translate
150 
151             // Combine the translations and check if interpolation is necessary.
152             double tx = extraAT.getTranslateX();
153             double ty = extraAT.getTranslateY();
154             tx += sg.transform.getTranslateX();
155             ty += sg.transform.getTranslateY();
156             int itx = (int) Math.floor(tx + 0.5);
157             int ity = (int) Math.floor(ty + 0.5);
158             if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR ||
159                 (closeToInteger(itx, tx) && closeToInteger(ity, ty)))
160             {
161                 renderImageCopy(sg, img, null, x+itx, y+ity, 0, 0, imgw, imgh);
162                 return;
163             }
164             checkfinalxform = false;
165         } else if (sg.transformState <= SunGraphics2D.TRANSFORM_TRANSLATESCALE &&
166                    ((txtype & (AffineTransform.TYPE_FLIP |
167                                AffineTransform.TYPE_MASK_ROTATION |
168                                AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0))
169         {
170             // Second optimization - both are some kind of translate or scale
171 
172             // Combine the scales and check if interpolation is necessary.
173 
174             // Transform source bounds by extraAT,
175             // then translate the bounds again by x, y
176             // then transform the bounds again by sg.transform
177             double coords[] = new double[] {
178                 0, 0, imgw, imgh,
179             };
180             extraAT.transform(coords, 0, coords, 0, 2);
181             coords[0] += x;
182             coords[1] += y;
183             coords[2] += x;
184             coords[3] += y;
185             sg.transform.transform(coords, 0, coords, 0, 2);
186 
187             if (tryCopyOrScale(sg, img, 0, 0, imgw, imgh,
188                                null, interpType, coords))
189             {
190                 return;
191             }
192             checkfinalxform = false;
193         } else {
194             checkfinalxform = true;
195         }
196 
197         // Begin Transform
198         AffineTransform tx = new AffineTransform(sg.transform);
199         tx.translate(x, y);
200         tx.concatenate(extraAT);
201 
202         // Do not try any more optimizations if either of the cases
203         // above was tried as we have already verified that the
204         // resulting transform will not simplify.
205         if (checkfinalxform) {
206             // In this case neither of the above simple transform
207             // pairs was found so we will do some final tests on
208             // the final rendering transform which may be the
209             // simple product of two complex transforms.
210             transformImage(sg, img, tx, interpType, 0, 0, imgw, imgh, null);
211         } else {
212             renderImageXform(sg, img, tx, interpType, 0, 0, imgw, imgh, null);
213         }
214     }
215 
216     /*
217      * This method is called with a final rendering transform that
218      * has combined all of the information about the Graphics2D
219      * transform attribute with the transformations specified by
220      * the arguments to the drawImage call.
221      * Its role is to see if the combined transform ends up being
222      * acceleratable by either a renderImageCopy or renderImageScale
223      * once all of the math is done.
224      *
225      * Note: The transform supplied here has an origin that is
226      * already adjusted to point to the device location where
227      * the (sx1, sy1) location of the source image should be placed.
228      */
229     protected void transformImage(SunGraphics2D sg, Image img,
230                                   AffineTransform tx, int interpType,
231                                   int sx1, int sy1, int sx2, int sy2,
232                                   Color bgColor)
233     {
234         // Transform 3 source corners by tx and analyze them
235         // for simplified operations (Copy or Scale).  Using
236         // 3 points lets us analyze any kind of transform,
237         // even transforms that involve very tiny amounts of
238         // rotation or skew to see if they degenerate to a
239         // simple scale or copy operation within the allowable
240         // error bounds.
241         // Note that we use (0,0,w,h) instead of (sx1,sy1,sx2,sy2)
242         // because the transform is already translated such that
243         // the origin is where sx1, sy1 should go.
244         double coords[] = new double[6];
245         /* index:  0  1    2  3    4  5  */
246         /* coord: (0, 0), (w, h), (0, h) */
247         coords[2] = sx2 - sx1;
248         coords[3] = coords[5] = sy2 - sy1;
249         tx.transform(coords, 0, coords, 0, 3);
250         // First test if the X coords of the transformed UL
251         // and LL points match and that the Y coords of the
252         // transformed LR and LL points also match.
253         // If they do then it is a "rectilinear" transform and
254         // tryCopyOrScale will make sure it is upright and
255         // integer-based.
256         if (Math.abs(coords[0] - coords[4]) < MAX_TX_ERROR &&
257             Math.abs(coords[3] - coords[5]) < MAX_TX_ERROR &&
258             tryCopyOrScale(sg, img, sx1, sy1, sx2, sy2,
259                            bgColor, interpType, coords))
260         {
261             return;
262         }
263 
264         renderImageXform(sg, img, tx, interpType, sx1, sy1, sx2, sy2, bgColor);
265     }
266 
267     /*
268      * Check the bounding coordinates of the transformed source
269      * image to see if they fall on integer coordinates such
270      * that they will cause no interpolation anomalies if we
271      * use our simplified Blit or ScaledBlit operations instead
272      * of a full transform operation.
273      */
274     protected boolean tryCopyOrScale(SunGraphics2D sg,
275                                      Image img,
276                                      int sx1, int sy1,
277                                      int sx2, int sy2,
278                                      Color bgColor, int interpType,
279                                      double coords[])
280     {
281         double dx = coords[0];
282         double dy = coords[1];
283         double dw = coords[2] - dx;
284         double dh = coords[3] - dy;
285         // First check if width and height are very close to img w&h.
286         if (closeToInteger(sx2-sx1, dw) && closeToInteger(sy2-sy1, dh)) {
287             // Round location to nearest pixel and then test
288             // if it will cause interpolation anomalies.
289             int idx = (int) Math.floor(dx + 0.5);
290             int idy = (int) Math.floor(dy + 0.5);
291             if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR ||
292                 (closeToInteger(idx, dx) && closeToInteger(idy, dy)))
293             {
294                 renderImageCopy(sg, img, bgColor,
295                                 idx, idy,
296                                 sx1, sy1, sx2-sx1, sy2-sy1);
297                 return true;
298             }
299         }
300         // (For now) We can only use our ScaledBlits if the image
301         // is upright (i.e. dw & dh both > 0)
302         if (dw > 0 && dh > 0) {
303             if (renderImageScale(sg, img, bgColor, interpType,
304                                  sx1, sy1, sx2, sy2,
305                                  coords[0], coords[1], coords[2], coords[3]))
306             {
307                 return true;
308             }
309         }
310         return false;
311     }
312 
313     /*
314      * Return a BufferedImage of the requested type with the indicated
315      * subimage of the original image located at 0,0 in the new image.
316      * If a bgColor is supplied, composite the original image over that
317      * color with a SrcOver operation, otherwise make a SrcNoEa copy.
318      */
319     BufferedImage makeBufferedImage(Image img, Color bgColor, int type,
320                                     int sx1, int sy1, int sx2, int sy2)
321     {
322         final int width = sx2 - sx1;
323         final int height = sy2 - sy1;
324         final BufferedImage bimg = new BufferedImage(width, height, type);
325         final SunGraphics2D g2d = (SunGraphics2D) bimg.createGraphics();
326         g2d.setComposite(AlphaComposite.Src);
327         if (bgColor != null) {
328             g2d.setColor(bgColor);
329             g2d.fillRect(0, 0, width, height);
330             g2d.setComposite(AlphaComposite.SrcOver);
331         }
332         g2d.copyImage(img, 0, 0, sx1, sy1, width, height, null, null);
333         g2d.dispose();
334         return bimg;
335     }
336 
337     protected void renderImageXform(SunGraphics2D sg, Image img,
338                                     AffineTransform tx, int interpType,
339                                     int sx1, int sy1, int sx2, int sy2,
340                                     Color bgColor)
341     {
342         Region clip = sg.getCompClip();
343         SurfaceData dstData = sg.surfaceData;
344         SurfaceData srcData = dstData.getSourceSurfaceData(img,
345                                                            SunGraphics2D.TRANSFORM_GENERIC,
346                                                            sg.imageComp,
347                                                            bgColor);
348 
349         if (srcData == null) {
350             img = getBufferedImage(img);
351             srcData = dstData.getSourceSurfaceData(img,
352                                                    SunGraphics2D.TRANSFORM_GENERIC,
353                                                    sg.imageComp,
354                                                    bgColor);
355             if (srcData == null) {
356                 // REMIND: Is this correct?  Can this happen?
357                 return;
358             }
359         }
360 
361         if (isBgOperation(srcData, bgColor)) {
362             // We cannot perform bg operations during transform so make
363             // an opaque temp image with the appropriate background
364             // and work from there.
365             img = makeBufferedImage(img, bgColor, BufferedImage.TYPE_INT_RGB,
366                                     sx1, sy1, sx2, sy2);
367             // Temp image has appropriate subimage at 0,0 now.
368             sx2 -= sx1;
369             sy2 -= sy1;
370             sx1 = sy1 = 0;
371 
372             srcData = dstData.getSourceSurfaceData(img,
373                                                    SunGraphics2D.TRANSFORM_GENERIC,
374                                                    sg.imageComp,
375                                                    bgColor);
376         }
377 
378         SurfaceType srcType = srcData.getSurfaceType();
379         TransformHelper helper = TransformHelper.getFromCache(srcType);
380 
381         if (helper == null) {
382             /* We have no helper for this source image type.
383              * But we know that we do have helpers for both RGB and ARGB,
384              * so convert to one of those types depending on transparency.
385              * ARGB_PRE might be a better choice if the source image has
386              * alpha, but it may cause some recursion here since we only
387              * tend to have converters that convert to ARGB.
388              */
389             int type = ((srcData.getTransparency() == Transparency.OPAQUE)
390                         ? BufferedImage.TYPE_INT_RGB
391                         : BufferedImage.TYPE_INT_ARGB);
392             img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2);
393             // Temp image has appropriate subimage at 0,0 now.
394             sx2 -= sx1;
395             sy2 -= sy1;
396             sx1 = sy1 = 0;
397 
398             srcData = dstData.getSourceSurfaceData(img,
399                                                    SunGraphics2D.TRANSFORM_GENERIC,
400                                                    sg.imageComp,
401                                                    null);
402             srcType = srcData.getSurfaceType();
403             helper = TransformHelper.getFromCache(srcType);
404             // assert(helper != null);
405         }
406 
407         AffineTransform itx;
408         try {
409             itx = tx.createInverse();
410         } catch (NoninvertibleTransformException e) {
411             // Non-invertible transform means no output
412             return;
413         }
414 
415         /*
416          * Find the maximum bounds on the destination that will be
417          * affected by the transformed source.  First, transform all
418          * four corners of the source and then min and max the resulting
419          * destination coordinates of the transformed corners.
420          * Note that tx already has the offset to sx1,sy1 accounted
421          * for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the
422          * source coordinates.
423          */
424         double coords[] = new double[8];
425         /* corner:  UL      UR      LL      LR   */
426         /* index:  0  1    2  3    4  5    6  7  */
427         /* coord: (0, 0), (w, 0), (0, h), (w, h) */
428         coords[2] = coords[6] = sx2 - sx1;
429         coords[5] = coords[7] = sy2 - sy1;
430         tx.transform(coords, 0, coords, 0, 4);
431         double ddx1, ddy1, ddx2, ddy2;
432         ddx1 = ddx2 = coords[0];
433         ddy1 = ddy2 = coords[1];
434         for (int i = 2; i < coords.length; i += 2) {
435             double d = coords[i];
436             if (ddx1 > d) ddx1 = d;
437             else if (ddx2 < d) ddx2 = d;
438             d = coords[i+1];
439             if (ddy1 > d) ddy1 = d;
440             else if (ddy2 < d) ddy2 = d;
441         }
442         int dx1 = (int) Math.floor(ddx1);
443         int dy1 = (int) Math.floor(ddy1);
444         int dx2 = (int) Math.ceil(ddx2);
445         int dy2 = (int) Math.ceil(ddy2);
446 
447         SurfaceType dstType = dstData.getSurfaceType();
448         MaskBlit maskblit;
449         Blit blit;
450         if (sg.compositeState <= SunGraphics2D.COMP_ALPHA) {
451             /* NOTE: We either have, or we can make,
452              * a MaskBlit for any alpha composite type
453              */
454             maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre,
455                                              sg.imageComp,
456                                              dstType);
457 
458             /* NOTE: We can only use the native TransformHelper
459              * func to go directly to the dest if both the helper
460              * and the MaskBlit are native.
461              * All helpers are native at this point, but some MaskBlit
462              * objects are implemented in Java, so we need to check.
463              */
464             if (maskblit.getNativePrim() != 0) {
465                 // We can render directly.
466                 helper.Transform(maskblit, srcData, dstData,
467                                  sg.composite, clip,
468                                  itx, interpType,
469                                  sx1, sy1, sx2, sy2,
470                                  dx1, dy1, dx2, dy2,
471                                  null, 0, 0);
472                 return;
473             }
474             blit = null;
475         } else {
476             /* NOTE: We either have, or we can make,
477              * a Blit for any composite type, even Custom
478              */
479             maskblit = null;
480             blit = Blit.getFromCache(SurfaceType.IntArgbPre,
481                                      sg.imageComp,
482                                      dstType);
483         }
484 
485         // We need to transform to a temp image and then copy
486         // just the pieces that are valid data to the dest.
487         BufferedImage tmpimg = new BufferedImage(dx2-dx1, dy2-dy1,
488                                                  BufferedImage.TYPE_INT_ARGB);
489         SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg);
490         SurfaceType tmpType = tmpData.getSurfaceType();
491         MaskBlit tmpmaskblit =
492             MaskBlit.getFromCache(SurfaceType.IntArgbPre,
493                                   CompositeType.SrcNoEa,
494                                   tmpType);
495         /*
496          * The helper function fills a temporary edges buffer
497          * for us with the bounding coordinates of each scanline
498          * in the following format:
499          *
500          * edges[0, 1] = [top y, bottom y)
501          * edges[2, 3] = [left x, right x) of top row
502          * ...
503          * edges[h*2, h*2+1] = [left x, right x) of bottom row
504          *
505          * all coordinates in the edges array will be relative to dx1, dy1
506          *
507          * edges thus has to be h*2+2 in length
508          */
509         int edges[] = new int[(dy2-dy1)*2+2];
510         // It is important that edges[0]=edges[1]=0 when we call
511         // Transform in case it must return early and we would
512         // not want to render anything on an error condition.
513         helper.Transform(tmpmaskblit, srcData, tmpData,
514                          AlphaComposite.Src, null,
515                          itx, interpType,
516                          sx1, sy1, sx2, sy2,
517                          0, 0, dx2-dx1, dy2-dy1,
518                          edges, dx1, dy1);
519 
520         /*
521          * Now copy the results, scanline by scanline, into the dest.
522          * The edges array helps us minimize the work.
523          */
524         int index = 2;
525         for (int y = edges[0]; y < edges[1]; y++) {
526             int relx1 = edges[index++];
527             int relx2 = edges[index++];
528             if (relx1 >= relx2) {
529                 continue;
530             }
531             if (maskblit != null) {
532                 maskblit.MaskBlit(tmpData, dstData,
533                                   sg.composite, clip,
534                                   relx1, y,
535                                   dx1+relx1, dy1+y,
536                                   relx2 - relx1, 1,
537                                   null, 0, 0);
538             } else {
539                 blit.Blit(tmpData, dstData,
540                           sg.composite, clip,
541                           relx1, y,
542                           dx1+relx1, dy1+y,
543                           relx2 - relx1, 1);
544             }
545         }
546     }
547 
548     // Render an image using only integer translation
549     // (no scale or transform or sub-pixel interpolated translations).
550     protected boolean renderImageCopy(SunGraphics2D sg, Image img,
551                                       Color bgColor,
552                                       int dx, int dy,
553                                       int sx, int sy,
554                                       int w, int h)
555     {
556         Region clip = sg.getCompClip();
557         SurfaceData dstData = sg.surfaceData;
558 
559         int attempts = 0;
560         // Loop up to twice through; this gives us a chance to
561         // revalidate the surfaceData objects in case of an exception
562         // and try it once more
563         while (true) {
564             SurfaceData srcData =
565                 dstData.getSourceSurfaceData(img,
566                                              SunGraphics2D.TRANSFORM_ISIDENT,
567                                              sg.imageComp,
568                                              bgColor);
569             if (srcData == null) {
570                 return false;
571             }
572 
573             try {
574                 SurfaceType srcType = srcData.getSurfaceType();
575                 SurfaceType dstType = dstData.getSurfaceType();
576                 blitSurfaceData(sg, clip,
577                                 srcData, dstData, srcType, dstType,
578                                 sx, sy, dx, dy, w, h, bgColor);
579                 return true;
580             } catch (NullPointerException e) {
581                 if (!(SurfaceData.isNull(dstData) ||
582                       SurfaceData.isNull(srcData)))
583                 {
584                     // Something else caused the exception, throw it...
585                     throw e;
586                 }
587                 return false;
588                 // NOP if we have been disposed
589             } catch (InvalidPipeException e) {
590                 // Always catch the exception; try this a couple of times
591                 // and fail silently if the system is not yet ready to
592                 // revalidate the source or dest surfaceData objects.
593                 ++attempts;
594                 clip = sg.getCompClip();   // ensures sg.surfaceData is valid
595                 dstData = sg.surfaceData;
596                 if (SurfaceData.isNull(dstData) ||
597                     SurfaceData.isNull(srcData) || (attempts > 1))
598                 {
599                     return false;
600                 }
601             }
602         }
603     }
604 
605     // Render an image using only integer scaling (no transform).
606     protected boolean renderImageScale(SunGraphics2D sg, Image img,
607                                        Color bgColor, int interpType,
608                                        int sx1, int sy1,
609                                        int sx2, int sy2,
610                                        double dx1, double dy1,
611                                        double dx2, double dy2)
612     {
613         // Currently only NEAREST_NEIGHBOR interpolation is implemented
614         // for ScaledBlit operations.
615         if (interpType != AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
616             return false;
617         }
618 
619         Region clip = sg.getCompClip();
620         SurfaceData dstData = sg.surfaceData;
621 
622         int attempts = 0;
623         // Loop up to twice through; this gives us a chance to
624         // revalidate the surfaceData objects in case of an exception
625         // and try it once more
626         while (true) {
627             SurfaceData srcData =
628                 dstData.getSourceSurfaceData(img,
629                                              SunGraphics2D.TRANSFORM_TRANSLATESCALE,
630                                              sg.imageComp,
631                                              bgColor);
632 
633             if (srcData == null || isBgOperation(srcData, bgColor)) {
634                 return false;
635             }
636 
637             try {
638                 SurfaceType srcType = srcData.getSurfaceType();
639                 SurfaceType dstType = dstData.getSurfaceType();
640                 return scaleSurfaceData(sg, clip,
641                                         srcData, dstData, srcType, dstType,
642                                         sx1, sy1, sx2, sy2,
643                                         dx1, dy1, dx2, dy2);
644             } catch (NullPointerException e) {
645                 if (!SurfaceData.isNull(dstData)) {
646                     // Something else caused the exception, throw it...
647                     throw e;
648                 }
649                 return false;
650                 // NOP if we have been disposed
651             } catch (InvalidPipeException e) {
652                 // Always catch the exception; try this a couple of times
653                 // and fail silently if the system is not yet ready to
654                 // revalidate the source or dest surfaceData objects.
655                 ++attempts;
656                 clip = sg.getCompClip();  // ensures sg.surfaceData is valid
657                 dstData = sg.surfaceData;
658                 if (SurfaceData.isNull(dstData) ||
659                     SurfaceData.isNull(srcData) || (attempts > 1))
660                 {
661                     return false;
662                 }
663             }
664         }
665     }
666 
667     public boolean scaleImage(SunGraphics2D sg, Image img,
668                               int dx1, int dy1, int dx2, int dy2,
669                               int sx1, int sy1, int sx2, int sy2,
670                               Color bgColor)
671     {
672         int srcW, srcH, dstW, dstH;
673         int srcX, srcY, dstX, dstY;
674         boolean srcWidthFlip = false;
675         boolean srcHeightFlip = false;
676         boolean dstWidthFlip = false;
677         boolean dstHeightFlip = false;
678 
679         if (sx2 > sx1) {
680             srcW = sx2 - sx1;
681             srcX = sx1;
682         } else {
683             srcWidthFlip = true;
684             srcW = sx1 - sx2;
685             srcX = sx2;
686         }
687         if (sy2 > sy1) {
688             srcH = sy2-sy1;
689             srcY = sy1;
690         } else {
691             srcHeightFlip = true;
692             srcH = sy1-sy2;
693             srcY = sy2;
694         }
695         if (dx2 > dx1) {
696             dstW = dx2 - dx1;
697             dstX = dx1;
698         } else {
699             dstW = dx1 - dx2;
700             dstWidthFlip = true;
701             dstX = dx2;
702         }
703         if (dy2 > dy1) {
704             dstH = dy2 - dy1;
705             dstY = dy1;
706         } else {
707             dstH = dy1 - dy2;
708             dstHeightFlip = true;
709             dstY = dy2;
710         }
711         if (srcW <= 0 || srcH <= 0) {
712             return true;
713         }
714         // Only accelerate scale if it does not involve a flip or transform
715         if ((srcWidthFlip == dstWidthFlip) &&
716             (srcHeightFlip == dstHeightFlip) &&
717             isSimpleTranslate(sg))
718         {
719             double ddx1 = dstX + sg.transX;
720             double ddy1 = dstY + sg.transY;
721             double ddx2 = ddx1 + dstW;
722             double ddy2 = ddy1 + dstH;
723             if (renderImageScale(sg, img, bgColor, sg.interpolationType,
724                                  srcX, srcY, srcX+srcW, srcY+srcH,
725                                  ddx1, ddy1, ddx2, ddy2))
726             {
727                 return true;
728             }
729         }
730 
731         AffineTransform atfm = new AffineTransform(sg.transform);
732         atfm.translate(dx1, dy1);
733         double m00 = (double)(dx2-dx1)/(sx2-sx1);
734         double m11 = (double)(dy2-dy1)/(sy2-sy1);
735         atfm.scale(m00, m11);
736         atfm.translate(srcX-sx1, srcY-sy1);
737 
738         final int scale = SurfaceManager.getImageScale(img);
739         final int imgW = img.getWidth(null) * scale;
740         final int imgH = img.getHeight(null) * scale;
741         srcW += srcX;
742         srcH += srcY;
743         // Make sure we are not out of bounds
744         if (srcW > imgW) {
745             srcW = imgW;
746         }
747         if (srcH > imgH) {
748             srcH = imgH;
749         }
750         if (srcX < 0) {
751             atfm.translate(-srcX, 0);
752             srcX = 0;
753         }
754         if (srcY < 0) {
755             atfm.translate(0, -srcY);
756             srcY = 0;
757         }
758         if (srcX >= srcW || srcY >= srcH) {
759             return true;
760         }
761         // Note: src[WH] are currently the right and bottom coordinates.
762         // The following two lines would adjust src[WH] back to being
763         // dimensions.
764         //     srcW -= srcX;
765         //     srcH -= srcY;
766         // Since transformImage needs right and bottom coords we will
767         // omit this adjustment.
768 
769         transformImage(sg, img, atfm, sg.interpolationType,
770                        srcX, srcY, srcW, srcH, bgColor);
771         return true;
772     }
773 
774     /**
775      ** Utilities
776      ** The following methods are used by the public methods above
777      ** for performing various operations
778      **/
779 
780     /*
781      * This constant represents a tradeoff between the
782      * need to make sure that image transformations are
783      * "very close" to integer device coordinates before
784      * we decide to use an integer scale or copy operation
785      * as a substitute and the fact that roundoff errors
786      * in AffineTransforms are frequently introduced by
787      * performing multiple sequential operations on them.
788      *
789      * The evaluation of bug 4990624 details the potential
790      * for this error cutoff to result in display anomalies
791      * in different types of image operations and how this
792      * value represents a good compromise here.
793      */
794     private static final double MAX_TX_ERROR = .0001;
795 
796     public static boolean closeToInteger(int i, double d) {
797         return (Math.abs(d-i) < MAX_TX_ERROR);
798     }
799 
800     public static boolean isSimpleTranslate(SunGraphics2D sg) {
801         int ts = sg.transformState;
802         if (ts <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) {
803             // Integer translates are always "simple"
804             return true;
805         }
806         if (ts >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
807             // Scales and beyond are always "not simple"
808             return false;
809         }
810         // non-integer translates are only simple when not interpolating
811         if (sg.interpolationType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
812             return true;
813         }
814         return false;
815     }
816 
817     protected static boolean isBgOperation(SurfaceData srcData, Color bgColor) {
818         // If we cannot get the srcData, then cannot assume anything about
819         // the image
820         return ((srcData == null) ||
821                 ((bgColor != null) &&
822                  (srcData.getTransparency() != Transparency.OPAQUE)));
823     }
824 
825     protected BufferedImage getBufferedImage(Image img) {
826         if (img instanceof BufferedImage) {
827             return (BufferedImage)img;
828         }
829         // Must be VolatileImage; get BufferedImage representation
830         return ((VolatileImage)img).getSnapshot();
831     }
832 
833     /*
834      * Return the color model to be used with this BufferedImage and
835      * transform.
836      */
837     private ColorModel getTransformColorModel(SunGraphics2D sg,
838                                               BufferedImage bImg,
839                                               AffineTransform tx) {
840         ColorModel cm = bImg.getColorModel();
841         ColorModel dstCM = cm;
842 
843         if (tx.isIdentity()) {
844             return dstCM;
845         }
846         int type = tx.getType();
847         boolean needTrans =
848                 ((type & (AffineTransform.TYPE_MASK_ROTATION |
849                           AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
850         if (! needTrans &&
851               type != AffineTransform.TYPE_TRANSLATION &&
852               type != AffineTransform.TYPE_IDENTITY)
853         {
854             double[] mtx = new double[4];
855             tx.getMatrix(mtx);
856             // Check out the matrix.  A non-integral scale will force ARGB
857             // since the edge conditions cannot be guaranteed.
858             needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]);
859         }
860 
861         if (sg.renderHint != SunHints.INTVAL_RENDER_QUALITY) {
862             if (cm instanceof IndexColorModel) {
863                 Raster raster = bImg.getRaster();
864                 IndexColorModel icm = (IndexColorModel) cm;
865                 // Just need to make sure that we have a transparent pixel
866                 if (needTrans && cm.getTransparency() == Transparency.OPAQUE) {
867                     // Fix 4221407
868                     if (raster instanceof sun.awt.image.BytePackedRaster) {
869                         dstCM = ColorModel.getRGBdefault();
870                     }
871                     else {
872                         double[] matrix = new double[6];
873                         tx.getMatrix(matrix);
874                         if (matrix[1] == 0. && matrix[2] ==0.
875                             && matrix[4] == 0. && matrix[5] == 0.) {
876                             // Only scaling so do not need to create
877                         }
878                         else {
879                             int mapSize = icm.getMapSize();
880                             if (mapSize < 256) {
881                                 int[] cmap = new int[mapSize+1];
882                                 icm.getRGBs(cmap);
883                                 cmap[mapSize] = 0x0000;
884                                 dstCM = new
885                                     IndexColorModel(icm.getPixelSize(),
886                                                     mapSize+1,
887                                                     cmap, 0, true, mapSize,
888                                                     DataBuffer.TYPE_BYTE);
889                             }
890                             else {
891                                 dstCM = ColorModel.getRGBdefault();
892                             }
893                         }  /* if (matrix[0] < 1.f ...) */
894                     }   /* raster instanceof sun.awt.image.BytePackedRaster */
895                 } /* if (cm.getTransparency() == cm.OPAQUE) */
896             } /* if (cm instanceof IndexColorModel) */
897             else if (needTrans && cm.getTransparency() == Transparency.OPAQUE) {
898                 // Need a bitmask transparency
899                 // REMIND: for now, use full transparency since no loops
900                 // for bitmask
901                 dstCM = ColorModel.getRGBdefault();
902             }
903         } /* if (sg.renderHint == RENDER_QUALITY) */
904         else {
905 
906             if (cm instanceof IndexColorModel ||
907                 (needTrans && cm.getTransparency() == Transparency.OPAQUE))
908             {
909                 // Need a bitmask transparency
910                 // REMIND: for now, use full transparency since no loops
911                 // for bitmask
912                 dstCM = ColorModel.getRGBdefault();
913             }
914         }
915 
916         return dstCM;
917     }
918 
919     protected void blitSurfaceData(SunGraphics2D sg,
920                                    Region clipRegion,
921                                    SurfaceData srcData,
922                                    SurfaceData dstData,
923                                    SurfaceType srcType,
924                                    SurfaceType dstType,
925                                    int sx, int sy, int dx, int dy,
926                                    int w, int h,
927                                    Color bgColor)
928     {
929         if (w <= 0 || h <= 0) {
930             /*
931              * Fix for bugid 4783274 - BlitBg throws an exception for
932              * a particular set of anomalous parameters.
933              * REMIND: The native loops do proper clipping and would
934              * detect this situation themselves, but the Java loops
935              * all seem to trust their parameters a little too well
936              * to the point where they will try to process a negative
937              * area of pixels and throw exceptions.  The real fix is
938              * to modify the Java loops to do proper clipping so that
939              * they can deal with negative dimensions as well as
940              * improperly large dimensions, but that fix is too risky
941              * to integrate for Mantis at this point.  In the meantime
942              * eliminating the negative or zero dimensions here is
943              * "correct" and saves them from some nasty exceptional
944              * conditions, one of which is the test case of 4783274.
945              */
946             return;
947         }
948         CompositeType comp = sg.imageComp;
949         if (CompositeType.SrcOverNoEa.equals(comp) &&
950             (srcData.getTransparency() == Transparency.OPAQUE ||
951              (bgColor != null &&
952               bgColor.getTransparency() == Transparency.OPAQUE)))
953         {
954             comp = CompositeType.SrcNoEa;
955         }
956         if (!isBgOperation(srcData, bgColor)) {
957             Blit blit = Blit.getFromCache(srcType, comp, dstType);
958             blit.Blit(srcData, dstData, sg.composite, clipRegion,
959                       sx, sy, dx, dy, w, h);
960         } else {
961             BlitBg blit = BlitBg.getFromCache(srcType, comp, dstType);
962             blit.BlitBg(srcData, dstData, sg.composite, clipRegion,
963                         bgColor.getRGB(), sx, sy, dx, dy, w, h);
964         }
965     }
966 
967     protected boolean scaleSurfaceData(SunGraphics2D sg,
968                                        Region clipRegion,
969                                        SurfaceData srcData,
970                                        SurfaceData dstData,
971                                        SurfaceType srcType,
972                                        SurfaceType dstType,
973                                        int sx1, int sy1,
974                                        int sx2, int sy2,
975                                        double dx1, double dy1,
976                                        double dx2, double dy2)
977     {
978         CompositeType comp = sg.imageComp;
979         if (CompositeType.SrcOverNoEa.equals(comp) &&
980             (srcData.getTransparency() == Transparency.OPAQUE))
981         {
982             comp = CompositeType.SrcNoEa;
983         }
984 
985         ScaledBlit blit = ScaledBlit.getFromCache(srcType, comp, dstType);
986         if (blit != null) {
987             blit.Scale(srcData, dstData, sg.composite, clipRegion,
988                        sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2);
989             return true;
990         }
991         return false;
992     }
993 
994     protected static boolean imageReady(ToolkitImage sunimg,
995                                         ImageObserver observer)
996     {
997         if (sunimg.hasError()) {
998             if (observer != null) {
999                 observer.imageUpdate(sunimg,
1000                                      ImageObserver.ERROR|ImageObserver.ABORT,
1001                                      -1, -1, -1, -1);
1002             }
1003             return false;
1004         }
1005         return true;
1006     }
1007 
1008     public boolean copyImage(SunGraphics2D sg, Image img,
1009                              int x, int y,
1010                              Color bgColor,
1011                              ImageObserver observer) {
1012         if (!(img instanceof ToolkitImage)) {
1013             return copyImage(sg, img, x, y, bgColor);
1014         } else {
1015             ToolkitImage sunimg = (ToolkitImage)img;
1016             if (!imageReady(sunimg, observer)) {
1017                 return false;
1018             }
1019             ImageRepresentation ir = sunimg.getImageRep();
1020             return ir.drawToBufImage(sg, sunimg, x, y, bgColor, observer);
1021         }
1022     }
1023 
1024     public boolean copyImage(SunGraphics2D sg, Image img,
1025                              int dx, int dy, int sx, int sy, int w, int h,
1026                              Color bgColor,
1027                              ImageObserver observer) {
1028         if (!(img instanceof ToolkitImage)) {
1029             return copyImage(sg, img, dx, dy, sx, sy, w, h, bgColor);
1030         } else {
1031             ToolkitImage sunimg = (ToolkitImage)img;
1032             if (!imageReady(sunimg, observer)) {
1033                 return false;
1034             }
1035             ImageRepresentation ir = sunimg.getImageRep();
1036             return ir.drawToBufImage(sg, sunimg,
1037                                      dx, dy, (dx + w), (dy + h),
1038                                      sx, sy, (sx + w), (sy + h),
1039                                      bgColor, observer);
1040         }
1041     }
1042 
1043     public boolean scaleImage(SunGraphics2D sg, Image img,
1044                                 int x, int y,
1045                                 int width, int height,
1046                                 Color bgColor,
1047                                 ImageObserver observer) {
1048         if (!(img instanceof ToolkitImage)) {
1049             return scaleImage(sg, img, x, y, width, height, bgColor);
1050         } else {
1051             ToolkitImage sunimg = (ToolkitImage)img;
1052             if (!imageReady(sunimg, observer)) {
1053                 return false;
1054             }
1055             ImageRepresentation ir = sunimg.getImageRep();
1056             return ir.drawToBufImage(sg, sunimg, x, y, width, height, bgColor,
1057                                      observer);
1058         }
1059     }
1060 
1061     public boolean scaleImage(SunGraphics2D sg, Image img,
1062                               int dx1, int dy1, int dx2, int dy2,
1063                               int sx1, int sy1, int sx2, int sy2,
1064                               Color bgColor,
1065                               ImageObserver observer) {
1066         if (!(img instanceof ToolkitImage)) {
1067             return scaleImage(sg, img, dx1, dy1, dx2, dy2,
1068                               sx1, sy1, sx2, sy2, bgColor);
1069         } else {
1070             ToolkitImage sunimg = (ToolkitImage)img;
1071             if (!imageReady(sunimg, observer)) {
1072                 return false;
1073             }
1074             ImageRepresentation ir = sunimg.getImageRep();
1075             return ir.drawToBufImage(sg, sunimg, dx1, dy1, dx2, dy2,
1076                                      sx1, sy1, sx2, sy2, bgColor, observer);
1077         }
1078     }
1079 
1080     public boolean transformImage(SunGraphics2D sg, Image img,
1081                                   AffineTransform atfm,
1082                                   ImageObserver observer) {
1083         if (!(img instanceof ToolkitImage)) {
1084             transformImage(sg, img, 0, 0, atfm, sg.interpolationType);
1085             return true;
1086         } else {
1087             ToolkitImage sunimg = (ToolkitImage)img;
1088             if (!imageReady(sunimg, observer)) {
1089                 return false;
1090             }
1091             ImageRepresentation ir = sunimg.getImageRep();
1092             return ir.drawToBufImage(sg, sunimg, atfm, observer);
1093         }
1094     }
1095 
1096     public void transformImage(SunGraphics2D sg, BufferedImage img,
1097                                BufferedImageOp op, int x, int y)
1098     {
1099         if (op != null) {
1100             if (op instanceof AffineTransformOp) {
1101                 AffineTransformOp atop = (AffineTransformOp) op;
1102                 transformImage(sg, img, x, y,
1103                                atop.getTransform(),
1104                                atop.getInterpolationType());
1105                 return;
1106             } else {
1107                 img = op.filter(img, null);
1108             }
1109         }
1110         copyImage(sg, img, x, y, null);
1111     }
1112 }