5

假设我想要在 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));

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

[0.0, 8.0, 32.0, 255.0]

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

更新

我已经尝试过@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));

    }

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

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]

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

更新 2

抱歉,灰/白百分比当然应该颠倒过来。

4

3 回答 3

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

基本上你需要的是这个函数的函数。您需要找到给出gray您正在寻找的结果值的 R、G 和 B 值。由于等式中有 3 个参数,因此在大多数情况下,有很多 RGB 值会产生gray您正在寻找的值。

想一想:具有高 R 分量且 G 和 B 都没有给出灰色的 RGB 颜色,可能还有另一种具有一些 G 分量且 R 和 B 都没有给出相同灰色的 RGB 颜色,所以有多种可能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;
}

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

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

测试:

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

测试输出:

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

结果显示了我们基于算法的预期:首先“填充”R 分量,一旦达到 255,“填充”G 分量,最后使用 G 分量。

于 2014-10-17T14:21:08.973 回答
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);

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

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

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

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

于 2014-10-17T16:14:52.183 回答
0

颜色具有特定的亮度,如果颜色更灰,您希望保留该亮度。

亮度可能类似于:

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

所以new Color(Y, Y, Y)对应亮度相同的灰度值。灰化到特定百分比是一种插值。

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.
于 2014-10-17T15:26:14.590 回答