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