All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.image.MultitoneOp Maven / Gradle / Ivy

The newest version!
/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.day.image;

import java.awt.Color;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ByteLookupTable;
import java.awt.image.LookupOp;
import java.awt.image.LookupTable;
import java.awt.image.ShortLookupTable;

/**
 * The MultitoneOp class implements similar functionality as the
 * Photoshop Duplex function. It allows to specify one or more colors
 * and tonality curves for each color.
 * 

* The {@link #filter} method expects to get a grayscale image to which the * colorization operation is applied. * * @version $Revision$, $Date$ * @author fmeschbe * @since echidna * @audience wad */ public class MultitoneOp extends AbstractBufferedImageOp { /** * The {@link LookupTableHelper} used to create the lookup table for the * lookup operation in {@link #doFilter}. */ private static final LookupTableHelper tableHelper; /** * The {@link ColorCurve} objects used to calculate color values for each * level of intensity. */ private final ColorCurve[] colorCurves; /** * The maximum intensity value for all luminosities. This value is maximal * value of the sums of intensities of all color curves. */ private final float maxAlpha; static { /** * Set up the lookup table helper. Use the Byte version for all operating * systems except linux, which has to use the Short version. */ String os = System.getProperty("os.name", "-").toLowerCase(); tableHelper = (os.indexOf("linux") >= 0) ? (LookupTableHelper) new ShortLookupTableHelper(null) : (LookupTableHelper) new ByteLookupTableHelper(null); } /** * Creates a MultitoneOp containing a standard color curve with * the single color. This creates a monotone result. This is a convenience * constructor which is the same as calling * {@link #MultitoneOp(Color[], RenderingHints)} with an array containing * the single color directly. * * @param color The color to use in the operation * @param hints the specified RenderingHints, or null */ public MultitoneOp(Color color, RenderingHints hints) { this(new Color[]{ color }, hints); } /** * Creates a MultitoneOp containing a standard color curve with * more than one color. This creates a monotone result, where the color is * a mixture of the input colors. * * @param colors The colors to use in the operation * @param hints the specified RenderingHints, or null * * @throws NullPointerException if the colors array is null or * if any of the color values is null. */ public MultitoneOp(Color[] colors, RenderingHints hints) { super(hints); // check colors array if (colors == null) { throw new NullPointerException("colors"); } colorCurves = new ColorCurve[colors.length]; for (int i=0; i < colors.length; i++) { // check color if (colors[i] == null) { throw new NullPointerException("colors[" + i + "]"); } colorCurves[i] = new ColorCurve(colors[i]); } maxAlpha = calculateMaxAlpha(); } /** * Creates a MultitoneOp from the given {@link ColorCurve} * objects. * * @param colorCurves The {@link ColorCurve} objects from which to create * this MultitoneOp * @param hints the specified RenderingHints, or null * * @throws NullPointerException if the colorCurves parameter or any of the * entries is null. */ public MultitoneOp(ColorCurve[] colorCurves, RenderingHints hints) { super(hints); // check color curve array if (colorCurves == null) { throw new NullPointerException("colorCurves"); } this.colorCurves = new ColorCurve[colorCurves.length]; for (int i=0; i < colorCurves.length; i++) { // check color curve if (colorCurves[i] == null) { throw new NullPointerException("colorCurves" + i + "]"); } this.colorCurves[i] = new ColorCurve(colorCurves[i]); } maxAlpha = calculateMaxAlpha(); } /** * Performs the multi tone operation. Note that the source image is expected * to be a gray scale image and the destination image must support color * images. *

* Note: This class supports filtering within an image, that is, calling * this method with src == dst is legal. * * @param src The src image to be filtered. * @param dst The dest image into which to place the resized image. This * may be null in which case a new image with the * correct size will be created. * * @return The newly created image (if dest was null) or dest * into which the resized src image has been drawn. * * @throws NullPointerException if the src image is null. */ public BufferedImage filter(BufferedImage src, BufferedImage dst) { if (src != null && src == dst) { // if src == dest filter works on the same image, which is ok doFilter(src, dst); return dst; } else { return super.filter(src, dst); } } /** * Applies the {@link ColorCurve} objects to the grayscale source image and * stores the result in the destination image. * * @param src The source image * @param dst The destination image. This must not be null. */ protected void doFilter(BufferedImage src, BufferedImage dst) { int numcols = colorCurves.length; // extract the float color components float[][] cols = new float[numcols][4]; for (int i=0; i < numcols; i++) { colorCurves[i].getColor().getColorComponents(cols[i]); } // lookup table for 256 steps of 4 components LookupTableHelper helper = tableHelper.getInstance(); for (int i=0; i < 256; i++) { float alpha = 0; float red = 0; float green = 0; float blue = 0; // get pixel value from mixing colors scaled by the alpha at this // position, where the alpha is additionally scaled by the max alpa for (int j=0; j < numcols; j++) { // get the alpha value for entry i of color j float coljalpha = colorCurves[j].getLevel(i); // scale by the max. sum of the alphas coljalpha /= maxAlpha; alpha += coljalpha; red += cols[j][0] * coljalpha; green += cols[j][1] * coljalpha; blue += cols[j][2] * coljalpha; } // invariant: alpha <= 1 // mix-in white to fill alpha to 1 if (alpha < 1f) { // mix in some white // dunno about this 1.3 factor - seems to be closed to the // scale of Photoshop red/blue duotone scale ?? alpha = 1f - alpha * 1.3f; red = red + alpha; green = green + alpha; blue = blue + alpha; } // guard values - not really needed, just in case if (red > 1) red = 1; else if (red < 0) red = 0; if (green > 1) green = 1; else if (green < 0) green = 0; if (blue > 1) blue = 1; else if (blue < 0) blue = 0; helper.addMapping(255-i, 255 * red, 255 * green, 255 * blue, 0xff); } // keep the original alpha channel int w = src.getWidth(); int h = src.getHeight(); int[] srcAlpha = src.getRaster().getSamples(0, 0, w, h, 3, (int[]) null); // apply the filter LookupOp lop = new LookupOp(helper.getLookupTable(), getRenderingHints()); lop.filter(src, dst); // replace the original alpha channel dst.getRaster().setSamples(0, 0, w, h, 3, srcAlpha); } private float calculateMaxAlpha() { /** * get the max. sum of all alphas : the colors have a function (default * rising from 0..1) of color intensity. the alpha value must never be * more than one, but with more than one color occasionally being fully * opaque, we have to scale the alpha values. the point here is to find * the biggest alpha value to define the scaling factor. * currently this is simply the number of colors, as we only support * the default function 0..1 for all colors. */ float maxAlpha = 0f; for (int i=0; i < 256; i++) { float alpha = 0f; for (int j=0; j < colorCurves.length; j++) { alpha += colorCurves[j].getLevel(i); } if (alpha > maxAlpha) { maxAlpha = alpha; } } return maxAlpha; } //---------- internal interface/class to fix the ByteLookupTable bug #9432 - /** * The LookupTableHelper interface defines an interface which * will be implemented by imlementation specific helper data. *

* The issue of having this interface and companion classes is a bug in the * native lookup operation implementation on linux. To come around this bug * a ShortLookupTable is used on linux instead of a ByteLookupTable. The * drawback is that performance suffers when using the ShortLookupTable * because there is no native implementation for this operation. * * @version $Revision$, $Date$ * @author fmeschbe * @since gumbaer * @audience core */ private static interface LookupTableHelper { /** * Returns an instance of the implementation class. */ public LookupTableHelper getInstance(); /** * Adds a mapping for the given color value. * @param index The index at which to set the mapping. Must be in the * range 0..255. * @param red The color value for the red band * @param green The color value for the green band * @param blue The color value for the blue band * @param alpha The color value for the alpha band * @throws IndexOutOfBoundsException if the index is outside of the * range 0..255. */ public void addMapping(int index, float red, float green, float blue, float alpha); /** * Returns a new lookup table created from the mappings defined with the * {@link #addMapping} method. Each call to this method returns a new * instance of LookupTable. */ public LookupTable getLookupTable(); } /** * The ByteLookupTableHelper class implements the * {@link LookupTableHelper} interface to provide a ByteLookupTable. * * @version $Revision$, $Date$ * @author fmeschbe * @since gumbaer * @audience core */ private static class ByteLookupTableHelper implements LookupTableHelper { /** The table data */ private final byte[][] table; private ByteLookupTableHelper(byte[][] table) { this.table = table; } public LookupTableHelper getInstance() { return new ByteLookupTableHelper(new byte[4][256]); } public void addMapping(int index, float red, float green, float blue, float alpha) { table[0][index] = (byte) red; table[1][index] = (byte) green; table[2][index] = (byte) blue; table[3][index] = (byte) alpha; } public LookupTable getLookupTable() { return new ByteLookupTable(0, table); } } /** * The ShortLookupTableHelper class implements the * {@link LookupTableHelper} interface to provide a ShortLookupTable. * * @version $Revision$, $Date$ * @author fmeschbe * @since gumbaer * @audience core */ private static class ShortLookupTableHelper implements LookupTableHelper { /** The table data */ private final short[][] table; private ShortLookupTableHelper(short[][] table) { this.table = table; } public LookupTableHelper getInstance() { return new ShortLookupTableHelper(new short[4][256]); } public void addMapping(int index, float red, float green, float blue, float alpha) { table[0][index] = (short) red; table[1][index] = (short) green; table[2][index] = (short) blue; table[3][index] = (short) alpha; } public LookupTable getLookupTable() { return new ShortLookupTable(0, table); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy