4

I am trying to set a region of pixels in a mutable bitmap a different color in my android app. Unfortunately I cannot get setPixels() to work properly. I am constantly getting ArrayOutOfBoundsExceptions. I think it may have something to do with the stride, but I'm really not sure. That is the only parameter that I still don't understand. The only other post I have seen on setPixels (not setPixel) is here: drawBitmap() and setPixels(): what's the stride? and it did not help me. I tried setting the stride as 0, as the width of the bitmap, as the width of the bitmap - the area i'm trying to draw and it still crashes. Here is my code:

public void updateBitmap(byte[] buf, int offset, int x, int y, int width, int height) {
    // transform byte[] to int[]
    IntBuffer intBuf = ByteBuffer.wrap(buf).asIntBuffer();
    int[] intarray = new int[intBuf.remaining()];
    intBuf.get(intarray);                                       

    int stride = ??????
    screenBitmap.setPixels(intarray, offset, stride, x, y, width, height); // crash here

My bitmap is mutable, so I know that is not the problem. I am also certain that my byte array is being properly converted to an integer array. But I keep getting ArrayOutOfBoundsExceptions and I don't understand why. Please help me figure this out

EDIT - here is how I construct the fake input:

int width = 1300;
int height = 700;
byte[] buf = new byte[width * height * 4 * 4]; // adding another * 4 here seems to work... why?     
for (int i = 0; i < width * height * 4 * 4; i+=4) {
    buf[i] = (byte)255;
    buf[i + 1] = 3;
    buf[i + 2] = (byte)255;
    buf[i + 3] = 3;         
}
//(byte[] buf, int offset, int x, int y, int width, int height)  - for reference        
siv.updateBitmap(buf, 0, 0, 0, width, height);

So the width and height are the correct amount of ints (at least it should be).

EDIT2 - here is the code for the original creation of screenBitmap:

public Bitmap createABitmap() {
int w = 1366;
int h = 766;

byte[] buf = new byte[h * w * 4];

for (int i = 0; i < h * w * 4;i+=4) {
        buf[i] = (byte)255;
    buf[i+1] = (byte)255;
    buf[i+2] = 0;
    buf[i+3] = 0;
}

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

IntBuffer intBuf = ByteBuffer.wrap(buf).asIntBuffer();  
int[] intarray = new int[intBuf.remaining()];
intBuf.get(intarray);                                           

Bitmap bmp = Bitmap.createBitmap(metrics, w, h, itmap.Config.valueOf("ARGB_8888"));
bmp.setPixels(intarray, 0, w, 0, 0, w, h);
return bmp;
}

It seems to work in this instance, not sure what the difference is

Community
  • 1
  • 1
Trevor
  • 995
  • 3
  • 10
  • 25
  • Stride should be width. It's the amount in the 1d array you need to go forward to be down one, when it pretends to be a 2d array. – Tatarize Oct 12 '15 at 01:48
  • That doesn't work because setPixels() takes an intarray. Period. Your 4 color sample byte array is turning into various shades of blue. 0baaaaaaaarrrrrrrrggggggggbbbbbbbbb – Tatarize Oct 12 '15 at 01:53

3 Answers3

2

Short I think, stride is the width of the image contained in pixels.

long

What I take from the documentation, the algorithm should work somehow like this:

public void setPixels (int[] pixels, 
            int offset, 
            int stride, 
            int x, 
            int y, 
            int width, 
            int height)
    for (int rowIndex = 0; rowIndex < height; rowIndex++) {
        int rowStart = offset + stride * rowIndex; // row position in pixels
        int bitmapY = y + rowIndex; // y position in the bitmap
        for (int columnIndex = 0; columnIndex < width; columnIndex++) {
            int bitmapX = x + columnIndex; // x position in the bitmap
            int pixelsIndex = rowStart + columnIndex; // position in pixels
            setPixel(bitmapX, bitmapY, pixels[pixelsIndex]);

        }
    }
}

At least this is what I would do with these arguments because is allows you to have one pixels as source and cut out different images in different sizes. Feel free to correct the algorithm if I am wrong.

So, say you have an image with pixelsWidth and pixelsHeight in pixels. Then you want to copy a section at pixelsX and pixelsY with width and height to a bitmap at position bitmapX and bitmapY. This should be the call:

bitmap.setPixels(
    pixelsWidth * pixelsY + pixelsX, // offset
    pixelsWidth, // stride
    bitmapX, // x
    bitmapY, // y
    width,
    height)
User
  • 14,131
  • 2
  • 40
  • 59
1

probably it should be:

screenBitmap.setPixels(intarray, 0, width / 4, x, y, width / 4, height);

because you have converted byte to int. your error is ArrayOutOfBoundsExceptions. check whether the size intBuf.remaining() = width * height / 4.

yushulx
  • 11,695
  • 8
  • 37
  • 64
  • My array size is correct I think. If I create a fake input of a 1300 x 700 byte input (so 1300 x 700 x 4 bytes = 3,640,000), that creates an intarray of length 91,000, which is one fourth the total. In ARGB_8888 format each pixel is one 32 bit int, so 91,000 = 1300 x 700, so that should be enough pixel data to fill a 1300x700 section of the bitmap. However when I do 1300 x 700 x 4 x 4 pixels it fills up the right amount of space without crashing. Is my math wrong somehow? -EDIT: see above for how I construct the fake input – Trevor Sep 26 '13 at 05:56
  • which line your code crashed at?intBuf.remaining() or screenBitmap.setPixels? and what is your stride value? it cannot be displayed – yushulx Sep 26 '13 at 06:25
  • do the width and height values equal to the size of screenBitmap? – yushulx Sep 26 '13 at 06:29
  • my code crashes on bitmap.setPixels(...). I have tried numerous things with stride and none of them seem to work for the data I am receiving over the socket. For some reason when I add 4x as many pixels in the fake data it works. I do not understand why I need to do height * width * 16 to write that new section of the bitmap to my image. Currently I am receiving a 20x20 pixel block of data over the socket. This comes out to 1600 bytes (20 x 20 pixels x 4 bytes per pixel), which I have verified to be 400 ints. So numerically it seems like it should work, but its not... – Trevor Sep 26 '13 at 06:32
  • what are the width and height of your screenBitmap? How did you create it? – yushulx Sep 26 '13 at 06:33
  • See edit above for how I created screen bitmap. I posted the code – Trevor Sep 26 '13 at 06:35
  • could you have a try to make all (width, height) to (640, 480) including bitmap creation and fake construction? – yushulx Sep 26 '13 at 06:43
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/38077/discussion-between-trevor-and-yushulx) – Trevor Sep 26 '13 at 06:45
0

If you're trying to draw on a bitmap, your best approach would be to abandon this method and use a canvas instead:

Canvas canvas = new Canvas(screenBitmap);

You can then draw specific points (if you want to draw a pixel), or other shapes like rectangles, circles, etc:

canvas.drawPoint(x, y, paint);

etc

Hope this helps.

Gil Moshayof
  • 16,633
  • 4
  • 47
  • 58
  • Well I'm not trying to draw on the bitmap. I'm trying to simulate a real time video feed, so I will be updating the bitmap with new image data as it comes in. So I don't want to draw, I want to set the pixels to their new value – Trevor Sep 26 '13 at 05:37