安全丢弃 Java 图片 I/O 中的 ICC 配置文件信息

Safely discarding ICC profile information in Java Image I/O

我正在使用 Java 图像 I/O 和 Java 11 来读取 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, newHeight, 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, null); //write with no thumbnails and no metadata
imageWriter.write(null, iioImage, imageWriteParam);
…

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

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

从与某人的讨论中,我推断这些 ICC 配置文件可能用于颜色 spaces 而不是 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 以外的颜色 space,需要 ICC 配置文件?如果我丢弃 ICC 配置文件,图像颜色会不会不正确?我应该以某种方式强制 Java 图像 I/O 转换为 RGB 吗?

我的部分疑问无疑是由于我不了解颜色配置文件在文件中的工作方式,因此如果您知道一个好的(不会在具有字节级细节的泥浆)。

在此先感谢您分享您的专业知识。

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

然而,当前代码不仅“丢弃”ICC 配置文件,而且图像将始终以纯 RGB 颜色 space 结束,使用 sRGB 配置文件,并将由 Java2D一路走来。由于 sRGB 配置文件通常不会被嵌入,您的图像将使用将 sRGB 作为默认配置文件的软件按预期显示。


长答案:

  1. ImageReader.setInput方法的ignoreMetadata传递true,只是对[=72]的提示 =]它可能忽略元数据。它不会强制跳过元数据(很可能,ICC 配置文件不应被视为元数据)。来自 Java文档:

    The ignoreMetadata parameter, if set to true, allows the reader to disregard any metadata encountered during the read. [...] The reader may choose to disregard this setting and return metadata normally.

  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 软件按预期解释,我相信最近 JDKs 已删除支持,因此我的建议是在写入 JPEG 之前始终删除任何 alpha 通道。

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

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

您可以通过仔细研究 JDK.

的源代码来验证上面的大部分陈述