使用PNGJ而不使用BufferedImage读取PNG像素

4

我一直在尝试使用PNGJ编写等效的代码。我不想使用ImageIO.readBufferedImage.getRGB,而是想纯粹地使用PNGJ获取像素。下面的代码是我的ImageIO+BufferedImage版本。

BufferedImage bi = ImageIO.read( imageFile );
...
int[] pixels = new int[arrSize];
bi.getRGB( 0, 0, width, height, pixels, 0, width );

我想要类似于PNGJ的东西。
lint = (ImageLineInt) reader.readRow();
scanLine = lint.getScanline();
...
???

这是我的测试图片详情:
ImageInfo [列数=1530,行数=1980,位深度=8,通道数=3,每像素位数=24,每像素字节数=3,每行字节数=4590,每行样本数=4590,每行样本数P=4590,透明度=false,灰度=false,索引=false,打包=false]


1
如果我理解你对@FiReTiTi答案的评论,你的问题并不是如何使用PNGJ,而更多地是“如何更快地访问像素”?如果是这种情况,有几件事情可以做来加快ImageIO.read的速度。最大的收益通常是通过使用ImageIO.setUseCache(false)来实现的。 - Harald K
感谢@haraldK,我尝试了您发布的代码,您是正确的,它确实更快。在我的测试中,大约为50毫秒。虽然PNGJ仍比使用setUseCache(false)的ImageIO快(快了约40毫秒)。我成功地转换了PNGJ返回的整数。稍后我会发布它。 - Ezekiel Baniaga
3个回答

4
如Zek的回答所指出的,PNGJBufferedImage不同:它更低级,因为它不尝试以抽象的通用格式(独立于具体的PNG格式)表示图像像素,而是直接给出映射到PNG格式和颜色模型的格式中的值。
这意味着,例如,如果PNG图像是RGB,则会将三个RGB分量作为连续的(整数)值返回。如果PNG图像是索引的,则会得到索引(指向调色板的整数值)。如果PNG图像是灰度,则会得到单个整数的灰度值。等等。
如果您想以更通用的方式处理像素值(给我RGB值,不管底层颜色模型如何),您可以自己进行转换,或者利用ImageLineHelper类中的辅助方法。具体来说,请查看convert2rgba()getPixelARGB8()方法。
请注意,这个类只是一堆辅助方法,可能对您有用,也可能没有。要完全通用地转换为RGB很困难,因为存在许多复杂性和启发式方法——PNG允许多种透明度模式、深度、颜色空间……(如果图像是2位深度怎么办?如果它是16位呢?如果它有一个非标准的颜色配置文件或伽马校正,或者它只有一个透明度块,或者……等等)?如果您想支持完整的PNG图像集,几乎需要理解底层格式和复杂性。
(但是,但是...你说BufferedImage可以完全通用地、没有歧义和问题地做到这一点?not exactly
如果您可以接受限制为自己的颜色模型(RGB,8位,没有alpha通道)(并且没有透明的TRNS块或伽马特异性,或者您不关心),那么就很简单:
 for (int i = 0,j=0 ; i <  reader.imgInfo.cols; i++, j+=channels ) {
      int r = scanLine[j];
      int g = scanLine[j+1];
      int b = scanLine[j+2];
     // ...
 }

免责声明:我是PNGJ的编码者。


2
到目前为止,我已经找到了以下内容:https://code.google.com/p/pngj/wiki/FAQ 问:如何读写像素值?每个PngReader的readRow()调用都会返回一个IImageLine,它代表一行PNG图像;具体实现是可扩展的。但是默认的包含实现(PngReader或PngReaderInt)返回一个ImageLineInt。此类包装了一个scaline(getScanline()),即int []数组。该数组的每个元素对应于图像样本;因此,该数组长度为(至少)列x通道。
另一种格式是ImageLineByte,它完全相同,除了它将每个样本存储在一个字节中(优点:使用更少的内存,适用于8位图像的最佳速度;缺点:如果图像为16位每通道,则失去分辨率,并且执行算术操作更麻烦-Java中的字节是有符号的)。
扫描线中样本值的布局如下:
对于真彩色图像,RGB / RGBA样本按RGB(A)顺序排列:R G B R G B ...(没有alpha),或者R G B A R G B A ...(有alpha);这些值未进行缩放:如果bitdepth = 8,则仅处于0-255范围内(如果bitdepth = 16,则为0-65535,如果bitdepth = 4,则为0-15等)。对于索引图像,每个样本值对应于调色板索引II .... 对于灰度G / GA图像,它是灰度值GGG ...(没有alpha),或GA GA ...(有alpha)。
这是我使用的代码,稍后可以将其提供给BufferedImage(带有Transparency.TRANSLUCENT):
PngReader reader = new PngReader( inputPNGFile );
arrSize = (int) reader.imgInfo.getTotalPixels();

width = reader.imgInfo.cols;
height = reader.imgInfo.rows;
channels = reader.imgInfo.channels;    

ImageLineInt lint;
while ( reader.hasMoreRows() ) {
    lint = (ImageLineInt) reader.readRow();
    scanLine = lint.getScanline();

    for ( i = 0; i < width; i++ ) {
        offset = i * channels;

        // Adjust the following code depending on your source image.
        // I need the to set the alpha channel to 0xFF000000 since my destination image
        // is TRANSLUCENT : BufferedImage bi = CONFIG.createCompatibleImage( width, height, Transparency.TRANSLUCENT );
        // my source was 3 channels RGB without transparency
        nextPixel = ( scanLine[offset] << 16 ) | ( scanLine[offset + 1] << 8 ) | ( scanLine[offset + 2] ) | 0xFF000000;

        // I'm placing the pixels on a memory mapped file
        mem.putInt( nextPixel ); 
     }

}

0

你可以使用BufferedImage来纯粹获取像素。使用getRGB是访问像素值最慢的方式之一。你想要的是访问DataBuffer。

BufferedImage bi = ImageIO.read( imageFile );
byte[] pixels = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData() ;

在编程中,你需要注意图像编码(Byte、Int或Short)。


谢谢你的回答,但我不想在访问像素之前读取文件并将其转换为BufferedImage。这一行:BufferedImage bi = ImageIO.read(imageFile); 在我的测试中使用1530x1980的图像大约需要~200ms。使用PNGJ,我可以读取PNG的像素,但是我不知道如何处理PNGJ返回的int数组。我的目标是将其与BufferedImage.getRGB的返回值匹配。我正在重构我的代码,以便将BufferedImage.getRGB替换为PNGJ。 - Ezekiel Baniaga
我不使用PNGJ。为了帮助你进行测试,你可以使用getRaster().getSample(x,y,c)而不是getRGB()。这样会更容易检查值并比较结果。请记住,Java不使用无符号值。 - FiReTiTi
顺便提一下,在我的笔记本电脑上,我使用ImageIO读取42张1024x1024的RGB图像只需约560毫秒,因此每张图像只需约13.3毫秒(SSD硬盘)。也许你的问题出在别处... BufferedImage只是一个1D数组。 - FiReTiTi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接