Java - get pixel array from image
Asked Answered
N

7

139

I'm looking for the fastest way to get pixel data (int the form int[][]) from a BufferedImage. My goal is to be able to address pixel (x, y) from the image using int[x][y]. All the methods I have found do not do this (most of them return int[]s).

Neri answered 29/6, 2011 at 16:41 Comment(3)
If you're worried about speed, why do you want to copy the entire image to an array instead of just using getRGB and setRGB directly?Abase
@bemace: Because those methods appear to do more work than one might think, according to my profiling. Accessing an array seems way faster.Neri
@bemace: It's actually really intense: using an array is more than 800% faster than using getRGB and setRGB directly.Neri
M
202

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)
Madeup answered 27/2, 2012 at 19:15 Comment(22)
Yes but can you vectorize it?(block mode)Sukkah
Hi sorry. I'm confused. I thought a pixel was made of 3 rgb colours? How come the 'result' array is made up of single integer values? Surely ratio of the rbg values is lost when you add then together in that argb int variable?Ouachita
For those too lazy to read the code, there are two tests convertTo2DUsingGetRGB and convertTo2DWithoutUsingGetRGB. The first test on average takes 16 seconds. The second test on average takes 1.5 seconds. At first I thought the "s" and "ms" were two different columns. @Mota, great reference.Hiro
Thanks! This helped me with a different problem. Just an FYI: I got different results from each method from a greyscale JPG.Moan
@Mota, I am doing some experiment on image processing. This helped me lot. But how do I convert the pixel array to image again without loss of data? Please help me on thisElsyelton
@Reddy You could construct a BufferedImage object and then set the pixels' values using setRGB docs.oracle.com/javase/6/docs/api/java/awt/image/…. I'm pretty sure though there is a better way. In particular, you may want to take a look at the ImageIO.read methods. Either way, you won't lose data as long as the original pixel values remain unchanged or saved somewhere.Madeup
@Mota, I have tried and couldn't figure out how to create back the original image from pixel[][] array. Could you please post some link/code for that? I am pretty much basic to Image api, I am experimenting on pixel array data.Elsyelton
@Mota, code here pastebin.com/TeMvXWhs After output file generated, it's 71Kb, where original file is 80Kb and I can clearly notice some color difference. I don't want single pixel to lose it's valueElsyelton
@Reddy I gave it a try, and I do see a difference in the file size, which I'm not sure why! I have, however, been able to reproduce the exact pixel values using this code (using the alpha channel): pastebin.com/zukCK2tu You may need to modify the third argument of the BufferedImage constructor, depending on the image you're dealing with. Hope this helps a bit!Madeup
convertTo2DWithoutUsingGetRGB method could not work properly on gif type images. pixels arrays return number of pixels that not x3 (RGB). How to getting RGB data from one byte? How can be handle this?Hagiography
@Mota In convertTo2DUsingGetRGB why do you take result[row][col] = image.getRGB(col, row); instead of result[row][col] = image.getRGB(row, col);Matney
How do you get a simple 4 tuple instead of an integer after all the bitshifting?Josephina
@ErdinçTaşkın GIF uses an indexed ColorModel. Each value is actually an index into a table. The entries in the table hold the colour. The short answer is, if at all possible, avoid GIF! If that's not possible, you might need to look in to Color Models and Color Spaces. Actually, you might want to stick with the first option. It seems to be slower in part because it uses the Color Model to convert the colour. This is a step you need to take with GIF anyway. I haven't confirmed this, so take that last bit with a grain of salt.Durgy
@Mota FYI I found a small bug. (PS This is a great program! Thank you!) The Red and Blue channel comments in the WithoutUsingGetRG function are flipped. Red should be +1 and blue should be +3 (or +0 and +2 respectively in the No Alpha code).Acid
You could also make a final int pixel_offset = hasAlpha?1:0; and do ((int) pixels[pixel + pixel_offset + 1] & 0xff); // green; and merge the two loops into one.Anility
It seems that this no longer works. Usage gives java.lang.ClassCastException: java.awt.image.DataBufferInt cannot be cast to java.awt.image.DataBufferByte. Using DataBufferInt gives array of rgb[-16777216, -16777216, -16777216].Anility
People noticing a color difference and/or incorrect byte ordering: @Mota's code assumes a BGR ordering. You should check the incoming BufferedImage's type e.g. TYPE_INT_RGB or TYPE_3BYTE_BGR and handle appropriately. This is one of the things that getRGB() does for you, that makes it slower :-(Koral
Correct me if I'm wrong, but wouldn't it be more efficient to use |= instead of += for combining the values in method 2?Cwmbran
currently DataBuffer converts data to DataBufferInt instead of DataBufferByte. Length is now less to 3 times. You can update this fruitful answerCaul
How come you are using a BGR color scheme? I don't see anywhere in your code where you specified that your BufferedImage should be encoded in BGR. Is the coloring scheme included as metadata with the image? How can I access the coloring scheme of an image? ThanksPubis
No idea if this solution still works if you're using one of the "byte" types. I'm using a BufferedImage.TYPE_INT_ARGB and just got the same ClassCastException, so I changed it to return an int[] through a DataBufferInt (instead of the DataBufferByte). It's possible to convert these ints to new Color(intvalue) and then access the rgba values directly (with e.g. mycolor.getBlue()) but no idea how efficient that is exactly, compared to image.getRGB.Gentility
Guess for a BufferedImage coming from BufferedImage.subImage() the direct buffer access is not valid.Aeroneurosis
C
25

I found Mota's answer gave me a 10 times speed increase - so thanks Mota.

I've wrapped up the code in a convenient class which takes the BufferedImage in the constructor and exposes an equivalent getRBG(x,y) method which makes it a drop in replacement for code using BufferedImage.getRGB(x,y)

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

public class FastRGB
{

    private int width;
    private int height;
    private boolean hasAlphaChannel;
    private int pixelLength;
    private byte[] pixels;

    FastRGB(BufferedImage image)
    {

        pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        width = image.getWidth();
        height = image.getHeight();
        hasAlphaChannel = image.getAlphaRaster() != null;
        pixelLength = 3;
        if (hasAlphaChannel)
        {
            pixelLength = 4;
        }

    }

    int getRGB(int x, int y)
    {
        int pos = (y * pixelLength * width) + (x * pixelLength);

        int argb = -16777216; // 255 alpha
        if (hasAlphaChannel)
        {
            argb = (((int) pixels[pos++] & 0xff) << 24); // alpha
        }

        argb += ((int) pixels[pos++] & 0xff); // blue
        argb += (((int) pixels[pos++] & 0xff) << 8); // green
        argb += (((int) pixels[pos++] & 0xff) << 16); // red
        return argb;
    }
}
Cheju answered 3/11, 2014 at 11:13 Comment(2)
I'm new to processing image files in java. Can you explain why making the getRGB() this way is faster/better/more optimal than the Color API's getRGB() ? Appreciate !Axel
@Axel Please take a look at this answer https://mcmap.net/q/168182/-which-amongst-pixelgrabber-vs-getrgb-is-faster. For more details type java why getrgb is slow in your favorite search engine.Breathless
D
24

Something like this?

int[][] pixels = new int[w][h];

for( int i = 0; i < w; i++ )
    for( int j = 0; j < h; j++ )
        pixels[i][j] = img.getRGB( i, j );
Denham answered 29/6, 2011 at 16:46 Comment(6)
Isn't that incredibly inefficient? I'd have though BufferedImage would store the pixels using a 2D int array, anyway?Neri
I'm pretty sure the image is stored internally as a single-dimensional data structure. So the operation will take O(W*H) no matter how you do it. You could avoiding the method call overhead by storing it into a single-dimensional array first and get converting the single-dimensional array to a 2D-array.Denham
@Neri if you want all pixels in an array, this is about as efficient as it getsKumiss
+1, I don't think this accesses the Raster's data buffer, which is definitely a good thing since that results in acceleration punting.Irreligion
@Denham This method is slower. Check the method by Mota , that is faster than this conventional method.Kirit
This would be a better answer if you explained how the code you provided answers the question.Tchao
K
12

Mota's answer is great unless your BufferedImage came from a Monochrome Bitmap. A Monochrome Bitmap has only 2 possible values for its pixels (for example 0 = black and 1 = white). When a Monochrome Bitmap is used then the

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

call returns the raw Pixel Array data in such a fashion that each byte contains more than one pixel.

So when you use a Monochrome Bitmap image to create your BufferedImage object then this is the algorithm you want to use:

/**
 * This returns a true bitmap where each element in the grid is either a 0
 * or a 1. A 1 means the pixel is white and a 0 means the pixel is black.
 * 
 * If the incoming image doesn't have any pixels in it then this method
 * returns null;
 * 
 * @param image
 * @return
 */
public static int[][] convertToArray(BufferedImage image)
{

    if (image == null || image.getWidth() == 0 || image.getHeight() == 0)
        return null;

    // This returns bytes of data starting from the top left of the bitmap
    // image and goes down.
    // Top to bottom. Left to right.
    final byte[] pixels = ((DataBufferByte) image.getRaster()
            .getDataBuffer()).getData();

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

    int[][] result = new int[height][width];

    boolean done = false;
    boolean alreadyWentToNextByte = false;
    int byteIndex = 0;
    int row = 0;
    int col = 0;
    int numBits = 0;
    byte currentByte = pixels[byteIndex];
    while (!done)
    {
        alreadyWentToNextByte = false;

        result[row][col] = (currentByte & 0x80) >> 7;
        currentByte = (byte) (((int) currentByte) << 1);
        numBits++;

        if ((row == height - 1) && (col == width - 1))
        {
            done = true;
        }
        else
        {
            col++;

            if (numBits == 8)
            {
                currentByte = pixels[++byteIndex];
                numBits = 0;
                alreadyWentToNextByte = true;
            }

            if (col == width)
            {
                row++;
                col = 0;

                if (!alreadyWentToNextByte)
                {
                    currentByte = pixels[++byteIndex];
                    numBits = 0;
                }
            }
        }
    }

    return result;
}
Kettle answered 12/1, 2015 at 18:24 Comment(0)
K
4

If useful, try this:

BufferedImage imgBuffer = ImageIO.read(new File("c:\\image.bmp"));

byte[] pixels = (byte[])imgBuffer.getRaster().getDataElements(0, 0, imgBuffer.getWidth(), imgBuffer.getHeight(), null);
Kanaka answered 10/1, 2013 at 17:7 Comment(1)
An explanation would be helpfulYoakum
B
1

Here is another FastRGB implementation found here:

public class FastRGB {
    public int width;
    public int height;
    private boolean hasAlphaChannel;
    private int pixelLength;
    private byte[] pixels;

    FastRGB(BufferedImage image) {
        pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        width = image.getWidth();
        height = image.getHeight();
        hasAlphaChannel = image.getAlphaRaster() != null;
        pixelLength = 3;
        if (hasAlphaChannel)
            pixelLength = 4;
    }

    short[] getRGB(int x, int y) {
        int pos = (y * pixelLength * width) + (x * pixelLength);
        short rgb[] = new short[4];
        if (hasAlphaChannel)
            rgb[3] = (short) (pixels[pos++] & 0xFF); // Alpha
        rgb[2] = (short) (pixels[pos++] & 0xFF); // Blue
        rgb[1] = (short) (pixels[pos++] & 0xFF); // Green
        rgb[0] = (short) (pixels[pos++] & 0xFF); // Red
        return rgb;
    }
}

What is this?

Reading an image pixel by pixel through BufferedImage's getRGB method is quite slow, this class is the solution for this.

The idea is that you construct the object by feeding it a BufferedImage instance, and it reads all the data at once and stores them in an array. Once you want to get pixels, you call getRGB

Dependencies

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

Considerations

Although FastRGB makes reading pixels much faster, it could lead to high memory usage, as it simply stores a copy of the image. So if you have a 4MB BufferedImage in the memory, once you create the FastRGB instance, the memory usage would become 8MB. You can however, recycle the BufferedImage instance after you create the FastRGB.

Be careful to not fall into OutOfMemoryException when using it on devices such as Android phones, where RAM is a bottleneck

Breathless answered 9/2, 2020 at 9:51 Comment(0)
F
-2

This worked for me:

BufferedImage bufImgs = ImageIO.read(new File("c:\\adi.bmp"));    
double[][] data = new double[][];
bufImgs.getData().getPixels(0,0,bufImgs.getWidth(),bufImgs.getHeight(),data[i]);    
Fiesole answered 27/5, 2013 at 3:33 Comment(2)
What's the variable i ?Ope
it's the iterator for dataFulfillment

© 2022 - 2024 — McMap. All rights reserved.