带有配置文件的Java CMYK到RGB。 输出太暗

有很多次问过类似的问题。 但我仍然不明白为什么在用ICC_Profile转换图片后输出太暗。 我尝试了很多配置文件:来自Adobe网站,以及图片本身。

在图像之前

在图像之前

在图像之后

在图像之后

Iterator readers = ImageIO.getImageReadersByFormatName("jpeg"); ImageReader reader = null; while (readers.hasNext()){ reader = readers.next(); if (reader.canReadRaster()){ break; } } // read ImageInputStream ios = ImageIO.createImageInputStream(new FileInputStream(new File(myPic.jpg))); reader.setInput(ios); Raster r = reader.readRaster(0, null); BufferedImage result = new BufferedImage(r.getWidth(), r.getHeight(), bufferedImage.TYPE_INT_RGB); WritableRaster resultRaster = result.getRaster(); ICC_Profile iccProfile = ICC_Profile.getInstance(new File("profile_name.icc"); ColorSpace cs = new ICC_ColorSpace(iccProfile); ColorConvertOp cmykToRgb = new ColorConvertOp(cs, result.getColorModel().getColorSpace(), null); cmykToRgb.filter(r, resultRaster); // write ImageIo.write(resul, "jpg", new File("myPic.jpg")); 

转换图片后,我还需要做些什么吗?

这个问题并不是全新的。 但由于我花了很多时间在这个问题上并提出了一个有效的解决方案,我想我会在这里发布。 该解决方案需要Sanselan(或现在称为Apache Commons Imaging),它需要合理的CMYK颜色配置文件(.icc文件)。 您可以从Adobe或eci.org获取后者 。

基本问题是Java – 开箱即用 – 只能读取RGB中的JPEG文件。 如果您有CMYK文件,则需要区分常规CMYK,Adobe CMYK(具有反转值,即255表示无墨水,0表示最大墨水)和Adobe CYYK(某些变体具有反转颜色)。

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

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

我对我的解决方案不太满意。 虽然颜色大多是好的,但是暗区域略微太亮,特别是黑色不是完全黑色。 如果有人知道我可以改进什么,我会很高兴听到它。

更新:

我已经弄清楚如何解决亮度问题。 或者更确切地说:来自12monkeys-imageio项目的人们(参见这篇文章 )。 它与颜色渲染意图有关。

修复是添加以下行,这对我很有用。 基本上,颜色配置文件被修改,因为似乎没有其他方法可以告诉ColorConvertOp类使用感知颜色渲染意图。

  if (cmykProfile.getProfileClass() != ICC_Profile.CLASS_DISPLAY) { byte[] profileData = cmykProfile.getData(); // Need to clone entire profile, due to a JDK 7 bug if (profileData[ICC_Profile.icHdrRenderingIntent] == ICC_Profile.icPerceptual) { intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first cmykProfile = ICC_Profile.getInstance(profileData); } } 

...

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

就像我说的那样,想法是将CMYK图片转换为RGB,并在我的应用程序中使用它们。

但由于某种原因,ConvertOp不进行任何CMYK到RGB的转换。 它将numBand数量减少到3,就是这样。 我决定尝试CMYKtoRGB算法。

即获取图像,识别其ColorSpace并读取或转换它。

另一个问题是Photoshop。 我在互联网上找到的这句话。

在adobe的情况下,它在元数据中包括CMYK配置文件,但随后将原始图像数据保存为反转的YCbCrK颜色。

最后,我可以通过下面的算法实现我的目标。 到目前为止我没有使用icc_profiles,输出看起来有点暗..我得到了正确的RGB图像看起来很好。

伪代码

 BufferedImage result = null; Raster r = reader.readRaster() if (r.getNumBands != 4){ result = reader.read(0); } else { if (isPhotoshopYCCK(reader)){ result = YCCKtoCMYKtoRGB(r); }else{ result = CMYKtoRGB(r); } } private boolean isPhotoshopYCCK(reader){ // read IIOMetadata from reader and according to // http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html decide which ColorSpace is used // or maybe there is another way to do it int transform = ... // 2 or 0 or something else return transform; } 

显示YCCKtoCMYKtoRGB或CMYKtoRGB算法没有任何意义。 它很容易在互联网上找到。

非常感谢你的帮助。

如果您使用相同的配置文件在两种完全不同的方式上遇到相同的问题,我认为,文件(profile_name.icc)不正常。

  1. 您正在以CMYK的forms将结果写为Jpeg,即好像它是以RGB格式编写的。 所以,正如我所看到的那样,错误是你正在寻找它的代码片段:-)

  2. 正确的结果导致最后一行。