/* * Copyright (c) 2008, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name "TwelveMonkeys" nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.twelvemonkeys.image; import java.awt.*; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.RasterOp; import java.awt.image.WritableRaster; /** * This BufferedImageOp simply copies pixels, converting to a * {@code IndexColorModel}. * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author last modified by $Author: haku $ * * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/CopyDither.java#1 $ * */ public class CopyDither implements BufferedImageOp, RasterOp { protected IndexColorModel indexColorModel = null; /** * Creates a {@code CopyDither}, using the given * {@code IndexColorModel} for dithering into. * * @param pICM an IndexColorModel. */ public CopyDither(IndexColorModel pICM) { // Store colormodel indexColorModel = pICM; } /** * Creates a {@code CopyDither}, with no fixed * {@code IndexColorModel}. The colormodel will be generated for each * filtering, unless the dest image allready has an * {@code IndexColorModel}. */ public CopyDither() { } /** * Creates a compatible {@code BufferedImage} to dither into. * Only {@code IndexColorModel} allowed. * * @return a compatible {@code BufferedImage} * * @throws ImageFilterException if {@code pDestCM} is not {@code null} or * an instance of {@code IndexColorModel}. */ public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) { if (pDestCM == null) { return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, indexColorModel); } else if (pDestCM instanceof IndexColorModel) { return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) pDestCM); } else { throw new ImageFilterException("Only IndexColorModel allowed."); } } /** * Creates a compatible {@code Raster} to dither into. * Only {@code IndexColorModel} allowed. * * @param pSrc * * @return a {@code WritableRaster} */ public final WritableRaster createCompatibleDestRaster(Raster pSrc) { return createCompatibleDestRaster(pSrc, getICM(pSrc)); } public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) { return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight()); } /** * Returns the bounding box of the filtered destination image. Since * this is not a geometric operation, the bounding box does not * change. * @param pSrc the {@code BufferedImage} to be filtered * @return the bounds of the filtered definition image. */ public final Rectangle2D getBounds2D(BufferedImage pSrc) { return getBounds2D(pSrc.getRaster()); } /** * Returns the bounding box of the filtered destination Raster. Since * this is not a geometric operation, the bounding box does not * change. * @param pSrc the {@code Raster} to be filtered * @return the bounds of the filtered definition {@code Raster}. */ public final Rectangle2D getBounds2D(Raster pSrc) { return pSrc.getBounds(); } /** * Returns the location of the destination point given a * point in the source. If {@code dstPt} is not * {@code null}, it will be used to hold the return value. * Since this is not a geometric operation, the {@code srcPt} * will equal the {@code dstPt}. * @param pSrcPt a {@code Point2D} that represents a point * in the source image * @param pDstPt a {@code Point2D}that represents the location * in the destination * @return the {@code Point2D} in the destination that * corresponds to the specified point in the source. */ public final Point2D getPoint2D(Point2D pSrcPt, Point2D pDstPt) { // Create new Point, if needed if (pDstPt == null) { pDstPt = new Point2D.Float(); } // Copy location pDstPt.setLocation(pSrcPt.getX(), pSrcPt.getY()); // Return dest return pDstPt; } /** * Returns the rendering mHints for this op. * @return the {@code RenderingHints} object associated * with this op. */ public final RenderingHints getRenderingHints() { return null; } /** * Converts a int triplet to int ARGB. */ private static int toIntARGB(int[] pRGB) { return 0xff000000 // All opaque | (pRGB[0] << 16) | (pRGB[1] << 8) | (pRGB[2]); /* | ((int) (pRGB[0] << 16) & 0x00ff0000) | ((int) (pRGB[1] << 8) & 0x0000ff00) | ((int) (pRGB[2] ) & 0x000000ff); */ } /** * Performs a single-input/single-output dither operation, applying basic * Floyd-Steinberg error-diffusion to the image. * * @param pSource the source image * @param pDest the destiantion image * * @return the destination image, or a new image, if {@code pDest} was * {@code null}. */ public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) { // Create destination image, if none provided if (pDest == null) { pDest = createCompatibleDestImage(pSource, getICM(pSource)); } else if (!(pDest.getColorModel() instanceof IndexColorModel)) { throw new ImageFilterException("Only IndexColorModel allowed."); } // Filter rasters filter(pSource.getRaster(), pDest.getRaster(), (IndexColorModel) pDest.getColorModel()); return pDest; } /** * Performs a single-input/single-output dither operation, applying basic * Floyd-Steinberg error-diffusion to the image. * * @param pSource * @param pDest * * @return the destination raster, or a new raster, if {@code pDest} was * {@code null}. */ public final WritableRaster filter(final Raster pSource, WritableRaster pDest) { return filter(pSource, pDest, getICM(pSource)); } private IndexColorModel getICM(BufferedImage pSource) { return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY)); } private IndexColorModel getICM(Raster pSource) { return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource)); } private IndexColorModel createIndexColorModel(Raster pSource) { BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_INT_ARGB); image.setData(pSource); return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY); } /** * Performs a single-input/single-output pixel copy operation. * * @param pSource * @param pDest * @param pColorModel * * @return the destination raster, or a new raster, if {@code pDest} was * {@code null}. */ public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) { int width = pSource.getWidth(); int height = pSource.getHeight(); if (pDest == null) { pDest = createCompatibleDestRaster(pSource, pColorModel); } // temp buffers final int[] inRGB = new int[4]; Object pixel = null; // TODO: Use getPixels instead of getPixel for better performance? // Loop through image data for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Get rgb from original raster // DON'T KNOW IF THIS WILL WORK FOR ALL TYPES..? pSource.getPixel(x, y, inRGB); // Get pixel value... // It is VERY important that we are using an IndexColorModel that // support reverse color lookup for speed. pixel = pColorModel.getDataElements(toIntARGB(inRGB), pixel); // And set it pDest.setDataElements(x, y, pixel); } } return pDest; } }