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