1

I am working on a Java application with lots of geoemtric shapes of color red, green and blue are drawn on a JPanel.

Now, I'd like for overlapping shapes to have their overlap area drawn with the color of adding those two colors like the diagram on right image.

Pictured below is the current real screenshot using draw colors with transparency, but that didn't work. Next by is an edited image of the desired result. How can I achieve this?

enter image description here enter image description here

Current graphics drawn with:

public class DrawPanel extends JPanel
    Color red, green, blue;
    DrawPanel(){
        red = new Color(255, 0, 0, 150);
        green = new Color(0, 255, 0, 150);
        blue = new Color(0, 0, 255, 150);
        this.setBackground(Color.WHITE);
        this.setSize(400, 300);
        this.setFocusable(true);
        this.requestFocus();
        this.setVisible(true);
    }        

    @Override
    protected void paintComponent(java.awt.Graphics g) {
        g.setColor(red);
        g.fillRect(50, 50, 100, 100);
        g.setColor(blue);
        g.fillRect(75, 75, 100, 100);
        g.setColor(green);
        g.fillRect(100, 100, 100, 100);
    }
}

Edit: I figured out that using

g.setXORMode(Color.Black)

Made the colors mix as intended:

enter image description here

HOWEVER! It seemed to be extremely resource heavy to do it this way. The program would freeze for several seconds and even crash my computer!

Zaffaro
  • 131
  • 2
  • 12
  • Added some code. Just thought it'd be kinda trivial. Believe it or not, it's not my homework but a larger hobby project I'm working on. But all the details are irrelevant to the question and I thought maybe there was a stupidly easy solution to this like some sorta g.setColorMixMode. – Zaffaro Feb 26 '17 at 00:04
  • There's a method in rectangle that returns the intersecting rectangle between 2 rectangles. I'm sure your solution can include that. intersection(Rectangle r) Computes the intersection of this Rectangle with the specified Rectangle. Is that yellow intersection in your picture supposed to be light blue? – Nick Ziebert Feb 26 '17 at 01:08
  • @Zaffaro, `I figured out that using g.setXORMode(Color.Black) Made the colors mix as intended:` - where did you add that code? I copied the code you posted and added that statement at the beginning of the painting method and it didn't work. Post the code you used as a solution for the benefit or others. – camickr Feb 26 '17 at 03:32
  • By the way, whenever you override paintComponent it's important to always call `super.paintComponent(g)`; I initially forgot to include that in my answer below, but I've added it now. It doesn't affect the compositing of colors, but it's the best practice. See this question for more info: http://stackoverflow.com/questions/28724609/what-does-super-paintcomponentg-do – David Conrad Feb 26 '17 at 13:44

1 Answers1

2

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);
    }
}
David Conrad
  • 15,432
  • 2
  • 42
  • 54