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