Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create X% percent gray color in Java?

Suppose I want 25% or 31% gray color 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));

obvious fact, that RGC relates with density (?) non linear

[0.0, 8.0, 32.0, 255.0]

So, how to create color of a given density?

UPDATE

I have tried methods, proposed by @icza and @hlg and also one more found by me:

    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 wonder which one is correct?

UPDATE 2

Sorry, gray/white percentage should be, of course, reversed.

like image 356
Suzan Cioc Avatar asked Oct 17 '14 14:10

Suzan Cioc


People also ask

How do I convert to grayscale in Java?

Syntax: File input = new File("digital_image_processing. jpg"); BufferedImage image = ImageIO. read(input); To transform the image from RGB to Grayscale format by using method cvtColor() in the Imgproc class.

Why is RGB Gray?

The RGB scale is calibrated so that when a color's three red/green/blue numbers are equal, the color is a shade of gray. E.g. red=50 green=50 blue=50 is gray, without any bias towards red, green, or blue hue.

How do you calculate RGB intensity?

The intensity is the sum of the RGB values normalized to 1: 1 1= 3(R + G+ B). (10.11) - R+G+B.


2 Answers

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

0.2989, 0.5870, 0.1140

Source: Converting 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 what you need is the inverse of this function. You need to find R, G and B values which give the result gray value you are looking for. Since there are 3 parameters in the equation, in most of the cases there are lots of RGB values which will result in the gray value you are looking for.

Just think of it: an RGB color with high R component and none of G and B gives a gray, there may be another RGB color with some G component and none of R and B which gives the same gray color, so there are multiple possible RGB solutions to the desired gray color.

The Algorithm

Here is one possible solution. What it does is it tries to set the first of the RGB components to be as big so multiplying by its weight will give back the gray. If it "overflows" beyond 255, it is cut, we decrease the gray with the amount the max value of the component can "represent" and we try to do this for the next component with the remaining gray amount.

Here I use a gray input range of 0..255. If you want to specify it in percent, just convert it like 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 verify 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: R component is "filled" first, once it reaches 255, G component gets "filled" and last the G component gets used.

like image 127
icza Avatar answered Oct 19 '22 14:10

icza


The huge differences are due to the gamma encoding in sRGB (Wikipedia). sRGB is the default color space used in the Color constructor. If you set your colors using a linear RGB color space instead, the grey 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, when setting the pixel by using Color.getRGB and ImageBuffer.setRGB, the linear grey scale values are converted to sRGB and back. Thus they are gamma encoded and decoded, yielding rounding errors depending on the chosen color space.

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

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

Note, that you have to round the percentage values, e.g. for 25% you can not store 63.75. If you need more precision, use TYPE_USHORT_GRAY instead of TYPE_BYTE_GRAY.

like image 36
hlg Avatar answered Oct 19 '22 14:10

hlg