Search code examples
androidgraphicsbitmaprgbaargb

Android: bitmap to RGBA and back


I'm trying to write a couple of methods to convert an Android Bitmap to an RGBA byte array and then back to a Bitmap. The problem is that I don't seem to hit the formula, because the colors are always coming back wrong. I have tried with several different assumptions but to no avail.

So, this is the method to convert from Bitmap to RGBA that I think is fine:

public static byte[] bitmapToRgba(Bitmap bitmap) {
    int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
    byte[] bytes = new byte[pixels.length * 4];
    bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
    int i = 0;
    for (int pixel : pixels) {
        // Get components assuming is ARGB
        int A = (pixel >> 24) & 0xff;
        int R = (pixel >> 16) & 0xff;
        int G = (pixel >> 8) & 0xff;
        int B = pixel & 0xff;
        bytes[i++] = (byte) R;
        bytes[i++] = (byte) G;
        bytes[i++] = (byte) B;
        bytes[i++] = (byte) A;
    }
    return bytes;
}

And this is the method aimed at creating back a bitmap from those bytes that is not working as expected:

public static Bitmap bitmapFromRgba(int width, int height, byte[] bytes) {
    int[] pixels = new int[bytes.length / 4];
    int j = 0;

    // It turns out Bitmap.Config.ARGB_8888 is in reality RGBA_8888!
    // Source: https://stackoverflow.com/a/47982505/1160360
    // Now, according to my own experiments, it seems it is ABGR... this sucks.
    // So we have to change the order of the components

    for (int i = 0; i < pixels.length; i++) {
        byte R = bytes[j++];
        byte G = bytes[j++];
        byte B = bytes[j++];
        byte A = bytes[j++];

        int pixel = (A << 24) | (B << 16) | (G << 8) | R;
        pixels[i] = pixel;
    }

    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    bitmap.copyPixelsFromBuffer(IntBuffer.wrap(pixels));
    return bitmap;
}

That's my last implementation, though I have tried several different ones without success. I'm assuming createBitmap expects ABGR in spite of specifying ARGB_8888 because I have done experiments hardcoding all the pixels to things like:

0xff_ff_00_00 -> got blue 
0xff_00_ff_00 -> got green
0xff_00_00_ff -> got red

Anyway maybe that assumption is wrong and a consequence of some other mistaken one before.

I think the main problem may be related to the use of signed numeric values, since there are no unsigned ones in Java (well, there's something in Java 8+ but on one hand I don't think it should be necessary to use these, and on the other it is not supported by older Android versions that I need to support).

Any help will be very appreciated.

Thanks a lot in advance!


Solution

  • I solved it myself. There are a number of issues but all these began with this line:

    bitmap.copyPixelsFromBuffer(IntBuffer.wrap(pixels));
    

    That seems to be mixing up the color components in the wrong way. Maybe it's something related to byte order (little/big indian stuff), in any case I worked it around using setPixels instead:

    bitmap.setPixels(pixels, 0, width, 0, 0, width, height); 
    

    This is the final code that's working as expected, just in case it's useful for someone else:

    public static byte[] bitmapToRgba(Bitmap bitmap) {
        if (bitmap.getConfig() != Bitmap.Config.ARGB_8888)
            throw new IllegalArgumentException("Bitmap must be in ARGB_8888 format");
        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        byte[] bytes = new byte[pixels.length * 4];
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        int i = 0;
        for (int pixel : pixels) {
            // Get components assuming is ARGB
            int A = (pixel >> 24) & 0xff;
            int R = (pixel >> 16) & 0xff;
            int G = (pixel >> 8) & 0xff;
            int B = pixel & 0xff;
            bytes[i++] = (byte) R;
            bytes[i++] = (byte) G;
            bytes[i++] = (byte) B;
            bytes[i++] = (byte) A;
        }
        return bytes;
    }
    
    public static Bitmap bitmapFromRgba(int width, int height, byte[] bytes) {
        int[] pixels = new int[bytes.length / 4];
        int j = 0;
    
        for (int i = 0; i < pixels.length; i++) {
            int R = bytes[j++] & 0xff;
            int G = bytes[j++] & 0xff;
            int B = bytes[j++] & 0xff;
            int A = bytes[j++] & 0xff;
    
            int pixel = (A << 24) | (R << 16) | (G << 8) | B;
            pixels[i] = pixel;
        }
    
    
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
        return bitmap;
    }