[SOLVED] Why is copyPixelsFromBuffer giving incorrect color? setPixels is correct but slow

Issue

For my android app I am getting a ByteBuffer from native code. It contains the pixel color values to create a bitmap.

Original image –

enter image description here

I used copyPixelsFromBuffer on bitmap, but I am getting incorrect color on displaying the bitmap.

Here is the code for this approach –

Approach 1

ByteBuffer buffer = ...

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
buffer.rewind();
bitmap.copyPixelsFromBuffer(buffer);

Approx. time – ~0.4 ms
Result – Wrong colors –
enter image description here

Approach 2

Next I tried setPixels. It still gives wrong colors and is more than 10 times slower and uses extra memory for int[]. Please note that buffer.hasArray() is false, so I can’t get array from buffer.

ByteBuffer buffer = ...

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
buffer.rewind();

int[] pixels = new int[width * height];

for (int i = 0; i < width * height; i++) {
    int a = buffer.get();
    int r = buffer.get();
    int g = buffer.get();
    int b = buffer.get();
    pixels[i] = a << 24 | r << 16 | g << 8 | b;
}
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

Approx. time – ~4.0 ms
Result – Wrong colors –

enter image description here

Approach 3

This time I used setPixels but with the pixel values taken from IntBuffer representation of ByteBuffer. The colors are correct but the time is still high and there is extra memory allocation.

ByteBuffer buffer = ...
IntBuffer intBuffer = buffer.asIntBuffer();

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
buffer.rewind();

int[] pixels = new int[width * height];

for (int i = 0; i < width * height; i++) {
    pixels[i] = intBuffer.get();
}
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

Approx. time – ~3.0 ms
Result – Correct colors –

enter image description here

Any hints on why I am getting wrong colors with copyPixelsFromBuffer? I want to use it instead of setPixels as it is faster and does not require extra memory allocation.

Solution

I figured out the problem – even though the Bitmap.Config is said to be ARGB_8888, it really is RGBA. I think it is a huge bug in android developer documentation and code.

The same issue has been noted in this question – Is Android's ARGB_8888 Bitmap internal format always RGBA?

And the ndk documentation correctly notes the format to be ANDROID_BITMAP_FORMAT_RGBA_8888

Solution is simple – create the buffer with RGBA format. Or on the java side switch the channels, something like below –

for (int i = 0; i < width * height; i++) {
    Byte a = buffer.get();
    Byte r = buffer.get();
    Byte g = buffer.get();
    Byte b = buffer.get();
    bufferCopy.put(r);
    bufferCopy.put(g);
    bufferCopy.put(b);
    bufferCopy.put(a);
}

This is not very efficient code, but gets the job done.

Answered By – Vinayak Garg

Answer Checked By – David Goodson (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *