com.day.image.MultitoneOp Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*************************************************************************
*
* 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);
}
}
}