Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

drawRect() isn't working properly on certain colors

I've always created rectangles with outlines like this (using Graphics(2D)):

g.setColor(aColor);
g.fillRect(x, y, width, height);
g.setColor(anotherColor);
g.drawRect(x, y, width, height);

This works fine, except with some colors like Color.BLUE. There are lines that don't have the same thickness:

enter image description here

May be hard to see on the first sight, but if you look closely you will realize that the left line is too thick and the right line is too thin. This happens also with other colors, just not so obviously: (I'm still not sure if this happens with cyan, can't exactly tell)

enter image description here

I can't make sense of this because the black line is just being drawn onto the inner blue rectangle, the inner rectangle shouldn't have an effect on it. (without fillRect() the lines have even thicknesses)

I've provided an example below that will probably help you see the difference better. My Question: Why is this happening with certain RGB-colors and how do I fix it?

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashMap;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.WindowConstants;

public class LineExample {

    Color colors[] = new Color[] { Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN,
            Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.WHITE, Color.YELLOW };
    String colorNames[] = new String[] { "Black", "Blue", "Cyan", "Dark Gray", "Gray", "Green", "Light Gray", "Magenta",
            "Orange", "Pink", "Red", "White", "Yellow" };
    HashMap<String, Color> hashMap = new HashMap<String, Color>();
    Color currentColor = colors[2];

    public LineExample() {

        fillHashMap(hashMap);

        JFrame frame = new JFrame();

        JPanel mainPanel = new JPanel(new BorderLayout());
        JPanel northPanel = new JPanel(new FlowLayout());
        JPanel centerPanel = new JPanel(new GridLayout(1, 2));
        CustomPanel customPanel = new CustomPanel();
        BluePanel bluePanel = new BluePanel();

        JComboBox<String> comboBox = new JComboBox<String>();
        addItems(comboBox);
        comboBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                currentColor = hashMap.get(comboBox.getSelectedItem());
                centerPanel.repaint();
            }
        });

        JToggleButton toggleButton = new JToggleButton("Switch");
        toggleButton.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                centerPanel.removeAll();
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    centerPanel.add(bluePanel);
                    centerPanel.add(customPanel);
                } else if (e.getStateChange() == ItemEvent.DESELECTED) {
                    centerPanel.add(customPanel);
                    centerPanel.add(bluePanel);
                }
                centerPanel.revalidate();
                centerPanel.repaint();
            }
        });

        northPanel.add(comboBox);
        northPanel.add(toggleButton);
        centerPanel.add(customPanel);
        centerPanel.add(bluePanel);
        mainPanel.add(northPanel, BorderLayout.NORTH);
        mainPanel.add(centerPanel, BorderLayout.CENTER);

        frame.setContentPane(mainPanel);

        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(250, 250);
        frame.setVisible(true);

    }

    public void addItems(JComboBox<String> comboBox) {
        for (int i = 0; i < colors.length; i++) {
            comboBox.addItem(colorNames[i]);
        }
        comboBox.setSelectedIndex(2);
    }

    public void fillHashMap(HashMap<String, Color> hashmap) {
        for (int i = 0; i < colors.length; i++) {
            hashMap.put(colorNames[i], colors[i]);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new LineExample();
            }
        });
    }

    public class BluePanel extends JPanel {
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            int width = 100;
            int height = 100;
            int x = ((this.getWidth() - width) / 2);
            int y = ((this.getHeight() - height) / 2);
            g.setColor(Color.BLUE);
            g.fillRect(x, y, width, height);
            g.setColor(Color.BLACK);
            g.drawRect(x, y, width, height);
        }
    }

    public class CustomPanel extends JPanel {
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            int width = 100;
            int height = 100;
            int x = ((this.getWidth() - width) / 2);
            int y = ((this.getHeight() - height) / 2);
            g.setColor(currentColor);
            g.fillRect(x, y, width, height);
            g.setColor(Color.BLACK);
            g.drawRect(x, y, width, height);
        }
    }

}
like image 959
Lukas Rotter Avatar asked Sep 15 '15 21:09

Lukas Rotter


1 Answers

This is an interesting problem that arises because of subpixel arrangements in LCD displays. I took some pictures of my monitor with my phone to explain this effect.

First we look at the left side. The line appears to be around 2 pixels thick. This is due to the fact that going left to right on the line is a black to blue transition. Since red and green pixels can't be illuminated for the colour blue, we have to wait until the next blue subpixel after the line is supposed to end.

enter image description here

Then we look at the right side. This line is approximately 1 pixel thick. This time it's a black to white transition, so the red and green pixels can be illuminated right after the line.

enter image description here

This problem arises as the Java graphics library is not subpixel aware (at least for shapes). You'll always have this problem for some combination of colours on every monitor. It'll be most apparent for the red green blue primary colours, which is why it's hard to tell in your cyan example.

like image 196
Zong Avatar answered Sep 22 '22 01:09

Zong