001 /*
002 * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.awt.image;
027
028 import java.awt.color.ICC_Profile;
029 import java.awt.geom.Rectangle2D;
030 import java.awt.Rectangle;
031 import java.awt.RenderingHints;
032 import java.awt.geom.Point2D;
033 import sun.awt.image.ImagingLib;
034
035 /**
036 * This class implements a convolution from the source
037 * to the destination.
038 * Convolution using a convolution kernel is a spatial operation that
039 * computes the output pixel from an input pixel by multiplying the kernel
040 * with the surround of the input pixel.
041 * This allows the output pixel to be affected by the immediate neighborhood
042 * in a way that can be mathematically specified with a kernel.
043 *<p>
044 * This class operates with BufferedImage data in which color components are
045 * premultiplied with the alpha component. If the Source BufferedImage has
046 * an alpha component, and the color components are not premultiplied with
047 * the alpha component, then the data are premultiplied before being
048 * convolved. If the Destination has color components which are not
049 * premultiplied, then alpha is divided out before storing into the
050 * Destination (if alpha is 0, the color components are set to 0). If the
051 * Destination has no alpha component, then the resulting alpha is discarded
052 * after first dividing it out of the color components.
053 * <p>
054 * Rasters are treated as having no alpha channel. If the above treatment
055 * of the alpha channel in BufferedImages is not desired, it may be avoided
056 * by getting the Raster of a source BufferedImage and using the filter method
057 * of this class which works with Rasters.
058 * <p>
059 * If a RenderingHints object is specified in the constructor, the
060 * color rendering hint and the dithering hint may be used when color
061 * conversion is required.
062 *<p>
063 * Note that the Source and the Destination may not be the same object.
064 * @version 10 Feb 1997
065 * @see Kernel
066 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
067 * @see java.awt.RenderingHints#KEY_DITHERING
068 */
069 public class ConvolveOp implements BufferedImageOp, RasterOp {
070 Kernel kernel;
071 int edgeHint;
072 RenderingHints hints;
073 /**
074 * Edge condition constants.
075 */
076
077 /**
078 * Pixels at the edge of the destination image are set to zero. This
079 * is the default.
080 */
081
082 public static final int EDGE_ZERO_FILL = 0;
083
084 /**
085 * Pixels at the edge of the source image are copied to
086 * the corresponding pixels in the destination without modification.
087 */
088 public static final int EDGE_NO_OP = 1;
089
090 /**
091 * Constructs a ConvolveOp given a Kernel, an edge condition, and a
092 * RenderingHints object (which may be null).
093 * @param kernel the specified <code>Kernel</code>
094 * @param edgeCondition the specified edge condition
095 * @param hints the specified <code>RenderingHints</code> object
096 * @see Kernel
097 * @see #EDGE_NO_OP
098 * @see #EDGE_ZERO_FILL
099 * @see java.awt.RenderingHints
100 */
101 public ConvolveOp(Kernel kernel, int edgeCondition,
102 RenderingHints hints) {
103 this .kernel = kernel;
104 this .edgeHint = edgeCondition;
105 this .hints = hints;
106 }
107
108 /**
109 * Constructs a ConvolveOp given a Kernel. The edge condition
110 * will be EDGE_ZERO_FILL.
111 * @param kernel the specified <code>Kernel</code>
112 * @see Kernel
113 * @see #EDGE_ZERO_FILL
114 */
115 public ConvolveOp(Kernel kernel) {
116 this .kernel = kernel;
117 this .edgeHint = EDGE_ZERO_FILL;
118 }
119
120 /**
121 * Returns the edge condition.
122 * @return the edge condition of this <code>ConvolveOp</code>.
123 * @see #EDGE_NO_OP
124 * @see #EDGE_ZERO_FILL
125 */
126 public int getEdgeCondition() {
127 return edgeHint;
128 }
129
130 /**
131 * Returns the Kernel.
132 * @return the <code>Kernel</code> of this <code>ConvolveOp</code>.
133 */
134 public final Kernel getKernel() {
135 return (Kernel) kernel.clone();
136 }
137
138 /**
139 * Performs a convolution on BufferedImages. Each component of the
140 * source image will be convolved (including the alpha component, if
141 * present).
142 * If the color model in the source image is not the same as that
143 * in the destination image, the pixels will be converted
144 * in the destination. If the destination image is null,
145 * a BufferedImage will be created with the source ColorModel.
146 * The IllegalArgumentException may be thrown if the source is the
147 * same as the destination.
148 * @param src the source <code>BufferedImage</code> to filter
149 * @param dst the destination <code>BufferedImage</code> for the
150 * filtered <code>src</code>
151 * @return the filtered <code>BufferedImage</code>
152 * @throws NullPointerException if <code>src</code> is <code>null</code>
153 * @throws IllegalArgumentException if <code>src</code> equals
154 * <code>dst</code>
155 * @throws ImagingOpException if <code>src</code> cannot be filtered
156 */
157 public final BufferedImage filter(BufferedImage src,
158 BufferedImage dst) {
159 if (src == null) {
160 throw new NullPointerException("src image is null");
161 }
162 if (src == dst) {
163 throw new IllegalArgumentException(
164 "src image cannot be the "
165 + "same as the dst image");
166 }
167
168 boolean needToConvert = false;
169 ColorModel srcCM = src.getColorModel();
170 ColorModel dstCM;
171 BufferedImage origDst = dst;
172
173 // Can't convolve an IndexColorModel. Need to expand it
174 if (srcCM instanceof IndexColorModel) {
175 IndexColorModel icm = (IndexColorModel) srcCM;
176 src = icm.convertToIntDiscrete(src.getRaster(), false);
177 srcCM = src.getColorModel();
178 }
179
180 if (dst == null) {
181 dst = createCompatibleDestImage(src, null);
182 dstCM = srcCM;
183 origDst = dst;
184 } else {
185 dstCM = dst.getColorModel();
186 if (srcCM.getColorSpace().getType() != dstCM
187 .getColorSpace().getType()) {
188 needToConvert = true;
189 dst = createCompatibleDestImage(src, null);
190 dstCM = dst.getColorModel();
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 } 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 } else if (src == dst) {
238 throw new IllegalArgumentException(
239 "src image cannot be the "
240 + "same as the dst image");
241 } else if (src.getNumBands() != dst.getNumBands()) {
242 throw new ImagingOpException(
243 "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 if (destCM == null) {
266 destCM = src.getColorModel();
267 // Not much support for ICM
268 if (destCM instanceof IndexColorModel) {
269 destCM = ColorModel.getRGBdefault();
270 }
271 }
272
273 int w = src.getWidth();
274 int h = src.getHeight();
275 image = new BufferedImage(destCM, destCM
276 .createCompatibleWritableRaster(w, h), destCM
277 .isAlphaPremultiplied(), null);
278
279 return image;
280 }
281
282 /**
283 * Creates a zeroed destination Raster with the correct size and number
284 * of bands, given this source.
285 */
286 public WritableRaster createCompatibleDestRaster(Raster src) {
287 return src.createCompatibleWritableRaster();
288 }
289
290 /**
291 * Returns the bounding box of the filtered destination image. Since
292 * this is not a geometric operation, the bounding box does not
293 * change.
294 */
295 public final Rectangle2D getBounds2D(BufferedImage src) {
296 return getBounds2D(src.getRaster());
297 }
298
299 /**
300 * Returns the bounding box of the filtered destination Raster. Since
301 * this is not a geometric operation, the bounding box does not
302 * change.
303 */
304 public final Rectangle2D getBounds2D(Raster src) {
305 return src.getBounds();
306 }
307
308 /**
309 * Returns the location of the destination point given a
310 * point in the source. If dstPt is non-null, it will
311 * be used to hold the return value. Since this is not a geometric
312 * operation, the srcPt will equal the dstPt.
313 */
314 public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
315 if (dstPt == null) {
316 dstPt = new Point2D.Float();
317 }
318 dstPt.setLocation(srcPt.getX(), srcPt.getY());
319
320 return dstPt;
321 }
322
323 /**
324 * Returns the rendering hints for this op.
325 */
326 public final RenderingHints getRenderingHints() {
327 return hints;
328 }
329 }
|