1

This answer suggests that it's over 10 times faster to loop pixel array instead of using BufferedImage.getRGB. Such difference is too important to by ignored in my computer vision program. For that reason, O rewritten my IntegralImage method to calculate integral image using the pixel array:

/* Generate an integral image. Every pixel on such image contains sum of colors or all the
     pixels before and itself.
  */
  public static double[][][] integralImage(BufferedImage image) {
    //Cache width and height in variables
    int w = image.getWidth();
    int h = image.getHeight();
    //Create the 2D array as large as the image is
    //Notice that I use [Y, X] coordinates to comply with the formula
    double integral_image[][][] = new double[h][w][3];

    //Variables for the image pixel array looping
    final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    //final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    //If the image has alpha, there will be 4 elements per pixel
    final boolean hasAlpha = image.getAlphaRaster() != null;
    final int pixel_size = hasAlpha?4:3;
    //If there's alpha it's the first of 4 values, so we skip it
    final int pixel_offset = hasAlpha?1:0;
    //Coordinates, will be iterated too
    //It's faster than calculating them using % and multiplication
    int x=0;
    int y=0;

    int pixel = 0;
    //Tmp storage for color
    int[] color = new int[3];
    //Loop through pixel array
    for(int i=0, l=pixels.length; i<l; i+=pixel_size) {
      //Prepare all the colors in advance
      color[2] = ((int) pixels[pixel + pixel_offset] & 0xff); // blue;
      color[1] = ((int) pixels[pixel + pixel_offset + 1] & 0xff); // green;
      color[0] = ((int) pixels[pixel + pixel_offset + 2] & 0xff); // red;
      //For every color, calculate the integrals
      for(int j=0; j<3; j++) {
        //Calculate the integral image field
        double A = (x > 0 && y > 0) ? integral_image[y-1][x-1][j] : 0;
        double B = (x > 0) ? integral_image[y][x-1][j] : 0;
        double C = (y > 0) ? integral_image[y-1][x][j] : 0;
        integral_image[y][x][j] = - A + B + C + color[j];
      }
      //Iterate coordinates
      x++;
      if(x>=w) {
        x=0;
        y++;        
      }
    }
    //Return the array
    return integral_image;
  }

The problem is that if I use this debug output in the for loop:

  if(x==0) {
    System.out.println("rgb["+pixels[pixel+pixel_offset+2]+", "+pixels[pixel+pixel_offset+1]+", "+pixels[pixel+pixel_offset]+"]");
    System.out.println("rgb["+color[0]+", "+color[1]+", "+color[2]+"]");
  }

This is what I get:

rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
...

So how should I properly retrieve pixel array for BufferedImage images?

Community
  • 1
  • 1
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • `$ printf "%x\n" -16777216` prints: ffffffffff000000. Leading f's are due to sign extension from storing a short in an int. And-ing with 0xFFFFFFFF in the println should give you a better representation. – laune Mar 24 '15 at 07:11
  • @MadProgrammer The pixel array stores one color per entry (plus one entry for alpha if present). SO there should be no calculations at all. – Tomáš Zato Mar 24 '15 at 07:25
  • @laune Unfortunatelly, it's still this: `rgb[-16777216, -16777216, -16777216]` after applying your advice. If I `&` it with `0xFF` I get zero. – Tomáš Zato Mar 24 '15 at 07:29
  • @TomášZato Well, there you go :P – MadProgrammer Mar 24 '15 at 07:33
  • @TomášZato Sorry, just recognizing your comment ;) – MadProgrammer Mar 24 '15 at 07:39
  • 1
    (Oops, I was one level below, thinking of short.) If you need an unsigned 4 byte integer, you must use long, i.e., `(long)(x & 0xFFFFFFFFL)`. Sorry. – laune Mar 24 '15 at 07:52
  • @laune I compared all values in the `pixels` array. They are all the same. The problem is with retrieving the array. – Tomáš Zato Mar 24 '15 at 08:17
  • Is `i` supposed to be `pixel` in the code sample above? I don't see `pixel` ever being incremented...? – Harald K Mar 24 '15 at 09:35
  • @haraldK haha, good point. Still, the behavior is also strange in my separate test sample - sudenly, it throws `java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferInt` for the image that is loaded exactly the same - using `ImageIO.read(...);`. I'm lost and confused. I'll probably research further and ask a better question. – Tomáš Zato Mar 24 '15 at 11:27
  • Also completely normal. Different files (and file formats) uses different "pixel layouts". The various `ImageReader` plugins used by `ImageIO` will normally choose a layout that naturally fits the layout of the file (format). Some images will use `byte[]` as backing, some will use `int[]`. If you need to handle arbitrary files, you also need to handle different pixel layouts. There's a reason for the `getRGB` methods of `BufferedImage`. It's easy to understand and thus write correct code. :-) – Harald K Mar 24 '15 at 11:34
  • @haraldK I used the same image file in both cases. Only the class path was different. – Tomáš Zato Mar 24 '15 at 11:37
  • Different implementations of `ImageReader`s might also use different pixel layouts for the exact same file... The different class path probably gave you a different `ImageReader` implementation for the same format. Sorry for introducing all these concepts, but I think they are necessary to understand the underlying issues here. In any case, may I suggest that you just use `getRGB` for now, and get your algorithm working, *and then* optimize it? :-) – Harald K Mar 24 '15 at 11:39
  • 1
    Referring to the discussion here: For tasks like this, you can convert the image to `...INT_ARGB` (see http://stackoverflow.com/a/25365269/ for example). This will be pretty fast, and allow to quickly and reliably access the data buffer `int[]` array for further processing. A side note: When you are fetching the data buffer this way, *painting* the image on the screen may become significantly slower (but if you mainly want to process the image for CV, this may not be as relevant as the speedup that you can achieve for this compared to `getRGB`). – Marco13 Mar 24 '15 at 11:49
  • @Marco13 Can I similarly enforce the `byte` array? I'm handling colors separatelly anyway as red, green and blue. If they are already separed it will save me work. – Tomáš Zato Mar 24 '15 at 12:11
  • @Marco13, Tomáš Zato I don't think `getRGB` will be slow if your type is `TYPE_INT_ARGB` because the pixels in the backing array will then match the format returned by `getRGB` (packed int ARGB format, sRGB color space). The reason it's slow (for other types) is that it has to convert the pixels to this format. – Harald K Mar 24 '15 at 12:27
  • @TomášZato You can convert the image to `TYPE_3BYTE_BGR` in the same way as Marco13 suggests. Then you'll have the R, G and B bytes separated (pixel interleaved). The data buffer will be `DataBufferByte`. – Harald K Mar 24 '15 at 12:32

1 Answers1

2

A bug in the code above, that is easily missed, is that the for loop doesn't loop as you'd expect. The for loop updates i, while the loop body uses pixel for its array indexing. Thus, you will only ever see the values of pixel 1, 2 and 3.


Apart from that:

The "problem" with the negative pixel values, is most likely that the code assumes a BufferedImage that stores its pixels in "pixel interleaved" form, however, they are stored "pixel packed". That is, all samples (R, G, B and A) for one pixel is stored in a single sample, an int. This will be the case for all BufferedImage.TYPE_INT_* types (while the BufferedImage.TYPE_nBYTE_* types are stored interleaved).

It's completely normal to have negative values in the raster, this will happen for any pixel that is less than 50% transparent (more than or equal to 50% opaque), because of how the 4 samples are packed into the int, and because int is a signed type in Java.

In this case, use:

int[] color = new int[3];

for (int i = 0; i < pixels.length; i++) {
    // Assuming TYPE_INT_RGB, TYPE_INT_ARGB or TYPE_INT_ARGB_PRE
    // For TYPE_INT_BGR, you need to reverse the colors.

    // You seem to ignore alpha, is that correct?
    color[0] = ((pixels[i] >> 16) & 0xff); // red;
    color[1] = ((pixels[i] >>  8) & 0xff); // green;
    color[2] = ( pixels[i]        & 0xff); // blue;

    // The rest of the computations...
}

Another possibility, is that you have created a custom type image (BufferedImage.TYPE_CUSTOM) that really uses a 32 bit unsigned int per sample. This is possible, however, int is still a signed entity in Java, so you need to mask off the sign bit. To complicate this a little, in Java -1 & 0xFFFFFFFF == -1 because any computation on an int will still be an int, unless you explicitly say otherwise (doing the same on a byte or short value would have "scaled up" to int). To get a positive value, you need to use a long value like this: -1 & 0xFFFFFFFFL (which is 4294967295).

In this case, use:

long[] color = new long[3];

for(int i = 0; i < pixels.length / pixel_size; i += pixel_size) {
    // Somehow assuming BGR order in input, and RGB output (color)
    // Still ignoring alpha
    color[0] = (pixels[i + pixel_offset + 2] & 0xFFFFFFFFL); // red;
    color[1] = (pixels[i + pixel_offset + 1] & 0xFFFFFFFFL); // green;
    color[2] = (pixels[i + pixel_offset    ] & 0xFFFFFFFFL); // blue;

    // The rest of the computations...
}

I don't know what type of image you have, so I can't say for sure which one is the problem, but it's one of those. :-)

PS: BufferedImage.getAlphaRaster() is a possibly an expensive and also inaccurate way to tell if the image has alpha. It's better to just use image.getColorModel().hasAlpha(). See also hasAlpha vs getAlphaRaster.

Community
  • 1
  • 1
Harald K
  • 26,314
  • 7
  • 65
  • 111
  • Frankly, I didn't *think* anything. I just followed accepted answer that has 70 upvotes. For `TYPE_` I use whatever `ImageIO.read(...);` uses. But really, whatever calculations you do, there's one really strange thing - if I compare values over the whole array, they are the same. My input image is definitelly not homogenous color - it's a screenshot actually. – Tomáš Zato Mar 24 '15 at 11:14
  • @TomášZato See my comment to the question. You don't seem to increment your array index.. – Harald K Mar 24 '15 at 11:15
  • @TomášZato Ok, fair enough. I changed the wording to "the code assumes". :-) – Harald K Mar 24 '15 at 11:36