无失真地读取灰度PNG图像文件

时间:2015-09-15 10:30:57

标签: java bufferedimage javax.imageio grayscale

我需要阅读和处理大量灰度级的PNG文件。我的意思是,如果它们在Photoshop或GIMP中打开,则图像模式为灰度 - 而不是具有灰度值的RGB图像。

ImageIO似乎没有实现这一目标。它似乎将所有图像文件视为sRGB。这会破坏灰度值。我需要读取和处理这些PNG文件,其中(在我的代码中)每个像素具有完全相同的值,就像我在Photoshop或GIMP中打开灰度文件一样。有没有人知道一些可以实现这个目标的开源软件呢?或者更好的如何使用ImageIO实现这一目标。

其他信息:

我在BufferedImage上使用getRGB()。图像文件中的基础像素是0x86。我知道这不一定对应于包含0xFF868686的ARGB像素,因为这取决于亮度/伽玛。但是,如果没有带有gamma类型参数的getter,我原本期望默认映射为ARGB = 0xFF868686。如果我使用GIMP或Photoshop将包含值为0x86的像素的灰度图像转换为RGB,则像素将变为0xFF868686。这是明显的默认值。

然而,ImageIO似乎使用了一个奇怪的伽玛(无论你喜欢与否)和灰度图像文件,这些图像文件在映射到ARGB之后使得灰度像素非常非常轻。在这种情况下,0x86映射到0xFFC0C0C0。这不仅非常轻,还可能导致大量数据丢失,因为许多灰度值可以映射到更少的ARGB值。这种失真不会导致数据丢失的唯一时间是非常暗的灰度图像。适当的Gamma取决于上下文,不同的物理介质会不同地扭曲亮度。但是,在没有上下文的情况下,映射:0x86 - > 0xFF868686最有意义 - 见证了GIMP和Photoshop的选择。

将getRGB()问题留在一边,加载了灰度图像(使用ImageIO.read(imageFile)),BufferedImage的getType()方法返回Type = 0(自定义)而不是Type = 10(TYPE_BYTE_GRAY)正如我所料。

简而言之,ImageIO似乎并没有提供一种简单易用的高级读取和操作现有灰度图像的方法。我原本希望不要乱用Rasters,ICC,采样等。我也不想将所有灰度图像文件物理转换为RGB。我想要的只是BufferedImage的API load()方法,就像GIMP或Photoshop中的打开文件一样。我无法做到这一点。我希望这是我的无知,而不是Java ImageIO的限制。

可能的解决方案:

在挖掘之后,我将提供以下内容作为访问基础灰度值的可能技术:

final File imageFile = new File( "test.png" );
final BufferedImage image = ImageIO.read( imageFile );
// --- Confirm that image has ColorSpace Type is GRAY and PixelSize==16
final Raster raster = image.getData();
// --- Confirm that: raster.getTransferType() == DataBuffer.TYPE_BYTE

for( int x=0, xLimit=image.getWidth(); x < xLimit; x++ ) {
    for( int y=0, yLimit=image.getHeight(); y < yLimit; y++ ) {

        final Object dataObject = raster.getDataElements( x, y, null );
        // --- Confirm that dataObject is instance of byte[]
        final byte[] pixelData = (byte[]) dataObject;
        // --- Confirm that: pixelData.length == 2

        final int grayscalePixelValue = pixelData[0] & 0xFF;
        final int grayscalePixelAlpha = pixelData[1] & 0xFF;

        // --- Do something with the grayscale pixel data
    }
}

javadoc不是很好,所以我不能保证这是正确的,但它似乎对我有用。

2 个答案:

答案 0 :(得分:2)

如果您想尝试第三方(我的)lib:https://github.com/leonbloy/pngj/

如果你确定图像是纯灰度(8位,没有alpha,没有调色板,没有配置文件),那很简单:

    PngReaderByte pngr = new PngReaderByte(new File(filename)); // 
    if (pngr.imgInfo.channels!=1 || pngr.imgInfo.bitDepth != 8 || pngr.imgInfo.indexed)
        throw new RuntimeException("This method is for gray images");
    for (int row = 0; row < pngr.imgInfo.rows; row++) { 
        ImageLineByte line = pngr.readRowByte();
        byte [] buf = line.getScanlineByte();
        // do what you want
    }
    pngr.end(); 

答案 1 :(得分:0)