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:
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)
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);
}
}
}
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With