Search code examples
javacolorsalphagraphics2d

Wrong colors when painting in Graphics2D at 16bit color depth


While working with an old PC1, I found something that I thought to be a strange rendering error.

The problem occurs when the color depth is set to 16bit (32768 colors).

Particularly, it shows up when painting colors with a low alpha value with a Graphics2D. The following is a MVCE to reproduce the issue. It just paints a few sets of lines, with a white color whose alpha value ranges from 4 to 12. The result of running this program is shown in the following screenshot:

OddColors

One can see that some of the colors are actually green for some alpha values, although there clearly should be 50 9 shades of grey.

(Again, it only appears when the color depth is set to 16bit!)

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class _WTFPaintTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().add(new JPanel()
        {
            @Override
            protected void paintComponent(Graphics gr)
            {
                super.paintComponent(gr);
                Graphics2D g = (Graphics2D)gr;
                g.setColor(Color.BLACK);
                g.fillRect(0,0,getWidth(),getHeight());

                for (int a=4; a<=12; a++)
                {
                    int x = (a-3) * 50;
                    g.setColor(Color.WHITE);
                    g.drawString(String.valueOf(a), x, 25);

                    g.setColor(new Color(255,255,255,a));
                    for (int j=0; j<3; j++)
                    {
                        for (int i=0; i<50; i++)
                        {
                            g.drawLine(x,50+i,x+25,75);
                        }
                    }
                }
            }
        });

        f.setSize(550,200);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

(I post it here with its original name, to emphasize how irritating I found this...)

What is the reason for the odd color rendering in this case?


1: WinXP, 32 bit, NVIDIA GeForce 2 graphics card


Solution

  • The reason for this behavior is that the color components (Red, Green and Blue) are represented with a different number of bits in 16-bit color mode.

    The 16 bits are distributed among the color components as follows:

    • Red is represented with 5 bits
    • Green is represented with 6 bits
    • Blue is represented with 5 bits

    This means that a 16 bit color has 25 = 32 different shades of red and blue, but 26 = 64 shades of green.

    (Green receives the additional bit, because the sensitivity of the human eye is greater for shades of green than for shades of red or blue)

    Now, when a standard 24 bit RGB color is converted to a 16 bit color, sampling errors will occur. In a 24 bit color, each color component has 8 bits. These are converted to the corresponding component for the 16 bit color by taking the highest-order bits of the color component.

    In the example screenshot, one can see the lines that are painted with an alpha value of 7. The RGB representation of this color is (7,7,7). The binary representation of this color is (00000111b,00000111b,00000111b). When taking the highest-order bits for each color component, the results are

    • 5 bits for Red : 00000111b -> 00000b
    • 6 bits for Green: 00000111b -> 000001b
    • 5 bits for Blue: 00000111b -> 00000b

    The green component is thus the only component with a non-zero value. The green component of the actual color that is then displayed on the screen can be imagined as the 6-bit green component, left-shifted by 2 bits:

    • 000001b << 2 = 00000100b

    This is confirmed by examining the corresponding pixels in the screenshot, which are stored as 0x000C00, or 24 bit RGB values (0,12,0): A dark shade of green.