| /* |
| * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package java.awt.image; |
| |
| import java.awt.color.ICC_Profile; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.Rectangle; |
| import java.awt.RenderingHints; |
| import java.awt.geom.Point2D; |
| import java.lang.annotation.Native; |
| import sun.awt.image.ImagingLib; |
| |
| /** |
| * This class implements a convolution from the source |
| * to the destination. |
| * Convolution using a convolution kernel is a spatial operation that |
| * computes the output pixel from an input pixel by multiplying the kernel |
| * with the surround of the input pixel. |
| * This allows the output pixel to be affected by the immediate neighborhood |
| * in a way that can be mathematically specified with a kernel. |
| *<p> |
| * This class operates with BufferedImage data in which color components are |
| * premultiplied with the alpha component. If the Source BufferedImage has |
| * an alpha component, and the color components are not premultiplied with |
| * the alpha component, then the data are premultiplied before being |
| * convolved. If the Destination has color components which are not |
| * premultiplied, then alpha is divided out before storing into the |
| * Destination (if alpha is 0, the color components are set to 0). If the |
| * Destination has no alpha component, then the resulting alpha is discarded |
| * after first dividing it out of the color components. |
| * <p> |
| * Rasters are treated as having no alpha channel. If the above treatment |
| * of the alpha channel in BufferedImages is not desired, it may be avoided |
| * by getting the Raster of a source BufferedImage and using the filter method |
| * of this class which works with Rasters. |
| * <p> |
| * If a RenderingHints object is specified in the constructor, the |
| * color rendering hint and the dithering hint may be used when color |
| * conversion is required. |
| *<p> |
| * Note that the Source and the Destination may not be the same object. |
| * @see Kernel |
| * @see java.awt.RenderingHints#KEY_COLOR_RENDERING |
| * @see java.awt.RenderingHints#KEY_DITHERING |
| */ |
| public class ConvolveOp implements BufferedImageOp, RasterOp { |
| Kernel kernel; |
| int edgeHint; |
| RenderingHints hints; |
| /** |
| * Edge condition constants. |
| */ |
| |
| /** |
| * Pixels at the edge of the destination image are set to zero. This |
| * is the default. |
| */ |
| |
| @Native public static final int EDGE_ZERO_FILL = 0; |
| |
| /** |
| * Pixels at the edge of the source image are copied to |
| * the corresponding pixels in the destination without modification. |
| */ |
| @Native public static final int EDGE_NO_OP = 1; |
| |
| /** |
| * Constructs a ConvolveOp given a Kernel, an edge condition, and a |
| * RenderingHints object (which may be null). |
| * @param kernel the specified {@code Kernel} |
| * @param edgeCondition the specified edge condition |
| * @param hints the specified {@code RenderingHints} object |
| * @see Kernel |
| * @see #EDGE_NO_OP |
| * @see #EDGE_ZERO_FILL |
| * @see java.awt.RenderingHints |
| */ |
| public ConvolveOp(Kernel kernel, int edgeCondition, RenderingHints hints) { |
| this.kernel = kernel; |
| this.edgeHint = edgeCondition; |
| this.hints = hints; |
| } |
| |
| /** |
| * Constructs a ConvolveOp given a Kernel. The edge condition |
| * will be EDGE_ZERO_FILL. |
| * @param kernel the specified {@code Kernel} |
| * @see Kernel |
| * @see #EDGE_ZERO_FILL |
| */ |
| public ConvolveOp(Kernel kernel) { |
| this.kernel = kernel; |
| this.edgeHint = EDGE_ZERO_FILL; |
| } |
| |
| /** |
| * Returns the edge condition. |
| * @return the edge condition of this {@code ConvolveOp}. |
| * @see #EDGE_NO_OP |
| * @see #EDGE_ZERO_FILL |
| */ |
| public int getEdgeCondition() { |
| return edgeHint; |
| } |
| |
| /** |
| * Returns the Kernel. |
| * @return the {@code Kernel} of this {@code ConvolveOp}. |
| */ |
| public final Kernel getKernel() { |
| return (Kernel) kernel.clone(); |
| } |
| |
| /** |
| * Performs a convolution on BufferedImages. Each component of the |
| * source image will be convolved (including the alpha component, if |
| * present). |
| * If the color model in the source image is not the same as that |
| * in the destination image, the pixels will be converted |
| * in the destination. If the destination image is null, |
| * a BufferedImage will be created with the source ColorModel. |
| * The IllegalArgumentException may be thrown if the source is the |
| * same as the destination. |
| * @param src the source {@code BufferedImage} to filter |
| * @param dst the destination {@code BufferedImage} for the |
| * filtered {@code src} |
| * @return the filtered {@code BufferedImage} |
| * @throws NullPointerException if {@code src} is {@code null} |
| * @throws IllegalArgumentException if {@code src} equals |
| * {@code dst} |
| * @throws ImagingOpException if {@code src} cannot be filtered |
| */ |
| public final BufferedImage filter (BufferedImage src, BufferedImage dst) { |
| if (src == null) { |
| throw new NullPointerException("src image is null"); |
| } |
| if (src == dst) { |
| throw new IllegalArgumentException("src image cannot be the "+ |
| "same as the dst image"); |
| } |
| |
| boolean needToConvert = false; |
| ColorModel srcCM = src.getColorModel(); |
| ColorModel dstCM; |
| BufferedImage origDst = dst; |
| |
| // Can't convolve an IndexColorModel. Need to expand it |
| if (srcCM instanceof IndexColorModel) { |
| IndexColorModel icm = (IndexColorModel) srcCM; |
| src = icm.convertToIntDiscrete(src.getRaster(), false); |
| srcCM = src.getColorModel(); |
| } |
| |
| if (dst == null) { |
| dst = createCompatibleDestImage(src, null); |
| dstCM = srcCM; |
| origDst = dst; |
| } |
| else { |
| dstCM = dst.getColorModel(); |
| if (srcCM.getColorSpace().getType() != |
| dstCM.getColorSpace().getType()) |
| { |
| needToConvert = true; |
| dst = createCompatibleDestImage(src, null); |
| dstCM = dst.getColorModel(); |
| } |
| else if (dstCM instanceof IndexColorModel) { |
| dst = createCompatibleDestImage(src, null); |
| dstCM = dst.getColorModel(); |
| } |
| } |
| |
| if (ImagingLib.filter(this, src, dst) == null) { |
| throw new ImagingOpException ("Unable to convolve src image"); |
| } |
| |
| if (needToConvert) { |
| ColorConvertOp ccop = new ColorConvertOp(hints); |
| ccop.filter(dst, origDst); |
| } |
| else if (origDst != dst) { |
| java.awt.Graphics2D g = origDst.createGraphics(); |
| try { |
| g.drawImage(dst, 0, 0, null); |
| } finally { |
| g.dispose(); |
| } |
| } |
| |
| return origDst; |
| } |
| |
| /** |
| * Performs a convolution on Rasters. Each band of the source Raster |
| * will be convolved. |
| * The source and destination must have the same number of bands. |
| * If the destination Raster is null, a new Raster will be created. |
| * The IllegalArgumentException may be thrown if the source is |
| * the same as the destination. |
| * @param src the source {@code Raster} to filter |
| * @param dst the destination {@code WritableRaster} for the |
| * filtered {@code src} |
| * @return the filtered {@code WritableRaster} |
| * @throws NullPointerException if {@code src} is {@code null} |
| * @throws ImagingOpException if {@code src} and {@code dst} |
| * do not have the same number of bands |
| * @throws ImagingOpException if {@code src} cannot be filtered |
| * @throws IllegalArgumentException if {@code src} equals |
| * {@code dst} |
| */ |
| public final WritableRaster filter (Raster src, WritableRaster dst) { |
| if (dst == null) { |
| dst = createCompatibleDestRaster(src); |
| } |
| else if (src == dst) { |
| throw new IllegalArgumentException("src image cannot be the "+ |
| "same as the dst image"); |
| } |
| else if (src.getNumBands() != dst.getNumBands()) { |
| throw new ImagingOpException("Different number of bands in src "+ |
| " and dst Rasters"); |
| } |
| |
| if (ImagingLib.filter(this, src, dst) == null) { |
| throw new ImagingOpException ("Unable to convolve src image"); |
| } |
| |
| return dst; |
| } |
| |
| /** |
| * Creates a zeroed destination image with the correct size and number |
| * of bands. If destCM is null, an appropriate ColorModel will be used. |
| * @param src Source image for the filter operation. |
| * @param destCM ColorModel of the destination. Can be null. |
| * @return a destination {@code BufferedImage} with the correct |
| * size and number of bands. |
| */ |
| public BufferedImage createCompatibleDestImage(BufferedImage src, |
| ColorModel destCM) { |
| BufferedImage image; |
| |
| int w = src.getWidth(); |
| int h = src.getHeight(); |
| |
| WritableRaster wr = null; |
| |
| if (destCM == null) { |
| destCM = src.getColorModel(); |
| // Not much support for ICM |
| if (destCM instanceof IndexColorModel) { |
| destCM = ColorModel.getRGBdefault(); |
| } else { |
| /* Create destination image as similar to the source |
| * as it possible... |
| */ |
| wr = src.getData().createCompatibleWritableRaster(w, h); |
| } |
| } |
| |
| if (wr == null) { |
| /* This is the case when destination color model |
| * was explicitly specified (and it may be not compatible |
| * with source raster structure) or source is indexed image. |
| * We should use destination color model to create compatible |
| * destination raster here. |
| */ |
| wr = destCM.createCompatibleWritableRaster(w, h); |
| } |
| |
| image = new BufferedImage (destCM, wr, |
| destCM.isAlphaPremultiplied(), null); |
| |
| return image; |
| } |
| |
| /** |
| * Creates a zeroed destination Raster with the correct size and number |
| * of bands, given this source. |
| */ |
| public WritableRaster createCompatibleDestRaster(Raster src) { |
| return src.createCompatibleWritableRaster(); |
| } |
| |
| /** |
| * Returns the bounding box of the filtered destination image. Since |
| * this is not a geometric operation, the bounding box does not |
| * change. |
| */ |
| public final Rectangle2D getBounds2D(BufferedImage src) { |
| return getBounds2D(src.getRaster()); |
| } |
| |
| /** |
| * Returns the bounding box of the filtered destination Raster. Since |
| * this is not a geometric operation, the bounding box does not |
| * change. |
| */ |
| public final Rectangle2D getBounds2D(Raster src) { |
| return src.getBounds(); |
| } |
| |
| /** |
| * Returns the location of the destination point given a |
| * point in the source. If dstPt is non-null, it will |
| * be used to hold the return value. Since this is not a geometric |
| * operation, the srcPt will equal the dstPt. |
| */ |
| public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { |
| if (dstPt == null) { |
| dstPt = new Point2D.Float(); |
| } |
| dstPt.setLocation(srcPt.getX(), srcPt.getY()); |
| |
| return dstPt; |
| } |
| |
| /** |
| * Returns the rendering hints for this op. |
| */ |
| public final RenderingHints getRenderingHints() { |
| return hints; |
| } |
| } |