RGB转CMYK和反向算法

12

我正在尝试实现一个计算RGB和CMYK之间互相转换的解决方案。以下是我的代码:

  public static int[] rgbToCmyk(int red, int green, int blue)
    {
        int black = Math.min(Math.min(255 - red, 255 - green), 255 - blue);

        if (black!=255) {
            int cyan    = (255-red-black)/(255-black);
            int magenta = (255-green-black)/(255-black);
            int yellow  = (255-blue-black)/(255-black);
            return new int[] {cyan,magenta,yellow,black};
        } else {
            int cyan = 255 - red;
            int magenta = 255 - green;
            int yellow = 255 - blue;
            return new int[] {cyan,magenta,yellow,black};
        }
    }

    public static int[] cmykToRgb(int cyan, int magenta, int yellow, int black)
    {
        if (black!=255) {
            int R = ((255-cyan) * (255-black)) / 255; 
            int G = ((255-magenta) * (255-black)) / 255; 
            int B = ((255-yellow) * (255-black)) / 255;
            return new int[] {R,G,B};
        } else {
            int R = 255 - cyan;
            int G = 255 - magenta;
            int B = 255 - yellow;
            return new int[] {R,G,B};
        }
    }

每个人都希望快速得到答案,指定时间是没有用的。 - Eric
这个解决方案对你有用吗?我看到你尝试去掉ICC_Colorspace,你能保持它运行吗? - TacB0sS
6个回答

9

正如Lea Verou所说,您应该利用颜色空间信息,因为没有一种算法可以从RGB映射到CMYK。 Adobe提供了一些ICC颜色配置文件供下载1,但我不确定它们的许可证是什么。

一旦您拥有了颜色配置文件,类似下面的内容就可以完成工作:

import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.util.Arrays;


public class ColorConv {
    final static String pathToCMYKProfile = "C:\\UncoatedFOGRA29.icc";

    public static float[] rgbToCmyk(float... rgb) throws IOException {
        if (rgb.length != 3) {
            throw new IllegalArgumentException();
        }
        ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(pathToCMYKProfile));
        float[] fromRGB = instance.fromRGB(rgb);
        return fromRGB;
    }
    public static float[] cmykToRgb(float... cmyk) throws IOException {
        if (cmyk.length != 4) {
            throw new IllegalArgumentException();
        }
        ColorSpace instance = new ICC_ColorSpace(ICC_Profile.getInstance(pathToCMYKProfile));
        float[] fromRGB = instance.toRGB(cmyk);
        return fromRGB;
    }

    public static void main(String... args) {
        try {
            float[] rgbToCmyk = rgbToCmyk(1.0f, 1.0f, 1.0f);
            System.out.println(Arrays.toString(rgbToCmyk));
            System.out.println(Arrays.toString(cmykToRgb(rgbToCmyk[0], rgbToCmyk[1], rgbToCmyk[2], rgbToCmyk[3])));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这只是将 RGB 转换为颜色空间调整后的 RGB 吗?当我使用 BogusColorSpace 运行此示例时,我只会得到相同的 R、G、B 值,而不是一个正确的 C、M、Y、K 数组。 - tresf
请注意,我已经针对驱动程序提供的“.icc”配置文件运行了此示例,并似乎也得到了 R、G、B 值。 但如果我们真的想直接与 C、M、Y、K 墨盒/色带/墨粉交流,该怎么办呢?如何获取这些4个值呢? - tresf
回答自己的问题... 显然并非所有的 .icc 配置文件都是相同的。我刚刚测试了一个供应商提供的 .icc 配置文件,它位于 C:\Windows\system32\spool\drivers\color 内部,它没有返回 CMYK -- 而是 RGB 值... 然而在这个评论中提到的 Adobe 驱动程序确实返回了 CMYK。 :) - tresf

9
为了准确地将RGB和CMYK值相互转换,就像Photoshop一样,您需要使用ICC颜色配置文件。在网络上找到的所有简单算法解决方案(如上面发布的解决方案)都是不准确的,并且产生的颜色超出了CMYK颜色范围(例如,它们将CMYK(100、0、0、0)转换为rgb(0、255、255),显然是错误的,因为rgb(0、255、255)无法用CMYK进行再现)。 请查看java.awt.color.ICC_ColorSpacejava.awt.color.ICC_Profile类以使用ICC颜色配置文件转换颜色。 至于颜色配置文件本身,Adobe免费分发它们。

这些ICC类是来自JDK 7吗?我在Java 6文档中找不到任何关于它们的参考。 - Lawrence Dol
据我所知,它们既不是新的(我在两年前就用过它们),也不属于某些外部库,它们是内置的。 - Lea Verou

5
更好的方法:

一种更好的做法:

    try {
        // The "from" CMYK colorspace
        ColorSpace cmykColorspace = new ICC_ColorSpace(ICC_Profile.getInstance("icc/CoatedFOGRA27.icc"));
        // The "to" RGB colorspace
        ColorSpace rgbColorspace = new ICC_ColorSpace(ICC_Profile.getInstance("icc/AdobeRGB1998.icc"));

        // Bring in to CIEXYZ colorspace (refer to Java documentation: http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/color/ColorSpace.html)
        float[] ciexyz = cmykColorspace.toCIEXYZ(cmyk);
        float[] thisColorspace = rgbColorspace.fromCIEXYZ(ciexyz);
        float[] rgb = thisColorspace;
        Color c = new Color(rgb[0], rgb[1], rgb[2]);

        // Format RGB as Hex and return
        return String.format("#%06x", c.getRGB() & 0xFFFFFF);
    } catch (IOException e) { e.printStackTrace(); }

0
为了正确显示CMYK图像,应该包含颜色空间信息作为ICC配置文件。因此,最好的方法是使用可以轻松提取的Sanselan ICC配置文件:
ICC_Profile iccProfile = Sanselan.getICCProfile(new File("filename.jpg"));
ColorSpace cs = new ICC_ColorSpace(iccProfile);    

如果图像没有附加ICC配置文件,我将使用Adobe profiles作为默认值。

现在的问题是,您不能仅使用ImageIO加载具有自定义颜色空间的JPEG文件,因为它会失败并抛出异常,指责它不支持某些颜色空间或类似的东西。因此,您必须使用光栅来处理:

JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(new ByteArrayInputStream(data));
Raster srcRaster = decoder.decodeAsRaster();

BufferedImage result = new BufferedImage(srcRaster.getWidth(), srcRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster resultRaster = result.getRaster();

ColorConvertOp cmykToRgb = new ColorConvertOp(cs, result.getColorModel().getColorSpace(), null);
cmykToRgb.filter(srcRaster, resultRaster);

然后您可以在需要的任何地方使用result,它将转换颜色。

然而,在实践中,我遇到了一些图像(用相机拍摄并用Photoshop处理),它们的颜色值被反转,因此生成的图像总是反转的,即使再次反转它们也太亮了。虽然我仍然不知道何时确切地使用它(当我需要反转像素值时),但我有一个算法来纠正这些值,并逐像素转换颜色:

JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(new ByteArrayInputStream(data));
Raster srcRaster =  decoder.decodeAsRaster();

BufferedImage ret = new BufferedImage(srcRaster.getWidth(), srcRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster resultRaster = ret.getRaster();

for (int x = srcRaster.getMinX(); x < srcRaster.getWidth(); ++x)
    for (int y = srcRaster.getMinY(); y < srcRaster.getHeight(); ++y) {

        float[] p = srcRaster.getPixel(x, y, (float[])null);

        for (int i = 0; i < p.length; ++i)
            p[i] = 1 - p[i] / 255f;

        p = cs.toRGB(p);

        for (int i = 0; i < p.length; ++i)
            p[i] = p[i] * 255f;

        resultRaster.setPixel(x, y, p);
    }

我相信RasterOp或ColorConvertOp可以用于使对话更有效率,但这对我来说已经足够了。

说真的,没有必要使用这些简化的CMYK到RGB转换算法,因为您可以使用嵌入在图像中或从Adobe免费获取的ICC配置文件。如果嵌入了配置文件,则生成的图像将看起来更好,甚至可能完美。


-1
public static String makeCMYKString(int color) {
    double red = Color.red(color);
    double green = Color.green(color);
    double blue = Color.blue(color);
    double red1 = red / 255;
    double green1 = green / 255;
    double blue1 = blue / 255;
    double max = (Math.max(Math.max(red1, green1), blue1));
    double K = 1 - max;
    double C = (1 - red1 - K) / (1 - K);
    double M = (1 - green1 - K) / (1 - K);
    double Y = (1 - blue1 - K) / (1 - K);
    double CMYK[] = {C, M, Y, K};
    String cmyk = "CMYK = (" + Math.round(C * 100) + " , " + Math.round(M * 100) + " , " + Math.round(Y * 100) + " , " + Math.round(K * 100) + ")";
    return cmyk;
}

-3

这里有一个与你的问题完全相同的问题

这是那个页面的复制粘贴:

/** CMYK to RGB conversion */
/* Adobe PhotoShop algorithm */
cyan = Math.min(255, cyan + black); //black is from K
magenta = Math.min(255, magenta + black);
yellow = Math.min(255, yellow + black);
rgb[0] = 255 - cyan;
rgb[1] = 255 - magenta;
rgb[2] = 255 - yellow;


/* GNU Ghostscript algorithm -- this is better*/
int colors = 255 - black;
rgb[0] = colors * (255 - cyan)/255;
rgb[1] = colors * (255 - magenta)/255;
rgb[2] = colors * (255 - yellow)/255;

11
这些转换公式的结果非常糟糕,几乎没有什么用。它们在网络上的广泛传播并不能改善这一事实。请停止进一步传播它们。 - Codo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接