如何在Java中创建X%百分比的灰色?

Suz*_*ioc 4 java rgb color-space hsb

假设我想在Java中使用25%或31%的灰色?

以下代码显示

    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));
Run Code Online (Sandbox Code Playgroud)

显而易见的事实是,RGC与密度(?)非线性相关

[0.0, 8.0, 32.0, 255.0]
Run Code Online (Sandbox Code Playgroud)

那么,如何创建给定密度的颜色?

UPDATE

我尝试了@icza和@hlg提出的方法,还有一个由我发现的方法:

    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));

    }
Run Code Online (Sandbox Code Playgroud)

并且都给出了不同的结果!

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]
Run Code Online (Sandbox Code Playgroud)

现在我想知道哪一个是正确的?

更新2

对不起,灰/白百分比当然应该是相反的.

icz*_*cza 6

将RGB颜色转换为灰度时,使用以下权重:

0.2989,0.5870,0.1140

来源:将RGB转换为灰度/强度

在维基百科上:http://en.wikipedia.org/wiki/Grayscale

正式地说:

gray = 0.2989*R + 0.5870*G + 0.1140*B
Run Code Online (Sandbox Code Playgroud)

基本上你需要的是这个函数的反转.您需要找到R,G和B值,它们会给出gray您要查找的结果值.由于等式中有3个参数,因此在大多数情况下,有大量RGB值会产生gray您要查找的值.

想一想:具有高R分量且没有G和B的RGB颜色给出灰色,可能有另一种RGB颜色带有一些G成分而没有R和B给出相同的灰色,因此有多种可能RGB解决方案到所需的灰色.

算法

这是一种可能的解决方案.它的作用是尝试将第一个RGB组件设置为大,因此乘以其重量将会返回gray.如果它"溢出"超过255,则会被切断,我们会gray减去组件最大值可以"代表"的数量,并且我们会尝试对剩余gray金额的下一个组件执行此操作.

这里我使用gray输入范围0..255.如果要以百分比形式指定它,只需将其转换为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;
}
Run Code Online (Sandbox Code Playgroud)

要进行验证,请使用以下方法:

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

测试:

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]);
}
Run Code Online (Sandbox Code Playgroud)

测试输出:

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
Run Code Online (Sandbox Code Playgroud)

结果显示了我们基于算法的预期:R组件首先被"填充",一旦达到255,G组件被"填充",最后G组件被使用.


hlg*_*hlg 2

巨大的差异是由于 sRGB 中的伽玛编码(维基百科)造成的。sRGB 是构造函数中使用的默认颜色空间Color。如果您使用线性 RGB 颜色空间来设置颜色,则灰度值不会失真:

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);
Run Code Online (Sandbox Code Playgroud)

Color.getRGB但是,当使用和设置像素时ImageBuffer.setRGB,线性灰度值将转换为 sRGB 并返回。因此,它们是伽玛编码和解码的,根据所选的色彩空间产生舍入误差。

这些错误可以通过直接将原始像素数据设置在灰度颜色模型后面来避免:

WritableRaster writable = image.getRaster();
writable.setPixel(0,0, new int[]{64});
Run Code Online (Sandbox Code Playgroud)

请注意,您必须对百分比值进行四舍五入,例如,对于 25%,您无法存储63.75。如果您需要更高的精度,请使用TYPE_USHORT_GRAY代替TYPE_BYTE_GRAY.