001    /*****************************************************************************
002     * Copyright (C) The Apache Software Foundation. All rights reserved.        *
003     * ------------------------------------------------------------------------- *
004     * This software is published under the terms of the Apache Software License *
005     * version 1.1, a copy of which has been included with this distribution in  *
006     * the LICENSE file.                                                         *
007     *****************************************************************************/
008    
009    package com.kitfox.svg.batik;
010    
011    import java.awt.Color;
012    import java.awt.Composite;
013    import java.awt.Graphics2D;
014    import java.awt.GraphicsConfiguration;
015    import java.awt.GraphicsDevice;
016    import java.awt.Point;
017    import java.awt.Rectangle;
018    import java.awt.RenderingHints;
019    import java.awt.Shape;
020    import java.awt.color.ColorSpace;
021    import java.awt.geom.AffineTransform;
022    import java.awt.geom.Rectangle2D;
023    import java.awt.image.BufferedImage;
024    import java.awt.image.ColorModel;
025    import java.awt.image.ComponentSampleModel;
026    import java.awt.image.DataBuffer;
027    import java.awt.image.DataBufferByte;
028    import java.awt.image.DataBufferInt;
029    import java.awt.image.DataBufferShort;
030    import java.awt.image.DataBufferUShort;
031    import java.awt.image.DirectColorModel;
032    import java.awt.image.Raster;
033    import java.awt.image.RenderedImage;
034    import java.awt.image.SampleModel;
035    import java.awt.image.SinglePixelPackedSampleModel;
036    import java.awt.image.WritableRaster;
037    import java.awt.image.renderable.RenderContext;
038    import java.awt.image.renderable.RenderableImage;
039    import java.lang.ref.Reference;
040    import java.lang.ref.WeakReference;
041    
042    /**
043     *
044     * @author  kitfox
045     */
046    public class GraphicsUtil
047    {
048        
049        /** Creates a new instance of GraphicsUtil */
050        public GraphicsUtil()
051        {
052        }
053        
054        /**
055         * Create a new ColorModel with it's alpha premultiplied state matching
056         * newAlphaPreMult.
057         * @param cm The ColorModel to change the alpha premult state of.
058         * @param newAlphaPreMult The new state of alpha premult.
059         * @return   A new colorModel that has isAlphaPremultiplied()
060         *           equal to newAlphaPreMult.
061         */
062        public static ColorModel coerceColorModel(ColorModel cm, boolean newAlphaPreMult)
063        {
064            if (cm.isAlphaPremultiplied() == newAlphaPreMult)
065                return cm;
066            
067            // Easiest way to build proper colormodel for new Alpha state...
068            // Eventually this should switch on known ColorModel types and
069            // only fall back on this hack when the CM type is unknown.
070            WritableRaster wr = cm.createCompatibleWritableRaster(1,1);
071            return cm.coerceData(wr, newAlphaPreMult);
072        }
073        
074        /**
075         * Coerces data within a bufferedImage to match newAlphaPreMult,
076         * Note that this can not change the colormodel of bi so you
077         *
078         * @param wr The raster to change the state of.
079         * @param cm The colormodel currently associated with data in wr.
080         * @param newAlphaPreMult The desired state of alpha Premult for raster.
081         * @return A new colormodel that matches newAlphaPreMult.
082         */
083        public static ColorModel coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult)
084        {
085            
086            // System.out.println("CoerceData: " + cm.isAlphaPremultiplied() +
087            //                    " Out: " + newAlphaPreMult);
088            if (cm.hasAlpha()== false)
089                // Nothing to do no alpha channel
090                return cm;
091            
092            if (cm.isAlphaPremultiplied() == newAlphaPreMult)
093                // nothing to do alpha state matches...
094                return cm;
095            
096            // System.out.println("CoerceData: " + wr.getSampleModel());
097            
098            int [] pixel = null;
099            int    bands = wr.getNumBands();
100            float  norm;
101            if (newAlphaPreMult)
102            {
103                if (is_BYTE_COMP_Data(wr.getSampleModel()))
104                    mult_BYTE_COMP_Data(wr);
105                else if (is_INT_PACK_Data(wr.getSampleModel(), true))
106                    mult_INT_PACK_Data(wr);
107                else
108                {
109                    norm = 1f/255f;
110                    int x0, x1, y0, y1, a, b;
111                    float alpha;
112                    x0 = wr.getMinX();
113                    x1 = x0+wr.getWidth();
114                    y0 = wr.getMinY();
115                    y1 = y0+wr.getHeight();
116                    for (int y=y0; y<y1; y++)
117                        for (int x=x0; x<x1; x++)
118                        {
119                            pixel = wr.getPixel(x,y,pixel);
120                            a = pixel[bands-1];
121                            if ((a >= 0) && (a < 255))
122                            {
123                                alpha = a*norm;
124                                for (b=0; b<bands-1; b++)
125                                    pixel[b] = (int)(pixel[b]*alpha+0.5f);
126                                wr.setPixel(x,y,pixel);
127                            }
128                        }
129                }
130            } else
131            {
132                if (is_BYTE_COMP_Data(wr.getSampleModel()))
133                    divide_BYTE_COMP_Data(wr);
134                else if (is_INT_PACK_Data(wr.getSampleModel(), true))
135                    divide_INT_PACK_Data(wr);
136                else
137                {
138                    int x0, x1, y0, y1, a, b;
139                    float ialpha;
140                    x0 = wr.getMinX();
141                    x1 = x0+wr.getWidth();
142                    y0 = wr.getMinY();
143                    y1 = y0+wr.getHeight();
144                    for (int y=y0; y<y1; y++)
145                        for (int x=x0; x<x1; x++)
146                        {
147                            pixel = wr.getPixel(x,y,pixel);
148                            a = pixel[bands-1];
149                            if ((a > 0) && (a < 255))
150                            {
151                                ialpha = 255/(float)a;
152                                for (b=0; b<bands-1; b++)
153                                    pixel[b] = (int)(pixel[b]*ialpha+0.5f);
154                                wr.setPixel(x,y,pixel);
155                            }
156                        }
157                }
158            }
159            
160            return coerceColorModel(cm, newAlphaPreMult);
161        }
162        
163        
164        public static boolean is_INT_PACK_Data(SampleModel sm,
165        boolean requireAlpha)
166        {
167            // Check ColorModel is of type DirectColorModel
168            if(!(sm instanceof SinglePixelPackedSampleModel)) return false;
169            
170            // Check transfer type
171            if(sm.getDataType() != DataBuffer.TYPE_INT)       return false;
172            
173            SinglePixelPackedSampleModel sppsm;
174            sppsm = (SinglePixelPackedSampleModel)sm;
175            
176            int [] masks = sppsm.getBitMasks();
177            if (masks.length == 3)
178            {
179                if (requireAlpha) return false;
180            } else if (masks.length != 4)
181                return false;
182            
183            if(masks[0] != 0x00ff0000) return false;
184            if(masks[1] != 0x0000ff00) return false;
185            if(masks[2] != 0x000000ff) return false;
186            if ((masks.length == 4) &&
187            (masks[3] != 0xff000000)) return false;
188            
189            return true;
190        }
191        
192        protected static void mult_INT_PACK_Data(WritableRaster wr)
193        {
194            // System.out.println("Multiply Int: " + wr);
195            
196            SinglePixelPackedSampleModel sppsm;
197            sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
198            
199            final int width = wr.getWidth();
200            
201            final int scanStride = sppsm.getScanlineStride();
202            DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
203            final int base
204            = (db.getOffset() +
205            sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
206            wr.getMinY()-wr.getSampleModelTranslateY()));
207            int n=0;
208            // Access the pixel data array
209            final int pixels[] = db.getBankData()[0];
210            for (int y=0; y<wr.getHeight(); y++)
211            {
212                int sp = base + y*scanStride;
213                final int end = sp + width;
214                while (sp < end)
215                {
216                    int pixel = pixels[sp];
217                    int a = pixel>>>24;
218                    if ((a>=0) && (a<255))
219                    {
220                        pixels[sp] = ((a << 24) |
221                        ((((pixel&0xFF0000)*a)>>8)&0xFF0000) |
222                        ((((pixel&0x00FF00)*a)>>8)&0x00FF00) |
223                        ((((pixel&0x0000FF)*a)>>8)&0x0000FF));
224                    }
225                    sp++;
226                }
227            }
228        }
229        
230        protected static void divide_INT_PACK_Data(WritableRaster wr)
231        {
232            // System.out.println("Divide Int");
233            
234            SinglePixelPackedSampleModel sppsm;
235            sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
236            
237            final int width = wr.getWidth();
238            
239            final int scanStride = sppsm.getScanlineStride();
240            DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
241            final int base
242            = (db.getOffset() +
243            sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
244            wr.getMinY()-wr.getSampleModelTranslateY()));
245            int pixel, a, aFP, n=0;
246            // Access the pixel data array
247            final int pixels[] = db.getBankData()[0];
248            for (int y=0; y<wr.getHeight(); y++)
249            {
250                int sp = base + y*scanStride;
251                final int end = sp + width;
252                while (sp < end)
253                {
254                    pixel = pixels[sp];
255                    a = pixel>>>24;
256                    if (a<=0)
257                    {
258                        pixels[sp] = 0x00FFFFFF;
259                    }
260                    else if (a<255)
261                    {
262                        aFP = (0x00FF0000/a);
263                        pixels[sp] =
264                        ((a << 24) |
265                        (((((pixel&0xFF0000)>>16)*aFP)&0xFF0000)    ) |
266                        (((((pixel&0x00FF00)>>8) *aFP)&0xFF0000)>>8 ) |
267                        (((((pixel&0x0000FF))    *aFP)&0xFF0000)>>16));
268                    }
269                    sp++;
270                }
271            }
272        }
273        
274        public static boolean is_BYTE_COMP_Data(SampleModel sm)
275        {
276            // Check ColorModel is of type DirectColorModel
277            if(!(sm instanceof ComponentSampleModel))    return false;
278            
279            // Check transfer type
280            if(sm.getDataType() != DataBuffer.TYPE_BYTE) return false;
281            
282            return true;
283        }
284        
285        protected static void mult_BYTE_COMP_Data(WritableRaster wr)
286        {
287            // System.out.println("Multiply Int: " + wr);
288            
289            ComponentSampleModel csm;
290            csm = (ComponentSampleModel)wr.getSampleModel();
291            
292            final int width = wr.getWidth();
293            
294            final int scanStride = csm.getScanlineStride();
295            final int pixStride  = csm.getPixelStride();
296            final int [] bandOff = csm.getBandOffsets();
297            
298            DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
299            final int base
300            = (db.getOffset() +
301            csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
302            wr.getMinY()-wr.getSampleModelTranslateY()));
303            
304            
305            int a=0;
306            int aOff = bandOff[bandOff.length-1];
307            int bands = bandOff.length-1;
308            int b, i;
309            
310            // Access the pixel data array
311            final byte pixels[] = db.getBankData()[0];
312            for (int y=0; y<wr.getHeight(); y++)
313            {
314                int sp = base + y*scanStride;
315                final int end = sp + width*pixStride;
316                while (sp < end)
317                {
318                    a = pixels[sp+aOff]&0xFF;
319                    if (a!=0xFF)
320                        for (b=0; b<bands; b++)
321                        {
322                            i = sp+bandOff[b];
323                            pixels[i] = (byte)(((pixels[i]&0xFF)*a)>>8);
324                        }
325                    sp+=pixStride;
326                }
327            }
328        }
329        
330        protected static void divide_BYTE_COMP_Data(WritableRaster wr)
331        {
332            // System.out.println("Multiply Int: " + wr);
333            
334            ComponentSampleModel csm;
335            csm = (ComponentSampleModel)wr.getSampleModel();
336            
337            final int width = wr.getWidth();
338            
339            final int scanStride = csm.getScanlineStride();
340            final int pixStride  = csm.getPixelStride();
341            final int [] bandOff = csm.getBandOffsets();
342            
343            DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
344            final int base
345            = (db.getOffset() +
346            csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
347            wr.getMinY()-wr.getSampleModelTranslateY()));
348            
349            
350            int a=0;
351            int aOff = bandOff[bandOff.length-1];
352            int bands = bandOff.length-1;
353            int b, i;
354            // Access the pixel data array
355            final byte pixels[] = db.getBankData()[0];
356            for (int y=0; y<wr.getHeight(); y++)
357            {
358                int sp = base + y*scanStride;
359                final int end = sp + width*pixStride;
360                while (sp < end)
361                {
362                    a = pixels[sp+aOff]&0xFF;
363                    if (a==0)
364                    {
365                        for (b=0; b<bands; b++)
366                            pixels[sp+bandOff[b]] = (byte)0xFF;
367                    } else if (a<255)
368                    {
369                        int aFP = (0x00FF0000/a);
370                        for (b=0; b<bands; b++)
371                        {
372                            i = sp+bandOff[b];
373                            pixels[i] = (byte)(((pixels[i]&0xFF)*aFP)>>>16);
374                        }
375                    }
376                    sp+=pixStride;
377                }
378            }
379        }
380        
381        
382    }