【问题标题】:BufferedImage reports incorrect color model typeBufferedImage 报告不正确的颜色模型类型
【发布时间】:2017-06-22 15:24:03
【问题描述】:

我正在使用一些第三方软件将图像转换为 PDF,并且我注意到一些膨胀的文件大小。经过一番挖掘,我确认没有保留图像的颜色模型。黑白(1 位)图像正在转换为 RGB 颜色模型。

通过库挖掘显示了一些颜色模型检测:

switch (awtColorSpace.getType()) {
    case ColorSpace.TYPE_RGB:
        return PDDeviceRGB.INSTANCE;
    case ColorSpace.TYPE_GRAY:
        return PDDeviceGray.INSTANCE;
    case ColorSpace.TYPE_CMYK:
        return PDDeviceCMYK.INSTANCE;
    default:
        throw new UnsupportedOperationException("color space not implemented: "
                + awtColorSpace.getType());
}

这些图像总是以 RGB 格式返回。我决定编写一些测试,它们似乎证实了这一点:

package com.acme;

import org.junit.Test;

import javax.imageio.ImageIO;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;

import static org.junit.Assert.*;

public class ImageColorDetectionTest {

    @Test
    public void colorImage() throws Exception {
        // Colorspace: sRGB, Depth: 8-bit, Channel depth: Red: 8-bit Green: 8-bit Blue: 8-bit
        BufferedImage image = readImage("/color.png");
        assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
    }

    @Test
    public void greyscaleImage() throws Exception {
        // Colorspace: Gray, Depth: 8-bit, Channel depth: Gray: 8-bit
        BufferedImage image = readImage("/greyscale.png");
        assertEquals(ColorSpace.TYPE_GRAY, image.getColorModel().getColorSpace().getType());
    }

    @Test
    public void blackAndWhiteImage() throws Exception {
        // Colorspace: Gray, Depth: 8/1-bit, Channel depth: Gray: 1-bit
        BufferedImage image = readImage("/bw.png");
        assertEquals(ColorSpace.TYPE_GRAY, image.getColorModel().getColorSpace().getType());
    }

    protected BufferedImage readImage(String path) throws IOException {
        try (InputStream content = this.getClass().getResourceAsStream(path)) {
            return ImageIO.read(content);
        }
    }

}

blackAndWhiteImage 测试总是失败。颜色空间类型为 5 (RGB)。这是 JDK 中的错误还是我在这里遗漏了一些基本的东西?

测试图片:

色彩空间:sRGB,深度:8 位,通道深度:红色:8 位绿色:8 位蓝色:8 位

色彩空间:灰色,深度:8 位,通道深度:灰色:8 位

色彩空间:灰色,深度:8/1 位,通道深度:灰色:1 位

Imagemagick 识别:

magick identify -verbose bw.png
Image: bw.png
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  Class: PseudoClass
  Geometry: 329x247+0+0
  Units: Undefined
  Type: Bilevel
  Base type: Palette
  Endianess: Undefined
  Colorspace: Gray
  Depth: 8/1-bit
  Channel depth:
    Gray: 1-bit
  Channel statistics:
    Pixels: 81263
    Gray:
      min: 0 (0)
      max: 255 (1)
      mean: 110.66 (0.433961)
      standard deviation: 126.384 (0.495623)
      kurtosis: -1.92901
      skewness: 0.266484
      entropy: 0.98738
  Colors: 2
  Histogram:
     45998: (  0,  0,  0) #000000 gray(0)
     35265: (255,255,255) #FFFFFF gray(255)
  Colormap entries: 2
  Colormap:
         0: (  0,  0,  0,255) #000000FF graya(0,1)
         1: (255,255,255,255) #FFFFFFFF graya(255,1)
  Rendering intent: Undefined
  Gamma: 0.45455
  Chromaticity:
    red primary: (0.64,0.33)
    green primary: (0.3,0.6)
    blue primary: (0.15,0.06)
    white point: (0.3127,0.329)
  Matte color: grey74
  Background color: white
  Border color: srgb(223,223,223)
  Transparent color: none
  Interlace: None
  Intensity: Undefined
  Compose: Over
  Page geometry: 329x247+0+0
  Dispose: Undefined
  Iterations: 0
  Compression: Zip
  Orientation: Undefined
  Properties:
    date:create: 2017-06-22T09:33:09-05:00
    date:modify: 2017-06-22T09:33:09-05:00
    png:bKGD: chunk was found (see Background color, above)
    png:cHRM: chunk was found (see Chromaticity, above)
    png:gAMA: gamma=0.45455 (See Gamma, above)
    png:IHDR.bit-depth-orig: 1
    png:IHDR.bit_depth: 1
    png:IHDR.color-type-orig: 0
    png:IHDR.color_type: 0 (Grayscale)
    png:IHDR.interlace_method: 0 (Not interlaced)
    png:IHDR.width,height: 329, 247
    png:text: 2 tEXt/zTXt/iTXt chunks were found
    png:tIME: 2017-06-21T10:12:36Z
    signature: 689d59f57ef9b4d58011f92e26f937d9d58cf1ca1ccbcaad6bad7bdd0552fcfa
  Artifacts:
    verbose: true
  Tainted: False
  Filesize: 3.69KB
  Number pixels: 81.3K
  User time: 0.000u
  Elapsed time: 0:01.000
  Version: ImageMagick 7.0.5-4 Q16 x86_64 2017-03-25 http://www.imagemagick.org

【问题讨论】:

    标签: java javax.imageio


    【解决方案1】:

    我看到您已经有一个公认的答案,但无论如何我都会尝试解释这一点。这不是错误。但我同意,这有时是违反直觉的。

    您的测试使用图像的解码内存表示的颜色空间,并将其与编码文件的预期颜色空间进行比较。解码文件时(在您的示例中使用 ImageIO.read),ImageReader 插件通常会将图像转换为可快速在屏幕上绘制的内存表示。这可能与存储在磁盘上时空间效率最高的表示完全不同。

    例如,每个样本使用少于 8 位的灰度图像通常会转换为 IndexColorModel,即使 PNG 文件不包含 PLTE 块。而且,IndexColorModel 始终使用 sRGB 颜色空间(RGB 类型),即使它只包含灰度值。这对于显示的像素无关紧要,无论如何它都是黑白的,但它对您的测试很重要。

    可以使用ImageIO API 获取文件中实际编码的色彩空间:

    try (ImageInputStream content = ImageIO.createImageInputStream(this.getClass().getResourceAsStream(path))) {
        ImageReader reader = ImageIO.getImageReaders(input).next(); // Assumes PNGImageReader is always there
        reader.setInput(input);
    
        IIOMetadata metadata = reader.getImageMetadata(0);
        Node nativeTree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
        Node standardTree = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
    
        // ... Get color space information as needed using DOM traversal
    }
    

    我跳过了实际值的阅读,因为它变得非常冗长,但它非常简单。所有值都是Strings。有关详细信息,请参阅IIOMetadata 类文档。

    bw.png 文件的元数据以两种不同的输出表示形式包含以下内容。

    原生元数据:

    <javax_imageio_png_1.0>
            <IHDR width="329" height="247" bitDepth="1" colorType="Grayscale" compressionMethod="deflate" filterMethod="adaptive" interlaceMethod="none"/>
            <bKGD>
                    <bKGD_Grayscale gray="1"/>
            </bKGD>
            <cHRM whitePointX="31270" whitePointY="32900" redX="64000" redY="33000" greenX="30000" greenY="60000" blueX="15000" blueY="6000"/>
            <gAMA value="45455"/>
            <tIME year="2017" month="6" day="21" hour="10" minute="12" second="36"/>
    </javax_imageio_png_1.0>
    

    标准的“插件中性”元数据(跳过不相关的值):

    <javax_imageio_1.0>
            <Chroma>
                    <ColorSpaceType name="GRAY"/>
                    <NumChannels value="1"/>
                    <Gamma value="0.45455"/>
                    <BlackIsZero value="TRUE"/>
                    <BackgroundColor red="1" green="1" blue="1"/>
            </Chroma>
            <Compression ... />
            <Data>
                    <PlanarConfiguration value="PixelInterleaved"/>
                    <SampleFormat value="UnsignedIntegral"/>
                    <BitsPerSample value="1"/>
            </Data>
            <Dimension ... />
            <Document ... />
            <Transparency>
                    <Alpha value="none"/>
            </Transparency>
    </javax_imageio_1.0>
    

    如果您的实际图像是 TIFF 或多种其他格式,最好使用标准元数据格式,即从 Chroma 节点获取 ColorSpaceType 名称。

    【讨论】:

    • 更好的答案。我一直在关注这个问题,因为我也多次成为膨胀的BufferedImage 的受害者……谢谢你 haraldK!这是我在编程中遇到的最令人沮丧的一堵墙……问题太详细了,无法尝试寻找答案。 @MikeCantrell 这应该是公认的答案。
    • 非常感谢您的详细解释。这绝对可以解决问题。我会看看是否可以向我们的 PDF API 提交补丁以改进他们的颜色模型检测。
    【解决方案2】:

    我认为这来自您的bw.png 文件。据我了解,1 位 PNG 要么是灰度,要么是索引(调色板),索引将使用 RGB 空间,因此您将有 2 种颜色(#000000#ffffff)。检查您用于创建 PNG 的任何工具,看看它是否可以让您在灰度和索引之间进行选择。您可能还想查看 PNG 块以验证文件是否按预期创建。

    这可能有用:TweakPNG

    【讨论】:

    • 不幸的是,我无法控制图像的创建方式。我只是使用 imagemagick 来为这个特定的测试创建这些。大多数图像实际上是 tiff,但它们可以是任何东西。
    • 嗯,也许它确实与 png 格式有关。我不确定如何检查调色板类型(为上面的图像转储了 imagemagick 标识)。我无法使用您引用的 TweakPNG 应用程序,因为我没有 Windows 机器,但我确实使用 tiff/jpg 重新创建了它并且它通过了。我想我需要回溯一下我原来的问题,找出为什么它与这个测试不一致。
    • ImageMagick 的输出与我在 TweakPNG 中看到的非常接近。我注意到的一件事是在第三个 (b/w) 文件中存在 cHRM 块。它不存在于彩色或灰色文件中。
    • 另外,请查看 Unix/Linux 的 pngtools(包含适用于 Ubuntu 和 Debian 的软件包)。
    猜你喜欢
    • 1970-01-01
    • 2016-01-28
    • 2023-04-10
    • 2022-01-02
    • 2011-11-14
    • 2012-03-30
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    相关资源
    最近更新 更多