1 /* 2 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.plaf.nimbus; 26 27 import java.awt.AlphaComposite; 28 import java.awt.Graphics2D; 29 import java.awt.Transparency; 30 import java.awt.GraphicsConfiguration; 31 import java.awt.GraphicsEnvironment; 32 import java.awt.image.BufferedImage; 33 import java.awt.image.Raster; 34 import java.awt.image.WritableRaster; 35 import java.awt.image.ColorModel; 36 37 /** 38 * EffectUtils 39 * 40 * @author Created by Jasper Potts (Jun 18, 2007) 41 */ 42 class EffectUtils { 43 44 /** 45 * Clear a transparent image to 100% transparent 46 * 47 * @param img The image to clear 48 */ clearImage(BufferedImage img)49 static void clearImage(BufferedImage img) { 50 Graphics2D g2 = img.createGraphics(); 51 g2.setComposite(AlphaComposite.Clear); 52 g2.fillRect(0, 0, img.getWidth(), img.getHeight()); 53 g2.dispose(); 54 } 55 56 // ================================================================================================================= 57 // Blur 58 59 /** 60 * Apply Gaussian Blur to Image 61 * 62 * @param src The image tp 63 * @param dst The destination image to draw blured src image into, null if you want a new one created 64 * @param radius The blur kernel radius 65 * @return The blured image 66 */ gaussianBlur(BufferedImage src, BufferedImage dst, int radius)67 static BufferedImage gaussianBlur(BufferedImage src, BufferedImage dst, int radius) { 68 int width = src.getWidth(); 69 int height = src.getHeight(); 70 if (dst == null || dst.getWidth() != width || dst.getHeight() != height || src.getType() != dst.getType()) { 71 dst = createColorModelCompatibleImage(src); 72 } 73 float[] kernel = createGaussianKernel(radius); 74 if (src.getType() == BufferedImage.TYPE_INT_ARGB) { 75 int[] srcPixels = new int[width * height]; 76 int[] dstPixels = new int[width * height]; 77 getPixels(src, 0, 0, width, height, srcPixels); 78 // horizontal pass 79 blur(srcPixels, dstPixels, width, height, kernel, radius); 80 // vertical pass 81 //noinspection SuspiciousNameCombination 82 blur(dstPixels, srcPixels, height, width, kernel, radius); 83 // the result is now stored in srcPixels due to the 2nd pass 84 setPixels(dst, 0, 0, width, height, srcPixels); 85 } else if (src.getType() == BufferedImage.TYPE_BYTE_GRAY) { 86 byte[] srcPixels = new byte[width * height]; 87 byte[] dstPixels = new byte[width * height]; 88 getPixels(src, 0, 0, width, height, srcPixels); 89 // horizontal pass 90 blur(srcPixels, dstPixels, width, height, kernel, radius); 91 // vertical pass 92 //noinspection SuspiciousNameCombination 93 blur(dstPixels, srcPixels, height, width, kernel, radius); 94 // the result is now stored in srcPixels due to the 2nd pass 95 setPixels(dst, 0, 0, width, height, srcPixels); 96 } else { 97 throw new IllegalArgumentException("EffectUtils.gaussianBlur() src image is not a supported type, type=[" + 98 src.getType() + "]"); 99 } 100 return dst; 101 } 102 103 /** 104 * <p>Blurs the source pixels into the destination pixels. The force of the blur is specified by the radius which 105 * must be greater than 0.</p> <p>The source and destination pixels arrays are expected to be in the INT_ARGB 106 * format.</p> <p>After this method is executed, dstPixels contains a transposed and filtered copy of 107 * srcPixels.</p> 108 * 109 * @param srcPixels the source pixels 110 * @param dstPixels the destination pixels 111 * @param width the width of the source picture 112 * @param height the height of the source picture 113 * @param kernel the kernel of the blur effect 114 * @param radius the radius of the blur effect 115 */ blur(int[] srcPixels, int[] dstPixels, int width, int height, float[] kernel, int radius)116 private static void blur(int[] srcPixels, int[] dstPixels, 117 int width, int height, 118 float[] kernel, int radius) { 119 float a; 120 float r; 121 float g; 122 float b; 123 124 int ca; 125 int cr; 126 int cg; 127 int cb; 128 129 for (int y = 0; y < height; y++) { 130 int index = y; 131 int offset = y * width; 132 133 for (int x = 0; x < width; x++) { 134 a = r = g = b = 0.0f; 135 136 for (int i = -radius; i <= radius; i++) { 137 int subOffset = x + i; 138 if (subOffset < 0 || subOffset >= width) { 139 subOffset = (x + width) % width; 140 } 141 142 int pixel = srcPixels[offset + subOffset]; 143 float blurFactor = kernel[radius + i]; 144 145 a += blurFactor * ((pixel >> 24) & 0xFF); 146 r += blurFactor * ((pixel >> 16) & 0xFF); 147 g += blurFactor * ((pixel >> 8) & 0xFF); 148 b += blurFactor * ((pixel) & 0xFF); 149 } 150 151 ca = (int) (a + 0.5f); 152 cr = (int) (r + 0.5f); 153 cg = (int) (g + 0.5f); 154 cb = (int) (b + 0.5f); 155 156 dstPixels[index] = ((ca > 255 ? 255 : ca) << 24) | 157 ((cr > 255 ? 255 : cr) << 16) | 158 ((cg > 255 ? 255 : cg) << 8) | 159 (cb > 255 ? 255 : cb); 160 index += height; 161 } 162 } 163 } 164 165 /** 166 * <p>Blurs the source pixels into the destination pixels. The force of the blur is specified by the radius which 167 * must be greater than 0.</p> <p>The source and destination pixels arrays are expected to be in the BYTE_GREY 168 * format.</p> <p>After this method is executed, dstPixels contains a transposed and filtered copy of 169 * srcPixels.</p> 170 * 171 * @param srcPixels the source pixels 172 * @param dstPixels the destination pixels 173 * @param width the width of the source picture 174 * @param height the height of the source picture 175 * @param kernel the kernel of the blur effect 176 * @param radius the radius of the blur effect 177 */ blur(byte[] srcPixels, byte[] dstPixels, int width, int height, float[] kernel, int radius)178 static void blur(byte[] srcPixels, byte[] dstPixels, 179 int width, int height, 180 float[] kernel, int radius) { 181 float p; 182 int cp; 183 for (int y = 0; y < height; y++) { 184 int index = y; 185 int offset = y * width; 186 for (int x = 0; x < width; x++) { 187 p = 0.0f; 188 for (int i = -radius; i <= radius; i++) { 189 int subOffset = x + i; 190 // if (subOffset < 0) subOffset = 0; 191 // if (subOffset >= width) subOffset = width-1; 192 if (subOffset < 0 || subOffset >= width) { 193 subOffset = (x + width) % width; 194 } 195 int pixel = srcPixels[offset + subOffset] & 0xFF; 196 float blurFactor = kernel[radius + i]; 197 p += blurFactor * pixel; 198 } 199 cp = (int) (p + 0.5f); 200 dstPixels[index] = (byte) (cp > 255 ? 255 : cp); 201 index += height; 202 } 203 } 204 } 205 createGaussianKernel(int radius)206 static float[] createGaussianKernel(int radius) { 207 if (radius < 1) { 208 throw new IllegalArgumentException("Radius must be >= 1"); 209 } 210 211 float[] data = new float[radius * 2 + 1]; 212 213 float sigma = radius / 3.0f; 214 float twoSigmaSquare = 2.0f * sigma * sigma; 215 float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI); 216 float total = 0.0f; 217 218 for (int i = -radius; i <= radius; i++) { 219 float distance = i * i; 220 int index = i + radius; 221 data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot; 222 total += data[index]; 223 } 224 225 for (int i = 0; i < data.length; i++) { 226 data[i] /= total; 227 } 228 229 return data; 230 } 231 232 // ================================================================================================================= 233 // Get/Set Pixels helper methods 234 235 /** 236 * <p>Returns an array of pixels, stored as integers, from a <code>BufferedImage</code>. The pixels are grabbed from 237 * a rectangular area defined by a location and two dimensions. Calling this method on an image of type different 238 * from <code>BufferedImage.TYPE_INT_ARGB</code> and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the 239 * image.</p> 240 * 241 * @param img the source image 242 * @param x the x location at which to start grabbing pixels 243 * @param y the y location at which to start grabbing pixels 244 * @param w the width of the rectangle of pixels to grab 245 * @param h the height of the rectangle of pixels to grab 246 * @param pixels a pre-allocated array of pixels of size w*h; can be null 247 * @return <code>pixels</code> if non-null, a new array of integers otherwise 248 * @throws IllegalArgumentException is <code>pixels</code> is non-null and of length < w*h 249 */ getPixels(BufferedImage img, int x, int y, int w, int h, byte[] pixels)250 static byte[] getPixels(BufferedImage img, 251 int x, int y, int w, int h, byte[] pixels) { 252 if (w == 0 || h == 0) { 253 return new byte[0]; 254 } 255 256 if (pixels == null) { 257 pixels = new byte[w * h]; 258 } else if (pixels.length < w * h) { 259 throw new IllegalArgumentException("pixels array must have a length >= w*h"); 260 } 261 262 int imageType = img.getType(); 263 if (imageType == BufferedImage.TYPE_BYTE_GRAY) { 264 Raster raster = img.getRaster(); 265 return (byte[]) raster.getDataElements(x, y, w, h, pixels); 266 } else { 267 throw new IllegalArgumentException("Only type BYTE_GRAY is supported"); 268 } 269 } 270 271 /** 272 * <p>Writes a rectangular area of pixels in the destination <code>BufferedImage</code>. Calling this method on an 273 * image of type different from <code>BufferedImage.TYPE_INT_ARGB</code> and <code>BufferedImage.TYPE_INT_RGB</code> 274 * will unmanage the image.</p> 275 * 276 * @param img the destination image 277 * @param x the x location at which to start storing pixels 278 * @param y the y location at which to start storing pixels 279 * @param w the width of the rectangle of pixels to store 280 * @param h the height of the rectangle of pixels to store 281 * @param pixels an array of pixels, stored as integers 282 * @throws IllegalArgumentException is <code>pixels</code> is non-null and of length < w*h 283 */ setPixels(BufferedImage img, int x, int y, int w, int h, byte[] pixels)284 static void setPixels(BufferedImage img, 285 int x, int y, int w, int h, byte[] pixels) { 286 if (pixels == null || w == 0 || h == 0) { 287 return; 288 } else if (pixels.length < w * h) { 289 throw new IllegalArgumentException("pixels array must have a length >= w*h"); 290 } 291 int imageType = img.getType(); 292 if (imageType == BufferedImage.TYPE_BYTE_GRAY) { 293 WritableRaster raster = img.getRaster(); 294 raster.setDataElements(x, y, w, h, pixels); 295 } else { 296 throw new IllegalArgumentException("Only type BYTE_GRAY is supported"); 297 } 298 } 299 300 /** 301 * <p>Returns an array of pixels, stored as integers, from a 302 * <code>BufferedImage</code>. The pixels are grabbed from a rectangular 303 * area defined by a location and two dimensions. Calling this method on 304 * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code> 305 * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p> 306 * 307 * @param img the source image 308 * @param x the x location at which to start grabbing pixels 309 * @param y the y location at which to start grabbing pixels 310 * @param w the width of the rectangle of pixels to grab 311 * @param h the height of the rectangle of pixels to grab 312 * @param pixels a pre-allocated array of pixels of size w*h; can be null 313 * @return <code>pixels</code> if non-null, a new array of integers 314 * otherwise 315 * @throws IllegalArgumentException is <code>pixels</code> is non-null and 316 * of length < w*h 317 */ getPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels)318 public static int[] getPixels(BufferedImage img, 319 int x, int y, int w, int h, int[] pixels) { 320 if (w == 0 || h == 0) { 321 return new int[0]; 322 } 323 324 if (pixels == null) { 325 pixels = new int[w * h]; 326 } else if (pixels.length < w * h) { 327 throw new IllegalArgumentException("pixels array must have a length" + 328 " >= w*h"); 329 } 330 331 int imageType = img.getType(); 332 if (imageType == BufferedImage.TYPE_INT_ARGB || 333 imageType == BufferedImage.TYPE_INT_RGB) { 334 Raster raster = img.getRaster(); 335 return (int[]) raster.getDataElements(x, y, w, h, pixels); 336 } 337 338 // Unmanages the image 339 return img.getRGB(x, y, w, h, pixels, 0, w); 340 } 341 342 /** 343 * <p>Writes a rectangular area of pixels in the destination 344 * <code>BufferedImage</code>. Calling this method on 345 * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code> 346 * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p> 347 * 348 * @param img the destination image 349 * @param x the x location at which to start storing pixels 350 * @param y the y location at which to start storing pixels 351 * @param w the width of the rectangle of pixels to store 352 * @param h the height of the rectangle of pixels to store 353 * @param pixels an array of pixels, stored as integers 354 * @throws IllegalArgumentException is <code>pixels</code> is non-null and 355 * of length < w*h 356 */ setPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels)357 public static void setPixels(BufferedImage img, 358 int x, int y, int w, int h, int[] pixels) { 359 if (pixels == null || w == 0 || h == 0) { 360 return; 361 } else if (pixels.length < w * h) { 362 throw new IllegalArgumentException("pixels array must have a length" + 363 " >= w*h"); 364 } 365 366 int imageType = img.getType(); 367 if (imageType == BufferedImage.TYPE_INT_ARGB || 368 imageType == BufferedImage.TYPE_INT_RGB) { 369 WritableRaster raster = img.getRaster(); 370 raster.setDataElements(x, y, w, h, pixels); 371 } else { 372 // Unmanages the image 373 img.setRGB(x, y, w, h, pixels, 0, w); 374 } 375 } 376 377 /** 378 * <p>Returns a new <code>BufferedImage</code> using the same color model 379 * as the image passed as a parameter. The returned image is only compatible 380 * with the image passed as a parameter. This does not mean the returned 381 * image is compatible with the hardware.</p> 382 * 383 * @param image the reference image from which the color model of the new 384 * image is obtained 385 * @return a new <code>BufferedImage</code>, compatible with the color model 386 * of <code>image</code> 387 */ createColorModelCompatibleImage(BufferedImage image)388 public static BufferedImage createColorModelCompatibleImage(BufferedImage image) { 389 ColorModel cm = image.getColorModel(); 390 return new BufferedImage(cm, 391 cm.createCompatibleWritableRaster(image.getWidth(), 392 image.getHeight()), 393 cm.isAlphaPremultiplied(), null); 394 } 395 396 /** 397 * <p>Returns a new translucent compatible image of the specified width and 398 * height. That is, the returned <code>BufferedImage</code> is compatible with 399 * the graphics hardware. If the method is called in a headless 400 * environment, then the returned BufferedImage will be compatible with 401 * the source image.</p> 402 * 403 * @param width the width of the new image 404 * @param height the height of the new image 405 * @return a new translucent compatible <code>BufferedImage</code> of the 406 * specified width and height 407 */ createCompatibleTranslucentImage(int width, int height)408 public static BufferedImage createCompatibleTranslucentImage(int width, 409 int height) { 410 return isHeadless() ? 411 new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) : 412 getGraphicsConfiguration().createCompatibleImage(width, height, 413 Transparency.TRANSLUCENT); 414 } 415 isHeadless()416 private static boolean isHeadless() { 417 return GraphicsEnvironment.isHeadless(); 418 } 419 420 // Returns the graphics configuration for the primary screen getGraphicsConfiguration()421 private static GraphicsConfiguration getGraphicsConfiguration() { 422 return GraphicsEnvironment.getLocalGraphicsEnvironment(). 423 getDefaultScreenDevice().getDefaultConfiguration(); 424 } 425 426 } 427