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