blob: 178dbc86f4081c11df0f14482eb7d442390e0f83 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
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 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 */
68public 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}