Android : some problems when converting drawable image to byte array - android

I want to convert an image in my app to a Base64 encoded string. This image may be of any type like jpeg, png etc.
What I have done is, I converted the drawable to a Bitmap. Then I converted this Bitmap to ByteArrayOutputStream using compress metheod And I am converting this ByteArrayOutputStream to byte array. And then I am encoding it to Base64 using encodeToString().
I can display the image using the above method if the image is of PNG or JPEG.
ByteArrayOutputStream objByteOutput = new ByteArrayOutputStream();
imgBitmap.compress(CompressFormat.JPEG, 0, objByteOutput);
But the problem is if the image is in any other types than PNG or JPEG, how can I display the image?
Or please suggest me some another method to get byte array from Bitmap.
Thank you...

I'd suggest using
http://developer.android.com/reference/android/graphics/Bitmap.html#copyPixelsToBuffer(java.nio.Buffer)
and specify a ByteBuffer, then you can use .array() on the ByteBuffer if it is implemented (it's an optional method) or .get(byte[]) to get it if .array() doesn't exist.
Update:
In order to determine the size of the buffer to create you should use Bitmap.getByteCount(). However this is only present on API 12 and up, so you would need to use Bitmap.getWidth()*Bitmap.getHeight()*4 - the reason for 4 is that the Bitmap uses a series of pixels (internal representation may be less but shouldn't ever be more), each being an ARGB value with 0-255 hence 4 bytes per pixel.
You can get the same with Bitmap.getHeight() * Bitmap.getRowBytes() - here's some code I used to verify this worked:
BitmapDrawable bmd = (BitmapDrawable) getResources().getDrawable(R.drawable.icon);
Bitmap bm = bmd.getBitmap();
ByteBuffer byteBuff = ByteBuffer.allocate(bm.getWidth() * bm.getHeight() * 4);
byteBuff.rewind();
bm.copyPixelsToBuffer(byteBuff);
byte[] tmp = new byte[bm.getWidth() * bm.getHeight() * 4];
byteBuff.rewind();
byteBuff.get(tmp);
It's not nice code, but it gets the byte array out.

Related

Android Image size after compressed from bitmap is very large than original image load to bitmap

I am now a newbie to android programming.
I am very confused when processing with images.
I am now trying to load a jpeg image to bitmap and then convert bitmap to byte array and vice versa.
I am testing with the first case: load jpeg to bitmap and get byte array.
My input image 520 x 390 (24 bit color) 24KB JPEG.
My output byte array is ~ 290000 bytes ~ 290KB, very large from the original one.
How can I convert it into byte array with the same size or nearly the same size as the original JPEG?
Therefore, I wonder if the inverse conversion will convert byte array to jpeg the same size or not ?
Here is my code:
Resources r = this.getResources();
Bitmap bm = BitmapFactory.decodeResource(r,R.drawable.plot);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG,100,baos);
byte[] byteArray = baos.toByteArray();
bitmap.compress has a property of quality (the 2nd one). try reducing if from 100 to a lower number (0-100).
The JPEG you loaded is most likely to have a level of compression, which is what makes JPEGs smaller, by trading quality for size.
In your code you are setting a compression level of 100, this means no compression at all, so the bitmap is saved in JPEG format, but as it is without any size/quality reduction at all.
If you change the compression level to anything lower than 100 you will see how the file size gets smaller. Try between 80 to 90 to keep a good quality while getting a smaller file.
How can I convert it into byte array with the same size or nearly the same size as the original JPEG?
Just load the jpg file in a byte array if you need it in a byte array.
There is no need to convert to bitmap first.

Compressing bitmap returns always the same byte size

I'm trying to compress a bitmap with JPG compression function.
This is my piece of code:
ByteArrayOutputStream out = new ByteArrayOutputStream();
originalBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
byte[] newArray = out.toByteArray();
Bitmap compressed = BitmapFactory.decodeByteArray(newArray, 0, newArray.length);
The strange behavior is that if I change the compression factor (ie. from 80 to 50) the size of the "out" array will change.... but the bitmap "compressed" remain always with the same byte number as the "originalBitmap".
Someone can explain to me why?!?
Thanks in advance...
The amount of bytes that a Bitmap takes is: X * Y * D, where:
X is the width, in pixels
Y is the height, in pixels
D is the bit depth, in bytes per pixel, which by default is 4
It does not matter whether the Bitmap was loaded from a JPEG, a PNG, a WebP, or anything else. The number of bytes is determined solely from the resolution (width times height) and the bit depth.
So, in your case, you are successfully creating a Bitmap with the same number of bytes as before, just with a bit fuzzier output.

CopyPixelsFromBuffer works. But decode its ByteArray returns null

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(byteBuffer);
// bitmap is valid and can be displayed
I extracted the ByteArray from the valid byteBuffer. But it returns null when I tried to decodeByteArray. Can someone explain why it's the case.
byteBuffer.rewind();
byteBuffer.get(byteArray, 0, byteBuffer.capacity());
Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0 , byteArray.length);
// returns null
I believe the 2 functions do different things and expect different data.
copyPixelsFromBuffer()
is used to import raw pixel information into an existing Bitmap image which already has size and pixel depth configured.
BitmapFactory.decodeByteArray()
is used to create a bitmap from a byte array containing the full bitmap file data, not just the raw pixels. That's why the function doesn't take (or need) size and pixel depth information, because it gets it all from the bytes passed to it.

cv::imencode required for outgoing byte[] in Android OpenCV?

I have been struggling with sending back a CV:Mat in JNI as a Java byte[] so that that it can be decoded successfully with BitmapFactory.decode(). When I first bring in my byte[] array(constructed using data from an Android Bitmap) from the Java side, I am able to successfully use it in the C++ OpenCV functions. I do this by building a Mat from the byte[] coming in and calling cv::imdecode on the Mat.
The problem comes when I return back to Android and attempt to use BitmapFactory to decode the byte array into an Android Bitmap. It returns null which indicates that there was a problem in the decoding. Am I incorrectly performing the operations before I return from JNI? Do I need to use cv::imencode since I had to use cv::imdecode on the entering byte[]?
Any and all help appreciated! Code sample is below where I convert the data I need from the Mat in JNI.
NOTE I am aware of using Apache Android_Bitmap functions, but using a byte array is a requirement I am currently working on.
//inData is a char* pointer that is set to a char* cast of the jbyte* pointer for the
// incoming Array.
cv::Mat inMat = cv::Mat(rows, columns, CV_8UC4, inData);
cv::Mat decodedMat = cv::imdecode(inMat, 1);
//convertImage is a function that changes the color space from BGR to Gray and then Gray to
//RGB.
convertImage(decodedMat, decodedMat);
cv::cvtColor(decodedMat, decodedMat, CV_RGB2RGBA);
jbyteArray jDataArray = env->NewByteArray(pixelDataLength);
env->SetByteArrayRegion(jDataArray,0,pixelDataLength,(jbyte*)decodedMat.data);
env->SetObjectField(in,dataArray,jDataArray);
env->ReleaseByteArrayElements(pixelData, bytePointerForIn, 0);
BitmapFactory expects the data provided to it to be in a known file format, but you are passing it raw pixels. You could make it work by calling cv::imencode, but perhaps a more natural solution for loading images from raw pixel data would be to create the original Java Bitmap object as mutable, and then calling the copyPixelsToBuffer and copyPixelsFromBuffer methods to get and set the pixel data in that object.
Solved! It was combination of my original version of sending in the bytes by using Bitmap.compress() and then on the returned pixels using copyPixelsFromBuffer. Thanks to Buddy for pointing me in the right direction.
Android:
//input was the original bitmap that was used to construct the byte[] array. I then used input.compress() in JPEG format to a Byte. Very important for it to be compressed in a format that will be recognized in cv::imdecode.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
input.compress(CompressFormat.JPEG, 100, bos);
data1.pixelData = bos.toByteArray();
...
//Reconstruction after JNI call
ByteBuffer buffer2 = ByteBuffer.wrap(data1.pixelData);
Bitmap returnFromConvert = Bitmap.createBitmap(input.getWidth(),
input.getHeight(),Bitmap.Config.ARGB_8888);
returnFromConvert.copyPixelsFromBuffer(buffer2);

Grayscaled bitmaps in android

I have an array of bytes that correspond to a "grayscaled bitmap" (one byte->one pixel), and I need to create a PNG file for this image.
The method below works, but the png created is HUGE, as the Bitmap I am using is an ARGB_8888 bitmap, which takes 4 bytes per pixel instead of 1 byte.
I haven't been able to make it work with other Bitmap.Config different than ARGB_8888. Maybe ALPHA_8 is what I need, but I have not been able to make it work.
I have also tried the toGrayScale method which is included in some other posts (Convert a Bitmap to GrayScale in Android), but I have the same issue with the size.
public static boolean createPNGFromGrayScaledBytes(ByteBuffer grayBytes, int width,
int height,File pngFile) throws IOException{
if (grayBytes.remaining()!=width*height){
Logger.error(Tag, "Unexpected error: size mismatch [remaining:"+grayBytes.remaining()+"][width:"+width+"][height:"+height+"]", null);
return false;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// for each byte, I set it in three color channels.
int gray,color;
int x=0,y=0;
while(grayBytes.remaining()>0){
gray = grayBytes.get();
// integer may be negative as byte is signed. make them positive.
if (gray<0){gray+=256;}
// for each byte, I set it in three color channels.
color= Color.argb(-1, gray, gray, gray);
bitmap.setPixel(x, y, color);
x++;
if (x==width){
x=0;
y++;
}
}
FileOutputStream fos=null;
fos = new FileOutputStream(pngFile);
boolean result= bitmap.compress(Bitmap.CompressFormat.PNG,100,fos);
fos.close();
return result;
}
EDIT: Link to the generated file (it may look nonsense, but is just created with randon data).
http://www.tempfiles.net/download/201208/256402/huge_png.html
Any help will be greatly appreciated.
As you've noticed, saving a grayscale image as RGB is expensive. If you have luminance data then it would be better to save as a Grayscale PNG rather than an RGB PNG.
The bitmap and image functionality available in the Android Framework is really geared towards reading and writing image formats that are supported by the framework and UI components. Grayscale PNG is not included here.
If you want to save out a Grayscale PNG on Android then you'll need to use a library like http://code.google.com/p/pngj/
If you use OPENCV for Android library, you can use the library to save a binary data to a png file.
My way is:
in jni part,
set Mat whose data begin with the byte array:
jbyte* _ByteArray_BrightnessImgForOCR = env->GetByteArrayElements(ByteArray_BrightnessImgForOCR, 0);
Mat img(ByteArray_BrightnessImgForOCR_h, ByteArray_BrightnessImgForOCR_w, CV_8UC1, (unsigned char *) _ByteArray_BrightnessImgForOCR);
And then write it to a png file.
imwrite("/mnt/sdcard/binaryImg_forOCR.png", img);
Of course, you need to take some time to get yourself familiar with OpenCV and Java native coding. Following OpenCV for Android examples, it is fast to learn.

Categories

Resources