在 Java Image I/O

问题描述

我在 Java 11 中使用 Java Image I/O 来读取 JPEG 图像,通过绘制缩放版本来缩放它,然后写入它。 (我使用 this technique 是因为它产生了良好的结果,并且我尝试了 JAI 子样本平均技术在图像的某些侧面留下了黑色边框。)

重要的是,我将丢弃所有元数据。(稍后我会单独将一点元数据写回最终图像,但这与本讨论无关。)

…
imageReader.setInput(imageInputStream,true,true); //ignore Metadata
oldImage = imageReader.read(0,imageReadParam);
…
Image scaledImage = oldImage.getScaledInstance(newWidth,newHeight,Image.SCALE_SMOOTH);
int oldImageType = oldImage.getType();
int newImageType = oldImageType != BufferedImage.TYPE_CUSTOM ? oldImageType //use the existing image type if it isn't custom
    : (oldImage.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; //otherwise use RGB unless ARGB is needed for transparency
newImage = new BufferedImage(newWidth,newImageType);
final Graphics2D graphics = newImage.createGraphics();
try {
  graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  graphics.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
  graphics.drawImage(scaledImage,null,null);
} finally {
  graphics.dispose();
}
scaledImage.flush();
…
imageWriter.setoutput(imageOutputStream);
IIOImage iioImage = new IIOImage(newImage,null); //write with no thumbnails and no Metadata
imageWriter.write(null,iioImage,imageWriteParam);
…

某些输入图像具有 ICC 配置文件元数据部分。我担心丢弃 ICC 配置文件元数据是否会改变结果图像的显示方式。我了解图像颜色配置文件的概念,但我不知道它们如何在文件中工作以及它们如何与色彩空间交互。

我的问题总结:如果我在使用 Java Image I/O 处理图像时总是丢弃 ICC 配置文件和其他与配置文件相关的元数据部分,我如何确保生成的图像颜色正确?

通过与某人的讨论,我推断这些 ICC 配置文件可能适用于 sRGB 以外的色彩空间。 (这是真的吗?)如果是这样,使用 Java Image I/O 将图像转换为 sRGB 是否考虑了 ICC 配置文件,以便我可以丢弃它?如果是这样,我怎么知道我是否正在转换为 sRGB?

在上面的代码中,我尝试使用现有的图像类型(因为我认为最好不要更改任何内容),除非类型是“自定义”,在这种情况下,我选择了一种简单的 RGB 类型。

int newImageType = oldImageType != BufferedImage.TYPE_CUSTOM ? oldImageType //use the existing image type if it isn't custom
    : (oldImage.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; //otherwise use RGB unless ARGB is needed for transparency

但是如果我尽可能使用旧的图像类型,是否可能使用 RGB 以外的颜色空间,需要 ICC 配置文件?如果我丢弃 ICC 配置文件,图像颜色会不正确吗?我应该以某种方式强制 Java Image I/O 转换为 RGB 吗?

我的部分疑问无疑是由于我不了解文件中的颜色配置文件是如何工作的,因此如果您知道一个好的概述(不会迷失在泥泞中),也将不胜感激带有字节级别的详细信息)。

提前感谢您分享您的专业知识。

解决方法

简短回答:如果您从 JPEG 中丢弃 ICC 颜色配置文件,则无法确保颜色正确(除非您也完全控制这些文件的读取)。

然而,当前的代码不只是“丢弃”ICC 配置文件,相反,图像将始终以纯 RGB 颜色空间结束,使用 sRGB 配置文件,并且会在此过程中由 Java2D 正确转换。由于通常不会嵌入 sRGB 配置文件,因此您的图像将使用使用 sRGB 作为默认配置文件的软件按预期显示。


长答案:

  1. true 方法的 ignoreMetadata 传递 ImageReader.setInput,只是一个提示给读者它可能 忽略元数据。它不强制跳过元数据(而且很可能,无论如何不应将 ICC 配置文件视为元数据)。来自 JavaDoc:

    ignoreMetadata 参数,如果设置为 true,则允许读取器忽略读取过程中遇到的任何元数据。 [...] 读者可以选择忽略此设置并正常返回元数据。

  2. 在 Java 中处理 BufferedImage 时,图像将始终使用颜色配置文件。您的 oldImage 应该要么具有来自 JPEG 的原始颜色配置文件,或者从此配置文件转换为 sRGB。这究竟是如何工作的(如果有的话)完全取决于插件。但据我所知,标准 ImageIO JPEG 插件会读取嵌入的 ICC 配置文件,并将结果转换为 sRGB。

  3. 我不确定使用 Image.getScaledImage 时会发生什么,因为我不使用旧的 AWT Image API。最有可能的是,图像已经使用了 sRGB,并且仍将使用 sRGB 配置文件。它不应该修改颜色。

  4. 在任何情况下,newImage 在 sRGB 配置文件中将始终为 RGB,因为没有配置文件信息传递到所使用的 BufferedImage 构造函数。非 RGB 图像将始终为 TYPE_CUSTOM,因此转换为 TYPE_INT_(A)RGB。请注意,带有 alpha 通道的图像通常不会被其他 JPEG 软件按预期解释,而且我相信最近的 JDK 已取消支持,因此我的建议是在写入 JPEG 之前始终删除任何 alpha 通道。

  5. 最后,对于标准 ImageIO JPEG 插件,writer.write(没有任何元数据)将简单地将图像以 JFIF 格式(如果可能)写为 JPEG。如果要写入的图像在非标准配置文件中,它只会写入 ICC 配置文件。对于 sRGB,它不会写一个(我假设 imageWriteParam 都是默认值)。

PS:原始 JFIF 格式没有指定默认颜色配置文件,但我相信后来的修订(如 IEC 61966-2-1)确实将 sRGB 指定为默认值。 Exif 格式允许将 sRGB 指定为颜色配置文件,而无需实际嵌入它,方法是将 Exif ColorSpace 标签(标签 40961)设置为 1。标准的 JPEG 插件不写入 Exif 文件(没有您的重要代码)。

您可以通过仔细研究 JDK 的源代码来验证上述大部分陈述。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...