在 Java 中将图像转换为 sRGB 会使图像太亮
In Java converting an image to sRGB makes the image too bright
我有多个图像,其中嵌入了自定义配置文件,我想将图像转换为 sRGB 以便将其提供给浏览器。我见过如下代码:
BufferedImage image = ImageIO.read(fileIn);
ColorSpace ics = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter(image, null);
ImageIO.write(result, "PNG", fileOut);
其中 fileIn
和 fileOut
分别是代表输入文件和输出文件的文件对象。这在一定程度上起作用。问题是生成的图像比原始图像亮。如果我要在 photoshop 中转换颜色 space,颜色将看起来相同。事实上,如果我用 photoshop 调出两张图片并截屏并对颜色进行采样,它们是相同的。 photoshop 做了哪些上面的代码没有做的事情,我该怎么做才能解决这个问题?
有多种类型的图像正在转换,包括 JPEG、PNG 和 TIFF。我尝试使用 TwelveMonkeys 读取 JPEG 和 TIFF 图像,但我仍然得到相同的效果,即图像太亮。当应用于一开始就没有嵌入配置文件的图像时,转换过程似乎最糟糕。
编辑:
我添加了一些示例图片来帮助解释问题。
- 这张图片中嵌入了颜色配置文件。在某些浏览器上查看,这个和下一个之间没有明显区别,但在 Chrome、Mac、OSX 和 Windows 中查看,它目前看起来比应该的暗.这是我的问题首先出现的地方。我需要将图像转换为可以在 Chrome.
中正确显示的内容
- 这是一张用 ImageMagick 转换为 Adobe RGB 1998 颜色配置文件的图像,Chrome 似乎能够正确显示。
- 这是我使用上面的代码转换的图像,它看起来比应有的颜色要浅。
(请注意,上面的图片是在 imgur 上,所以要使它们更大,只需删除文件名末尾,文件扩展名之前的 "t"。)
这是我最初的解决方案,但我不喜欢必须使用 ImageMagick。我根据最终坚持使用的解决方案创建了另一个答案。
我放弃了,最终使用 im4java,它是 image magick 命令行工具的包装器。当我使用以下代码获取 BufferedImage 时,效果非常好。
IMOperation op = new IMOperation();
op.addImage(fileIn.getAbsolutePath());
op.profile(colorFileIn.getAbsolutePath());
op.addImage("png:-");
ConvertCmd cmd = new ConvertCmd();
Stream2BufferedImage s2b = new Stream2BufferedImage();
cmd.setOutputConsumer(s2b);
cmd.run(op);
BufferedImage image = s2b.getImage();
我还可以在需要时使用该库应用 CMYK 配置文件进行打印。如果 ColorConvertOp 正确地进行了转换,那就太好了,但至少现在,这是我的解决方案。为了与我的问题保持一致,在问题中实现相同效果的 im4java 代码是:
ConvertCmd cmd = new ConvertCmd();
IMOperation op = new IMOperation();
op.addImage(fileIn.getAbsolutePath());
op.profile(colorFileIn.getAbsolutePath());
op.addImage(fileOut.getAbsolutePath());
cmd.run(op);
其中 colorFileIn.getAboslutePath()
是机器上 sRGB 颜色配置文件的位置。由于 im4java 使用命令行,因此执行操作的方式并不直接,但对库进行了详细解释 here。正如问题中所解释的,我最初遇到的问题是 image magick 无法在我的 Mac 上工作。我使用 brew 安装它,但结果是 Mac 你必须像 brew install imagemagick --with-little-cms
那样安装它。那个图像 magick 对我来说很好用。
我找到了一个不需要 ImageMagick 的解决方案。基本上 Java 在加载图像时不考虑配置文件,所以如果有的话,就需要加载它。这是我为完成此操作所做的代码片段:
private BufferedImage loadBufferedImage(InputStream inputStream) throws IOException, BadElementException {
byte[] imageBytes = IOUtils.toByteArray(inputStream);
BufferedImage incorrectImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
if (incorrectImage.getColorModel() instanceof ComponentColorModel) {
// Java does not respect the color profile embedded in a component based image, so if there is a color
// profile, detected using iText, then create a buffered image with the correct profile.
Image iTextImage = Image.getInstance(imageBytes);
com.itextpdf.text.pdf.ICC_Profile iTextProfile = iTextImage.getICCProfile();
if (iTextProfile == null) {
// If no profile is present than the image should be processed as is.
return incorrectImage;
} else {
// If there is a profile present then create a buffered image with the profile embedded.
byte[] profileData = iTextProfile.getData();
ICC_Profile profile = ICC_Profile.getInstance(profileData);
ICC_ColorSpace ics = new ICC_ColorSpace(profile);
boolean hasAlpha = incorrectImage.getColorModel().hasAlpha();
boolean isAlphaPremultiplied = incorrectImage.isAlphaPremultiplied();
int transparency = incorrectImage.getTransparency();
int transferType = DataBuffer.TYPE_BYTE;
ComponentColorModel ccm = new ComponentColorModel(ics, hasAlpha, isAlphaPremultiplied, transparency, transferType);
return new BufferedImage(ccm, incorrectImage.copyData(null), isAlphaPremultiplied, null);
}
}
else if (incorrectImage.getColorModel() instanceof IndexColorModel) {
return incorrectImage;
}
else {
throw new UnsupportedEncodingException("Unsupported color model type.");
}
}
这个答案确实使用了 iText,它通常用于 PDF 创建和操作,但它恰好正确处理了 ICC 配置文件,我已经在我的项目中依赖它,所以它恰好是比 ImageMagick 更好的选择。
那么问题中的代码最终如下:
BufferedImage image = loadBufferedImage(new FileInputStream(fileIn));
ColorSpace ics = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter(image, null);
ImageIO.write(result, "PNG", fileOut);
效果很好。
我有多个图像,其中嵌入了自定义配置文件,我想将图像转换为 sRGB 以便将其提供给浏览器。我见过如下代码:
BufferedImage image = ImageIO.read(fileIn);
ColorSpace ics = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter(image, null);
ImageIO.write(result, "PNG", fileOut);
其中 fileIn
和 fileOut
分别是代表输入文件和输出文件的文件对象。这在一定程度上起作用。问题是生成的图像比原始图像亮。如果我要在 photoshop 中转换颜色 space,颜色将看起来相同。事实上,如果我用 photoshop 调出两张图片并截屏并对颜色进行采样,它们是相同的。 photoshop 做了哪些上面的代码没有做的事情,我该怎么做才能解决这个问题?
有多种类型的图像正在转换,包括 JPEG、PNG 和 TIFF。我尝试使用 TwelveMonkeys 读取 JPEG 和 TIFF 图像,但我仍然得到相同的效果,即图像太亮。当应用于一开始就没有嵌入配置文件的图像时,转换过程似乎最糟糕。
编辑:
我添加了一些示例图片来帮助解释问题。
- 这张图片中嵌入了颜色配置文件。在某些浏览器上查看,这个和下一个之间没有明显区别,但在 Chrome、Mac、OSX 和 Windows 中查看,它目前看起来比应该的暗.这是我的问题首先出现的地方。我需要将图像转换为可以在 Chrome. 中正确显示的内容
- 这是一张用 ImageMagick 转换为 Adobe RGB 1998 颜色配置文件的图像,Chrome 似乎能够正确显示。
- 这是我使用上面的代码转换的图像,它看起来比应有的颜色要浅。
(请注意,上面的图片是在 imgur 上,所以要使它们更大,只需删除文件名末尾,文件扩展名之前的 "t"。)
这是我最初的解决方案,但我不喜欢必须使用 ImageMagick。我根据最终坚持使用的解决方案创建了另一个答案。
我放弃了,最终使用 im4java,它是 image magick 命令行工具的包装器。当我使用以下代码获取 BufferedImage 时,效果非常好。
IMOperation op = new IMOperation();
op.addImage(fileIn.getAbsolutePath());
op.profile(colorFileIn.getAbsolutePath());
op.addImage("png:-");
ConvertCmd cmd = new ConvertCmd();
Stream2BufferedImage s2b = new Stream2BufferedImage();
cmd.setOutputConsumer(s2b);
cmd.run(op);
BufferedImage image = s2b.getImage();
我还可以在需要时使用该库应用 CMYK 配置文件进行打印。如果 ColorConvertOp 正确地进行了转换,那就太好了,但至少现在,这是我的解决方案。为了与我的问题保持一致,在问题中实现相同效果的 im4java 代码是:
ConvertCmd cmd = new ConvertCmd();
IMOperation op = new IMOperation();
op.addImage(fileIn.getAbsolutePath());
op.profile(colorFileIn.getAbsolutePath());
op.addImage(fileOut.getAbsolutePath());
cmd.run(op);
其中 colorFileIn.getAboslutePath()
是机器上 sRGB 颜色配置文件的位置。由于 im4java 使用命令行,因此执行操作的方式并不直接,但对库进行了详细解释 here。正如问题中所解释的,我最初遇到的问题是 image magick 无法在我的 Mac 上工作。我使用 brew 安装它,但结果是 Mac 你必须像 brew install imagemagick --with-little-cms
那样安装它。那个图像 magick 对我来说很好用。
我找到了一个不需要 ImageMagick 的解决方案。基本上 Java 在加载图像时不考虑配置文件,所以如果有的话,就需要加载它。这是我为完成此操作所做的代码片段:
private BufferedImage loadBufferedImage(InputStream inputStream) throws IOException, BadElementException {
byte[] imageBytes = IOUtils.toByteArray(inputStream);
BufferedImage incorrectImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
if (incorrectImage.getColorModel() instanceof ComponentColorModel) {
// Java does not respect the color profile embedded in a component based image, so if there is a color
// profile, detected using iText, then create a buffered image with the correct profile.
Image iTextImage = Image.getInstance(imageBytes);
com.itextpdf.text.pdf.ICC_Profile iTextProfile = iTextImage.getICCProfile();
if (iTextProfile == null) {
// If no profile is present than the image should be processed as is.
return incorrectImage;
} else {
// If there is a profile present then create a buffered image with the profile embedded.
byte[] profileData = iTextProfile.getData();
ICC_Profile profile = ICC_Profile.getInstance(profileData);
ICC_ColorSpace ics = new ICC_ColorSpace(profile);
boolean hasAlpha = incorrectImage.getColorModel().hasAlpha();
boolean isAlphaPremultiplied = incorrectImage.isAlphaPremultiplied();
int transparency = incorrectImage.getTransparency();
int transferType = DataBuffer.TYPE_BYTE;
ComponentColorModel ccm = new ComponentColorModel(ics, hasAlpha, isAlphaPremultiplied, transparency, transferType);
return new BufferedImage(ccm, incorrectImage.copyData(null), isAlphaPremultiplied, null);
}
}
else if (incorrectImage.getColorModel() instanceof IndexColorModel) {
return incorrectImage;
}
else {
throw new UnsupportedEncodingException("Unsupported color model type.");
}
}
这个答案确实使用了 iText,它通常用于 PDF 创建和操作,但它恰好正确处理了 ICC 配置文件,我已经在我的项目中依赖它,所以它恰好是比 ImageMagick 更好的选择。
那么问题中的代码最终如下:
BufferedImage image = loadBufferedImage(new FileInputStream(fileIn));
ColorSpace ics = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorConvertOp cco = new ColorConvertOp(ics, null);
BufferedImage result = cco.filter(image, null);
ImageIO.write(result, "PNG", fileOut);
效果很好。