Ezekiel Baniaga
Ezekiel Baniaga

Reputation: 953

Reading PNG pixels using PNGJ without BufferedImage

I've been struggling on trying to do the equivalent code using PNGJ. I don't want to use ImageIO.read and BufferedImage.getRGB. Instead I want to get the pixels purely using PNGJ. The code below was my ImageIO+BufferedImage version.

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

I want something like in PNGJ's

lint = (ImageLineInt) reader.readRow();
scanLine = lint.getScanline();
...
???

Here are my test image details:
ImageInfo [cols=1530, rows=1980, bitDepth=8, channels=3, bitspPixel=24, bytesPixel=3, bytesPerRow=4590, samplesPerRow=4590, samplesPerRowP=4590, alpha=false, greyscale=false, indexed=false, packed=false]

Upvotes: 4

Views: 1951

Answers (3)

leonbloy
leonbloy

Reputation: 75896

As Zek's answer points out, PNGJ is different from BufferedImage : it's more low level, in that it does not try to represent the image pixels in an abstract general format (independent of the concrete PNG format), but instead gives you the values in format that maps directly to the PNG format and color model.

This means that, for example, if the PNG image is RGB you'll get the three RGB components as succesive (integer) values. If the PNG image is indexed, you'll get the index (integer value that points to a palette). If the PNG image is gray scale, you'll get the gray value as a single integer. Etc.

If you want to treat the pixel values in a more general way (give me the RGB values, no matter the underlying color model), you can do the conversion yourself, or take advantage of the helper methods in ImageLineHelper class. Specifically, look at the methods convert2rgba() and getPixelARGB8().

Bear in mind, though, that this class is merely a bunch of helper methods, that might be useful for you - or not. It's difficult to have a completely general conversion to RGB, because there are many complications and heuristics - PNG allows several transparency modes, depths, color spaces... (what if the image is 2bits depth? what if it's 16 bits? what if it has a non-standard color profile or gamma correction, or it has a single transparency chunk, or... etc)? If you want to support the full set of PNG images, it's almost required to understand the underlying format and the complications.

(But, but... BufferedImage does that, with total generality, without ambiguities and without problems, you say? not exactly)

If you are OK with restricting to your colour model (RGB, 8 bits, no alpha) (and you have no transparency TRNS chunk or gamma peculiarities, or don't care) then it's quite simple:

 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];
     // ...
 }

Disclaimer: I'm the PNGJ coder.

Upvotes: 4

Ezekiel Baniaga
Ezekiel Baniaga

Reputation: 953

So far here's what I've found. https://code.google.com/p/pngj/wiki/FAQ

How do I read/write the pixels values? Each PngReader's readRow() call returns a IImageLine, which represents a PNG image row; the concrete implementation is extensible. But the default included implementation (PngReader or PngReaderInt) returns a ImageLineInt. This class wraps an scaline (getScanline()) which is an int[] array. Each element of this array correspond to a image sample ; so, the array length is (at least) columns x channels.

An alternative format is ImageLineByte, which is identical except that it stores each sample in a byte (advantage: less memory usage, optimal speed for 8bits images; disadvantages: loses resolution if image is 16bits-per-channel, and it's more cumbersome to do arithmetic -bytes in Java are signed).

The layout of sample values inside the scanline is as follows:

For true colour images, RGB/RGBA the samples are in RGB(A) order: R G B R G B ... (no alpha) or R G B A R G B A ... (with alpha); the values are not scaled: they will be in the 0-255 range only if bitdepth=8 (0-65535 if bitdepth=16, 0-15 if bitdepth=4, etc). For indexed images, each sample value correspond to the palette index I I .... For grayscale G/GA images it's the gray value G G G ... (no alpha) or G A G A ... (with alpha).

Here's the code I used that can be fed to a BufferedImage(with Transparency.TRANSLUCENT) later:

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 ); 
     }

}

Upvotes: 2

FiReTiTi
FiReTiTi

Reputation: 5888

You can have the pixels purely using BufferedImage. get RGB is one of the slowest way to access the pixels values. What you want is to access the DataBuffer.

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

You have just to be careful about the image encoding (Byte, Int or Short).

Upvotes: 0

Related Questions