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 }