blob: 881dc0c7160c53e121c2e846d7743da861293ca5 [file] [log] [blame]
/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.ext.awt.image.renderable;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.image.BufferedImage;
import org.apache.batik.ext.awt.image.GraphicsUtil;
/**
* This provides an implementation of all the composite rules in SVG.
*
* @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
* @version $Id$
*/
public class SVGComposite
implements Composite {
public static final SVGComposite OVER
= new SVGComposite(CompositeRule.OVER);
public static final SVGComposite IN
= new SVGComposite(CompositeRule.IN);
public static final SVGComposite OUT
= new SVGComposite(CompositeRule.OUT);
public static final SVGComposite ATOP
= new SVGComposite(CompositeRule.ATOP);
public static final SVGComposite XOR
= new SVGComposite(CompositeRule.XOR);
public static final SVGComposite MULTIPLY
= new SVGComposite(CompositeRule.MULTIPLY);
public static final SVGComposite SCREEN
= new SVGComposite(CompositeRule.SCREEN);
public static final SVGComposite DARKEN
= new SVGComposite(CompositeRule.DARKEN);
public static final SVGComposite LIGHTEN
= new SVGComposite(CompositeRule.LIGHTEN);
CompositeRule rule;
public CompositeRule getRule() { return rule; }
public SVGComposite(CompositeRule rule) {
this.rule = rule;
}
public boolean equals(Object o) {
if (o instanceof SVGComposite) {
SVGComposite svgc = (SVGComposite)o;
return (svgc.getRule() == getRule());
} else if (o instanceof AlphaComposite) {
AlphaComposite ac = (AlphaComposite)o;
switch (getRule().getRule()) {
case CompositeRule.RULE_OVER:
return (ac == AlphaComposite.SrcOver);
case CompositeRule.RULE_IN:
return (ac == AlphaComposite.SrcIn);
case CompositeRule.RULE_OUT:
return (ac == AlphaComposite.SrcOut);
default:
return false;
}
}
return false;
}
public CompositeContext createContext(ColorModel srcCM,
ColorModel dstCM,
RenderingHints hints) {
if (false) {
ColorSpace srcCS = srcCM.getColorSpace();
ColorSpace dstCS = dstCM.getColorSpace();
System.out.println("srcCS: " + srcCS);
System.out.println("dstCS: " + dstCS);
System.out.println
("lRGB: " + ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB));
System.out.println
("sRGB: " + ColorSpace.getInstance(ColorSpace.CS_sRGB));
}
switch (rule.getRule()) {
case CompositeRule.RULE_OVER:
return new OverCompositeContext(srcCM, dstCM);
case CompositeRule.RULE_IN:
return new InCompositeContext (srcCM, dstCM);
case CompositeRule.RULE_OUT:
return new OutCompositeContext (srcCM, dstCM);
case CompositeRule.RULE_ATOP:
return new AtopCompositeContext(srcCM, dstCM);
case CompositeRule.RULE_XOR:
return new XorCompositeContext (srcCM, dstCM);
case CompositeRule.RULE_ARITHMETIC:
float [] coeff = rule.getCoefficients();
return new ArithCompositeContext(srcCM, dstCM,
coeff[0], coeff[1],
coeff[2], coeff[3]);
case CompositeRule.RULE_MULTIPLY:
return new MultiplyCompositeContext(srcCM, dstCM);
case CompositeRule.RULE_SCREEN:
return new ScreenCompositeContext(srcCM, dstCM);
case CompositeRule.RULE_DARKEN:
return new DarkenCompositeContext(srcCM, dstCM);
case CompositeRule.RULE_LIGHTEN:
return new LightenCompositeContext(srcCM, dstCM);
default:
throw new UnsupportedOperationException
("Unknown composite rule requested.");
}
}
public static abstract class AlphaPreCompositeContext
implements CompositeContext {
ColorModel srcCM, dstCM;
AlphaPreCompositeContext(ColorModel srcCM, ColorModel dstCM) {
this.srcCM = srcCM;
this.dstCM = dstCM;
}
public void dispose() {
srcCM = null;
dstCM = null;
}
protected abstract void precompose(Raster src, Raster dstIn,
WritableRaster dstOut);
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
ColorModel srcPreCM = srcCM;
if (!srcCM.isAlphaPremultiplied())
srcPreCM = GraphicsUtil.coerceData((WritableRaster)src,
srcCM, true);
ColorModel dstPreCM = dstCM;
if (!dstCM.isAlphaPremultiplied())
dstPreCM = GraphicsUtil.coerceData((WritableRaster)dstIn,
dstCM, true);
precompose(src, dstIn, dstOut);
if (!srcCM.isAlphaPremultiplied())
GraphicsUtil.coerceData((WritableRaster)src,
srcPreCM, false);
if (!dstCM.isAlphaPremultiplied()) {
GraphicsUtil.coerceData(dstOut, dstPreCM, false);
if (dstIn != dstOut)
GraphicsUtil.coerceData((WritableRaster)dstIn,
dstPreCM, false);
}
}
}
public static class OverCompositeContext
extends AlphaPreCompositeContext {
OverCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
final int norm = (1<<24)/255;
final int pt5 = (1<<23);
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int dstM = (255-srcPix[sp+3])*norm;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
/*
public static class OverCompositeContext_INT_PACK
extends AlphaPreCompositeContext {
OverCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
SinglePixelPackedSampleModel srcSPPSM;
srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel();
final int srcScanStride = srcSPPSM.getScanlineStride();
DataBufferInt srcDB = (DataBufferInt)src.getDataBuffer();
final int [] srcPixels = srcDB.getBankData()[0];
final int srcBase =
(srcDB.getOffset() +
srcSPPSM.getOffset(x0-src.getSampleModelTranslateX(),
y0-src.getSampleModelTranslateY()));
SinglePixelPackedSampleModel dstInSPPSM;
dstInSPPSM = (SinglePixelPackedSampleModel)dstIn.getSampleModel();
final int dstInScanStride = dstInSPPSM.getScanlineStride();
DataBufferInt dstInDB = (DataBufferInt)dstIn.getDataBuffer();
final int [] dstInPixels = dstInDB.getBankData()[0];
final int dstInBase =
(dstInDB.getOffset() +
dstInSPPSM.getOffset(x0-dstIn.getSampleModelTranslateX(),
y0-dstIn.getSampleModelTranslateY()));
SinglePixelPackedSampleModel dstOutSPPSM;
dstOutSPPSM = (SinglePixelPackedSampleModel)dstOut.getSampleModel();
final int dstOutScanStride = dstOutSPPSM.getScanlineStride();
DataBufferInt dstOutDB = (DataBufferInt)dstOut.getDataBuffer();
final int [] dstOutPixels = dstOutDB.getBankData()[0];
final int dstOutBase =
(dstOutDB.getOffset() +
dstOutSPPSM.getOffset(x0-dstOut.getSampleModelTranslateX(),
y0-dstOut.getSampleModelTranslateY()));
int [] srcPix = null;
int [] dstOutPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
final int norm = (1<<24)/255;
final int pt5 = (1<<23);
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int dstM = (255-srcPix[sp+3])*norm;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
/**/
public static class InCompositeContext
extends AlphaPreCompositeContext {
InCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
final int norm = (1<<24)/255;
final int pt5 = (1<<23);
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int srcM = dstPix[sp+3]*norm;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class OutCompositeContext
extends AlphaPreCompositeContext {
OutCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
final int norm = (1<<24)/255;
final int pt5 = (1<<23);
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int srcM = (255-dstPix[sp+3])*norm;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class AtopCompositeContext
extends AlphaPreCompositeContext {
AtopCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
final int norm = (1<<24)/255;
final int pt5 = (1<<23);
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int srcM = ( dstPix[sp+3])*norm;
final int dstM = (255-srcPix[sp+3])*norm;
dstPix[sp] =(srcPix[sp]*srcM + dstPix[sp]*dstM +pt5)>>>24;
++sp;
dstPix[sp] =(srcPix[sp]*srcM + dstPix[sp]*dstM +pt5)>>>24;
++sp;
dstPix[sp] =(srcPix[sp]*srcM + dstPix[sp]*dstM +pt5)>>>24;
sp+=2;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class XorCompositeContext
extends AlphaPreCompositeContext {
XorCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
final int norm = (1<<24)/255;
final int pt5 = (1<<23);
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int srcM = (255-dstPix[sp+3])*norm;
final int dstM = (255-srcPix[sp+3])*norm;
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM + pt5)>>>24; ++sp;
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM + pt5)>>>24; ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class ArithCompositeContext
extends AlphaPreCompositeContext {
float k1, k2, k3, k4;
ArithCompositeContext(ColorModel srcCM,
ColorModel dstCM,
float k1, float k2, float k3, float k4) {
super(srcCM, dstCM);
this.k1 = k1;
this.k2 = k2;
this.k3 = k3;
this.k4 = k4;
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
float kk1 = k1/255.0f;
float kk4 = k4*255.0f+0.5f;
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
for (int i=0; i<srcPix.length; i++) {
dstPix[i] =(int)((kk1*srcPix[i]*dstPix[i]) +
k2*srcPix[i] + k3*dstPix[i] + kk4);
if (dstPix[i] < 0) dstPix[i] = 0;
else if (dstPix[i] > 255) dstPix[i] = 255;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
/**
* The following classes implement the various blend modes from SVG.
*/
public static class MultiplyCompositeContext
extends AlphaPreCompositeContext {
MultiplyCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int srcM = 255-dstPix[sp+3];
final int dstM = 255-srcPix[sp+3];
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM +
srcPix[sp]*dstPix[sp])/255; ++sp;
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM +
srcPix[sp]*dstPix[sp])/255; ++sp;
dstPix[sp] = (srcPix[sp]*srcM +
dstPix[sp]*dstM +
srcPix[sp]*dstPix[sp])/255; ++sp;
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((dstPix[sp]*srcPix[sp])/255)); ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class ScreenCompositeContext
extends AlphaPreCompositeContext {
ScreenCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((srcPix[sp]*dstPix[sp])/255)); ++sp;
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((srcPix[sp]*dstPix[sp])/255)); ++sp;
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((srcPix[sp]*dstPix[sp])/255)); ++sp;
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((dstPix[sp]*srcPix[sp])/255)); ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class DarkenCompositeContext
extends AlphaPreCompositeContext {
DarkenCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int alp = sp +3;
final int srcM = 255-dstPix[alp];
final int dstM = 255-srcPix[alp];
while (sp < alp) {
int t1 = ((srcM*srcPix[sp])/255) + dstPix[sp];
int t2 = ((dstM*dstPix[sp])/255) + srcPix[sp];
if (t1 > t2) dstPix[sp] = t2;
else dstPix[sp] = t1;
++sp;
}
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((dstPix[sp]*srcPix[sp])/255)); ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
public static class LightenCompositeContext
extends AlphaPreCompositeContext {
LightenCompositeContext(ColorModel srcCM, ColorModel dstCM) {
super(srcCM, dstCM);
}
public void precompose(Raster src, Raster dstIn,
WritableRaster dstOut) {
int [] srcPix = null;
int [] dstPix = null;
int x=dstOut.getMinX();
int w=dstOut.getWidth();
int y0=dstOut.getMinY();
int y1=y0 + dstOut.getHeight();
for (int y = y0; y<y1; y++) {
srcPix = src.getPixels (x, y, w, 1, srcPix);
dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
int sp = 0;
int end = w*4;
while(sp<end) {
final int alp = sp +3;
final int srcM = 255-dstPix[alp];
final int dstM = 255-srcPix[alp];
while (sp < alp) {
int t1 = ((srcM*srcPix[sp])/255) + dstPix[sp];
int t2 = ((dstM*dstPix[sp])/255) + srcPix[sp];
if (t1 > t2) dstPix[sp] = t1;
else dstPix[sp] = t2;
++sp;
}
dstPix[sp] = (srcPix[sp] + dstPix[sp] -
((dstPix[sp]*srcPix[sp])/255)); ++sp;
}
dstOut.setPixels(x, y, w, 1, dstPix);
}
}
}
}