How to create X% percent gray in Java?

Suppose I want 25% or 31% gray in Java?

The following code shows

    BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_BYTE_GRAY);

    image.setRGB(0, 0, new Color(0,0,0).getRGB());
    image.setRGB(1, 0, new Color(50, 50, 50).getRGB());
    image.setRGB(0, 1, new Color(100,100,100).getRGB());
    image.setRGB(1, 1, new Color(255,255,255).getRGB());

    Raster raster = image.getData();
    double[] data = raster.getPixels(0, 0, raster.getWidth(), raster.getHeight(), (double[]) null);

    System.out.println(Arrays.toString(data));

      

the obvious fact that RGC refers to the density (?) nonlinear

[0.0, 8.0, 32.0, 255.0]

      

So how do you create a color of a given density?

UPDATE

I have tried the methods suggested by @icza and @hlg and also another one I found:

    double[] data;
    Raster raster;
    BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY);

    float[] grays = {0, 0.25f, 0.5f, 0.75f, 1};

    ColorSpace linearRGB = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
    ColorSpace GRAY = ColorSpace.getInstance(ColorSpace.CS_GRAY);

    Color color;
    int[] rgb;

    for(int i=0; i<grays.length; ++i) {

        System.out.println("\n\nShould be " + (grays[i]*100) + "% gray");

        color = new Color(linearRGB, new float[] {grays[i], grays[i], grays[i]}, 1f);

        image.setRGB(0, 0, color.getRGB());
        raster = image.getData();
        data = raster.getPixels(0, 0, 1, 1, (double[]) null);

        System.out.println("data by CS_LINEAR_RGB (hlg method) = " + Arrays.toString(data));

        color = new Color(GRAY, new float[] {grays[i]}, 1f);

        image.setRGB(0, 0, color.getRGB());
        raster = image.getData();
        data = raster.getPixels(0, 0, 1, 1, (double[]) null);

        System.out.println("data by CS_GRAY = " + Arrays.toString(data));

        rgb = getRGB(Math.round(grays[i]*255));

        color = new Color(rgb[0], rgb[1], rgb[2]);

        image.setRGB(0, 0, color.getRGB());
        raster = image.getData();
        data = raster.getPixels(0, 0, 1, 1, (double[]) null);

        System.out.println("data by icza method = " + Arrays.toString(data));

    }

      

and all gave different results!

Should be 0.0% gray
data by CS_LINEAR_RGB (hlg method) = [0.0]
data by CS_GRAY = [0.0]
data by icza method = [0.0]


Should be 25.0% gray
data by CS_LINEAR_RGB (hlg method) = [63.0]
data by CS_GRAY = [64.0]
data by icza method = [36.0]


Should be 50.0% gray
data by CS_LINEAR_RGB (hlg method) = [127.0]
data by CS_GRAY = [128.0]
data by icza method = [72.0]


Should be 75.0% gray
data by CS_LINEAR_RGB (hlg method) = [190.0]
data by CS_GRAY = [192.0]
data by icza method = [154.0]


Should be 100.0% gray
data by CS_LINEAR_RGB (hlg method) = [254.0]
data by CS_GRAY = [254.0]
data by icza method = [255.0]

      

Now I am wondering which one is correct?

UPDATE 2

Sorry the gray / white percentage should of course be canceled.

+3


source to share


3 answers


The huge differences are related to sRGB gamma encoding ( Wikipedia ). sRGB is the default color space used in the constructor Color

. If you set colors using linear RGB color space, the grayscale values ​​are not distorted:

ColorSpace linearRGB = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
Color grey50 = new Color(linearRGB, new float[]{50f/255,50f/255,50f/255}, 1f);
Color grey100 = new Color(linearRGB, new float[]{100f/255,100f/255,100f/255}, 1f);
Color grey255 = new Color(linearRGB, new float[]{1f,1f,1f}, 1f);

      

However, adjusting the pixel with Color.getRGB

and ImageBuffer.setRGB

converts linear grayscale values ​​to and from sRGB. Thus, they are gamma encoded and decoded, resulting in rounding errors depending on the selected color space.



These errors can be avoided by directly setting the original pixel data behind the gray color model:

WritableRaster writable = image.getRaster();
writable.setPixel(0,0, new int[]{64});

      

Note that you need to round up percentages, eg. for 25% you cannot save 63.75

. If you need higher precision, use TYPE_USHORT_GRAY

instead TYPE_BYTE_GRAY

.

+2


source


When converting RGB color to grayscale, the following weights are used:

0.2989, 0.5870, 0.1140

Source: Convert RGB to Grayscale / Intensity

And on Wikipedia: http://en.wikipedia.org/wiki/Grayscale

So formally:

gray = 0.2989*R + 0.5870*G + 0.1140*B

      

Basically you want the inverse function. You need to find the R, G and B values ​​that give the result gray

you are looking for. Since there are 3 parameters in the equation, in most cases there are many RGB values ​​that will result in the value gray

you are looking for.

Think about it: an RGB color with a high R component and none of G and B will be gray, there might be a different RGB color with some G component, and none of R and B will have the same gray, so represent several possible RGB solutions for the desired gray.

Algorithm

Here's one possible solution. What it does is try to establish that the first of the RGB components will be so large as to multiply its weight, will return a value gray

. If it "overflows" at 255, it is truncated, we decrease gray

with the amount that the maximum value of the component can "represent", and we try to do this for the next component with the remaining amount gray

.



Here I am using an input range gray

0..255

. If you want to specify it as a percentage, just convert it as gray = 255*percent/100

.

private static double[] WEIGHTS = { 0.2989, 0.5870, 0.1140 };

public static int[] getRGB(int gray) {
    int[] rgb = new int[3];

    for (int i = 0; i < 3; i++) {
        rgb[i] = (int) (gray / WEIGHTS[i]);
        if (rgb[i] < 256)
            return rgb; // Successfully "distributed" all of gray, return it

        // Not quite there, cut it...
        rgb[i] = 255;
        // And distribute the remaining on the rest of the RGB components:
        gray -= (int) (255 * WEIGHTS[i]);
    }

    return rgb;
}

      

To test it, use the following method:

public static int toGray(int[] rgb) {
    double gray = 0;
    for (int i = 0; i < 3; i++)
        gray += rgb[i] * WEIGHTS[i];
    return (int) gray;
}

      

Test:

for (int gray = 0; gray <= 255; gray += 50) {
    int[] rgb = getRGB(gray);
    System.out.printf("Input: %3d, Output: %3d,   RGB: %3d, %3d, %3d\n",
            gray, toGray(rgb), rgb[0], rgb[1], rgb[2]);
}

      

Test output:

Input:   0, Output:   0,   RGB:   0,   0,   0
Input:  50, Output:  49,   RGB: 167,   0,   0
Input: 100, Output:  99,   RGB: 255,  40,   0
Input: 150, Output: 150,   RGB: 255, 126,   0
Input: 200, Output: 200,   RGB: 255, 211,   0
Input: 250, Output: 250,   RGB: 255, 255, 219

      

The results show what we expected based on the algorithm: the R component is first filled, as soon as it reaches 255, the G component gets "filled" and the last G component is used.

+4


source


The color has a certain brightness that you want to keep if the color is grayer.

The brightness can be something like this:

Y = 0.2989*R + 0.5870*G + 0.1140*B
Y = 0.2126*R + 0.7152*G + 0.0722*B

      

So new Color(Y, Y, Y)

corresponds to a gray value with the same brightness. Grayness up to a certain percentage is interpolation.

Color grayed(Color color, int perc) {
    double percGrayed = perc / 100.0;
    double percColored = 1.0 - percGrayed;

    double[] weights = { 0.2989, 0.5870, 0.1140 };
    double[] rgb = { color.getR(), color.getG(), color.getB() };

    // Determine luminance:
    double y = 0.0;
    for (int i = 0; i < 3; ++i) {
        y += weights[i] * rgb[i];
    }

    // Interpolate between (R, G, B) and (Y, Y, Y):
    for (int i = 0; i < 3; ++i) {
        rgb[i] *= percColoured;
        rgb[i] += y * percGrayed;
    }

    return new Color((int)rgb[0], (int)rgb[1], (int)rgb[2]);
}

Color grayedColor = grayed(color, 30); // 30% grayed.

      

0


source







All Articles