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