001    /*
002    *
003    
004    Licensed under the Apache License, Version 2.0 (the "License");
005    you may not use this file except in compliance with the License.
006    You may obtain a copy of the License at
007    
008       http://www.apache.org/licenses/LICENSE-2.0
009    
010    Unless required by applicable law or agreed to in writing, software
011    distributed under the License is distributed on an "AS IS" BASIS,
012    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013    See the License for the specific language governing permissions and
014    limitations under the License.
015    */
016    
017    package railo.runtime.img.filter;import java.awt.Color;
018    import java.awt.Component;
019    import java.awt.Graphics;
020    import java.awt.Graphics2D;
021    import java.awt.Image;
022    import java.awt.Rectangle;
023    import java.awt.Shape;
024    import java.awt.geom.AffineTransform;
025    import java.awt.image.BufferedImage;
026    import java.awt.image.ImageObserver;
027    import java.awt.image.ImageProducer;
028    import java.awt.image.PixelGrabber;
029    import java.awt.image.Raster;
030    import java.awt.image.WritableRaster;
031    
032    /**
033     * A class containing some static utility methods for dealing with BufferedImages.
034     */
035    public abstract class ImageUtils {
036            
037            private static BufferedImage backgroundImage = null;
038    
039            /**
040         * Cretae a BufferedImage from an ImageProducer.
041         * @param producer the ImageProducer
042         * @return a new TYPE_INT_ARGB BufferedImage
043         */
044        public static BufferedImage createImage(ImageProducer producer) {
045                    PixelGrabber pg = new PixelGrabber(producer, 0, 0, -1, -1, null, 0, 0);
046                    try {
047                            pg.grabPixels();
048                    } catch (InterruptedException e) {
049                            throw new RuntimeException("Image fetch interrupted");
050                    }
051                    if ((pg.status() & ImageObserver.ABORT) != 0)
052                            throw new RuntimeException("Image fetch aborted");
053                    if ((pg.status() & ImageObserver.ERROR) != 0)
054                            throw new RuntimeException("Image fetch error");
055                    BufferedImage p = new BufferedImage(pg.getWidth(), pg.getHeight(), BufferedImage.TYPE_INT_ARGB);
056                    p.setRGB(0, 0, pg.getWidth(), pg.getHeight(), (int[])pg.getPixels(), 0, pg.getWidth());
057                    return p;
058            }
059            
060            /**
061             * Convert an Image into a TYPE_INT_ARGB BufferedImage. If the image is already of this type, the original image is returned unchanged.
062         * @param image the image to convert
063         * @return the converted image
064             */
065            public static BufferedImage convertImageToARGB( Image image ) {
066                    if ( image instanceof BufferedImage && ((BufferedImage)image).getType() == BufferedImage.TYPE_INT_ARGB )
067                            return (BufferedImage)image;
068                    BufferedImage p = new BufferedImage( image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
069                    Graphics2D g = p.createGraphics();
070                    g.drawImage( image, 0, 0, null );
071                    g.dispose();
072                    return p;
073            }
074            
075            /**
076             * Returns a *copy* of a subimage of image. This avoids the performance problems associated with BufferedImage.getSubimage.
077         * @param image the image
078         * @param x the x position
079         * @param y the y position
080         * @param w the width
081         * @param h the height
082         * @return the subimage
083             */
084            public static BufferedImage getSubimage( BufferedImage image, int x, int y, int w, int h ) {
085                    BufferedImage newImage = new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
086                    Graphics2D g = newImage.createGraphics();
087                    g.drawRenderedImage( image, AffineTransform.getTranslateInstance(-x, -y) );
088                    g.dispose();
089                    return newImage;
090            }
091    
092            /**
093             * Clones a BufferedImage.
094         * @param image the image to clone
095         * @return the cloned image
096             */
097            public static BufferedImage cloneImage( BufferedImage image ) {
098                    BufferedImage newImage = new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB );
099                    Graphics2D g = newImage.createGraphics();
100                    g.drawRenderedImage( image, null );
101                    g.dispose();
102                    return newImage;
103            }
104    
105            /**
106             * Paint a check pattern, used for a background to indicate image transparency.
107         * @param c the component to draw into
108         * @param g the Graphics objects
109         * @param x the x position
110         * @param y the y position
111         * @param width the width
112         * @param height the height
113             */
114            public static void paintCheckedBackground(Component c, Graphics g, int x, int y, int width, int height) {
115                    if ( backgroundImage == null ) {
116                            backgroundImage = new BufferedImage( 64, 64, BufferedImage.TYPE_INT_ARGB );
117                            Graphics bg = backgroundImage.createGraphics();
118                            for ( int by = 0; by < 64; by += 8 ) {
119                                    for ( int bx = 0; bx < 64; bx += 8 ) {
120                                            bg.setColor( ((bx^by) & 8) != 0 ? Color.lightGray : Color.white );
121                                            bg.fillRect( bx, by, 8, 8 );
122                                    }
123                            }
124                            bg.dispose();
125                    }
126    
127                    if ( backgroundImage != null ) {
128                            Shape saveClip = g.getClip();
129                            Rectangle r = g.getClipBounds();
130                            if (r == null)
131                                    r = new Rectangle(c.getSize());
132                            r = r.intersection(new Rectangle(x, y, width, height));
133                            g.setClip(r);
134                            int w = backgroundImage.getWidth();
135                            int h = backgroundImage.getHeight();
136                            if (w != -1 && h != -1) {
137                                    int x1 = (r.x / w) * w;
138                                    int y1 = (r.y / h) * h;
139                                    int x2 = ((r.x + r.width + w - 1) / w) * w;
140                                    int y2 = ((r.y + r.height + h - 1) / h) * h;
141                                    for (y = y1; y < y2; y += h)
142                                            for (x = x1; x < x2; x += w)
143                                                    g.drawImage(backgroundImage, x, y, c);
144                            }
145                            g.setClip(saveClip);
146                    }
147            }
148    
149        /**
150         * Calculates the bounds of the non-transparent parts of the given image.
151         * @param p the image
152         * @return the bounds of the non-transparent area
153         */
154            public static Rectangle getSelectedBounds(BufferedImage p) {
155                    int width = p.getWidth();
156            int height = p.getHeight();
157                    int maxX = 0, maxY = 0, minX = width, minY = height;
158                    boolean anySelected = false;
159                    int y1;
160                    int [] pixels = null;
161                    
162                    for (y1 = height-1; y1 >= 0; y1--) {
163                            pixels = getRGB( p, 0, y1, width, 1, pixels );
164                            for (int x = 0; x < minX; x++) {
165                                    if ((pixels[x] & 0xff000000) != 0) {
166                                            minX = x;
167                                            maxY = y1;
168                                            anySelected = true;
169                                            break;
170                                    }
171                            }
172                            for (int x = width-1; x >= maxX; x--) {
173                                    if ((pixels[x] & 0xff000000) != 0) {
174                                            maxX = x;
175                                            maxY = y1;
176                                            anySelected = true;
177                                            break;
178                                    }
179                            }
180                            if ( anySelected )
181                                    break;
182                    }
183                    pixels = null;
184                    for (int y = 0; y < y1; y++) {
185                            pixels = getRGB( p, 0, y, width, 1, pixels );
186                            for (int x = 0; x < minX; x++) {
187                                    if ((pixels[x] & 0xff000000) != 0) {
188                                            minX = x;
189                                            if ( y < minY )
190                                                    minY = y;
191                                            anySelected = true;
192                                            break;
193                                    }
194                            }
195                            for (int x = width-1; x >= maxX; x--) {
196                                    if ((pixels[x] & 0xff000000) != 0) {
197                                            maxX = x;
198                                            if ( y < minY )
199                                                    minY = y;
200                                            anySelected = true;
201                                            break;
202                                    }
203                            }
204                    }
205                    if ( anySelected )
206                            return new Rectangle( minX, minY, maxX-minX+1, maxY-minY+1 );
207                    return null;
208            }
209    
210            /**
211             * Compose src onto dst using the alpha of sel to interpolate between the two.
212             * I can't think of a way to do this using AlphaComposite.
213         * @param src the source raster
214         * @param dst the destination raster
215         * @param sel the mask raster
216             */
217            public static void composeThroughMask(Raster src, WritableRaster dst, Raster sel) {
218                    int x = src.getMinX();
219                    int y = src.getMinY();
220                    int w = src.getWidth();
221                    int h = src.getHeight();
222    
223                    int srcRGB[] = null;
224                    int selRGB[] = null;
225                    int dstRGB[] = null;
226    
227                    for ( int i = 0; i < h; i++ ) {
228                            srcRGB = src.getPixels(x, y, w, 1, srcRGB);
229                            selRGB = sel.getPixels(x, y, w, 1, selRGB);
230                            dstRGB = dst.getPixels(x, y, w, 1, dstRGB);
231    
232                            int k = x;
233                            for ( int j = 0; j < w; j++ ) {
234                                    int sr = srcRGB[k];
235                                    int dir = dstRGB[k];
236                                    int sg = srcRGB[k+1];
237                                    int dig = dstRGB[k+1];
238                                    int sb = srcRGB[k+2];
239                                    int dib = dstRGB[k+2];
240                                    int sa = srcRGB[k+3];
241                                    int dia = dstRGB[k+3];
242    
243                                    float a = selRGB[k+3]/255f;
244                                    float ac = 1-a;
245    
246                                    dstRGB[k] = (int)(a*sr + ac*dir); 
247                                    dstRGB[k+1] = (int)(a*sg + ac*dig); 
248                                    dstRGB[k+2] = (int)(a*sb + ac*dib); 
249                                    dstRGB[k+3] = (int)(a*sa + ac*dia);
250                                    k += 4;
251                            }
252    
253                            dst.setPixels(x, y, w, 1, dstRGB);
254                            y++;
255                    }
256            }
257    
258            /**
259             * A convenience method for getting ARGB pixels from an image. This tries to avoid the performance
260             * penalty of BufferedImage.getRGB unmanaging the image.
261         * @param image   a BufferedImage object
262         * @param x       the left edge of the pixel block
263         * @param y       the right edge of the pixel block
264         * @param width   the width of the pixel arry
265         * @param height  the height of the pixel arry
266         * @param pixels  the array to hold the returned pixels. May be null.
267         * @return the pixels
268         * @see #setRGB
269         */
270            public static int[] getRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {
271                    int type = image.getType();
272                    if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB )
273                            return (int [])image.getRaster().getDataElements( x, y, width, height, pixels );
274                    return image.getRGB( x, y, width, height, pixels, 0, width );
275        }
276    
277            /**
278             * A convenience method for setting ARGB pixels in an image. This tries to avoid the performance
279             * penalty of BufferedImage.setRGB unmanaging the image.
280         * @param image   a BufferedImage object
281         * @param x       the left edge of the pixel block
282         * @param y       the right edge of the pixel block
283         * @param width   the width of the pixel arry
284         * @param height  the height of the pixel arry
285         * @param pixels  the array of pixels to set
286         * @see #getRGB
287             */
288            public static void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {
289                    int type = image.getType();
290                    if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB )
291                            image.getRaster().setDataElements( x, y, width, height, pixels );
292                    else
293                            image.setRGB( x, y, width, height, pixels, 0, width );
294        }
295    }
296