A Java Graphics2D
has a Composite
which, in conjuction with CompositeContext
, determines how colors are composited. Unfortunately, none of the modes provided by AlphaComposite
do what you want. It is possible to create a custom compositor that adds colors together, but it isn't simple. Once you have such a compositor, call Graphics2D::setComposite
to set it as the compositor to use for drawing.
I found someone who had already created an additive compositor. I've copied that code here with some modifications, in case that post becomes unavailable. In particular, I changed it to use int
instead of float
for the samples, which is more likely to match the actual SampleModel
backing the Rasters
.
There is one problem with additive composition which is that the background color of the JPanel is also included in the addition. To get results like the ones you're looking for, you need to exclude this background color the way a "green screen" is used as a chroma-key. I've added that to the additive compositor from JuddMann:
AdditiveComposite.java:
import java.awt.*;
import java.awt.image.*;
import java.util.Objects;
public class AdditiveComposite implements Composite {
private final Color chromaKey;
public AdditiveComposite(final Color chromaKey) {
this.chromaKey = Objects.requireNonNull(chromaKey);
}
public CompositeContext createContext(ColorModel srcColorModel,
ColorModel dstColorModel, RenderingHints hints) {
return new AdditiveCompositeContext(chromaKey);
}
}
AdditiveCompositeContext.java:
import java.awt.*;
import java.awt.image.*;
import java.util.Objects;
public class AdditiveCompositeContext implements CompositeContext {
private final Color chromaKey;
public AdditiveCompositeContext(final Color chromaKey) {
this.chromaKey = Objects.requireNonNull(chromaKey);
}
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
int r = chromaKey.getRed(), g = chromaKey.getGreen(), b = chromaKey.getBlue();
int[] pxSrc = new int[src.getNumBands()];
int[] pxDst = new int[dstIn.getNumBands()];
int chans = Math.min(src.getNumBands(), dstIn.getNumBands());
for (int x = 0; x < dstIn.getWidth(); x++) {
for (int y = 0; y < dstIn.getHeight(); y++) {
pxSrc = src.getPixel(x, y, pxSrc);
pxDst = dstIn.getPixel(x, y, pxDst);
int alpha = pxSrc.length > 3? alpha = pxSrc[3] : 255;
if (pxDst[0] == r && pxDst[1] == g && pxDst[2] == b) {
pxDst[0] = 0; pxDst[1] = 0; pxDst[2] = 0;
}
for (int i = 0; i < 3 && i < chans; i++) {
pxDst[i] = Math.min(255, (pxSrc[i] * alpha / 255) + (pxDst[i]));
dstOut.setPixel(x, y, pxDst);
}
}
}
}
@Override public void dispose() { }
}
(I also made some formatting changes and removed unused variables.)
The author, JuddMann, mentioned some performance considerations in a comment. The code calls getPixel
twice and setPixel
once for every pixel in the region being composited. It is possible to call getPixels
and setPixels
instead to get entire scan lines or blocks of pixels at a time, but in some tests I did this seemed to have little effect on the time it took, with either method taking a few tens of milliseconds for the paint method in the demo below.
Here is a quick demo. Compo.java:
import java.awt.*;
import javax.swing.*;
public class Compo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame("Compo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.setLocation(400, 100);
JPanel buttons = new JPanel();
frame.add(buttons, BorderLayout.NORTH);
frame.add(new Compo(), BorderLayout.CENTER);
frame.setVisible(true);
}
@Override public void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D) g1;
g.setComposite(new AdditiveComposite(getBackground()));
int x = getWidth() / 5;
int y = getHeight() / 5;
g.setColor(Color.RED);
g.fillOval(3*x/2, y, 2*x, 2*y);
g.setColor(Color.BLUE);
g.fillOval(2*x, 2*y, 2*x, 2*y);
g.setColor(Color.GREEN);
g.fillOval(x, 2*y, 2*x, 2*y);
}
}