1 /* 2 * Copyright (c) 1997, 2013, 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 26 package java.awt.image; 27 28 import java.awt.color.ICC_Profile; 29 import java.awt.geom.Rectangle2D; 30 import java.awt.Rectangle; 31 import java.awt.RenderingHints; 32 import java.awt.geom.Point2D; 33 import java.lang.annotation.Native; 34 import sun.awt.image.ImagingLib; 35 36 /** 37 * This class implements a convolution from the source 38 * to the destination. 39 * Convolution using a convolution kernel is a spatial operation that 40 * computes the output pixel from an input pixel by multiplying the kernel 41 * with the surround of the input pixel. 42 * This allows the output pixel to be affected by the immediate neighborhood 43 * in a way that can be mathematically specified with a kernel. 44 *<p> 45 * This class operates with BufferedImage data in which color components are 46 * premultiplied with the alpha component. If the Source BufferedImage has 47 * an alpha component, and the color components are not premultiplied with 48 * the alpha component, then the data are premultiplied before being 49 * convolved. If the Destination has color components which are not 50 * premultiplied, then alpha is divided out before storing into the 51 * Destination (if alpha is 0, the color components are set to 0). If the 52 * Destination has no alpha component, then the resulting alpha is discarded 53 * after first dividing it out of the color components. 54 * <p> 55 * Rasters are treated as having no alpha channel. If the above treatment 56 * of the alpha channel in BufferedImages is not desired, it may be avoided 57 * by getting the Raster of a source BufferedImage and using the filter method 58 * of this class which works with Rasters. 59 * <p> 60 * If a RenderingHints object is specified in the constructor, the 61 * color rendering hint and the dithering hint may be used when color 62 * conversion is required. 63 *<p> 64 * Note that the Source and the Destination may not be the same object. 65 * @see Kernel 66 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING 67 * @see java.awt.RenderingHints#KEY_DITHERING 68 */ 69 public class ConvolveOp implements BufferedImageOp, RasterOp { 70 Kernel kernel; 71 int edgeHint; 72 RenderingHints hints; 73 /** 74 * Edge condition constants. 75 */ 76 77 /** 78 * Pixels at the edge of the destination image are set to zero. This 79 * is the default. 80 */ 81 82 @Native public static final int EDGE_ZERO_FILL = 0; 83 84 /** 85 * Pixels at the edge of the source image are copied to 86 * the corresponding pixels in the destination without modification. 87 */ 88 @Native public static final int EDGE_NO_OP = 1; 89 90 /** 91 * Constructs a ConvolveOp given a Kernel, an edge condition, and a 92 * RenderingHints object (which may be null). 93 * @param kernel the specified <code>Kernel</code> 94 * @param edgeCondition the specified edge condition 95 * @param hints the specified <code>RenderingHints</code> object 96 * @see Kernel 97 * @see #EDGE_NO_OP 98 * @see #EDGE_ZERO_FILL 99 * @see java.awt.RenderingHints 100 */ 101 public ConvolveOp(Kernel kernel, int edgeCondition, RenderingHints hints) { 102 this.kernel = kernel; 103 this.edgeHint = edgeCondition; 104 this.hints = hints; 105 } 106 107 /** 108 * Constructs a ConvolveOp given a Kernel. The edge condition 109 * will be EDGE_ZERO_FILL. 110 * @param kernel the specified <code>Kernel</code> 111 * @see Kernel 112 * @see #EDGE_ZERO_FILL 113 */ 114 public ConvolveOp(Kernel kernel) { 115 this.kernel = kernel; 116 this.edgeHint = EDGE_ZERO_FILL; 117 } 118 119 /** 120 * Returns the edge condition. 121 * @return the edge condition of this <code>ConvolveOp</code>. 122 * @see #EDGE_NO_OP 123 * @see #EDGE_ZERO_FILL 124 */ 125 public int getEdgeCondition() { 126 return edgeHint; 127 } 128 129 /** 130 * Returns the Kernel. 131 * @return the <code>Kernel</code> of this <code>ConvolveOp</code>. 132 */ 133 public final Kernel getKernel() { 134 return (Kernel) kernel.clone(); 135 } 136 137 /** 138 * Performs a convolution on BufferedImages. Each component of the 139 * source image will be convolved (including the alpha component, if 140 * present). 141 * If the color model in the source image is not the same as that 142 * in the destination image, the pixels will be converted 143 * in the destination. If the destination image is null, 144 * a BufferedImage will be created with the source ColorModel. 145 * The IllegalArgumentException may be thrown if the source is the 146 * same as the destination. 147 * @param src the source <code>BufferedImage</code> to filter 148 * @param dst the destination <code>BufferedImage</code> for the 149 * filtered <code>src</code> 150 * @return the filtered <code>BufferedImage</code> 151 * @throws NullPointerException if <code>src</code> is <code>null</code> 152 * @throws IllegalArgumentException if <code>src</code> equals 153 * <code>dst</code> 154 * @throws ImagingOpException if <code>src</code> cannot be filtered 155 */ 156 public final BufferedImage filter (BufferedImage src, BufferedImage dst) { 157 if (src == null) { 158 throw new NullPointerException("src image is null"); 159 } 160 if (src == dst) { 161 throw new IllegalArgumentException("src image cannot be the "+ 162 "same as the dst image"); 163 } 164 165 boolean needToConvert = false; 166 ColorModel srcCM = src.getColorModel(); 167 ColorModel dstCM; 168 BufferedImage origDst = dst; 169 170 // Can't convolve an IndexColorModel. Need to expand it 171 if (srcCM instanceof IndexColorModel) { 172 IndexColorModel icm = (IndexColorModel) srcCM; 173 src = icm.convertToIntDiscrete(src.getRaster(), false); 174 srcCM = src.getColorModel(); 175 } 176 177 if (dst == null) { 178 dst = createCompatibleDestImage(src, null); 179 dstCM = srcCM; 180 origDst = dst; 181 } 182 else { 183 dstCM = dst.getColorModel(); 184 if (srcCM.getColorSpace().getType() != 185 dstCM.getColorSpace().getType()) 186 { 187 needToConvert = true; 188 dst = createCompatibleDestImage(src, null); 189 dstCM = dst.getColorModel(); 190 } 191 else if (dstCM instanceof IndexColorModel) { 192 dst = createCompatibleDestImage(src, null); 193 dstCM = dst.getColorModel(); 194 } 195 } 196 197 if (ImagingLib.filter(this, src, dst) == null) { 198 throw new ImagingOpException ("Unable to convolve src image"); 199 } 200 201 if (needToConvert) { 202 ColorConvertOp ccop = new ColorConvertOp(hints); 203 ccop.filter(dst, origDst); 204 } 205 else if (origDst != dst) { 206 java.awt.Graphics2D g = origDst.createGraphics(); 207 try { 208 g.drawImage(dst, 0, 0, null); 209 } finally { 210 g.dispose(); 211 } 212 } 213 214 return origDst; 215 } 216 217 /** 218 * Performs a convolution on Rasters. Each band of the source Raster 219 * will be convolved. 220 * The source and destination must have the same number of bands. 221 * If the destination Raster is null, a new Raster will be created. 222 * The IllegalArgumentException may be thrown if the source is 223 * the same as the destination. 224 * @param src the source <code>Raster</code> to filter 225 * @param dst the destination <code>WritableRaster</code> for the 226 * filtered <code>src</code> 227 * @return the filtered <code>WritableRaster</code> 228 * @throws NullPointerException if <code>src</code> is <code>null</code> 229 * @throws ImagingOpException if <code>src</code> and <code>dst</code> 230 * do not have the same number of bands 231 * @throws ImagingOpException if <code>src</code> cannot be filtered 232 * @throws IllegalArgumentException if <code>src</code> equals 233 * <code>dst</code> 234 */ 235 public final WritableRaster filter (Raster src, WritableRaster dst) { 236 if (dst == null) { 237 dst = createCompatibleDestRaster(src); 238 } 239 else if (src == dst) { 240 throw new IllegalArgumentException("src image cannot be the "+ 241 "same as the dst image"); 242 } 243 else if (src.getNumBands() != dst.getNumBands()) { 244 throw new ImagingOpException("Different number of bands in src "+ 245 " and dst Rasters"); 246 } 247 248 if (ImagingLib.filter(this, src, dst) == null) { 249 throw new ImagingOpException ("Unable to convolve src image"); 250 } 251 252 return dst; 253 } 254 255 /** 256 * Creates a zeroed destination image with the correct size and number 257 * of bands. If destCM is null, an appropriate ColorModel will be used. 258 * @param src Source image for the filter operation. 259 * @param destCM ColorModel of the destination. Can be null. 260 * @return a destination <code>BufferedImage</code> with the correct 261 * size and number of bands. 262 */ 263 public BufferedImage createCompatibleDestImage(BufferedImage src, 264 ColorModel destCM) { 265 BufferedImage image; 266 267 int w = src.getWidth(); 268 int h = src.getHeight(); 269 270 WritableRaster wr = null; 271 272 if (destCM == null) { 273 destCM = src.getColorModel(); 274 // Not much support for ICM 275 if (destCM instanceof IndexColorModel) { 276 destCM = ColorModel.getRGBdefault(); 277 } else { 278 /* Create destination image as similar to the source 279 * as it possible... 280 */ 281 wr = src.getData().createCompatibleWritableRaster(w, h); 282 } 283 } 284 285 if (wr == null) { 286 /* This is the case when destination color model 287 * was explicitly specified (and it may be not compatible 288 * with source raster structure) or source is indexed image. 289 * We should use destination color model to create compatible 290 * destination raster here. 291 */ 292 wr = destCM.createCompatibleWritableRaster(w, h); 293 } 294 295 image = new BufferedImage (destCM, wr, 296 destCM.isAlphaPremultiplied(), null); 297 298 return image; 299 } 300 301 /** 302 * Creates a zeroed destination Raster with the correct size and number 303 * of bands, given this source. 304 */ 305 public WritableRaster createCompatibleDestRaster(Raster src) { 306 return src.createCompatibleWritableRaster(); 307 } 308 309 /** 310 * Returns the bounding box of the filtered destination image. Since 311 * this is not a geometric operation, the bounding box does not 312 * change. 313 */ 314 public final Rectangle2D getBounds2D(BufferedImage src) { 315 return getBounds2D(src.getRaster()); 316 } 317 318 /** 319 * Returns the bounding box of the filtered destination Raster. Since 320 * this is not a geometric operation, the bounding box does not 321 * change. 322 */ 323 public final Rectangle2D getBounds2D(Raster src) { 324 return src.getBounds(); 325 } 326 327 /** 328 * Returns the location of the destination point given a 329 * point in the source. If dstPt is non-null, it will 330 * be used to hold the return value. Since this is not a geometric 331 * operation, the srcPt will equal the dstPt. 332 */ 333 public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { 334 if (dstPt == null) { 335 dstPt = new Point2D.Float(); 336 } 337 dstPt.setLocation(srcPt.getX(), srcPt.getY()); 338 339 return dstPt; 340 } 341 342 /** 343 * Returns the rendering hints for this op. 344 */ 345 public final RenderingHints getRenderingHints() { 346 return hints; 347 } 348 }