Java - get pixel array from image

后端 未结 7 1070
面向向阳花
面向向阳花 2020-11-22 03:52

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

相关标签:
7条回答
  • 2020-11-22 04:17

    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

    0 讨论(0)
  • 2020-11-22 04:20

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 04:23

    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 );
    
    0 讨论(0)
  • 2020-11-22 04:28

    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);
    
    0 讨论(0)
  • 2020-11-22 04:35

    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)
    
    0 讨论(0)
  • 2020-11-22 04:35

    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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题