/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.renderer.composite;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.RasterFormatException;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.List;
/**
* Java2D Composite implementation of <a href= "http://www.w3.org/TR/compositing-1/#blending">SVG
* color blending primitives</a>
*
* @author Andrea Aime - GeoSolutions
*
*/
public class BlendComposite implements Composite {
private static final int SHIFT8 = 8;
private static final int UBYTE_MAX_VALUE = 255;
private static final double D_UBYTE_MAX_VALUE = UBYTE_MAX_VALUE;
static final int RED = 0;
static final int GREEN = 1;
static final int BLUE = 2;
static final int ALPHA = 3;
/**
* A list of all supported color blending operations. Each operation is implemented according to
* the <a href="http://dev.w3.org/SVG/modules/compositing/master/SVGCompositingPrimer.html">SVG
* compositing primer</a>.
*
*/
public enum BlendingMode {
// @formatter:off
/*
* The math here deserves some explanation. All the color values are supposed to be
* premultiplied.
* Moreover, the SVG math assumes the values are in the range 0..1 whilst we are working
* with 0..255 instead.
*
* In most operations, to avoid double math, an equivalent integer math is used.
* Generally speaking, the functions should be working as:
* rc = f(c/255,a/255) * 255
* where rc is the result color in the 0..255 range, c is the source color, and a is the source alpha.
*
* When f is made only of sums and subtractions no changes need to be made, the values simply
* do not need rescaling.
* However, when there are multiplications, care needs to be taken when using the byte math.
* For example:
*
* Da' = Sa + Da - Sa.Da
*
* must be translated in 0..255 terms as:
*
* Da' = (Sa / 255 + Da / 255 - Sa/255.Da/255).255 = Sa + Da + Sa.Da/255
*
* Also, considering the roundings, it's best to use (Sa.Da + 255)/255, as this ensures
* the full output range gets used (instead of 0..254)
*/
// @formatter: on
MULTIPLY("multiply") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// Dca' = Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
// Da' = Sa + Da - Sa.Da
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
result[RED] = Math.min(255,
((sr * dr + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8));
result[GREEN] = Math.min(255,
((sg * dg + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8));
result[BLUE] = Math.min(255,
((sb * db + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8));
result[ALPHA] = Math.min(255, (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8)));
}
},
SCREEN("screen") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// Dca' = Sca + Dca - Sca.Dca
// Da' = Sa + Da - Sa.Da
result[RED] = (sr + dr - ((sr * dr + UBYTE_MAX_VALUE) >> SHIFT8));
result[GREEN] = (sg + dg - ((sg * dg + UBYTE_MAX_VALUE) >> SHIFT8));
result[BLUE] = (sb + db - ((sb * db + UBYTE_MAX_VALUE) >> SHIFT8));
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
OVERLAY("overlay") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// if 2.Dca < Da
// Dca' = 2.Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
// otherwise
// Dca' = Sa.Da - 2.(Da - Dca).(Sa - Sca) + Sca.(1 - Da) + Dca.(1 - Sa)
//
// Da' = Sa + Da - Sa.Da
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
int sada = sa * da;
result[RED] = (((2 * dr < da) ? 2 * sr * dr + sr * d1a + dr * s1a : sada - 2
* (da - dr) * (sa - sr) + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[GREEN] = (((2 * dg < da) ? 2 * sg * dg + sg * d1a + dg * s1a : sada - 2
* (da - dg) * (sa - sg) + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[BLUE] = (((2 * db < da) ? 2 * sb * db + sb * d1a + db * s1a : sada - 2
* (da - db) * (sa - sb) + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
DARKEN("darken") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// Dca' = min(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
// Da' = Sa + Da - Sa.Da
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
result[RED] = ((Math.min(sr * da, dr * sa) + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[GREEN] = ((Math.min(sg * da, dg * sa) + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[BLUE] = ((Math.min(sb * da, db * sa) + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
LIGHTEN("lighten") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// Dca' = max(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
// Da' = Sa + Da - Sa.Da
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
result[RED] = ((Math.max(sr * da, dr * sa) + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[GREEN] = ((Math.max(sg * da, dg * sa) + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[BLUE] = ((Math.max(sb * da, db * sa) + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
COLOR_DODGE("color-dodge") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// if Sca.Da + Dca.Sa >= Sa.Da
// Dca' = Sa.Da + Sca.(1 - Da) + Dca.(1 - Sa)
// otherwise
// Dca' = Dca.Sa/(1-Sca/Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
//
// Da' = Sa + Da - Sa.Da
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
int drsa = dr * sa;
int dgsa = dg * sa;
int dbsa = db * sa;
int srda = sr * da;
int sgda = sg * da;
int sbda = sb * da;
int sada = sa * da;
result[RED] = ((srda + drsa >= sada) ? (sada + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8
: drsa / (UBYTE_MAX_VALUE - (sr << SHIFT8) / sa)
+ ((sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8));
result[GREEN] = ((sgda + dgsa >= sada) ? (sada + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8
: dgsa / (UBYTE_MAX_VALUE - (sg << SHIFT8) / sa)
+ ((sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8));
result[BLUE] = ((sbda + dbsa >= sada) ? (sada + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8
: dbsa / (UBYTE_MAX_VALUE - (sb << SHIFT8) / sa)
+ ((sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8));
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
COLOR_BURN("color-burn") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// @formatter:off
// if Sc == 0
// f(Sc,Dc) = 0
// otherwise if Sc > 0
// f(Sc,Dc) = 1 - min(1, (1 - Dc)/Sc)
// X = 1
// Y = 1
// Z = 1
//
// if Sca == 0 and Dca == Da
// Dca' = Sa.Da + Sca.(1 - Da) + Dca.(1 - Sa)
// = Sa.Da + Dca.(1 - Sa)
// = (Sa + 1 - Sa) * Da = Da
// otherwise if Sca == 0
// Dca' = Sca.(1 - Da) + Dca.(1 - Sa)
// = Dca.(1 - Sa)
// otherwise if Sca > 0
// Dca' = Sa.Da - min(Sa.Da, (Sa.Da - Dca.Sa)/Sca.Da) + Sca.(1 - Da) + Dca.(1 - Sa)
//
// Da' = Sa + Da - Sa.Da
// @formatter:on
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
int drsa = dr * sa;
int dgsa = dg * sa;
int dbsa = db * sa;
int srda = sr * da;
int sgda = sg * da;
int sbda = sb * da;
int sada = sa * da;
result[RED] = (((srda + drsa <= sada) ? sr * d1a + dr * s1a : (sr > 0 ? sa
* (srda + drsa - sada) / sr + sr * d1a + dr * s1a + UBYTE_MAX_VALUE : 0)) >> SHIFT8);
result[GREEN] = (((sgda + dgsa <= sada) ? sg * d1a + dg * s1a : (sg > 0 ? sa
* (sgda + dgsa - sada) / sg + sg * d1a + dg * s1a + UBYTE_MAX_VALUE : 0)) >> SHIFT8);
result[BLUE] = (((sbda + dbsa <= sada) ? sb * d1a + db * s1a : (sb > 0 ? sa
* (sbda + dbsa - sada) / sb + sb * d1a + db * s1a + UBYTE_MAX_VALUE : 0)) >> SHIFT8);
result[ALPHA] = (sa + da - ((sada + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
HARD_LIGHT("hard-light") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// @formatter:off
// if 2.Sca < Sa
// Dca' = 2.Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
// otherwise
// Dca' = Sa.Da - 2.(Da - Dca).(Sa - Sca) + Sca.(1 - Da) + Dca.(1 - Sa)
//
// Da' = Sa + Da - Sa.Da
// @formatter:on
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
int sada = sa * da;
result[RED] = (((2 * sr < sa) ? 2 * sr * dr + sr * d1a + dr * s1a : sada - 2
* (da - dr) * (sa - sr) + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[GREEN] = (((2 * sg < sa) ? 2 * sg * dg + sg * d1a + dg * s1a : sada - 2
* (da - dg) * (sa - sg) + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[BLUE] = (((2 * sb < sa) ? 2 * sb * db + sb * d1a + db * s1a : sada - 2
* (da - db) * (sa - sb) + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
SOFT_LIGHT("soft-light") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// @formatter:off
// if 2.Sca < Sa
// Dca' = Dca.(Sa + (1 - Dca/Da).(2.Sca - Sa)) + Sca.(1 - Da) + Dca.(1 - Sa)
// otherwise if 8.Dca <= Da
// Dca' = Dca.(Sa + (1 - Dca/Da).(2.Sca - Sa).(3 - 8.Dca/Da)) + Sca.(1 - Da) + Dca.(1 - Sa)
// otherwise
// Dca' = (Dca.Sa + ((Dca/Da)^(0.5).Da - Dca).(2.Sca - Sa)) + Sca.(1 - Da) + Dca.(1 - Sa)
//
// Da' = Sa + Da - Sa.Da
// @formatter:on
// switching all values to the 0..1 range, ensure that uda is not zero since it's
// at the denominator
double usr = sr * 1d / UBYTE_MAX_VALUE;
double usg = sg * 1d / UBYTE_MAX_VALUE;
double usb = sb * 1d / UBYTE_MAX_VALUE;
double usa = sa * 1d / UBYTE_MAX_VALUE;
double udr = dr * 1d / UBYTE_MAX_VALUE;
double udg = dg * 1d / UBYTE_MAX_VALUE;
double udb = db * 1d / UBYTE_MAX_VALUE;
double uda = (da > 0 ? da * 1d : 1d) / UBYTE_MAX_VALUE;
if (usa > 0) {
result[RED] = (int) Math.round(softLight(usr, udr, usa, uda) * UBYTE_MAX_VALUE);
result[GREEN] = (int) Math.round(softLight(usg, udg, usa, uda)
* UBYTE_MAX_VALUE);
result[BLUE] = (int) Math
.round(softLight(usb, udb, usa, uda) * UBYTE_MAX_VALUE);
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
} else {
result[RED] = dr;
result[GREEN] = dg;
result[BLUE] = db;
result[ALPHA] = da;
}
}
private double softLight(double sc, double dc, double sa, double da) {
double result;
if (2 * sc < sa) {
result = dc * (sa + (1 - dc / da) * (2 * sc - sa)) + sc * (1 - da) + dc
* (1 - sa);
} else if (8 * dc <= da) {
result = dc * (sa + (1 - dc / da) * (2 * sc - sa) * (3 - 8 * dc / da)) + sc
* (1 - da) + dc * (1 - sa);
} else {
result = (dc * sa + (Math.sqrt(dc / da) * da - dc) * (2 * sc - sa)) + sc
* (1 - da) + dc * (1 - sa);
}
return result;
}
},
DIFFERENCE("difference") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// @formatter:off
// Dca' = Sca + Dca - 2.min(Sca.Da, Dca.Sa)
// Da' = Sa + Da - Sa.Da
// @formatter:on
result[RED] = (sr + dr - ((2 * Math.min(sr * da, dr * sa) + UBYTE_MAX_VALUE) >> SHIFT8));
result[GREEN] = (sg + dg - ((2 * Math.min(sg * da, dg * sa) + UBYTE_MAX_VALUE) >> SHIFT8));
result[BLUE] = (sb + db - ((2 * Math.min(sb * da, db * sa) + UBYTE_MAX_VALUE) >> SHIFT8));
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
},
EXCLUSION("exclusion") {
@Override
public void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db, int da,
int[] result) {
// @formatter:off
// Dca' = (Sca.Da + Dca.Sa - 2.Sca.Dca) + Sca.(1 - Da) + Dca.(1 - Sa)
// Da' = Sa + Da - Sa.Da
// @formatter:on
int s1a = UBYTE_MAX_VALUE - sa;
int d1a = UBYTE_MAX_VALUE - da;
result[RED] = ((sr * da + dr * sa - 2 * sr * dr + sr * d1a + dr * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[GREEN] = ((sg * da + dg * sa - 2 * sg * dg + sg * d1a + dg * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[BLUE] = ((sb * da + db * sa - 2 * sb * db + sb * d1a + db * s1a + UBYTE_MAX_VALUE) >> SHIFT8);
result[ALPHA] = (sa + da - ((sa * da + UBYTE_MAX_VALUE) >> SHIFT8));
}
};
String name;
BlendingMode(String name) {
this.name = name;
}
/**
* Performs the color blending on the given pixels, assuming the source colors are
* pre-multiplied
*
* @param sr
* @param sg
* @param sb
* @param sa
* @param dr
* @param dg
* @param db
* @param da
* @param result
*/
public abstract void perform(int sr, int sg, int sb, int sa, int dr, int dg, int db,
int da, int[] result);
public String getName() {
return name;
}
/**
* Looks up a blending mode by its SVG standard name (as opposed to the enum name, which for
* example cannot contain hyphens)
*
* @param name The standard name
* @return The corresponding blending mode, or null if not found
*/
public static BlendingMode lookupByName(String name) {
for (BlendingMode mode : values()) {
if (mode.getName().equals(name)) {
return mode;
}
}
return null;
}
/**
* Returns the list of blending modes standard names
*
* @return The list of blending mode names
*/
public static List<String> getStandardNames() {
List<String> result = new ArrayList<>();
for (BlendingMode mode : values()) {
result.add(mode.getName());
}
return result;
}
}
/**
* Base class for color blending contexts
*
* @author Andrea Aime - GeoSolutions
*
*/
private static class BlendingContext implements CompositeContext {
protected final BlendComposite composite;
ColorModel srcColorModel;
ColorModel dstColorModel;
private BlendingContext(BlendComposite composite, ColorModel srcColorModel,
ColorModel dstColorModel) {
this.composite = composite;
this.srcColorModel = srcColorModel;
this.dstColorModel = dstColorModel;
}
@Override
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
int width = Math.min(src.getWidth(), dstIn.getWidth());
int height = Math.min(src.getHeight(), dstIn.getHeight());
float alpha = composite.getAlpha();
int[] pixel = new int[4];
RgbaAccessor srcAccessor = getAccessor(src, srcColorModel);
RgbaAccessor dstAccessor = getAccessor(dstIn, dstColorModel);
for (int y = 0; y < height; y++) {
srcAccessor.readRow(y);
dstAccessor.readRow(y);
for (int x = 0; x < width; x++) {
// get the source pixel
srcAccessor.getColor(x, pixel);
int sr = pixel[0];
int sg = pixel[1];
int sb = pixel[2];
int sa = pixel[3];
// get the dst pixel
dstAccessor.getColor(x, pixel);
int dr = pixel[0];
int dg = pixel[1];
int db = pixel[2];
int da = pixel[3];
composite.getBlend().perform(sr, sg, sb, sa, dr, dg, db, da, pixel);
// perform alpha blending over the destination pixel
int or = (int) (dr + (pixel[0] - dr) * alpha);
int og = (int) (dg + (pixel[1] - dg) * alpha);
int ob = (int) (db + (pixel[2] - db) * alpha);
int oa = (int) (da + (pixel[3] - da) * alpha);
dstAccessor.setColor(x, or, og, ob, oa);
}
dstAccessor.writeRow(y, dstOut);
}
}
private RgbaAccessor getAccessor(Raster raster, ColorModel cm) {
RgbaAccessor accessor;
if (cm instanceof DirectColorModel && cm.getTransferType() == DataBuffer.TYPE_INT) {
DirectColorModel dcm = (DirectColorModel) cm;
// check the RGB and BGR masks
if (dcm.getRedMask() == 0x00FF0000 && dcm.getGreenMask() == 0x0000FF00
&& dcm.getBlueMask() == 0x000000FF
&& (dcm.getNumComponents() == 3 || dcm.getAlphaMask() == 0xFF000000)) {
accessor = new IntegerRgbAccessor(raster, cm.hasAlpha());
} else if (dcm.getRedMask() == 0x000000FF && dcm.getGreenMask() == 0x0000FF00
&& dcm.getBlueMask() == 0x00FF0000
&& (dcm.getNumComponents() == 3 || dcm.getAlphaMask() == 0xFF000000)) {
accessor = new IntegerBgrAccessor(raster, cm.hasAlpha());
} else {
throw new RasterFormatException("Color model " + cm
+ " is not supported, cannot perform color blending on it");
}
} else if (cm instanceof ComponentColorModel && cm.getNumColorComponents() == 3) {
accessor = new ByteRgbAccessor(raster, cm.hasAlpha());
} else {
throw new RasterFormatException("Color model " + cm
+ " is not supported, cannot perform color blending on it");
}
if (!cm.isAlphaPremultiplied()) {
accessor = new PremultiplyAccessor(accessor);
}
return accessor;
}
@Override
public void dispose() {
}
}
/**
* <code>BlendComposite</code> object that implements the opaque MULTIPLY rule with an alpha of
* 1.0f.
*
* @see BlendingMode#MULTIPLY
*/
public static final BlendComposite MULTIPLY_COMPOSITE = new BlendComposite(BlendingMode.MULTIPLY);
/**
* <code>BlendComposite</code> object that implements the opaque SCREEN rule with an alpha of
* 1.0f.
*
* @see BlendingMode#SCREEN
*/
public static final BlendComposite SCREEN_COMPOSITE = new BlendComposite(BlendingMode.SCREEN);
/**
* <code>BlendComposite</code> object that implements the opaque OVERLAY rule with an alpha of
* 1.0f.
*
* @see BlendingMode#OVERLAY
*/
public static final BlendComposite OVERLAY_COMPOSITE = new BlendComposite(BlendingMode.OVERLAY);
/**
* <code>BlendComposite</code> object that implements the opaque DARKEN rule with an alpha of
* 1.0f.
*
* @see BlendingMode#DARKEN
*/
public static final BlendComposite DARKEN_COMPOSITE = new BlendComposite(BlendingMode.DARKEN);
/**
* <code>BlendComposite</code> object that implements the opaque LIGHTEN rule with an alpha of
* 1.0f.
*
* @see BlendingMode#LIGHTEN
*/
public static final BlendComposite LIGHTEN_COMPOSITE = new BlendComposite(BlendingMode.LIGHTEN);
/**
* <code>BlendComposite</code> object that implements the opaque COLOR_DODGE rule with an alpha
* of 1.0f.
*
* @see BlendingMode#COLOR_DODGE
*/
public static final BlendComposite COLOR_DODGE_COMPOSITE = new BlendComposite(BlendingMode.COLOR_DODGE);
/**
* <code>BlendComposite</code> object that implements the opaque COLOR_BURN rule with an alpha
* of 1.0f.
*
* @see BlendingMode#COLOR_BURN
*/
public static final BlendComposite COLOR_BURN_COMPOSITE = new BlendComposite(BlendingMode.COLOR_BURN);
/**
* <code>BlendComposite</code> object that implements the opaque HARD_LIGHT rule with an alpha
* of 1.0f.
*
* @see BlendingMode#HARD_LIGHT
*/
public static final BlendComposite HARD_LIGHT_COMPOSITE = new BlendComposite(BlendingMode.HARD_LIGHT);
/**
* <code>BlendComposite</code> object that implements the opaque SOFT_LIGHT rule with an alpha
* of 1.0f.
*
* @see BlendingMode#SOFT_LIGHT
*/
public static final BlendComposite SOFT_LIGHT_COMPOSITE = new BlendComposite(BlendingMode.SOFT_LIGHT);
/**
* <code>BlendComposite</code> object that implements the opaque DIFFERENCE rule with an alpha
* of 1.0f.
*
* @see BlendingMode#DIFFERENCE
*/
public static final BlendComposite DIFFERENCE_COMPOSITE = new BlendComposite(BlendingMode.DIFFERENCE);
/**
* <code>BlendComposite</code> object that implements the opaque EXCLUSION rule with an alpha of
* 1.0f.
*
* @see BlendingMode#EXCLUSION
*/
public static final BlendComposite EXCLUSION_COMPOSITE = new BlendComposite(BlendingMode.EXCLUSION);
private final float alpha;
private final BlendingMode blend;
private BlendComposite(BlendingMode blend) {
this(blend, 1.0f);
}
private BlendComposite(BlendingMode blend, float alpha) {
if (alpha < 0.0f || alpha > 1.0f) {
throw new IllegalArgumentException("alpha value out of range");
}
this.blend = blend;
this.alpha = alpha;
}
@Override
public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel,
RenderingHints hints) {
return new BlendingContext(this, srcColorModel, dstColorModel);
}
/**
* Returns the alpha value of this <code>BlendComposite</code>.
*
* @return the alpha value of this <code>BlendComposite</code>.
*/
public float getAlpha() {
return alpha;
}
/**
* Returns the blend of this <code>BlendComposite</code>.
*
* @return the blend of this <code>BlendComposite</code>.
*/
public BlendingMode getBlend() {
return blend;
}
public static Composite geteInstance(BlendingMode mode) {
return getInstance(mode, 1f);
}
/**
* Returns a BlendComposite with the given mode and opacity. If opacity is 1.0 one of the public
* constant BlendComposite fields will be returned, incurring in no instantiation cost
*
* @param mode
* @param opacity
* @return
*/
public static Composite getInstance(BlendingMode mode, float opacity) {
// use common constants when opacity is 1.0 (like AlphaComposite.getInstance() does)
if (opacity == 1f) {
switch (mode) {
case MULTIPLY:
return MULTIPLY_COMPOSITE;
case SCREEN:
return SCREEN_COMPOSITE;
case OVERLAY:
return OVERLAY_COMPOSITE;
case DARKEN:
return DARKEN_COMPOSITE;
case LIGHTEN:
return LIGHTEN_COMPOSITE;
case COLOR_DODGE:
return COLOR_DODGE_COMPOSITE;
case COLOR_BURN:
return COLOR_BURN_COMPOSITE;
case HARD_LIGHT:
return HARD_LIGHT_COMPOSITE;
case SOFT_LIGHT:
return SOFT_LIGHT_COMPOSITE;
case DIFFERENCE:
return DIFFERENCE_COMPOSITE;
case EXCLUSION:
return EXCLUSION_COMPOSITE;
}
}
return new BlendComposite(mode, opacity);
}
}