Android: bitmap to RGBA and back - android

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!

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;
}

Related

Why is copyPixelsFromBuffer giving incorrect color? setPixels is correct but slow

For my android app I am getting a ByteBuffer from native code. It contains the pixel color values to create a bitmap.
Original image -
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 -
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 -
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 -
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.
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.
The Bitmap.Config.ARGB_8888 documentation mentions, "Use this formula to pack into 32 bits:
int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);". It calls for the color to be packed in ABGR_8888 format yet it is the ARGB_8888 format, and according this post is formally referred to as ANDROID_BITMAP_FORMAT_RGBA_8888 here (ABGR_8888 backwards). In any case, this Kotlin function will convert your colors for use in copyPixelsFromBuffer.
fun androidBitmapFormatRGBA8888(color: Int): Int {
val a = (color shr 24) and 255
val r = (color shr 16) and 255
val g = (color shr 8) and 255
val b = color and 255
return (a shl 24) or (b shl 16) or (g shl 8) or r
}

Android Low image quality / altered image when using glReadPixels

I've spend a lot of time trying to figure this out but can't see what I am doing wrong.
This is my original image
Image recaptured 5 times
Recapturing the image multiple times clearly shows that there is something not right. Capturing it once is just ok but twice is enough to clearly see the difference.
I found these similar issues on stackoverflow:
Bitmap quality using glReadPixels with frame buffer objects
Extract pixels from TextureSurface using glReadPixels resulting in bad image Bitmap
(sorry limited to links I can add)
Unfortunately none of the proposed suggestions/solutions fixed my issue.
This is my code:
private Bitmap createSnapshot (int x, int y, int w, int h) {
int bitmapBuffer[] = new int[w * h];
int bitmapSource[] = new int[w * h];
IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
intBuffer.position(0);
try {
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, intBuffer);
int offset1, offset2;
for (int i = 0; i < h; i++) {
offset1 = i * w;
offset2 = (h - i - 1) * w;
for (int j = 0; j < w; j++) {
int texturePixel = bitmapBuffer[offset1 + j];
int blue = (texturePixel >> 16) & 0xff;
int red = (texturePixel << 16) & 0x00ff0000;
int pixel = (texturePixel & 0xff00ff00) | red | blue;
bitmapSource[offset2 + j] = pixel;
}
}
} catch (GLException e) {
return null;
}
return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
}
I'm using OpenGL 2. For bitmap compression I am using PNG. Tested it using JPEG (quality 100) and the result is the same but slightly worse.
There is also a slight yellowish tint added to the image.

Android camera write text directly into frame

I have camera (the "deprecated" API) and camera PreviewCallback, in which I get frames. I use that to take pictures (not takePicure or PictureCallback, because I want it fast over quality) and save as jpeg, code snippset below:
#Override
public synchronized void onPreviewFrame(byte[] frame, Camera camera) {
YuvImage yuv = new YuvImage(frame, previewFormat, width, height, null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, width, height), 50, out);
out.toByteArray(); // this is my JPEG
// ... save this JPEG
}
I want to draw text, timestamp, into frame. I know how to do this by converting to Bitmap, using Canvas, drawing text and converting to Bitmap again. But this method is relatively slow (it's not biggest issue), but also i need the frame byte array to other things i.e. put into video and I would like to know if there is some method or lib to directly write text into that frame. I don't use preview (or rather, I use dummy preview), I'm asking how to change the frame byte array. I bet I'd need to do some mumble-jumble into byte array to make it work. The frame is in standard(?) format (YUV420/NV21).
Edit:
I managed to get a working function (getNV21). Of course it is far from efficient at it creates new Bitmap every frame and draws text to it but at least i have something I can work on, directly into yuv image. Still, answers would be appreciated.
private Bitmap drawText(String text, int rotation) {
Bitmap bitmap = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.rgb(255, 255, 255));
paint.setStrokeWidth(height/36);
paint.setTextSize(height/36);
paint.setShadowLayer(5f, 0f, 0f, Color.BLACK);
paint.setTypeface(Typeface.MONOSPACE);
if (rotation == 0 || rotation == 180) {
canvas.rotate(rotation,width/2,height/2);
} else {
canvas.translate(Math.abs(width-height), 0);
int w = width/2 - Math.abs(width/2-height/2);
int h = height/2;
canvas.rotate(-rotation,w,h);
}
canvas.drawText(text, 10, height-10, paint);
return bitmap;
}
byte [] getNV21(byte[] yuv, int inputWidth, int inputHeight, Bitmap scaled) {
int [] argb = new int[inputWidth * inputHeight];
scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
scaled.recycle();
return yuv;
}
private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
final int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize;
int a, R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++,index++,yIndex++) {
a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
R = (argb[index] & 0xff0000) >> 16;
G = (argb[index] & 0xff00) >> 8;
B = (argb[index] & 0xff) >> 0;
if (R == 0 && G == 0 && B == 0) {
if (j % 2 == 0 && index % 2 == 0) uvIndex+=2;
continue;
}
// well known RGB to YUV algorithm
Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
// NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
// meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other
// pixel AND every other scanline.
yuv420sp[yIndex] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0) {
yuv420sp[uvIndex] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V));
yuv420sp[uvIndex] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U));
}
}
}
}

How to set color to certain pixels of an image

I have an image in an ImageView. I would like to set certain pixels to red. I have made some progress, but the created image has lost its colour.
iv.setImageBitmap(processingBitmap(bitmap));
private Bitmap processingBitmap(Bitmap src){
Bitmap dest = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());
for(int x = 0; x < src.getWidth(); x++){
for(int y = 0; y < src.getHeight(); y++){
int pixelColor = src.getPixel(x, y);
int newPixel= Color.rgb(pixelColor, pixelColor, pixelColor);
dest.setPixel(x, y, newPixel);
}
}
for (int i=5; i<50; i++)
{
dest.setPixel(i, i, Color.rgb(255, 0, 0));
}
return dest;
}
If I use Bitmap dest = src.copy(src.getConfig(), src.isMutable()); instead of Bitmap dest = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig()); I get an error to the iv.setImageBitmap(processingBitmap(bitmap)); line:
09-06 18:03:37.226: E/AndroidRuntime(811): Caused by:
java.lang.IllegalStateException 09-06 18:03:37.226:
E/AndroidRuntime(811): at
android.graphics.Bitmap.setPixel(Bitmap.java:847)
I also don't know why do I have to copy-paste all the pixels and after then set those pixels to red I want. If I use only
for (int i=5; i<50; i++)
{
dest.setPixel(i, i, Color.rgb(255, 0, 0));
}
I get a black image with the red line.
Any help is appreciated!
Color.rgb() takes 3 bytes as Red, Green, Blue. You are tring to set the pixel-color on each of them.
Better try something like this
byte blue = (byte) ((pixelColor & 0xff));
byte green = (byte) (((pixelColor >> 8) & 0xff));
byte red = (byte) (((pixelColor >> 16) & 0xff));
int newPixel= Color.rgb(red , green , blue);

Android: conerting Image into byteArray

In my project I have an bitmap image. I need to convert this picture to byteArray in order to manipulate some bytes and after that save it as image.
with this code image = BitmapFactory.decodeResource(context.getResources(), R.drawable.tasnim); I have acces to width and height but how can I have access to bytes of this image?
Thanks
AFAIK Most correct way is:
ByteBuffer copyToBuffer(Bitmap bitmap){
int size = bitmap.getHeight() * bitmap.getRowBytes();
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
bitmap.copyPixelsToBuffer(buffer);
return buffer;
}
I'm assuming the OP wants to manipulate the pixels, not the header information of the Image...
Assuming your image is a Bitmap
int w = image.getWidth(), h = image.getHeight();
int[] rgbStream = new int[w * h];
image.getPixels(rgbStream, 0, w, 0, 0, w, h);
Of course, this gets you Pixel values as Integers...But you can always convert them again.
int t = w * h;
for (int i = 0; i < t; i++) {
pixel = rgbStream[i]; //get pixel value (ARGB)
int A = (pixel >> 24) & 0xFF; //Isolate Alpha value...
int R = (pixel >> 16) & 0xFF; //Isolate Red Channel value...
int G = (pixel >> 8) & 0xFF; //Isolate Green Channel value...
int B = pixel & 0xFF; //Isolate Blue Channel value...
//NOTE, A,R,G,B can be cast as bytes...
}

Categories

Resources