Добро пожаловать в «отличный» мир 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 делает то, что, безусловно, можно считать правильным. Это часто не то, что вы ожидаете, и вы можете копать глубже, чтобы узнать, почему или изменить поведение, если это не то, что вы хотите сделать.
Как вы рассчитываете количество цветов (как PNG, так и 'BufferedImage')? У вашего PNG есть кусок iCCP (ICC profile)? «ГАМА» (гамма) кусок? – haraldK