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
26package java.awt.image;
27
28import java.awt.color.ICC_Profile;
29import java.awt.geom.Rectangle2D;
30import java.awt.Rectangle;
31import java.awt.RenderingHints;
32import java.awt.geom.Point2D;
33import java.lang.annotation.Native;
34import 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 */
69public 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}
94     * @param edgeCondition the specified edge condition
95     * @param hints the specified {@code RenderingHints} 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}
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}.
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} of this {@code ConvolveOp}.
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} to filter
148     * @param dst the destination {@code BufferedImage} for the
149     *        filtered {@code src}
150     * @return the filtered {@code BufferedImage}
151     * @throws NullPointerException if {@code src} is {@code null}
152     * @throws IllegalArgumentException if {@code src} equals
153     *         {@code dst}
154     * @throws ImagingOpException if {@code src} 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} to filter
225     * @param dst the destination {@code WritableRaster} for the
226     *        filtered {@code src}
227     * @return the filtered {@code WritableRaster}
228     * @throws NullPointerException if {@code src} is {@code null}
229     * @throws ImagingOpException if {@code src} and {@code dst}
230     *         do not have the same number of bands
231     * @throws ImagingOpException if {@code src} cannot be filtered
232     * @throws IllegalArgumentException if {@code src} equals
233     *         {@code dst}
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} 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}
349