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