0

I'm trying to get the color of a Swing JFrame, for a chosen (x,y) pixel of the component.

For example, I want to know the color of a given JFrame at their (0,0) point.

The reason is that my component is a partially transparent overlay, with a JPanel underneath. For pixels that are opaque, the mouse events should be dealt by the overlay. For pixels that are transparent, the mouse events should instead be forwarded to the JPanel underneath.

Is this a way to do this?

Guillaume F.
  • 1,010
  • 7
  • 21

2 Answers2

2

Yes it's possible. Use the function getColorAt from the example below:

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class GUI {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(GUI::startUp); 

    }

    private static void startUp() {
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(1020,760);
        frame.setResizable(false);
        frame.setLayout(new FlowLayout());
        frame.getContentPane().setBackground(Color.BLUE);
        JTextArea jta = new JTextArea(40,40);
        jta.setEditable(false);
        jta.setBackground(Color.WHITE);
        frame.add(new JScrollPane(jta));
        frame.setVisible(true);
        SwingUtilities.invokeLater(() -> printColors(frame));
    }

    private static void printColors(JFrame frm) {
        System.out.println("Color at (1, 1): " + getColorAt(frm, new Point(1, 1)));
        System.out.println("Color at (300, 100): " + getColorAt(frm, new Point(300, 100)));
    }

    public static Color getColorAt(JFrame frm, Point p) {
        Rectangle rect = frm.getContentPane().getBounds();
        BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_ARGB);
        frm.getContentPane().paintAll(img.createGraphics());
        return new Color(img.getRGB(p.x, p.y), true);
    }
}
Sergiy Medvynskyy
  • 11,160
  • 1
  • 32
  • 48
  • (1+) but the question also deals with transparency so you will need to use: `new Color(…, true)` to make sure the alpha value is contained in the Color object so you can check for transparency. – camickr Mar 28 '20 at 23:05
1

I would like to say (hoping this will introduce better performance) that maybe, if you are willing to go with the Image approach for this, it could be good to create an image of dimensions 1x1 pixels and then translate its created graphics to match the requested point. And also reuse this image for subsequent samplings of the same Component (or even GraphicsConfiguration).

I did some performance tests with creating the following approaches:

  1. A method called getColorAtClipped which sets the clip of the created Graphics of the Image so not all operations have to be drawn.
  2. A method called getColorAtRelocation which sets the location of the component temporarily at the location needed to be sampled and then (what actually makes it faster) create an image of dimensions 1x1 and draw the parent on it. Although this method is not really thread safe for Swing as it requires to set the location of the Component back and forth. It also calls printAll for the parent Container which means more Components to be painted.
  3. Then a method called getColorAtTranslation which creates an 1x1 image and translates its Graphics instance so as the required location will be drawn actually at (0,0) which is the only pixel really in the image. This method turned out to be the fastest for up to this 3 first methods.
  4. Then why not reuse the same resources for subsequent samples?... So that leads me to the final approach: a class enclosing all required resources which participate in the sampling: the one called ComponentColorSampler in the following code

Testing code:

A code to test the performance of the above approaches follows in this section. If it is not correct, let me know in the comments, but be aware that I ran each method for about 3 million samplings in hope that collateral delays will be dwarfed. Every million samples of a test method, I printed some timings, and then restarted the process to test another million, up to 3.

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.function.IntBinaryOperator;
import java.util.function.Supplier;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Main {

    public static Color getColorAtClipped(final Component comp, final Point p) {
        final BufferedImage bimg = comp.getGraphicsConfiguration().createCompatibleImage(comp.getWidth(), comp.getHeight());
        final Graphics2D g2d = (Graphics2D) bimg.createGraphics();
        g2d.setClip(p.x, p.y, 1, 1);
        comp.printAll(g2d);
        g2d.dispose();
        final Color c = new Color(bimg.getRGB(p.x, p.y), true);
        bimg.flush();
        return c;
    }

    public static Color getColorAtRelocation(final Component comp, final Point p) {
        final Point loc = comp.getLocation();
        final BufferedImage bimg = comp.getGraphicsConfiguration().createCompatibleImage(1, 1);
        comp.setLocation(loc.x - p.x, loc.y - p.y);
        final Graphics2D g2d = (Graphics2D) bimg.createGraphics();
        //g2d.setClip(0, 0, 1, 1);
        comp.getParent().printAll(g2d);
        comp.setLocation(loc);
        g2d.dispose();
        final Color c = new Color(bimg.getRGB(0, 0), true);
        bimg.flush();
        return c;
    }

    public static Color getColorAtTranslation(final Component comp, final Point p) {
        final BufferedImage bimg = comp.getGraphicsConfiguration().createCompatibleImage(1, 1);
        final Graphics2D g2d = (Graphics2D) bimg.createGraphics();
        g2d.translate(-p.x, -p.y);
        //g2d.setClip(0, 0, 1, 1);
        comp.printAll(g2d);
        g2d.dispose();
        final Color c = new Color(bimg.getRGB(0, 0), true);
        bimg.flush();
        return c;
    }

    public static class ComponentColorSampler<C extends Component> implements AutoCloseable, IntBinaryOperator, Supplier<C> {
        private final C comp;
        private final BufferedImage bimg;
        private final Graphics2D g2d;
        private int x, y;

        public ComponentColorSampler(final C comp) {
            this.comp = Objects.requireNonNull(comp);
            bimg = comp.getGraphicsConfiguration().createCompatibleImage(1, 1);
            g2d = bimg.createGraphics();
            //g2d.setClip(0, 0, 1, 1);
            x = y = 0;
        }

        @Override
        public C get() {
            return comp;
        }

        @Override
        public int applyAsInt(final int x, final int y) {
            g2d.clearRect(0, 0, 1, 1);
            g2d.translate(this.x - x, this.y - y);
            this.x = x;
            this.y = y;
            comp.printAll(g2d);
            return bimg.getRGB(0, 0);
        }

        public Color sample(final int x, final int y) {
            return new Color(applyAsInt(x, y), true);
        }

        @Override
        public void close() {
            g2d.dispose();
            bimg.flush();
        }
    }

    public static class DrawPanel extends JPanel {
        private final int x, y;
        private Color c;

        public DrawPanel(final int x, final int y) {
            this.x = x;
            this.y = y;
            c = Color.BLUE;
        }

        @Override
        protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            g.setColor(c);
            g.fillRect(x, y, 1, 1);
        }

        public void setColor(final Color c) {
            this.c = Objects.requireNonNull(c);
            paintImmediately(0, 0, getWidth(), getHeight()); //Not sure yet.
            repaint(); //Just to be sure now.
        }
    }

    //@SuppressWarnings("SleepWhileInLoop")
    public static boolean checkValid(final DrawPanel dp, final Supplier<Color> sampler) throws InterruptedException, InvocationTargetException {
        for (final Color c: new Color[]{Color.BLUE, Color.RED, Color.BLACK, Color.WHITE, Color.BLACK, Color.CYAN}) {
            SwingUtilities.invokeAndWait(() -> dp.setColor(c));
            Thread.sleep(250); //Let it some time to change (not sure if needed).
            if (!Objects.equals(c, sampler.get()))
                return false;
        }
        return true;
    }

    public static long checkTime(final Supplier<Color> sampler) {
        final long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; ++i)
            sampler.get();
        return System.currentTimeMillis() - start;
    }

    public static void main(final String[] args) throws InterruptedException, InvocationTargetException {

        final Point p = new Point(100, 100);

        final DrawPanel contents = new DrawPanel(p.x, p.y);

        contents.setPreferredSize(new Dimension(200, 200));

        final JFrame frame = new JFrame("Printed!");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(contents);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);

        final ComponentColorSampler<Component> sampler = new ComponentColorSampler<>(contents);

        final Supplier<Color> clipped = () -> getColorAtClipped(contents, p),
                              relocation = () -> getColorAtRelocation(contents, p),
                              translation = () -> getColorAtTranslation(contents, p),
                              samplerSampler = () -> sampler.sample(p.x, p.y);

        System.out.println("#### Validity checks...");
        for (int i = 0; i < 3; ++i) {
            System.out.println("Batch " + (i + 1) + ':');
            System.out.println("> Clipped: " + checkValid(contents, clipped) + '.');
            System.out.println("> Relocation: " + checkValid(contents, relocation) + '.');
            System.out.println("> Translation: " + checkValid(contents, translation) + '.');
            System.out.println("> Sampler: " + checkValid(contents, samplerSampler) + '.');
        }

        System.out.println("#### Timings...");
        for (int i = 0; i < 3; ++i) {
            System.out.println("Batch " + (i + 1) + ':');
            System.out.println("> Clipped: " + checkTime(clipped) + "ms.");
            System.out.println("> Relocation: " + checkTime(relocation) + "ms.");
            System.out.println("> Translation: " + checkTime(translation) + "ms.");
            System.out.println("> Sampler: " + checkTime(samplerSampler) + "ms.");
        }

        System.out.println("#### Done.");
    }
}

Results:

Output of the program:

#### Validity checks...
Batch 1:
> Clipped: true.
> Relocation: true.
> Translation: true.
> Sampler: true.
Batch 2:
> Clipped: true.
> Relocation: true.
> Translation: true.
> Sampler: true.
Batch 3:
> Clipped: true.
> Relocation: true.
> Translation: true.
> Sampler: true.
#### Timings...
Batch 1:
> Clipped: 34668ms.
> Relocation: 22737ms.
> Translation: 5416ms.
> Sampler: 1152ms.
Batch 2:
> Clipped: 38521ms.
> Relocation: 22805ms.
> Translation: 5451ms.
> Sampler: 1156ms.
Batch 3:
> Clipped: 38275ms.
> Relocation: 22864ms.
> Translation: 5415ms.
> Sampler: 1163ms.
#### Done.

So the first approach is about 37 seconds for a million samples, the second approach is about 22, the third 5 and finally the last approach is just above 1 second (for a million samples). So ComponentColorSampler is the fastest implementation in these tests (about 865 samples per millisecond) and works on any Component. Validity checks just stand for somewhat verifying that the sampled color has the correct value.

Note: The tests are not Swing/thread safe but indicate what would be the performance if you used them properly (for example executing the samplings on the Event Dispatch Thread).

gthanop
  • 3,035
  • 2
  • 10
  • 27
  • All the OP wants to do is get the color. That is a trivial exercise. – WJS Apr 03 '20 at 20:49
  • 2
    @WJS ok. I just added some more information. That's all. We don't know how many times and how fast the samplings are going to take place, so that's why I posted. Maybe this will help later someone coming in with more requirements. – gthanop Apr 03 '20 at 20:51
  • 1
    I love your simulation. Since for my application, speed is not critical, I'll go with the "translation" method as it is both fast and easy to understand. One note: the code always returned the pixel as opaque until I changed the initialization of the buffered image to `new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB)`. – Guillaume F. Apr 09 '20 at 05:26
  • 1
    Thank you very much. Yes, indeed I only tested for opaque colors. But I am glad you found the solution. There is also a [createCompatibleImage](https://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-) which creates [translucent](https://docs.oracle.com/javase/8/docs/api/java/awt/Transparency.html#TRANSLUCENT) images, so you can try for example `comp.getGraphicsConfiguration().createCompatibleImage(1, 1, Transparency.TRANSLUCENT);` if you really need to be based on the component's graphics configuration. – gthanop Apr 11 '20 at 21:18