javaswingundefined-behaviorcmykjcolorchooser

Anomalous behavior (or possible bug) in JColorChooser


When using JColorChooser, entered CMYK values translate to a specific RGB color. When that color is entered manually on the RGB side, the CMYK values are not the same as before.

The following program can be used to demonstrate the behavior I am encountering.

import java.awt.*;
import javax.swing.*;

public class ColorChooserProblem {
    JFrame f = new JFrame("Testing Color Chooser");

    public static void main(String[] args) {
        new ColorChooserProblem().start();
    }

    public void start() {
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JColorChooser jc1 = new JColorChooser();
        JColorChooser jc2 = new JColorChooser();
        f.add(jc1, BorderLayout.NORTH);
        f.add(jc2, BorderLayout.SOUTH);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}
  1. In both panels, select CMYK and type in any valid numbers for CMYK. Both panels must have the same values.
  2. Now compare the RGB values for each panel. They should be the same.
  3. Select a single panel and reset the sliders to 0.
  4. Now re-enter the RGB values into that same panel.
  5. Switch to CMYK for both panels. The values in the panels that I see are different.

Note that when going the other way (i.e. selecting RGB first and re-entering the CMYK values), all works as one might expect. Am I missing something in what's expected of the conversion process, or is this a bug?

I am running Java 10 on Windows 10 and my IDE is Eclipse.

Also posted at http://www.javaprogrammingforums.com/java-theory-questions/41836-possible-bug-jcolorchooser.html


Solution

  • I did some debugging inside the color models used by JColorChooser, in particular ColorModelCMYK (package-private class).

    The calculation is mostly straightforward, except that all values 0..255 are converted to float 0.0..1.0 by scaling by 255.0f. This introduces roundoff errors in the least significant bit (of the IEEE754 float representation).

    Here C=254 is converted to ~R=1 (note that both arrays are the same object and it's updated in-place, so the CMYK values are lost in the conversion. rgb[0]*255f = 0.99999994

    With proper half-up rounding while converting back to integer values for displaying, this shouldn't be any issue. However, digging onto ColorModel itself, I found this function is used by the routine that converts the float array to a packed 32-bit RGB value:

    private static int to8bit(float value) {
        return (int) (255.0f * value);
    }
    

    It's truncated! I don't know if this is a bug, but it certainly is a usability issue.