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 }