2015-07-09 2 views
2

У меня есть изображение в градациях серого («lena»), с которым я хочу поэкспериментировать. Я получил его как файл размером 512x512 PNG с 216 оттенками серого цвета.Java ImageIO Grayscale PNG Issue

Что происходит, когда я читаю его с Java ImageIO, как что:

String name = args[0]; 
    File fi = new File(name); 
    BufferedImage img = ImageIO.read(fi); 

Я получаю BufferedImage с только 154 цветов! Я только понял это, потому что мои обработанные изображения выглядели желтыми, без глубокого черного.

Еще более раздражающим, когда я использую XnView, преобразует PNG в GIF, что в этом случае без потерь, прочитайте GIF с вышеуказанным кодом, я получаю все 216 цветов в моем BufferedImage.

Есть ли какая-то документация или описание, что происходит с моим PNG, когда ImageIO читает его? Есть ли настройки, чтобы исправить это? Я сделал эти эксперименты на довольно недавнем JDK1.8. Я просто потерял доверие к поддержке Java PNG, и позже я буду использовать цветной PNG.

+1

Как вы рассчитываете количество цветов (как PNG, так и 'BufferedImage')? У вашего PNG есть кусок iCCP (ICC profile)? «ГАМА» (гамма) кусок? – haraldK

ответ

4

Добро пожаловать в «отличный» мир Java с неявным управлением цветом!

Для Java (по крайней мере, ImageIO) все внутренне является sRGB, и это подразумевает управление цветом, что часто довольно контрпродуктивно для того, что на самом деле хочет сделать. Для изображений с серой шкалой, по крайней мере с использованием ImageIO с большинством читателей и, по крайней мере, для изображений с серой шкалой без встроенного профиля ICC (я еще не тестировал других), Java автоматически «назначает» профиль ICC с помощью WhitePoint = D50, Gamma = 1,0. Я тоже наткнулся на это.

А затем, когда вы получаете доступ к пикселям (предположим, вы используете img.getRGB() или что-то подобное?), Вы фактически получаете доступ к значениям sRGB (цветное пространство Java по умолчанию в Windows).

В результате преобразование в sRGB, имеющее гамму ~ 2.2 (гамма sRGB на самом деле немного сложнее, но близка к 2.2 в целом), это влияет на гамма-коррекцию с (1/Gamma) = 2.2 к изображению, (a) чтобы ваше изображение выглядело «светлым», и (b) из-за гамма-коррекции от 256 до 256 дискретных значений вы также эффективно теряете некоторые оттенки серого.

Вы также можете увидеть эффект, если доступ к данным вашего BufferedImage по-разному: а) доступ к своему профилю:

ColorSpace colorSpace = img.getColorModel().getColorSpace(); 
if (colorSpace instanceof ICC_ColorSpace) { 
    ICC_Profile profile = ((ICC_ColorSpace)colorSpace).getProfile(); 
    if (profile instanceof ICC_ProfileGray) { 
     float gamma = ((ICC_ProfileGray)profile).getGamma(); 
     system.out.println("Gray Profile Gamma: "+gamma); // 1.0 ! 
    } 
} 

б) получать доступ к некоторым пиксельные значения по-разному ...

//access sRGB values (Colors translated from img's ICC profile to sRGB) 
System.out.println("pixel 0,0 value (sRGB): " + Integer.toHexString(img.getRGB(0,0))); // getRGB() actually means "getSRGB()" 
//access raw raster data, this will give you the uncorrected gray value 
//as it is in the image file 
Raster raster = image.getRaster(); 
System.out.println("pixel 0,0 value (RAW gray value): " + Integer.toHexString(raster.getSample(0,0,0))); 

Если ваш пиксель (0,0) не случайно 100% черный или 100% белый, вы увидите, что значение sRGB «выше», чем серое значение, например gray = d1 -> sRGB = ffeaeaea (альфа, красный, зеленый, синий).

С моей точки зрения, это не только снижает уровень серого, но и делает ваше изображение более легким (примерно так же, как применение гамма-коррекции с 1/гамма-значением 2,2). Было бы логичнее, если бы Java для серых изображений без встроенного профиля ICC переводила серый в sRGB с R = G = B = grayValue или назначала профиль ICC Grey WhitePoint = D50, Gamma = 2.2 (по крайней мере, в Windows). Последний все равно заставит вас потерять пару серых тонов из-за того, что sRGB не является точно Gamma 2.2.

Относительно того, почему он работает с GIF: формат GIF не имеет понятия «серых чешуек» или профилей ICC, поэтому ваше изображение представляет собой изображение с цветовой палитрой 256 (256 цветов имеют 256 оттенков серого). При открытии GIF Java предполагает, что значения RGB равны sRGB.

Решение: В зависимости от того, что ваш фактический случай использования, решение для вас может быть, что вы получаете доступ к данным Растровые каждого пикселя вашего изображения (серый = raster.getSample (х, у, 0)) и поместите его в настройку изображения sRGB R = G = B = серый. Однако может быть более элегантный способ.

Что касается вашего доверия в Java или PNG: Я борюсь с Java ImageIO во многом из-за неявное преобразование цветов это делает. Идея состоит в том, чтобы встроить управление цветом без того, чтобы разработчикам не нужно было много знаний о управлении цветом. Это работает до некоторой степени, пока вы работаете только с sRGB (и ваш вход sRGB тоже или не имеет цветового профиля и, следовательно, может быть законно рассмотрен как sRGB). Неисправность начинается, если у вас есть другие цветовые пространства на ваших входных изображениях (например, AdobeRGB). Серый - тоже самое, особенно тот факт, что ImageIO предполагает (необычный) Gray Profile с Gamma = 1.0. Теперь, чтобы понять, что делает ImageIO, вам нужно не только знать свою ABC в управлении цветом, но также нужно выяснить, что делает Java. Я не нашел эту информацию в любой документации! Итог: ImageIO делает то, что, безусловно, можно считать правильным. Это часто не то, что вы ожидаете, и вы можете копать глубже, чтобы узнать, почему или изменить поведение, если это не то, что вы хотите сделать.

1

Как-то вы преобразовали изображение из линейного оттенка серого (gamma = 1.0) в оттенки серого sRGB (гамма = 1/2.2). Это можно продемонстрировать с помощью GraphicsMagick. Начните с Lenna.png скачанный из Википедии, а затем удалить SRGB кусок для создания lena.png, то

gm convert lena.png -colorspace gray -depth 8 -strip lena-gray.png 

Лено-gray.png имеет 216 цветов

gm convert lena-gray.png -gamma 2.2 -depth 8 -strip lena-gray-gm22.png 

Лено-серо-gm22.png имеет 154 цвета и выглядит размытым или бледным.

Я использую недавнюю бета-версию graphicsmagick (версия 1.4) с libpng-1.6.17.

Рассчитывать цвета я использовал ImageMagick:

identify -verbose file.png | grep Colors 

Я использовал

pngcheck -v file.png 

, чтобы убедиться, что Lenna.png содержит IHDR, SRGB, IDAT и IEND кусками, в то время как Лено-серый. png и lena-gray-gm22.png содержат только блоки IHDR, IDAT и IEND.

+0

Интересно, что число 154 снова появляется. Но я ничего не преобразовал. Я просто разрешаю ImageIO читать файл. Я имею в виду, что я все еще могу создать «полный» GIF из того же самого PNG, который не «выжил» ImageIO. Но, да, я думал, если ImageIO сделает некоторые предположения, например гамма, но не должен ли это включать или отключать это. – InI4

+0

Было бы полезно узнать, имеет ли ваше исходное изображение цветовую информацию (iCCP, cHRM, gAMA или sRGB). Вы можете использовать «pngcheck», чтобы узнать. Это может дать понять, почему ImageIO (или его плагин PNG) преобразует цветовое пространство от линейного до sRGB при чтении изображения. –

Смежные вопросы