1 /*
2 
3    Licensed to the Apache Software Foundation (ASF) under one or more
4    contributor license agreements.  See the NOTICE file distributed with
5    this work for additional information regarding copyright ownership.
6    The ASF licenses this file to You under the Apache License, Version 2.0
7    (the "License"); you may not use this file except in compliance with
8    the License.  You may obtain a copy of the License at
9 
10        http://www.apache.org/licenses/LICENSE-2.0
11 
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 
18  */
19 package ch.randelshofer.quaqua.ext.batik.ext.awt.image;
20 
21 import java.awt.Composite;
22 import java.awt.Graphics2D;
23 import java.awt.GraphicsConfiguration;
24 import java.awt.GraphicsDevice;
25 import java.awt.Point;
26 import java.awt.Rectangle;
27 import java.awt.RenderingHints;
28 import java.awt.Shape;
29 import java.awt.color.ColorSpace;
30 import java.awt.geom.AffineTransform;
31 import java.awt.geom.Rectangle2D;
32 import java.awt.image.BufferedImage;
33 import java.awt.image.ColorModel;
34 import java.awt.image.ComponentSampleModel;
35 import java.awt.image.DataBuffer;
36 import java.awt.image.DataBufferByte;
37 import java.awt.image.DataBufferInt;
38 import java.awt.image.DataBufferShort;
39 import java.awt.image.DataBufferUShort;
40 import java.awt.image.DirectColorModel;
41 import java.awt.image.Raster;
42 import java.awt.image.RenderedImage;
43 import java.awt.image.SampleModel;
44 import java.awt.image.SinglePixelPackedSampleModel;
45 import java.awt.image.WritableRaster;
46 import java.awt.image.renderable.RenderContext;
47 import java.awt.image.renderable.RenderableImage;
48 import java.lang.ref.Reference;
49 import java.lang.ref.WeakReference;
50 
51 import ch.randelshofer.quaqua.ext.batik.ext.awt.RenderingHintsKeyExt;
52 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.renderable.PaintRable;
53 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.AffineRed;
54 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.Any2LsRGBRed;
55 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.Any2sRGBRed;
56 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.BufferedImageCachableRed;
57 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.CachableRed;
58 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.FormatRed;
59 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.RenderedImageCachableRed;
60 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.TranslateRed;
61 
62 
63 /**
64  * Set of utility methods for Graphics.
65  * These generally bypass broken methods in Java2D or provide tweaked
66  * implementations.
67  *
68  * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
69  * @version $Id: GraphicsUtil.java 498740 2007-01-22 18:35:57Z dvholten $
70  */
71 public class GraphicsUtil {
72 
73     public static AffineTransform IDENTITY = new AffineTransform();
74 
75     /**
76      * Draws <tt>ri</tt> into <tt>g2d</tt>.  It does this be
77      * requesting tiles from <tt>ri</tt> and drawing them individually
78      * in <tt>g2d</tt> it also takes care of some colorspace and alpha
79      * issues.
80      * @param g2d The Graphics2D to draw into.
81      * @param ri  The image to be drawn.
82      */
drawImage(Graphics2D g2d, RenderedImage ri)83     public static void drawImage(Graphics2D g2d,
84                                  RenderedImage ri) {
85         drawImage(g2d, wrap(ri));
86     }
87 
88     /**
89      * Draws <tt>cr</tt> into <tt>g2d</tt>.  It does this be
90      * requesting tiles from <tt>ri</tt> and drawing them individually
91      * in <tt>g2d</tt> it also takes care of some colorspace and alpha
92      * issues.
93      * @param g2d The Graphics2D to draw into.
94      * @param cr  The image to be drawn.
95      */
drawImage(Graphics2D g2d, CachableRed cr)96     public static void drawImage(Graphics2D g2d,
97                                  CachableRed cr) {
98 
99         // System.out.println("DrawImage G: " + g2d);
100 
101         AffineTransform at = null;
102         while (true) {
103             if (cr instanceof AffineRed) {
104                 AffineRed ar = (AffineRed)cr;
105                 if (at == null)
106                     at = ar.getTransform();
107                 else
108                     at.concatenate(ar.getTransform());
109                 cr = ar.getSource();
110                 continue;
111             } else if (cr instanceof TranslateRed) {
112                 TranslateRed tr = (TranslateRed)cr;
113                 // System.out.println("testing Translate");
114                 int dx = tr.getDeltaX();
115                 int dy = tr.getDeltaY();
116                 if (at == null)
117                     at = AffineTransform.getTranslateInstance(dx, dy);
118                 else
119                     at.translate(dx, dy);
120                 cr = tr.getSource();
121                 continue;
122             }
123             break;
124         }
125         AffineTransform g2dAt   = g2d.getTransform();
126         if ((at == null) || (at.isIdentity()))
127             at = g2dAt;
128         else
129             at.preConcatenate(g2dAt);
130 
131         ColorModel srcCM = cr.getColorModel();
132         ColorModel g2dCM = getDestinationColorModel(g2d);
133         ColorSpace g2dCS = null;
134         if (g2dCM != null)
135             g2dCS = g2dCM.getColorSpace();
136         if (g2dCS == null)
137             // Assume device is sRGB
138             g2dCS = ColorSpace.getInstance(ColorSpace.CS_sRGB);
139 
140         ColorModel drawCM = g2dCM;
141         if ((g2dCM == null) || !g2dCM.hasAlpha()) {
142             // If we can't find out about our device or the device
143             // does not support alpha just use SRGB unpremultiplied
144             // (Just because this seems to work for us).
145             drawCM = sRGB_Unpre;
146         }
147 
148         if (cr instanceof BufferedImageCachableRed) {
149             // There is a huge win if we can use the BI directly here.
150             // This results in something like a 10x performance gain
151             // for images, the best thing is this is the common case.
152             if (g2dCS.equals(srcCM.getColorSpace()) &&
153                 drawCM.equals(srcCM)) {
154                 // System.err.println("Fast Case");
155                 g2d.setTransform(at);
156                 BufferedImageCachableRed bicr;
157                 bicr = (BufferedImageCachableRed)cr;
158                 g2d.drawImage(bicr.getBufferedImage(),
159                               bicr.getMinX(), bicr.getMinY(), null);
160                 g2d.setTransform(g2dAt);
161                 return;
162             }
163         }
164 
165         // Scaling down so do it before color conversion.
166         double determinant = at.getDeterminant();
167         if (!at.isIdentity() && (determinant <= 1.0)) {
168             if (at.getType() != AffineTransform.TYPE_TRANSLATION)
169                 cr = new AffineRed(cr, at, g2d.getRenderingHints());
170             else {
171                 int xloc = cr.getMinX() + (int)at.getTranslateX();
172                 int yloc = cr.getMinY() + (int)at.getTranslateY();
173                 cr = new TranslateRed(cr, xloc, yloc);
174             }
175         }
176 
177         if (g2dCS != srcCM.getColorSpace()) {
178             // System.out.println("srcCS: " + srcCM.getColorSpace());
179             // System.out.println("g2dCS: " + g2dCS);
180             // System.out.println("sRGB: " +
181             //                    ColorSpace.getInstance(ColorSpace.CS_sRGB));
182             // System.out.println("LsRGB: " +
183             //                    ColorSpace.getInstance
184             //                    (ColorSpace.CS_LINEAR_RGB));
185             if      (g2dCS == ColorSpace.getInstance(ColorSpace.CS_sRGB))
186                 cr = convertTosRGB(cr);
187             else if (g2dCS == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB))
188                 cr = convertToLsRGB(cr);
189         }
190         srcCM = cr.getColorModel();
191         if (!drawCM.equals(srcCM))
192             cr = FormatRed.construct(cr, drawCM);
193 
194         // Scaling up so do it after color conversion.
195         if (!at.isIdentity() && (determinant > 1.0))
196             cr = new AffineRed(cr, at, g2d.getRenderingHints());
197 
198         // Now CR is in device space, so clear the g2d transform.
199         g2d.setTransform(IDENTITY);
200 
201         // Ugly Hack alert.  This Makes it use our SrcOver implementation
202         // Which doesn't seem to have as many bugs as the JDK one when
203         // going between different src's and destinations (of course it's
204         // also a lot slower).
205         Composite g2dComposite = g2d.getComposite();
206         if (g2d.getRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING) ==
207             RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING) {
208             if (SVGComposite.OVER.equals(g2dComposite)) {
209                 g2d.setComposite(SVGComposite.OVER);
210             }
211         }
212         Rectangle crR  = cr.getBounds();
213         Shape     clip = g2d.getClip();
214 
215         try {
216             Rectangle clipR;
217             if (clip == null) {
218                 clip  = crR;
219                 clipR = crR;
220             } else {
221                 clipR   = clip.getBounds();
222 
223                 if ( ! clipR.intersects(crR) )
224                     return; // Nothing to draw...
225                 clipR = clipR.intersection(crR);
226             }
227 
228             Rectangle gcR = getDestinationBounds(g2d);
229             // System.out.println("ClipRects: " + clipR + " -> " + gcR);
230             if (gcR != null) {
231                 if ( ! clipR.intersects(gcR) )
232                     return; // Nothing to draw...
233                 clipR = clipR.intersection(gcR);
234             }
235 
236             // System.out.println("Starting Draw: " + cr);
237             // long startTime = System.currentTimeMillis();
238 
239             boolean useDrawRenderedImage = false;
240 
241             srcCM = cr.getColorModel();
242             SampleModel srcSM = cr.getSampleModel();
243             if ((srcSM.getWidth()*srcSM.getHeight()) >=
244                 (clipR.width*clipR.height))
245                 // if srcSM tiles are around the clip size
246                 // then just draw the renderedImage
247                 useDrawRenderedImage = true;
248 
249             Object atpHint = g2d.getRenderingHint
250                 (RenderingHintsKeyExt.KEY_AVOID_TILE_PAINTING);
251 
252             if (atpHint == RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_ON)
253                 useDrawRenderedImage = true; //for PDF and PS transcoders
254 
255             if (atpHint == RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_OFF)
256                 useDrawRenderedImage = false;
257 
258 
259             WritableRaster wr;
260             if (useDrawRenderedImage) {
261                 // This can be significantly faster but can also
262                 // require much more memory, so we only use it when
263                 // the clip size is smaller than the tile size.
264                 Raster r = cr.getData(clipR);
265                 wr = ((WritableRaster)r).createWritableChild
266                     (clipR.x, clipR.y, clipR.width, clipR.height,
267                      0, 0, null);
268 
269                 BufferedImage bi = new BufferedImage
270                     (srcCM, wr, srcCM.isAlphaPremultiplied(), null);
271 
272                 // Any of the drawImage calls that take an
273                 // Affine are prone to the 'CGGStackRestore: gstack
274                 // underflow' bug on Mac OS X.  This should work
275                 // around that problem.
276                 g2d.drawImage(bi, clipR.x, clipR.y, null);
277             } else {
278                 // Use tiles to draw image...
279                 wr = Raster.createWritableRaster(srcSM, new Point(0,0));
280                 BufferedImage bi = new BufferedImage
281                     (srcCM, wr, srcCM.isAlphaPremultiplied(), null);
282 
283                 int xt0 = cr.getMinTileX();
284                 int xt1 = xt0+cr.getNumXTiles();
285                 int yt0 = cr.getMinTileY();
286                 int yt1 = yt0+cr.getNumYTiles();
287                 int tw  = srcSM.getWidth();
288                 int th  = srcSM.getHeight();
289 
290                 Rectangle tR  = new Rectangle(0,0,tw,th);
291                 Rectangle iR  = new Rectangle(0,0,0,0);
292 
293                 if (false) {
294                     System.err.println("SrcCM: " + srcCM);
295                     System.err.println("CR: " + cr);
296                     System.err.println("CRR: " + crR + " TG: [" +
297                                        xt0 + ',' +
298                                        yt0 + ',' +
299                                        xt1 + ',' +
300                                        yt1 +"] Off: " +
301                                        cr.getTileGridXOffset() + ',' +
302                                        cr.getTileGridYOffset());
303                 }
304 
305                 int yloc = yt0*th+cr.getTileGridYOffset();
306                 int skip = (clipR.y-yloc)/th;
307                 if (skip <0) skip = 0;
308                 yt0+=skip;
309 
310                 int xloc = xt0*tw+cr.getTileGridXOffset();
311                 skip = (clipR.x-xloc)/tw;
312                 if (skip <0) skip = 0;
313                 xt0+=skip;
314 
315                 int endX = clipR.x+clipR.width-1;
316                 int endY = clipR.y+clipR.height-1;
317 
318                 if (false) {
319                     System.out.println("clipR: " + clipR + " TG: [" +
320                                        xt0 + ',' +
321                                        yt0 + ',' +
322                                        xt1 + ',' +
323                                        yt1 +"] Off: " +
324                                        cr.getTileGridXOffset() + ',' +
325                                        cr.getTileGridYOffset());
326                 }
327 
328 
329                 yloc = yt0*th+cr.getTileGridYOffset();
330                 int minX = xt0*tw+cr.getTileGridXOffset();
331                 int xStep = tw;
332                 xloc = minX;
333                 for (int y=yt0; y<yt1; y++, yloc += th) {
334                     if (yloc > endY) break;
335                     for (int x=xt0; x<xt1; x++, xloc+=xStep) {
336                         if ((xloc<minX) || (xloc > endX)) break;
337                         tR.x = xloc;
338                         tR.y = yloc;
339                         Rectangle2D.intersect(crR, tR, iR);
340 
341                         WritableRaster twr;
342                         twr = wr.createWritableChild(0, 0,
343                                                      iR.width, iR.height,
344                                                      iR.x, iR.y, null);
345 
346                         // System.out.println("Generating tile: " + twr);
347                         cr.copyData(twr);
348 
349                         // Make sure we only draw the region that was written.
350                         BufferedImage subBI;
351                         subBI = bi.getSubimage(0, 0, iR.width,  iR.height);
352 
353                         if (false) {
354                             System.out.println("Drawing: " + tR);
355                             System.out.println("IR: "      + iR);
356                         }
357 
358                         // For some reason using the transform version
359                         // causes a gStackUnderflow error but if I just
360                         // use the drawImage with an x & y it works.
361                         g2d.drawImage(subBI, iR.x, iR.y, null);
362                         // AffineTransform trans
363                         //  = AffineTransform.getTranslateInstance(iR.x, iR.y);
364                         // g2d.drawImage(subBI, trans, null);
365 
366                         // String label = "sub [" + x + ", " + y + "]: ";
367                         // org.ImageDisplay.showImage
368                         //     (label, subBI);
369                     }
370                     xStep = -xStep; // Reverse directions.
371                     xloc += xStep;   // Get back in bounds.
372                 }
373             }
374             // long endTime = System.currentTimeMillis();
375             // System.out.println("Time: " + (endTime-startTime));
376 
377 
378         } finally {
379             g2d.setTransform(g2dAt);
380             g2d.setComposite(g2dComposite);
381         }
382 
383         // System.out.println("Finished Draw");
384     }
385 
386 
387     /**
388      * Draws a <tt>Filter</tt> (<tt>RenderableImage</tt>) into a
389      * Graphics 2D after taking into account a particular
390      * <tt>RenderContext</tt>.<p>
391      *
392      * This method also attempts to unwind the rendering chain a bit.
393      * So it knows about certain operations (like affine, pad,
394      * composite), rather than applying each of these operations in
395      * turn it accounts for their affects through modifications to the
396      * Graphics2D. This avoids generating lots of intermediate images.
397      *
398      * @param g2d    The Graphics to draw into.
399      * @param filter The filter to draw
400      * @param rc The render context that controls the drawing operation.
401      */
drawImage(Graphics2D g2d, RenderableImage filter, RenderContext rc)402     public static void drawImage(Graphics2D      g2d,
403                                  RenderableImage filter,
404                                  RenderContext   rc) {
405 
406         AffineTransform origDev  = g2d.getTransform();
407         Shape           origClip = g2d.getClip();
408         RenderingHints  origRH   = g2d.getRenderingHints();
409 
410         Shape clip = rc.getAreaOfInterest();
411         if (clip != null)
412             g2d.clip(clip);
413         g2d.transform(rc.getTransform());
414         g2d.setRenderingHints(rc.getRenderingHints());
415 
416         drawImage(g2d, filter);
417 
418         g2d.setTransform(origDev);
419         g2d.setClip(origClip);
420         g2d.setRenderingHints(origRH);
421     }
422 
423     /**
424      * Draws a <tt>Filter</tt> (<tt>RenderableImage</tt>) into a
425      * Graphics 2D.<p>
426      *
427      * This method also attempts to unwind the rendering chain a bit.
428      * So it knows about certain operations (like affine, pad,
429      * composite), rather than applying each of these operations in
430      * turn it accounts for their affects through modifications to the
431      * Graphics2D.  This avoids generating lots of intermediate images.
432      *
433      * @param g2d    The Graphics to draw into.
434      * @param filter The filter to draw
435      */
drawImage(Graphics2D g2d, RenderableImage filter)436     public static void drawImage(Graphics2D g2d,
437                                  RenderableImage filter) {
438         if (filter instanceof PaintRable) {
439             PaintRable pr = (PaintRable)filter;
440             if (pr.paintRable(g2d))
441                 // paintRable succeeded so we are done...
442                 return;
443         }
444 
445         // Get our sources image...
446         // System.out.println("UnOpt: " + filter);
447         AffineTransform at = g2d.getTransform();
448         RenderedImage ri = filter.createRendering
449             (new RenderContext(at, g2d.getClip(), g2d.getRenderingHints()));
450 
451         if (ri == null)
452             return;
453 
454         g2d.setTransform(IDENTITY);
455         drawImage(g2d, GraphicsUtil.wrap(ri));
456         g2d.setTransform(at);
457     }
458 
459     /**
460      * This is a wrapper around the system's
461      * BufferedImage.createGraphics that arranges for bi to be stored
462      * in a Rendering hint in the returned Graphics2D.
463      * This allows for accurate determination of the 'devices' size,
464      * and colorspace.
465      * @param bi The BufferedImage that the returned Graphics should
466      *           draw into.
467      * @return A Graphics2D that draws into BufferedImage with <tt>bi</tt>
468      *         stored in a rendering hint.
469      */
createGraphics(BufferedImage bi, RenderingHints hints)470     public static Graphics2D createGraphics(BufferedImage bi,
471                                             RenderingHints hints) {
472         Graphics2D g2d = bi.createGraphics();
473         if (hints != null)
474             g2d.addRenderingHints(hints);
475         g2d.setRenderingHint(RenderingHintsKeyExt.KEY_BUFFERED_IMAGE,
476                              new WeakReference(bi));
477         g2d.clip(new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
478         return g2d;
479     }
480 
481 
createGraphics(BufferedImage bi)482     public static Graphics2D createGraphics(BufferedImage bi) {
483         Graphics2D g2d = bi.createGraphics();
484         g2d.setRenderingHint(RenderingHintsKeyExt.KEY_BUFFERED_IMAGE,
485                              new WeakReference(bi));
486         g2d.clip(new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
487         return g2d;
488     }
489 
490 
491     public static final boolean WARN_DESTINATION;
492 
493     static {
494         boolean warn = true;
495         try {
496             String s = System.getProperty
497                 ("ch.randelshofer.quaqua.ext.batik.warn_destination", "true");
498             warn = Boolean.valueOf(s).booleanValue();
499         } catch (SecurityException se) {
500         } catch (NumberFormatException nfe) {
501         } finally {
502             WARN_DESTINATION = warn;
503         }
504     }
505 
getDestination(Graphics2D g2d)506     public static BufferedImage getDestination(Graphics2D g2d) {
507         Object o = g2d.getRenderingHint
508             (RenderingHintsKeyExt.KEY_BUFFERED_IMAGE);
509         if (o != null)
510             return (BufferedImage)(((Reference)o).get());
511 
512         // Check if this is a BufferedImage G2d if so throw an error...
513         GraphicsConfiguration gc = g2d.getDeviceConfiguration();
514         GraphicsDevice gd = gc.getDevice();
515         if (WARN_DESTINATION &&
516             (gd.getType() == GraphicsDevice.TYPE_IMAGE_BUFFER) &&
517             (g2d.getRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING) !=
518                 RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING))
519             // throw new IllegalArgumentException
520             System.err.println
521                 ("Graphics2D from BufferedImage lacks BUFFERED_IMAGE hint");
522 
523         return null;
524     }
525 
getDestinationColorModel(Graphics2D g2d)526     public static ColorModel getDestinationColorModel(Graphics2D g2d) {
527         BufferedImage bi = getDestination(g2d);
528         if (bi != null)
529             return bi.getColorModel();
530 
531         GraphicsConfiguration gc = g2d.getDeviceConfiguration();
532         if (gc == null)
533             return null; // Can't tell
534 
535         // We are going to a BufferedImage but no hint was provided
536         // so we can't determine the destination Color Model.
537         if (gc.getDevice().getType() == GraphicsDevice.TYPE_IMAGE_BUFFER) {
538             if (g2d.getRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING) ==
539                 RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING)
540                 return sRGB_Unpre;
541 
542             // System.out.println("CM: " + gc.getColorModel());
543             // System.out.println("CS: " + gc.getColorModel().getColorSpace());
544             return null;
545         }
546 
547         return gc.getColorModel();
548     }
549 
getDestinationColorSpace(Graphics2D g2d)550     public static ColorSpace getDestinationColorSpace(Graphics2D g2d) {
551         ColorModel cm = getDestinationColorModel(g2d);
552         if (cm != null) return cm.getColorSpace();
553 
554         return null;
555     }
556 
getDestinationBounds(Graphics2D g2d)557     public static Rectangle getDestinationBounds(Graphics2D g2d) {
558         BufferedImage bi = getDestination(g2d);
559         if (bi != null)
560             return new Rectangle(0, 0, bi.getWidth(), bi.getHeight());
561 
562         GraphicsConfiguration gc = g2d.getDeviceConfiguration();
563 
564         // We are going to a BufferedImage but no hint was provided
565         // so we can't determine the destination bounds.
566         if (gc.getDevice().getType() == GraphicsDevice.TYPE_IMAGE_BUFFER)
567             return null;
568 
569         // This is a JDK 1.3ism, so we will just return null...
570         // return gc.getBounds();
571         return null;
572     }
573 
574 
575     /**
576      * Standard prebuilt Linear_sRGB color model with no alpha */
577     public static final ColorModel Linear_sRGB =
578         new DirectColorModel(ColorSpace.getInstance
579                              (ColorSpace.CS_LINEAR_RGB), 24,
580                              0x00FF0000, 0x0000FF00,
581                              0x000000FF, 0x0, false,
582                              DataBuffer.TYPE_INT);
583     /**
584      * Standard prebuilt Linear_sRGB color model with premultiplied alpha.
585      */
586     public static final ColorModel Linear_sRGB_Pre =
587         new DirectColorModel(ColorSpace.getInstance
588                              (ColorSpace.CS_LINEAR_RGB), 32,
589                              0x00FF0000, 0x0000FF00,
590                              0x000000FF, 0xFF000000, true,
591                              DataBuffer.TYPE_INT);
592     /**
593      * Standard prebuilt Linear_sRGB color model with unpremultiplied alpha.
594      */
595     public static final ColorModel Linear_sRGB_Unpre =
596         new DirectColorModel(ColorSpace.getInstance
597                              (ColorSpace.CS_LINEAR_RGB), 32,
598                              0x00FF0000, 0x0000FF00,
599                              0x000000FF, 0xFF000000, false,
600                              DataBuffer.TYPE_INT);
601 
602     /**
603      * Standard prebuilt sRGB color model with no alpha.
604      */
605     public static final ColorModel sRGB =
606         new DirectColorModel(ColorSpace.getInstance
607                              (ColorSpace.CS_sRGB), 24,
608                              0x00FF0000, 0x0000FF00,
609                              0x000000FF, 0x0, false,
610                              DataBuffer.TYPE_INT);
611     /**
612      * Standard prebuilt sRGB color model with premultiplied alpha.
613      */
614     public static final ColorModel sRGB_Pre =
615         new DirectColorModel(ColorSpace.getInstance
616                              (ColorSpace.CS_sRGB), 32,
617                              0x00FF0000, 0x0000FF00,
618                              0x000000FF, 0xFF000000, true,
619                              DataBuffer.TYPE_INT);
620     /**
621      * Standard prebuilt sRGB color model with unpremultiplied alpha.
622      */
623     public static final ColorModel sRGB_Unpre =
624         new DirectColorModel(ColorSpace.getInstance
625                              (ColorSpace.CS_sRGB), 32,
626                              0x00FF0000, 0x0000FF00,
627                              0x000000FF, 0xFF000000, false,
628                              DataBuffer.TYPE_INT);
629 
630     /**
631      * Method that returns either Linear_sRGB_Pre or Linear_sRGB_UnPre
632      * based on premult flag.
633      * @param premult True if the ColorModel should have premultiplied alpha.
634      * @return        a ColorMdoel with Linear sRGB colorSpace and
635      *                the alpha channel set in accordance with
636      *                <tt>premult</tt>
637      */
makeLinear_sRGBCM( boolean premult )638     public static ColorModel makeLinear_sRGBCM( boolean premult ) {
639 
640          return premult ? Linear_sRGB_Pre : Linear_sRGB_Unpre;
641     }
642 
643     /**
644      * Constructs a BufferedImage with a linear sRGB colorModel, and alpha.
645      * @param width   The desired width of the BufferedImage
646      * @param height  The desired height of the BufferedImage
647      * @param premult The desired state of alpha premultiplied
648      * @return        The requested BufferedImage.
649      */
makeLinearBufferedImage(int width, int height, boolean premult)650     public static BufferedImage makeLinearBufferedImage(int width,
651                                                         int height,
652                                                         boolean premult) {
653         ColorModel cm = makeLinear_sRGBCM(premult);
654         WritableRaster wr = cm.createCompatibleWritableRaster(width, height);
655         return new BufferedImage(cm, wr, premult, null);
656     }
657 
658     /**
659      * This method will return a CacheableRed that has it's data in
660      * the linear sRGB colorspace. If <tt>src</tt> is already in
661      * linear sRGB then this method does nothing and returns <tt>src</tt>.
662      * Otherwise it creates a transform that will convert
663      * <tt>src</tt>'s output to linear sRGB and returns that CacheableRed.
664      *
665      * @param src The image to convert to linear sRGB.
666      * @return    An equivilant image to <tt>src</tt> who's data is in
667      *            linear sRGB.
668      */
convertToLsRGB(CachableRed src)669     public static CachableRed convertToLsRGB(CachableRed src) {
670         ColorModel cm = src.getColorModel();
671         ColorSpace cs = cm.getColorSpace();
672         if (cs == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB))
673             return src;
674 
675         return new Any2LsRGBRed(src);
676     }
677 
678     /**
679      * This method will return a CacheableRed that has it's data in
680      * the sRGB colorspace. If <tt>src</tt> is already in
681      * sRGB then this method does nothing and returns <tt>src</tt>.
682      * Otherwise it creates a transform that will convert
683      * <tt>src</tt>'s output to sRGB and returns that CacheableRed.
684      *
685      * @param src The image to convert to sRGB.
686      * @return    An equivilant image to <tt>src</tt> who's data is in sRGB.
687      */
convertTosRGB(CachableRed src)688     public static CachableRed convertTosRGB(CachableRed src) {
689         ColorModel cm = src.getColorModel();
690         ColorSpace cs = cm.getColorSpace();
691         if (cs == ColorSpace.getInstance(ColorSpace.CS_sRGB))
692             return src;
693 
694         return new Any2sRGBRed(src);
695     }
696 
697     /**
698      * Convertes any RenderedImage to a CacheableRed.  <p>
699      * If <tt>ri</tt> is already a CacheableRed it casts it down and
700      * returns it.<p>
701      *
702      * In cases where <tt>ri</tt> is not already a CacheableRed it
703      * wraps <tt>ri</tt> with a helper class.  The wrapped
704      * CacheableRed "Pretends" that it has no sources since it has no
705      * way of inteligently handling the dependency/dirty region calls
706      * if it exposed the source.
707      * @param ri The RenderedImage to convert.
708      * @return   a CacheableRed that contains the same data as ri.
709      */
wrap(RenderedImage ri)710     public static CachableRed wrap(RenderedImage ri) {
711         if (ri instanceof CachableRed)
712             return (CachableRed) ri;
713         if (ri instanceof BufferedImage)
714             return new BufferedImageCachableRed((BufferedImage)ri);
715         return new RenderedImageCachableRed(ri);
716     }
717 
718     /**
719      * An internal optimized version of copyData designed to work on
720      * Integer packed data with a SinglePixelPackedSampleModel.  Only
721      * the region of overlap between src and dst is copied.
722      *
723      * Calls to this should be preflighted with is_INT_PACK_Data
724      * on both src and dest (requireAlpha can be false).
725      *
726      * @param src The source of the data
727      * @param dst The destination for the data.
728      */
copyData_INT_PACK(Raster src, WritableRaster dst)729     public static void copyData_INT_PACK(Raster src, WritableRaster dst) {
730         // System.out.println("Fast copyData");
731         int x0 = dst.getMinX();
732         if (x0 < src.getMinX()) x0 = src.getMinX();
733 
734         int y0 = dst.getMinY();
735         if (y0 < src.getMinY()) y0 = src.getMinY();
736 
737         int x1 = dst.getMinX()+dst.getWidth()-1;
738         if (x1 > src.getMinX()+src.getWidth()-1)
739             x1 = src.getMinX()+src.getWidth()-1;
740 
741         int y1 = dst.getMinY()+dst.getHeight()-1;
742         if (y1 > src.getMinY()+src.getHeight()-1)
743             y1 = src.getMinY()+src.getHeight()-1;
744 
745         int width  = x1-x0+1;
746         int height = y1-y0+1;
747 
748         SinglePixelPackedSampleModel srcSPPSM;
749         srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel();
750 
751         final int     srcScanStride = srcSPPSM.getScanlineStride();
752         DataBufferInt srcDB         = (DataBufferInt)src.getDataBuffer();
753         final int []  srcPixels     = srcDB.getBankData()[0];
754         final int     srcBase =
755             (srcDB.getOffset() +
756              srcSPPSM.getOffset(x0-src.getSampleModelTranslateX(),
757                                 y0-src.getSampleModelTranslateY()));
758 
759 
760         SinglePixelPackedSampleModel dstSPPSM;
761         dstSPPSM = (SinglePixelPackedSampleModel)dst.getSampleModel();
762 
763         final int     dstScanStride = dstSPPSM.getScanlineStride();
764         DataBufferInt dstDB         = (DataBufferInt)dst.getDataBuffer();
765         final int []  dstPixels     = dstDB.getBankData()[0];
766         final int     dstBase =
767             (dstDB.getOffset() +
768              dstSPPSM.getOffset(x0-dst.getSampleModelTranslateX(),
769                                 y0-dst.getSampleModelTranslateY()));
770 
771         if ((srcScanStride == dstScanStride) &&
772             (srcScanStride == width)) {
773             // System.out.println("VERY Fast copyData");
774 
775             System.arraycopy(srcPixels, srcBase, dstPixels, dstBase,
776                              width*height);
777         } else if (width > 128) {
778             int srcSP = srcBase;
779             int dstSP = dstBase;
780             for (int y=0; y<height; y++) {
781                 System.arraycopy(srcPixels, srcSP, dstPixels, dstSP, width);
782                 srcSP += srcScanStride;
783                 dstSP += dstScanStride;
784             }
785         } else {
786             for (int y=0; y<height; y++) {
787                 int srcSP = srcBase+y*srcScanStride;
788                 int dstSP = dstBase+y*dstScanStride;
789                 for (int x=0; x<width; x++)
790                     dstPixels[dstSP++] = srcPixels[srcSP++];
791             }
792         }
793     }
794 
copyData_FALLBACK(Raster src, WritableRaster dst)795     public static void copyData_FALLBACK(Raster src, WritableRaster dst) {
796         // System.out.println("Fallback copyData");
797 
798         int x0 = dst.getMinX();
799         if (x0 < src.getMinX()) x0 = src.getMinX();
800 
801         int y0 = dst.getMinY();
802         if (y0 < src.getMinY()) y0 = src.getMinY();
803 
804         int x1 = dst.getMinX()+dst.getWidth()-1;
805         if (x1 > src.getMinX()+src.getWidth()-1)
806             x1 = src.getMinX()+src.getWidth()-1;
807 
808         int y1 = dst.getMinY()+dst.getHeight()-1;
809         if (y1 > src.getMinY()+src.getHeight()-1)
810             y1 = src.getMinY()+src.getHeight()-1;
811 
812         int width  = x1-x0+1;
813         int [] data = null;
814 
815         for (int y = y0; y <= y1 ; y++)  {
816             data = src.getPixels(x0,y,width,1,data);
817             dst.setPixels       (x0,y,width,1,data);
818         }
819     }
820 
821     /**
822      * Copies data from one raster to another. Only the region of
823      * overlap between src and dst is copied.  <tt>Src</tt> and
824      * <tt>Dst</tt> must have compatible SampleModels.
825      *
826      * @param src The source of the data
827      * @param dst The destination for the data.
828      */
copyData(Raster src, WritableRaster dst)829     public static void copyData(Raster src, WritableRaster dst) {
830         if (is_INT_PACK_Data(src.getSampleModel(), false) &&
831             is_INT_PACK_Data(dst.getSampleModel(), false)) {
832             copyData_INT_PACK(src, dst);
833             return;
834         }
835 
836         copyData_FALLBACK(src, dst);
837     }
838 
839     /**
840      * Creates a new raster that has a <b>copy</b> of the data in
841      * <tt>ras</tt>.  This is highly optimized for speed.  There is
842      * no provision for changing any aspect of the SampleModel.
843      *
844      * This method should be used when you need to change the contents
845      * of a Raster that you do not "own" (ie the result of a
846      * <tt>getData</tt> call).
847      * @param ras The Raster to copy.
848      * @return    A writable copy of <tt>ras</tt>
849      */
copyRaster(Raster ras)850     public static WritableRaster copyRaster(Raster ras) {
851         return copyRaster(ras, ras.getMinX(), ras.getMinY());
852     }
853 
854 
855     /**
856      * Creates a new raster that has a <b>copy</b> of the data in
857      * <tt>ras</tt>.  This is highly optimized for speed.  There is
858      * no provision for changing any aspect of the SampleModel.
859      * However you can specify a new location for the returned raster.
860      *
861      * This method should be used when you need to change the contents
862      * of a Raster that you do not "own" (ie the result of a
863      * <tt>getData</tt> call).
864      *
865      * @param ras The Raster to copy.
866      *
867      * @param minX The x location for the upper left corner of the
868      *             returned WritableRaster.
869      *
870      * @param minY The y location for the upper left corner of the
871      *             returned WritableRaster.
872      *
873      * @return    A writable copy of <tt>ras</tt>
874      */
copyRaster(Raster ras, int minX, int minY)875     public static WritableRaster copyRaster(Raster ras, int minX, int minY) {
876         WritableRaster ret = Raster.createWritableRaster
877             (ras.getSampleModel(),
878              new Point(0,0));
879         ret = ret.createWritableChild
880             (ras.getMinX()-ras.getSampleModelTranslateX(),
881              ras.getMinY()-ras.getSampleModelTranslateY(),
882              ras.getWidth(), ras.getHeight(),
883              minX, minY, null);
884 
885         // Use System.arraycopy to copy the data between the two...
886         DataBuffer srcDB = ras.getDataBuffer();
887         DataBuffer retDB = ret.getDataBuffer();
888         if (srcDB.getDataType() != retDB.getDataType()) {
889             throw new IllegalArgumentException
890                 ("New DataBuffer doesn't match original");
891         }
892         int len   = srcDB.getSize();
893         int banks = srcDB.getNumBanks();
894         int [] offsets = srcDB.getOffsets();
895         for (int b=0; b< banks; b++) {
896             switch (srcDB.getDataType()) {
897             case DataBuffer.TYPE_BYTE: {
898                 DataBufferByte srcDBT = (DataBufferByte)srcDB;
899                 DataBufferByte retDBT = (DataBufferByte)retDB;
900                 System.arraycopy(srcDBT.getData(b), offsets[b],
901                                  retDBT.getData(b), offsets[b], len);
902                 break;
903             }
904             case DataBuffer.TYPE_INT: {
905                 DataBufferInt srcDBT = (DataBufferInt)srcDB;
906                 DataBufferInt retDBT = (DataBufferInt)retDB;
907                 System.arraycopy(srcDBT.getData(b), offsets[b],
908                                  retDBT.getData(b), offsets[b], len);
909                 break;
910             }
911             case DataBuffer.TYPE_SHORT: {
912                 DataBufferShort srcDBT = (DataBufferShort)srcDB;
913                 DataBufferShort retDBT = (DataBufferShort)retDB;
914                 System.arraycopy(srcDBT.getData(b), offsets[b],
915                                  retDBT.getData(b), offsets[b], len);
916                 break;
917             }
918             case DataBuffer.TYPE_USHORT: {
919                 DataBufferUShort srcDBT = (DataBufferUShort)srcDB;
920                 DataBufferUShort retDBT = (DataBufferUShort)retDB;
921                 System.arraycopy(srcDBT.getData(b), offsets[b],
922                                  retDBT.getData(b), offsets[b], len);
923                 break;
924             }
925             }
926         }
927 
928         return ret;
929     }
930 
931     /**
932      * Coerces <tt>ras</tt> to be writable.  The returned Raster continues to
933      * reference the DataBuffer from ras, so modifications to the returned
934      * WritableRaster will be seen in ras.<p>
935      *
936      * This method should only be used if you need a WritableRaster due to
937      * an interface (such as to construct a BufferedImage), but have no
938      * intention of modifying the contents of the returned Raster.  If
939      * you have any doubt about other users of the data in <tt>ras</tt>,
940      * use copyRaster (above).
941      * @param ras The raster to make writable.
942      * @return    A Writable version of ras (shares DataBuffer with
943      *            <tt>ras</tt>).
944      */
makeRasterWritable(Raster ras)945     public static WritableRaster makeRasterWritable(Raster ras) {
946         return makeRasterWritable(ras, ras.getMinX(), ras.getMinY());
947     }
948 
949     /**
950      * Coerces <tt>ras</tt> to be writable.  The returned Raster continues to
951      * reference the DataBuffer from ras, so modifications to the returned
952      * WritableRaster will be seen in ras.<p>
953      *
954      * You can specify a new location for the returned WritableRaster, this
955      * is especially useful for constructing BufferedImages which require
956      * the Raster to be at (0,0).
957      *
958      * This method should only be used if you need a WritableRaster due to
959      * an interface (such as to construct a BufferedImage), but have no
960      * intention of modifying the contents of the returned Raster.  If
961      * you have any doubt about other users of the data in <tt>ras</tt>,
962      * use copyRaster (above).
963      *
964      * @param ras The raster to make writable.
965      *
966      * @param minX The x location for the upper left corner of the
967      *             returned WritableRaster.
968      *
969      * @param minY The y location for the upper left corner of the
970      *             returned WritableRaster.
971      *
972      * @return A Writable version of <tT>ras</tt> with it's upper left
973      *         hand coordinate set to minX, minY (shares it's DataBuffer
974      *         with <tt>ras</tt>).
975      */
makeRasterWritable(Raster ras, int minX, int minY)976     public static WritableRaster makeRasterWritable(Raster ras,
977                                                     int minX, int minY) {
978         WritableRaster ret = Raster.createWritableRaster
979             (ras.getSampleModel(),
980              ras.getDataBuffer(),
981              new Point(0,0));
982         ret = ret.createWritableChild
983             (ras.getMinX()-ras.getSampleModelTranslateX(),
984              ras.getMinY()-ras.getSampleModelTranslateY(),
985              ras.getWidth(), ras.getHeight(),
986              minX, minY, null);
987         return ret;
988     }
989 
990     /**
991      * Create a new ColorModel with it's alpha premultiplied state matching
992      * newAlphaPreMult.
993      * @param cm The ColorModel to change the alpha premult state of.
994      * @param newAlphaPreMult The new state of alpha premult.
995      * @return   A new colorModel that has isAlphaPremultiplied()
996      *           equal to newAlphaPreMult.
997      */
998     public static ColorModel
coerceColorModel(ColorModel cm, boolean newAlphaPreMult)999         coerceColorModel(ColorModel cm, boolean newAlphaPreMult) {
1000         if (cm.isAlphaPremultiplied() == newAlphaPreMult)
1001             return cm;
1002 
1003         // Easiest way to build proper colormodel for new Alpha state...
1004         // Eventually this should switch on known ColorModel types and
1005         // only fall back on this hack when the CM type is unknown.
1006         WritableRaster wr = cm.createCompatibleWritableRaster(1,1);
1007         return cm.coerceData(wr, newAlphaPreMult);
1008     }
1009 
1010     /**
1011      * Coerces data within a bufferedImage to match newAlphaPreMult,
1012      * Note that this can not change the colormodel of bi so you
1013      *
1014      * @param wr The raster to change the state of.
1015      * @param cm The colormodel currently associated with data in wr.
1016      * @param newAlphaPreMult The desired state of alpha Premult for raster.
1017      * @return A new colormodel that matches newAlphaPreMult.
1018      */
1019     public static ColorModel
coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult)1020         coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult) {
1021 
1022         // System.out.println("CoerceData: " + cm.isAlphaPremultiplied() +
1023         //                    " Out: " + newAlphaPreMult);
1024         if ( ! cm.hasAlpha() )
1025             // Nothing to do no alpha channel
1026             return cm;
1027 
1028         if (cm.isAlphaPremultiplied() == newAlphaPreMult)
1029             // nothing to do alpha state matches...
1030             return cm;
1031 
1032         // System.out.println("CoerceData: " + wr.getSampleModel());
1033 
1034         if (newAlphaPreMult) {
1035             multiplyAlpha(wr);
1036         } else {
1037             divideAlpha(wr);
1038         }
1039 
1040         return coerceColorModel(cm, newAlphaPreMult);
1041     }
1042 
multiplyAlpha(WritableRaster wr)1043     public static void multiplyAlpha(WritableRaster wr) {
1044         if (is_BYTE_COMP_Data(wr.getSampleModel()))
1045             mult_BYTE_COMP_Data(wr);
1046         else if (is_INT_PACK_Data(wr.getSampleModel(), true))
1047             mult_INT_PACK_Data(wr);
1048         else {
1049             int [] pixel = null;
1050             int    bands = wr.getNumBands();
1051             float  norm = 1.0f/255f;
1052             int x0, x1, y0, y1, a, b;
1053             float alpha;
1054             x0 = wr.getMinX();
1055             x1 = x0+wr.getWidth();
1056             y0 = wr.getMinY();
1057             y1 = y0+wr.getHeight();
1058             for (int y=y0; y<y1; y++)
1059                 for (int x=x0; x<x1; x++) {
1060                     pixel = wr.getPixel(x,y,pixel);
1061                     a = pixel[bands-1];
1062                     if ((a >= 0) && (a < 255)) {
1063                         alpha = a*norm;
1064                         for (b=0; b<bands-1; b++)
1065                             pixel[b] = (int)(pixel[b]*alpha+0.5f);
1066                         wr.setPixel(x,y,pixel);
1067                     }
1068                 }
1069         }
1070     }
1071 
divideAlpha(WritableRaster wr)1072     public static void divideAlpha(WritableRaster wr) {
1073         if (is_BYTE_COMP_Data(wr.getSampleModel()))
1074             divide_BYTE_COMP_Data(wr);
1075         else if (is_INT_PACK_Data(wr.getSampleModel(), true))
1076             divide_INT_PACK_Data(wr);
1077         else {
1078             int x0, x1, y0, y1, a, b;
1079             float ialpha;
1080             int    bands = wr.getNumBands();
1081             int [] pixel = null;
1082 
1083             x0 = wr.getMinX();
1084             x1 = x0+wr.getWidth();
1085             y0 = wr.getMinY();
1086             y1 = y0+wr.getHeight();
1087             for (int y=y0; y<y1; y++)
1088                 for (int x=x0; x<x1; x++) {
1089                     pixel = wr.getPixel(x,y,pixel);
1090                     a = pixel[bands-1];
1091                     if ((a > 0) && (a < 255)) {
1092                         ialpha = 255/(float)a;
1093                         for (b=0; b<bands-1; b++)
1094                             pixel[b] = (int)(pixel[b]*ialpha+0.5f);
1095                         wr.setPixel(x,y,pixel);
1096                     }
1097                 }
1098         }
1099     }
1100 
1101     /**
1102      * Copies data from one bufferedImage to another paying attention
1103      * to the state of AlphaPreMultiplied.
1104      *
1105      * @param src The source
1106      * @param dst The destination
1107      */
1108     public static void
copyData(BufferedImage src, BufferedImage dst)1109         copyData(BufferedImage src, BufferedImage dst) {
1110         Rectangle srcRect = new Rectangle(0, 0,
1111                                           src.getWidth(), src.getHeight());
1112         copyData(src, srcRect, dst, new Point(0,0));
1113     }
1114 
1115 
1116     /**
1117      * Copies data from one bufferedImage to another paying attention
1118      * to the state of AlphaPreMultiplied.
1119      *
1120      * @param src The source
1121      * @param srcRect The Rectangle of source data to be copied
1122      * @param dst The destination
1123      * @param destP The Place for the upper left corner of srcRect in dst.
1124      */
1125     public static void
copyData(BufferedImage src, Rectangle srcRect, BufferedImage dst, Point destP)1126         copyData(BufferedImage src, Rectangle srcRect,
1127                  BufferedImage dst, Point destP) {
1128 
1129        /*
1130         if (srcCS != dstCS)
1131             throw new IllegalArgumentException
1132                 ("Images must be in the same ColorSpace in order "+
1133                  "to copy Data between them");
1134         */
1135         boolean srcAlpha = src.getColorModel().hasAlpha();
1136         boolean dstAlpha = dst.getColorModel().hasAlpha();
1137 
1138         // System.out.println("Src has: " + srcAlpha +
1139         //                    " is: " + src.isAlphaPremultiplied());
1140         //
1141         // System.out.println("Dst has: " + dstAlpha +
1142         //                    " is: " + dst.isAlphaPremultiplied());
1143 
1144         if (srcAlpha == dstAlpha)
1145             if (( ! srcAlpha ) ||
1146                 (src.isAlphaPremultiplied() == dst.isAlphaPremultiplied())) {
1147                 // They match one another so just copy everything...
1148                 copyData(src.getRaster(), dst.getRaster());
1149                 return;
1150             }
1151 
1152         // System.out.println("Using Slow CopyData");
1153 
1154         int [] pixel = null;
1155         Raster         srcR  = src.getRaster();
1156         WritableRaster dstR  = dst.getRaster();
1157         int            bands = dstR.getNumBands();
1158 
1159         int dx = destP.x-srcRect.x;
1160         int dy = destP.y-srcRect.y;
1161 
1162         int w  = srcRect.width;
1163         int x0 = srcRect.x;
1164         int y0 = srcRect.y;
1165         int y1 = y0+srcRect.height-1;
1166 
1167         if (!srcAlpha) {
1168             // Src has no alpha dest does so set alpha to 1.0 everywhere.
1169             // System.out.println("Add Alpha");
1170             int [] oPix = new int[bands*w];
1171             int out = (w*bands)-1; // The 2 skips alpha channel
1172             while(out >= 0) {
1173                 // Fill alpha channel with 255's
1174                 oPix[out] = 255;
1175                 out -= bands;
1176             }
1177 
1178             int b, in;
1179             for (int y=y0; y<=y1; y++) {
1180                 pixel = srcR.getPixels(x0,y,w,1,pixel);
1181                 in  = w*(bands-1)-1;
1182                 out = (w*bands)-2; // The 2 skips alpha channel on last pix
1183                 switch (bands) {
1184                 case 4:
1185                     while(in >= 0) {
1186                         oPix[out--] = pixel[in--];
1187                         oPix[out--] = pixel[in--];
1188                         oPix[out--] = pixel[in--];
1189                         out--;
1190                     }
1191                     break;
1192                 default:
1193                     while(in >= 0) {
1194                         for (b=0; b<bands-1; b++)
1195                             oPix[out--] = pixel[in--];
1196                         out--;
1197                     }
1198                 }
1199                 dstR.setPixels(x0+dx, y+dy, w, 1, oPix);
1200             }
1201         } else if (dstAlpha && dst.isAlphaPremultiplied()) {
1202             // Src and dest have Alpha but we need to multiply it for dst.
1203             // System.out.println("Mult Case");
1204             int a, b, alpha, in, fpNorm = (1<<24)/255, pt5 = 1<<23;
1205             for (int y=y0; y<=y1; y++) {
1206                 pixel = srcR.getPixels(x0,y,w,1,pixel);
1207                 in=bands*w-1;
1208                 switch (bands) {
1209                 case 4:
1210                     while(in >= 0) {
1211                         a = pixel[in];
1212                         if (a == 255)
1213                             in -= 4;
1214                         else {
1215                             in--;
1216                             alpha = fpNorm*a;
1217                             pixel[in] = (pixel[in]*alpha+pt5)>>>24; in--;
1218                             pixel[in] = (pixel[in]*alpha+pt5)>>>24; in--;
1219                             pixel[in] = (pixel[in]*alpha+pt5)>>>24; in--;
1220                         }
1221                     }
1222                     break;
1223                 default:
1224                     while(in >= 0) {
1225                         a = pixel[in];
1226                         if (a == 255)
1227                             in -= bands;
1228                         else {
1229                             in--;
1230                             alpha = fpNorm*a;
1231                             for (b=0; b<bands-1; b++) {
1232                                 pixel[in] = (pixel[in]*alpha+pt5)>>>24;
1233                                 in--;
1234                             }
1235                         }
1236                     }
1237                 }
1238                 dstR.setPixels(x0+dx, y+dy, w, 1, pixel);
1239             }
1240         } else if (dstAlpha && !dst.isAlphaPremultiplied()) {
1241             // Src and dest have Alpha but we need to divide it out for dst.
1242             // System.out.println("Div Case");
1243             int a, b, ialpha, in, fpNorm = 0x00FF0000, pt5 = 1<<15;
1244             for (int y=y0; y<=y1; y++) {
1245                 pixel = srcR.getPixels(x0,y,w,1,pixel);
1246                 in=(bands*w)-1;
1247                 switch(bands) {
1248                 case 4:
1249                     while(in >= 0) {
1250                         a = pixel[in];
1251                         if ((a <= 0) || (a >= 255))
1252                             in -= 4;
1253                         else {
1254                             in--;
1255                             ialpha = fpNorm/a;
1256                             pixel[in] = (pixel[in]*ialpha+pt5)>>>16; in--;
1257                             pixel[in] = (pixel[in]*ialpha+pt5)>>>16; in--;
1258                             pixel[in] = (pixel[in]*ialpha+pt5)>>>16; in--;
1259                         }
1260                     }
1261                     break;
1262                 default:
1263                     while(in >= 0) {
1264                         a = pixel[in];
1265                         if ((a <= 0) || (a >= 255))
1266                             in -= bands;
1267                         else {
1268                             in--;
1269                             ialpha = fpNorm/a;
1270                             for (b=0; b<bands-1; b++) {
1271                                 pixel[in] = (pixel[in]*ialpha+pt5)>>>16;
1272                                 in--;
1273                             }
1274                         }
1275                     }
1276                 }
1277                 dstR.setPixels(x0+dx, y+dy, w, 1, pixel);
1278             }
1279         } else if (src.isAlphaPremultiplied()) {
1280             int [] oPix = new int[bands*w];
1281             // Src has alpha dest does not so unpremult and store...
1282             // System.out.println("Remove Alpha, Div Case");
1283             int a, b, ialpha, in, out, fpNorm = 0x00FF0000, pt5 = 1<<15;
1284             for (int y=y0; y<=y1; y++) {
1285                 pixel = srcR.getPixels(x0,y,w,1,pixel);
1286                 in  = (bands+1)*w -1;
1287                 out = (bands*w)-1;
1288                 while(in >= 0) {
1289                     a = pixel[in]; in--;
1290                     if (a > 0) {
1291                         if (a < 255) {
1292                             ialpha = fpNorm/a;
1293                             for (b=0; b<bands; b++)
1294                                 oPix[out--] = (pixel[in--]*ialpha+pt5)>>>16;
1295                         } else
1296                             for (b=0; b<bands; b++)
1297                                 oPix[out--] = pixel[in--];
1298                     } else {
1299                         in -= bands;
1300                         for (b=0; b<bands; b++)
1301                             oPix[out--] = 255;
1302                     }
1303                 }
1304                 dstR.setPixels(x0+dx, y+dy, w, 1, oPix);
1305             }
1306         } else {
1307             // Src has unpremult alpha, dest does not have alpha,
1308             // just copy the color channels over.
1309             Rectangle dstRect = new Rectangle(destP.x, destP.y,
1310                                               srcRect.width, srcRect.height);
1311             for (int b=0; b<bands; b++)
1312                 copyBand(srcR, srcRect, b,
1313                          dstR, dstRect, b);
1314         }
1315     }
1316 
copyBand(Raster src, int srcBand, WritableRaster dst, int dstBand)1317     public static void copyBand(Raster         src, int srcBand,
1318                                 WritableRaster dst, int dstBand) {
1319 
1320         Rectangle sR   = src.getBounds();
1321         Rectangle dR   = dst.getBounds();
1322         Rectangle cpR  = sR.intersection(dR);
1323 
1324         copyBand(src, cpR, srcBand, dst, cpR, dstBand);
1325     }
1326 
copyBand(Raster src, Rectangle sR, int sBand, WritableRaster dst, Rectangle dR, int dBand)1327     public static void copyBand(Raster         src, Rectangle sR, int sBand,
1328                                 WritableRaster dst, Rectangle dR, int dBand) {
1329         int dy = dR.y -sR.y;
1330         int dx = dR.x -sR.x;
1331         sR = sR.intersection(src.getBounds());
1332         dR = dR.intersection(dst.getBounds());
1333         int width, height;
1334         if (dR.width  < sR.width)  width  = dR.width;
1335         else                       width  = sR.width;
1336         if (dR.height < sR.height) height = dR.height;
1337         else                       height = sR.height;
1338 
1339         int x = sR.x+dx;
1340         int [] samples = null;
1341         for (int y=sR.y; y< sR.y+height; y++) {
1342             samples = src.getSamples(sR.x, y, width, 1, sBand, samples);
1343             dst.setSamples(x, y+dy, width, 1, dBand, samples);
1344         }
1345     }
1346 
is_INT_PACK_Data(SampleModel sm, boolean requireAlpha)1347     public static boolean is_INT_PACK_Data(SampleModel sm,
1348                                            boolean requireAlpha) {
1349         // Check ColorModel is of type DirectColorModel
1350         if(!(sm instanceof SinglePixelPackedSampleModel)) return false;
1351 
1352         // Check transfer type
1353         if(sm.getDataType() != DataBuffer.TYPE_INT)       return false;
1354 
1355         SinglePixelPackedSampleModel sppsm;
1356         sppsm = (SinglePixelPackedSampleModel)sm;
1357 
1358         int [] masks = sppsm.getBitMasks();
1359         if (masks.length == 3) {
1360             if (requireAlpha) return false;
1361         } else if (masks.length != 4)
1362             return false;
1363 
1364         if(masks[0] != 0x00ff0000) return false;
1365         if(masks[1] != 0x0000ff00) return false;
1366         if(masks[2] != 0x000000ff) return false;
1367         if ((masks.length == 4) &&
1368             (masks[3] != 0xff000000)) return false;
1369 
1370         return true;
1371     }
1372 
is_BYTE_COMP_Data(SampleModel sm)1373         public static boolean is_BYTE_COMP_Data(SampleModel sm) {
1374             // Check ColorModel is of type DirectColorModel
1375             if(!(sm instanceof ComponentSampleModel))    return false;
1376 
1377             // Check transfer type
1378             if(sm.getDataType() != DataBuffer.TYPE_BYTE) return false;
1379 
1380             return true;
1381         }
1382 
divide_INT_PACK_Data(WritableRaster wr)1383     protected static void divide_INT_PACK_Data(WritableRaster wr) {
1384         // System.out.println("Divide Int");
1385 
1386         SinglePixelPackedSampleModel sppsm;
1387         sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
1388 
1389         final int width = wr.getWidth();
1390 
1391         final int scanStride = sppsm.getScanlineStride();
1392         DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
1393         final int base
1394             = (db.getOffset() +
1395                sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
1396                                wr.getMinY()-wr.getSampleModelTranslateY()));
1397 
1398         // Access the pixel data array
1399         final int[] pixels = db.getBankData()[0];
1400         for (int y=0; y<wr.getHeight(); y++) {
1401             int sp = base + y*scanStride;
1402             final int end = sp + width;
1403             while (sp < end) {
1404                 int pixel = pixels[sp];
1405                 int a = pixel>>>24;
1406                 if (a<=0) {
1407                     pixels[sp] = 0x00FFFFFF;
1408                 } else if (a<255) {
1409                     int aFP = (0x00FF0000/a);
1410                     pixels[sp] =
1411                         ((a << 24) |
1412                          (((((pixel&0xFF0000)>>16)*aFP)&0xFF0000)    ) |
1413                          (((((pixel&0x00FF00)>>8) *aFP)&0xFF0000)>>8 ) |
1414                          (((((pixel&0x0000FF))    *aFP)&0xFF0000)>>16));
1415                 }
1416                 sp++;
1417             }
1418         }
1419     }
1420 
mult_INT_PACK_Data(WritableRaster wr)1421     protected static void mult_INT_PACK_Data(WritableRaster wr) {
1422         // System.out.println("Multiply Int: " + wr);
1423 
1424         SinglePixelPackedSampleModel sppsm;
1425         sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
1426 
1427         final int width = wr.getWidth();
1428 
1429         final int scanStride = sppsm.getScanlineStride();
1430         DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
1431         final int base
1432             = (db.getOffset() +
1433                sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
1434                                wr.getMinY()-wr.getSampleModelTranslateY()));
1435         // Access the pixel data array
1436         final int[] pixels = db.getBankData()[0];
1437         for (int y=0; y<wr.getHeight(); y++) {
1438             int sp = base + y*scanStride;
1439             final int end = sp + width;
1440             while (sp < end) {
1441                 int pixel = pixels[sp];
1442                 int a = pixel>>>24;
1443                 if ((a>=0) && (a<255)) {   // this does NOT include a == 255 (0xff) !
1444                     pixels[sp] = ((a << 24) |
1445                                   ((((pixel&0xFF0000)*a)>>8)&0xFF0000) |
1446                                   ((((pixel&0x00FF00)*a)>>8)&0x00FF00) |
1447                                   ((((pixel&0x0000FF)*a)>>8)&0x0000FF));
1448                 }
1449                 sp++;
1450             }
1451         }
1452     }
1453 
1454 
divide_BYTE_COMP_Data(WritableRaster wr)1455     protected static void divide_BYTE_COMP_Data(WritableRaster wr) {
1456         // System.out.println("Multiply Int: " + wr);
1457 
1458         ComponentSampleModel csm;
1459         csm = (ComponentSampleModel)wr.getSampleModel();
1460 
1461         final int width = wr.getWidth();
1462 
1463         final int scanStride = csm.getScanlineStride();
1464         final int pixStride  = csm.getPixelStride();
1465         final int [] bandOff = csm.getBandOffsets();
1466 
1467         DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
1468         final int base
1469             = (db.getOffset() +
1470                csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
1471                              wr.getMinY()-wr.getSampleModelTranslateY()));
1472 
1473         int aOff = bandOff[bandOff.length-1];
1474         int bands = bandOff.length-1;
1475 
1476         // Access the pixel data array
1477         final byte[] pixels = db.getBankData()[0];
1478         for (int y=0; y<wr.getHeight(); y++) {
1479             int sp = base + y*scanStride;
1480             final int end = sp + width*pixStride;
1481             while (sp < end) {
1482               int a = pixels[sp+aOff]&0xFF;
1483               if (a==0) {
1484                 for ( int b=0; b<bands; b++)
1485                   pixels[sp+bandOff[b]] = (byte)0xFF;
1486               } else if (a<255) {         // this does NOT include a == 255 (0xff) !
1487                 int aFP = (0x00FF0000/a);
1488                 for ( int b=0; b<bands; b++) {
1489                   int i = sp+bandOff[b];
1490                   pixels[i] = (byte)(((pixels[i]&0xFF)*aFP)>>>16);
1491                 }
1492               }
1493               sp+=pixStride;
1494             }
1495         }
1496     }
1497 
mult_BYTE_COMP_Data(WritableRaster wr)1498     protected static void mult_BYTE_COMP_Data(WritableRaster wr) {
1499         // System.out.println("Multiply Int: " + wr);
1500 
1501         ComponentSampleModel csm;
1502         csm = (ComponentSampleModel)wr.getSampleModel();
1503 
1504         final int width = wr.getWidth();
1505 
1506         final int scanStride = csm.getScanlineStride();
1507         final int pixStride  = csm.getPixelStride();
1508         final int [] bandOff = csm.getBandOffsets();
1509 
1510         DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
1511         final int base
1512             = (db.getOffset() +
1513                csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
1514                              wr.getMinY()-wr.getSampleModelTranslateY()));
1515 
1516 
1517         int aOff = bandOff[bandOff.length-1];
1518         int bands = bandOff.length-1;
1519 
1520         // Access the pixel data array
1521         final byte[] pixels = db.getBankData()[0];
1522         for (int y=0; y<wr.getHeight(); y++) {
1523             int sp = base + y*scanStride;
1524             final int end = sp + width*pixStride;
1525             while (sp < end) {
1526               int a = pixels[sp+aOff]&0xFF;
1527               if (a!=0xFF)
1528                 for ( int b=0; b<bands; b++) {
1529                   int i = sp+bandOff[b];
1530                   pixels[i] = (byte)(((pixels[i]&0xFF)*a)>>8);
1531                 }
1532               sp+=pixStride;
1533             }
1534         }
1535     }
1536 
1537 /*
1538   This is skanky debugging code that might be useful in the future:
1539 
1540             if (count == 33) {
1541                 String label = "sub [" + x + ", " + y + "]: ";
1542                 org.ImageDisplay.showImage
1543                     (label, subBI);
1544                 org.ImageDisplay.printImage
1545                     (label, subBI,
1546                      new Rectangle(75-iR.x, 90-iR.y, 32, 32));
1547 
1548             }
1549 
1550 
1551             // if ((count++ % 50) == 10)
1552             //     org.ImageDisplay.showImage("foo: ", subBI);
1553 
1554 
1555             Graphics2D realG2D = g2d;
1556             while (realG2D instanceof sun.java2d.ProxyGraphics2D) {
1557                 realG2D = ((sun.java2d.ProxyGraphics2D)realG2D).getDelegate();
1558             }
1559             if (realG2D instanceof sun.awt.image.BufferedImageGraphics2D) {
1560                 count++;
1561                 if (count == 34) {
1562                     RenderedImage ri;
1563                     ri = ((sun.awt.image.BufferedImageGraphics2D)realG2D).bufImg;
1564                     // g2d.setComposite(SVGComposite.OVER);
1565                     // org.ImageDisplay.showImage("Bar: " + count, cr);
1566                     org.ImageDisplay.printImage("Bar: " + count, cr,
1567                                                 new Rectangle(75, 90, 32, 32));
1568 
1569                     org.ImageDisplay.showImage ("Foo: " + count, ri);
1570                     org.ImageDisplay.printImage("Foo: " + count, ri,
1571                                                 new Rectangle(75, 90, 32, 32));
1572 
1573                     System.out.println("BI: "   + ri);
1574                     System.out.println("BISM: " + ri.getSampleModel());
1575                     System.out.println("BICM: " + ri.getColorModel());
1576                     System.out.println("BICM class: " + ri.getColorModel().getClass());
1577                     System.out.println("BICS: " + ri.getColorModel().getColorSpace());
1578                     System.out.println
1579                         ("sRGB CS: " +
1580                          ColorSpace.getInstance(ColorSpace.CS_sRGB));
1581                     System.out.println("G2D info");
1582                     System.out.println("\tComposite: " + g2d.getComposite());
1583                     System.out.println("\tTransform" + g2d.getTransform());
1584                     java.awt.RenderingHints rh = g2d.getRenderingHints();
1585                     java.util.Set keys = rh.keySet();
1586                     java.util.Iterator iter = keys.iterator();
1587                     while (iter.hasNext()) {
1588                         Object o = iter.next();
1589 
1590                         System.out.println("\t" + o.toString() + " -> " +
1591                                            rh.get(o).toString());
1592                     }
1593 
1594                     ri = cr;
1595                     System.out.println("RI: "   + ri);
1596                     System.out.println("RISM: " + ri.getSampleModel());
1597                     System.out.println("RICM: " + ri.getColorModel());
1598                     System.out.println("RICM class: " + ri.getColorModel().getClass());
1599                     System.out.println("RICS: " + ri.getColorModel().getColorSpace());
1600                 }
1601             }
1602 */
1603 
1604 }
1605