如何在 Java 中正确地从 CMYK 转换为 RGB?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3123574/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:39:22  来源:igfitidea点击:

How to convert from CMYK to RGB in Java correctly?

javacmyk

提问by Mark

My Java code to convert a CMYK jpeg to RGB results in the output image being far too light - see code below. Can anyone suggest the correct way to do the conversion?

我将 CMYK jpeg 转换为 RGB 的 Java 代码导致输出图像太亮 - 请参阅下面的代码。谁能建议正确的转换方法?

The following code requires Java Advanced Image IOto read the jpeg and example-cmyk.jpg

以下代码需要Java Advanced Image IO读取jpeg和example-cmyk.jpg

import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.File;

import javax.imageio.ImageIO;

public class TestCmykToRgb {

    public static void main(String[] args) throws Exception {
        BufferedImage cmykImage = ImageIO.read(new File(
                "j:\temp\example-cmyk.jpg"));


        BufferedImage rgbImage = new BufferedImage(cmykImage.getWidth(),
                cmykImage.getHeight(), BufferedImage.TYPE_INT_RGB);

        ColorConvertOp op = new ColorConvertOp(null);
        op.filter(cmykImage, rgbImage);

        ImageIO.write(rgbImage, "JPEG", new File("j:\temp\example-rgb.jpg"));

    }
}

回答by crazyscot

CMYK to/fro RGB is difficult - you're converting between additive and subtractive colour. If you want an exact match, you need to look into per-device colour space profiles. What looks OK in one colour space usually doesn't when physically converted to another (i.e. proper CMYK output - not a naive preview on a monitor).

CMYK 来回 RGB 很困难 - 您正在加色和减色之间进行转换。如果您想要完全匹配,则需要查看每个设备的色彩空间配置文件。在一种颜色空间中看起来不错的东西在物理转换为另一种颜色空间时通常不会(即正确的 CMYK 输出 - 不是在监视器上的幼稚预览)。

From my own experience, converting RGB to CMYK naively tends to result in a image that is too dark. Given that you report the opposite in the converse direction, there's probably an approximate brightness adjustment curve to be found which will do a fair job (but watch out for strange non-linearities within the colour space). If you have access to Photoshop I understand it has some sort of CMYK preview option which might speed up the process of figuring out such an approximation.

根据我自己的经验,天真地将 RGB 转换为 CMYK 往往会导致图像太暗。鉴于您在相反的方向报告相反的情况,可能会找到一个近似的亮度调整曲线,这将做得很好(但要注意色彩空间内奇怪的非线性)。如果您可以访问 Photoshop,我知道它有某种 CMYK 预览选项,这可能会加快计算出这种近似值的过程。

回答by Saulius

I will copy my answer from the other thread:

我将从另一个线程复制我的答案:

In order to be displayed correctly CMYK images should contain color space informationas ICC Profile. So the best way is to use that ICC Profile which can be easily extracted with Sanselan:

为了正确显示 CMYK 图像应包含颜色空间信息作为 ICC 配置文件。所以最好的方法是使用可以用Sanselan轻松提取的 ICC 配置文件:

ICC_Profile iccProfile = Sanselan.getICCProfile(new File("filename.jpg"));
ColorSpace cs = new ICC_ColorSpace(iccProfile);    

In case there is no ICC Profile attached to the image, I would use Adobe profilesas default.

如果图像没有附加 ICC 配置文件,我会默认使用Adobe 配置文件

Now the problem is that you cannot just load JPEG file with custom color space using ImageIO as it will fail throwing an exception complaining that it does not support some color space or sthing like that. Hense you will have to work with rasters:

现在的问题是,您不能只使用 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);

You can then use resultwherever you need and it will have converted colors.

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

In practice, however I've come across some images (taken with camera and processed with Photoshop) that had somehow inverted color values so the resulting image was always inverted and even after inverting them once again they were too bright. Although I still have no idea how to find out when exactly to use it (when I need to invert pixel values), I have an algorithm that corrects these values and convert color pixel by pixel:

然而,在实践中,我遇到了一些图像(用相机拍摄并用 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);
    }

I'm pretty sure RasterOp or ColorConvertOp could be used to make conversation more efficient, but this was enough for me.

我很确定可以使用 RasterOp 或 ColorConvertOp 来提高对话效率,但这对我来说已经足够了。

Seriously, there is no need to use these simplified CMYK to RGB conversion algorithms as you can use ICC Profile that is embedded into image or available for free from Adobe. Resulting image is going to look better if not perfect (with embedded profile).

说真的,没有必要使用这些简化的 CMYK 到 RGB 转换算法,因为您可以使用嵌入到图像中或从 Adob​​e 免费获得的 ICC 配置文件。如果不完美(带有嵌入式配置文件),生成的图像看起来会更好。

回答by user1411778

My sollution is based on a previous answer. I used "USWebCoatedSWOP.icc":

我的解决方案基于以前的答案。我使用了“USWebCoatedSWOP.icc”:

        //load source image
        JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(srcImageInputStream);
        BufferedImage src = decoder.decodeAsBufferedImage();
        WritableRaster srcRaster = src.getRaster();
        //prepare result image
        BufferedImage result = new BufferedImage(srcRaster.getWidth(), srcRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
        WritableRaster resultRaster = result.getRaster();
        //prepare icc profiles
        ICC_Profile iccProfileCYMK = ICC_Profile.getInstance(new FileInputStream("path_to_cmyk_icc_profile"));
        ColorSpace sRGBColorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);

        //invert k channel
        for (int x = srcRaster.getMinX(); x < srcRaster.getWidth(); x++) {
            for (int y = srcRaster.getMinY(); y < srcRaster.getHeight(); y++) {
                float[] pixel = srcRaster.getPixel(x, y, (float[])null);
                pixel[3] = 255f-pixel[3];
                srcRaster.setPixel(x, y, pixel);
            }
        }

        //convert
        ColorConvertOp cmykToRgb = new ColorConvertOp(new ICC_ColorSpace(iccProfileCYMK), sRGBColorSpace, null);
        cmykToRgb.filter(srcRaster, resultRaster);

In other words:

换句话说:

  1. Open the image as BufferedImage.
  2. Get its raster.
  3. Invert the black channel in this raster.
  4. Convert to rgb
  1. 将图像作为 BufferedImage 打开。
  2. 获取它的光栅。
  3. 反转此栅格中的黑色通道。
  4. 转换为 rgb

回答by Codo

There is a lot of good stuff in the existing answers already. But none of them is a complete solution that handles the different kinds of CMYK JPEG images.

现有答案中已经有很多好东西。但它们都不是处理不同类型 CMYK JPEG 图像的完整解决方案。

For CMYK JPEG images, you need to distinguish between regular CMYK, Adobe CMYK (with inverted values, i.e. 255 for no ink and 0 for maximum ink) and Adobe CYYK (some variant with inverted colors as well).

对于 CMYK JPEG 图像,您需要区分常规 CMYK、Adobe CMYK(具有反转值,即 255 表示无墨水,0 表示最大墨水)和 Adob​​e CYYK(一些具有反转颜色的变体)。

This solution here requires Sanselan (or Apache Commons Imaging as it's called now) and it requires a reasonable CMYK color profile (.icc file). You can get the later one from Adobe or from eci.org.

这里的解决方案需要 Sanselan(或现在称为 Apache Commons Imaging),并且需要合理的 CMYK 颜色配置文件(.icc 文件)。您可以从 Adob​​e 或 eci.org 获得后者。

public class JpegReader {

    public static final int COLOR_TYPE_RGB = 1;
    public static final int COLOR_TYPE_CMYK = 2;
    public static final int COLOR_TYPE_YCCK = 3;

    private int colorType = COLOR_TYPE_RGB;
    private boolean hasAdobeMarker = false;

    public BufferedImage readImage(File file) throws IOException, ImageReadException {
        colorType = COLOR_TYPE_RGB;
        hasAdobeMarker = false;

        ImageInputStream stream = ImageIO.createImageInputStream(file);
        Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
        while (iter.hasNext()) {
            ImageReader reader = iter.next();
            reader.setInput(stream);

            BufferedImage image;
            ICC_Profile profile = null;
            try {
                image = reader.read(0);
            } catch (IIOException e) {
                colorType = COLOR_TYPE_CMYK;
                checkAdobeMarker(file);
                profile = Sanselan.getICCProfile(file);
                WritableRaster raster = (WritableRaster) reader.readRaster(0, null);
                if (colorType == COLOR_TYPE_YCCK)
                    convertYcckToCmyk(raster);
                if (hasAdobeMarker)
                    convertInvertedColors(raster);
                image = convertCmykToRgb(raster, profile);
            }

            return image;
        }

        return null;
    }

    public void checkAdobeMarker(File file) throws IOException, ImageReadException {
        JpegImageParser parser = new JpegImageParser();
        ByteSource byteSource = new ByteSourceFile(file);
        @SuppressWarnings("rawtypes")
        ArrayList segments = parser.readSegments(byteSource, new int[] { 0xffee }, true);
        if (segments != null && segments.size() >= 1) {
            UnknownSegment app14Segment = (UnknownSegment) segments.get(0);
            byte[] data = app14Segment.bytes;
            if (data.length >= 12 && data[0] == 'A' && data[1] == 'd' && data[2] == 'o' && data[3] == 'b' && data[4] == 'e')
            {
                hasAdobeMarker = true;
                int transform = app14Segment.bytes[11] & 0xff;
                if (transform == 2)
                    colorType = COLOR_TYPE_YCCK;
            }
        }
    }

    public static void convertYcckToCmyk(WritableRaster raster) {
        int height = raster.getHeight();
        int width = raster.getWidth();
        int stride = width * 4;
        int[] pixelRow = new int[stride];
        for (int h = 0; h < height; h++) {
            raster.getPixels(0, h, width, 1, pixelRow);

            for (int x = 0; x < stride; x += 4) {
                int y = pixelRow[x];
                int cb = pixelRow[x + 1];
                int cr = pixelRow[x + 2];

                int c = (int) (y + 1.402 * cr - 178.956);
                int m = (int) (y - 0.34414 * cb - 0.71414 * cr + 135.95984);
                y = (int) (y + 1.772 * cb - 226.316);

                if (c < 0) c = 0; else if (c > 255) c = 255;
                if (m < 0) m = 0; else if (m > 255) m = 255;
                if (y < 0) y = 0; else if (y > 255) y = 255;

                pixelRow[x] = 255 - c;
                pixelRow[x + 1] = 255 - m;
                pixelRow[x + 2] = 255 - y;
            }

            raster.setPixels(0, h, width, 1, pixelRow);
        }
    }

    public static void convertInvertedColors(WritableRaster raster) {
        int height = raster.getHeight();
        int width = raster.getWidth();
        int stride = width * 4;
        int[] pixelRow = new int[stride];
        for (int h = 0; h < height; h++) {
            raster.getPixels(0, h, width, 1, pixelRow);
            for (int x = 0; x < stride; x++)
                pixelRow[x] = 255 - pixelRow[x];
            raster.setPixels(0, h, width, 1, pixelRow);
        }
    }

    public static BufferedImage convertCmykToRgb(Raster cmykRaster, ICC_Profile cmykProfile) throws IOException {
        if (cmykProfile == null)
            cmykProfile = ICC_Profile.getInstance(JpegReader.class.getResourceAsStream("/ISOcoated_v2_300_eci.icc"));

        if (cmykProfile.getProfileClass() != ICC_Profile.CLASS_DISPLAY) {
            byte[] profileData = cmykProfile.getData();

            if (profileData[ICC_Profile.icHdrRenderingIntent] == ICC_Profile.icPerceptual) {
                intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first

                cmykProfile = ICC_Profile.getInstance(profileData);
            }
        }

        ICC_ColorSpace cmykCS = new ICC_ColorSpace(cmykProfile);
        BufferedImage rgbImage = new BufferedImage(cmykRaster.getWidth(), cmykRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
        WritableRaster rgbRaster = rgbImage.getRaster();
        ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
        ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
        cmykToRgb.filter(cmykRaster, rgbRaster);
        return rgbImage;
    }
}


static void intToBigEndian(int value, byte[] array, int index) {
    array[index]   = (byte) (value >> 24);
    array[index+1] = (byte) (value >> 16);
    array[index+2] = (byte) (value >>  8);
    array[index+3] = (byte) (value);
}

The code first tries to read the file using the regular method, which works for RGB files. If it fails, it reads the details of the color model (profile, Adobe marker, Adobe variant). Then it reads the raw pixel data (raster) and does all the necessary conversion (YCCK to CMYK, inverted colors, CMYK to RGB).

代码首先尝试使用常规方法读取文件,该方法适用于 RGB 文件。如果失败,它会读取颜色模型的详细信息(配置文件、Adobe 标记、Adobe 变体)。然后它读取原始像素数据(光栅)并进行所有必要的转换(YCCK 到 CMYK、反转颜色、CMYK 到 RGB)。

Update:

更新:

The original code has a slight problem: the result was too bright. The people from the twelvemonkeys-imageio project had the same problem (see this post) and have fixed it by patching the color profile such that Java uses a perceptual color render intent. The fix has been integrated into the above code.

原来的代码有一个小问题:结果太亮了。来自十二猴子-图像项目的人有同样的问题(见这篇文章)并通过修补颜色配置文件来修复它,这样 Java 使用感知颜色渲染意图。该修复程序已集成到上述代码中。

回答by Eyal

There is a new open source library which supports CMYK processing. All you need to do is to add the dependency to your project and a new reader will be added to the list of readers (while the known JPEGImageReader can't deal with CMYK). You will probably want to iterate over these readers and read the image using the first reader which doesn't throw exception. This package is a release candidate, but i am using it and it solved a huge problem that we had hard time dealing with.

有一个支持 CMYK 处理的新开源库。您需要做的就是将依赖项添加到您的项目中,一个新的阅读器将添加到阅读器列表中(而已知的 JPEGImageReader 无法处理 CMYK)。您可能希望遍历这些读取器并使用第一个不引发异常的读取器读取图像。这个包是一个候选版本,但我正在使用它,它解决了一个我们很难处理的大问题。

http://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-jpeg/

http://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-jpeg/

EDIT: as stated in the comments, you can now also find a stable release rather than RC.

编辑:如评论中所述,您现在还可以找到稳定版本而不是 RC。

You can do the iteration this way to get the BufferedImage, and after you got that, the rest is easy (you can use any existing image converting package to save it as another format):

您可以通过这种方式进行迭代以获取 BufferedImage,获得之后,剩下的就很简单了(您可以使用任何现有的图像转换包将其另存为另一种格式):

try (ImageInputStream input = ImageIO.createImageInputStream(source)) {

        // Find potential readers
        Iterator<ImageReader> readers = ImageIO.getImageReaders(input);

        // For each reader: try to read
        while (readers != null && readers.hasNext()) {
            ImageReader reader = readers.next();
            try {
                reader.setInput(input);
                BufferedImage image = reader.read(0);
                return image;
            } catch (IIOException e) {
                // Try next reader, ignore.
            } catch (Exception e) {
                // Unexpected exception. do not continue
                throw e;
            } finally {
                // Close reader resources
                reader.dispose();
            }
        }

        // Couldn't resize with any of the readers
        throw new IIOException("Unable to resize image");
    }

回答by Rohit

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