Java - Get Pixel Array from Image

Java - get pixel array from image

I was just playing around with this same subject, which is the fastest way to access the pixels. I currently know of two ways for doing this:

  1. Using BufferedImage's getRGB() method as described in @tskuzzy's answer.
  2. By accessing the pixels array directly using:

    byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();

If you are working with large images and performance is an issue, the first method is absolutely not the way to go. The getRGB() method combines the alpha, red, green and blue values into one int and then returns the result, which in most cases you'll do the reverse to get these values back.

The second method will return the red, green and blue values directly for each pixel, and if there is an alpha channel it will add the alpha value. Using this method is harder in terms of calculating indices, but is much faster than the first approach.

In my application I was able to reduce the time of processing the pixels by more than 90% by just switching from the first approach to the second!

Here is a comparison I've setup to compare the two approaches:

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import javax.imageio.ImageIO;

public class PerformanceTest {

public static void main(String[] args) throws IOException {

BufferedImage hugeImage = ImageIO.read(PerformanceTest.class.getResource("12000X12000.jpg"));

System.out.println("Testing convertTo2DUsingGetRGB:");
for (int i = 0; i < 10; i++) {
long startTime = System.nanoTime();
int[][] result = convertTo2DUsingGetRGB(hugeImage);
long endTime = System.nanoTime();
System.out.println(String.format("%-2d: %s", (i + 1), toString(endTime - startTime)));
}

System.out.println("");

System.out.println("Testing convertTo2DWithoutUsingGetRGB:");
for (int i = 0; i < 10; i++) {
long startTime = System.nanoTime();
int[][] result = convertTo2DWithoutUsingGetRGB(hugeImage);
long endTime = System.nanoTime();
System.out.println(String.format("%-2d: %s", (i + 1), toString(endTime - startTime)));
}
}

private static int[][] convertTo2DUsingGetRGB(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[][] result = new int[height][width];

for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
result[row][col] = image.getRGB(col, row);
}
}

return result;
}

private static int[][] convertTo2DWithoutUsingGetRGB(BufferedImage image) {

final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
final int width = image.getWidth();
final int height = image.getHeight();
final boolean hasAlphaChannel = image.getAlphaRaster() != null;

int[][] result = new int[height][width];
if (hasAlphaChannel) {
final int pixelLength = 4;
for (int pixel = 0, row = 0, col = 0; pixel + 3 < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += (((int) pixels[pixel] & 0xff) << 24); // alpha
argb += ((int) pixels[pixel + 1] & 0xff); // blue
argb += (((int) pixels[pixel + 2] & 0xff) << 8); // green
argb += (((int) pixels[pixel + 3] & 0xff) << 16); // red
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
} else {
final int pixelLength = 3;
for (int pixel = 0, row = 0, col = 0; pixel + 2 < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += -16777216; // 255 alpha
argb += ((int) pixels[pixel] & 0xff); // blue
argb += (((int) pixels[pixel + 1] & 0xff) << 8); // green
argb += (((int) pixels[pixel + 2] & 0xff) << 16); // red
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
}

return result;
}

private static String toString(long nanoSecs) {
int minutes = (int) (nanoSecs / 60000000000.0);
int seconds = (int) (nanoSecs / 1000000000.0) - (minutes * 60);
int millisecs = (int) ( ((nanoSecs / 1000000000.0) - (seconds + minutes * 60)) * 1000);


if (minutes == 0 && seconds == 0)
return millisecs + "ms";
else if (minutes == 0 && millisecs == 0)
return seconds + "s";
else if (seconds == 0 && millisecs == 0)
return minutes + "min";
else if (minutes == 0)
return seconds + "s " + millisecs + "ms";
else if (seconds == 0)
return minutes + "min " + millisecs + "ms";
else if (millisecs == 0)
return minutes + "min " + seconds + "s";

return minutes + "min " + seconds + "s " + millisecs + "ms";
}
}

Can you guess the output? ;)

Testing convertTo2DUsingGetRGB:
1 : 16s 911ms
2 : 16s 730ms
3 : 16s 512ms
4 : 16s 476ms
5 : 16s 503ms
6 : 16s 683ms
7 : 16s 477ms
8 : 16s 373ms
9 : 16s 367ms
10: 16s 446ms

Testing convertTo2DWithoutUsingGetRGB:
1 : 1s 487ms
2 : 1s 940ms
3 : 1s 785ms
4 : 1s 848ms
5 : 1s 624ms
6 : 2s 13ms
7 : 1s 968ms
8 : 1s 864ms
9 : 1s 673ms
10: 2s 86ms

BUILD SUCCESSFUL (total time: 3 minutes 10 seconds)

Getting pixel data from an image using java

This:

for(int i = 0; i < img.getHeight(); i++){
for(int j = 0; j < img.getWidth(); j++){
rgb = getPixelData(img, i, j);

Does not match up with this:

private static int[] getPixelData(BufferedImage img, int x, int y) {

You have i counting the rows and j the columns, i.e. i contains y values and j contains x values. That's backwards.

Java - Get a matrix of pixel values from an image

You could try something like this:

BufferedImage bf = //Assuming you have a buffered image
int[][] R = new int[bf.getWidth()][bf.getHeight()];
int[][] G = //Same as for R
int[][] B = //Same as for R

for(int r = 0; r < bf.getWidth(); r++)
{
for(int c = 0; c < bf.getHeight() c++)
{
//Uses the Java color class to do the conversion from int to RGB
Color temp = new Color(bf.getRGB(r, c));
R[r][c] = temp.getRed();
G[r][c] = temp.getGreen();
B[r][c] = temp.getBlue();
}
}

Retrieving pixel data from Java Image

You can draw the resulting Image onto a BufferedImage like this:

Image newImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING);
BufferedImage buffImg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) buffImg.getGraphics();
g2.drawImage(newImage, 0, 0, 10, 10, null);
g2.dispose();

Or you can scale the image directly by drawing it on another BufferedImage:

BufferedImage scaled = new BufferedImage(newWidth, newWidth, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) scaled.getGraphics();
g2.drawImage(originalImage, 0, 0, newWidth, newWidth, 0, 0, originalImage.getWidth(), originalImage.getHeight(), null);
g2.dispose();

The second approach will work correctly if the two BufferedImages have the same aspect ratio.

Convert 2D pixel array into BufferedImage

If your outputImage has already the good type and format, then you can simply do a 2D to 1D conversion using loops (assuming your array encoding is [nbRows][RowLength]):

private BufferedImage createImage(int[][] pixelData, BufferedImage outputImage)
{
int[] outputImagePixelData = ((DataBufferInt) outputImage.getRaster().getDataBuffer()).getData() ;

final int width = outputImage.getWidth() ;
final int height = outputImage.getHeight() ;

for (int y=0, pos=0 ; y < height ; y++)
for (int x=0 ; x < width ; x++, pos++)
outputImagePixelData[pos] = pixelData[y][x] ;

return outputImage;
}

But, the type INT is not really well defined in the BufferedImage. By default, you have TYPE_INT_RGB and TYPE_INT_ARGB, which concatenates the R, G, B, A values of a pixel encoding with a single INT. If you want to create a gray level BufferedImage of type INT with a single channel, then you should do:

private BufferedImage createImage(int[][] pixelData)
{
final int width = pixelData[0].length ;
final int height = pixelData.length ;
// First I create a BufferedImage with a DataBufferInt, with the appropriate dimensions and number of channels/bands/colors
ColorSpace myColorSpace = new FloatCS(ColorSpace.TYPE_GRAY, channel) ;
int[] bits = new int[]{32} ;
ColorModel myColorModel = new ComponentColorModel(myColorSpace,bits,false,false,ColorModel.OPAQUE,DataBuffer.TYPE_INT) ;
BufferedImage outputImage = new BufferedImage(myColorModel, myColorModel.createCompatibleWritableRaster(width, height), false, null) ;

int[] outputImagePixelData = ((DataBufferInt) outputImage.getRaster().getDataBuffer()).getData() ;

for (int y=0, pos=0 ; y < height ; y++)
for (int x=0 ; x < width ; x++, pos++)
outputImagePixelData[pos] = pixelData[y][x] ;

return outputImage ;
}

With FloatCS being the ColorSpace class. You have to create you own ColorSpace class when you want specific ColorSpace like Lab, HLS, etc.

public class FloatCS extends ColorSpace
{

private static final long serialVersionUID = -7713114653902159981L;

private ColorSpace rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB) ;

public FloatCS(int type, int channel)
{
super(type, channel) ;
}


@Override
public float[] fromCIEXYZ(float[] pixel)
{
return fromRGB(rgb.fromCIEXYZ(pixel)) ;
}

@Override
public float[] fromRGB(float[] RGB)
{
return RGB ;
}

@Override
public float[] toCIEXYZ(float[] pixel)
{
return rgb.toCIEXYZ(toRGB(pixel)) ;
}

@Override
public float[] toRGB(float[] nRGB)
{
return nRGB ;
}
}

Getting Pixel Values from Byte Array

The easiest way to access the pixels values in a BufferedImage is to use the Raster:

BufferedImage image = ...
for (int y=0 ; y < image.getHeight() ; y++)
for (int x=0 ; x < image.getWidth() ; x++)
for (int c=0 ; c < image.getRaster().getNumBands() ; c++)
final int value = image.getRaster().getSample(x, y, c) ; // Returns the value of the channel C of the pixel (x,y)

The raster will take care of the encoding for you, making it the easiest way to access the pixel values. However, the fastest way is to use the DataBuffer, but then you have to manage all the encodings.

/* This method takes a BufferedImage encoded with TYPE_INT_ARGB and copies the pixel values into an image encoded with TYPE_4BYTE_ABGR.*/
public static void IntToByte(BufferedImage source, BufferedImage result)
{
final byte[] bb = ((DataBufferByte)result.getRaster().getDataBuffer()).getData() ;
final int[] ib = ((DataBufferInt)source.getRaster().getDataBuffer()).getData() ;

switch ( source.getType() )
{
case BufferedImage.TYPE_INT_ARGB :
for (int i=0, b=0 ; i < ib.length ; i++, b+=4)
{
int p = ib[i] ;
bb[b] = (byte)((p & 0xFF000000) >> 24) ;
bb[b+3] = (byte)((p & 0xFF0000) >> 16) ;
bb[b+2] = (byte)((p & 0xFF00) >> 8) ;
bb[b+1] = (byte)( p & 0xFF) ;
}
break ;
// Many other case to manage...
}
}


Related Topics



Leave a reply



Submit