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