BufferedImage сообщает о неверном типе цветовой модели


Я использую некоторые сторонние программы для преобразования изображений в 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 бит цвет.формат PNG

Цветовое пространство: серый, глубина: 8 бит, глубина канала: серый: 8 бит градации серого.формат PNG

Цветовое пространство: серый, глубина: 8/1 бит, глубина канала: серый: 1 бит чёрно-белый.формат PNG

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
2 2

2 ответа:

Я вижу, что у вас уже есть приемлемый ответ, но я все равно попытаюсь объяснить это.. Это не ошибка. Но я согласен, иногда это противоречит интуиции.

Ваш тест использует цветовое пространство декодированного представления изображения в памяти и сравнивает его с ожидаемым цветовым пространством из закодированного файла. Когда файл декодируется (используя ImageIO.read в вашем примере), плагин ImageReader обычно преобразует изображение в представление в памяти, которое быстро рисуется на экране. Этот может сильно отличаться от представления, которое наиболее эффективно при хранении на диске. Например, изображение в оттенках серого, использующее менее 8 бит на образец, обычно преобразуется в IndexColorModel, Даже если файл PNG не содержит фрагмента PLTE. И, IndexColorModel всегда использует цветовое пространство sRGB (тип RGB), даже если оно содержит только серые значения. Это не имеет значения для отображаемых пикселей, которые будут черно-белыми независимо, но это имеет значение для вашего теста.

Это так можно получить цветовое пространство, которое было фактически закодировано в файле, используя API ImageIO:

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 или несколько других форматов, вероятно, лучше всего использовать стандартный формат метаданных, получив имя ColorSpaceType из узла Chroma.

Я думаю, что это исходит из вашего файла bw.png. Как я понимаю, 1-битные PNG - это либо оттенки серого, либо индексированные (палитра), и индексированные будут использовать пространство RGB, поэтому у вас будет 2 цвета (#000000 и #ffffff). Проверьте, какой инструмент вы используете для создания PNG, и посмотрите, дает ли он вам выбор между оттенками серого и индексированными. Вы также можете посмотреть на фрагменты PNG, чтобы убедиться, что файл создан так, как вы ожидаете.

Это может быть полезно: TweakPNG