Using BitmapFactory.Options causes changes in BitmapFactory - android

What I wanted to achieve is being able to calculate the height and width of a Bitmap from an inputstream without actually changing BitmapFactory.Options
This is what I did:
private Boolean testSize(InputStream inputStream){
BitmapFactory.Options Bitmp_Options = new BitmapFactory.Options();
Bitmp_Options.inJustDecodeBounds = true;
BitmapFactory.decodeResourceStream(getResources(), new TypedValue(), inputStream, new Rect(), Bitmp_Options);
int currentImageHeight = Bitmp_Options.outHeight;
int currentImageWidth = Bitmp_Options.outWidth;
if(currentImageHeight > 200 || currentImageWidth > 200){
Object obj = map.remove(pageCounter);
Log.i("Page recycled", obj.toString());
return true;
}
return false;
}
Now the main problem here is that it changes the BitmapFactory.Options to a state that can't decodeStream properly.
My question is there another way of resetting BitmapFactory.Options? or another possible solution?
Another method: (Note* that originalBitmap is null when the top method is applied)
This is was my original code:
Bitmap originalBitmap = BitmapFactory.decodeStream(InpStream);
Applying Deev's and Nobu Game's suggestion: (no change)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
Bitmap originalBitmap = BitmapFactory.decodeStream(InpStream,null,options);

You are attempting to read from the same stream twice. A stream doesn't work as a byte array. Once you've read data from it, you cannot read it again unless you reset the stream position. You can try to call InputStream.reset() after your first call to decodeStream() but not all InputStreams support this method.

If you are trying to reuse your Options object (which is not the case in your code sample by the way), then how are you trying to reuse it? What is the error message, what goes wrong?
Are you trying to reuse the Options object for actually decoding the Bitmap? Then just set inJustDecodeBounds to false.

A simple class to copy InputStream when inputStream.Mark and inputStream.Reset doesn't work.
To call:
CopyInputStream copyStream = new CopyInputStream(zip);
InputStream inputStream = copyStream.getIS();
I hope this helps someone. Here is the code.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class CopyInputStream {
private InputStream inputStream;
private ByteArrayOutputStream byteOutPutStream;
/*
* Copies the InputStream to be reused
*/
public CopyInputStream(InputStream is){
this.inputStream = is;
try{
int chunk = 0;
byte[] data = new byte[256];
while(-1 != (chunk = inputStream.read(data)))
{
byteOutPutStream.write(data, 0, chunk);
}
}catch (Exception e) {
// TODO: handle exception
}
}
/*
* Calls the finished inputStream
*/
public InputStream getIS(){
return (InputStream)new ByteArrayInputStream(byteOutPutStream.toByteArray());
}
}

Related

Java - Read from InputStream to ByteBuffer by chunk size

I am using Cronet API with our current API stack, specifically UploadDataProvider, there is a ByteBuffer with preset limit, seems like the limit size is fixed and we need to pass the data chunk by chunk. Our current API uses InputStream, and write chunk to OutputStream. We're using following code to work with infinite size of file:
byte[] buf = new byte[16 * 1024];
int bytesRead;
while ((bytesRead =inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
I'd like to achieve the same for this Cronet API, UploadDataProvider. My plan was in its read(UploadDataSink, ByteBuffer) method, whenever this read() method was called, read ByteBuffer's limit from inputStream, but my following code not working as expected.
public class MyUploadDataProvider extends UploadDataProvider {
private ReadableByteChannel byteChannel;
MyUploadDataProvider(InputStream inputStream) {
byteChannel = Channels.newChannel(inputStream);
}
#Override
public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException {
boolean finalChunk = false;
int read = this.byteChannel.read(byteBuffer);
if (read == -1) {
finalChunk = true;
}
uploadDataSink.onReadSucceeded(finalChunk);
}
}
Not sure why it read failed, can anyone please help me fix this? Thanks!

Dynamically load a class as byte array with ClassLoader in Android

I Am trying to load a class as a byte array so I could send it over the network and execute it remotely via Reflection. This Class (Bubble in this case) is in the same package. The thing is that I can't get the resource using the getResourceAsStream(classpath) method.
The .getResourceAsStream(classpath) is always returning null. I've tested this code in a Java project and worked properly. I think the problem is the resource path, does Android load a .class file?
private void doSomething() {
Bubble b = new Bubble();
try {
//Try to retrieve the class byte array
byte[] classBytes = getBytes(b.getClass());
} catch (IOException e) {
e.printStackTrace(System.err);
}
...
}
private byte[] getBytes(Class c) throws IOException{
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte b[] = new byte[1024];
String classpath = c.getCanonicalName().replace('.', File.pathSeparatorChar) + ".class";
//classpath is now, for example, com:myproject:Bubble.class
InputStream in = c.getClassLoader().getResourceAsStream(classpath);
int load;
while((load=in.read(b))>0){
out.write(b,0,load);
}
byte[] _r = out.toByteArray();
out.close();
in.close();
return _r;
}
Android uses dex file format for classes, and the best way would be to not send zipped (jarred) classes.dex, which contains all classes you need.

How to wrap Uri content with a nio.ByteBuffer on Android?

I'm trying to read content from a Uri on Android, and I need the final Object type passed to the underlying SDK to by a nio.ByteBuffer.
I can get my hands on an InputStream, via ContentResolver but didn't find a way to wrap it with an nio.ByteBuffer.
Is there a way to convert a Uri content to a nio.ByteBuffer on Android?
I've ended up downloading the content of the Uri locally and open it via other method to get the ByteBuffer
Suppose you are working on an Activity,
private ByteBuffer getByteBuffer(Uri uri){
try{
InputStream iStream = getContentResolver().openInputStream(uri);
if(iStream!=null){
//value of MAX_SIZE is up to your requirement
final int MAX_SIZE = 5000000;
byte[] byteArr = new byte[MAX_SIZE];
int arrSize = 0;
while(true){
int value = iStream.read(byteArr);
if(value == -1){
break;
}else{
arrSize += value;
}
}
iStream.close();
return ByteBuffer.wrap(byteArr, 0, arrSize);
}
}catch(IOException e){
//do something
}
return null;
}
Notes:
(i) InputStream.read(byte[] b) will return an Integer which indicate total number of bytes read into the byte array b at each time.
(ii) If InputStream.read(Byte[] b) returns -1, it indicates that it is the end of the inputStream.
(iii) arrSize stores the total number of bytes read, i.e. the length of byte[] b
(iv) ByteBuffer.wrap(byte[] b, int offset, int length) will wrap the byte array to give a ByteBuffer. You may check this reference
(v) ContentResolver.openInputStream(Uri uri) and InputStream.read(byte[] b) will throw IOException so you must handle it.
(vi) Caution: IndexOutOfBoundException might happen if arrSize > MAX_SIZE, you may need to add if-else clause to handle such issue.
Please feel free to comment or change the code if there is any mistake or if there is a faster way to do that. Happy coding

Hangs at "compressToJpeg"

See all 3 edits below
Update: Changed 'read()' to 'readFully()' and got some things working. 'decodeByteArray()' no longer returns null. The jpeg written to the card is actually a full image. The image is still not being drawn to my view. I will keep working and post another question if need be.
I am writing a video streaming client program that takes in images from a DataInputStream.
Because the server calls compressToJpeg() on these images, I figured I could simply take the byte array from my DataInputStream, decode it, and draw the Bitmap from decodeByteArray() to my SurfaceView. Unfortunately, that method only leaves me with decodeByteArray() returning null.
So I added an intermediate step. I created a compressFrame() method which simply takes the byte array from the stream, creates a new YuvImage instance, and uses that to call compressToJpeg(). When run, however, this method hangs on compressToJpeg and closes my client activity. There is no FC popup, and I get no errors in my log.
I have provided the run() of the thread calling the problem method, the method itself, and the log printed before the app closes.
EDIT: I've also added the ReceiverUtils.getFrame() just in case.
#Override
public void run() {
String fromServer = null;
int jpgSize;
byte[] buffer;
byte[] jpgData;
Bitmap image;
ByteArrayOutputStream jpgBaos = null;
while(isItOK && (receiver.textIn != null)){
if(!holder.getSurface().isValid()){
continue;
}
ReceiverUtils.init(holder);
//Receive 'ready' flag from camcorder
fromServer = ReceiverUtils.receiveText(receiver);
while((fromServer != null)){
Log.e("From server: ", fromServer); //logging here because of null pointers when done in Utils
if(fromServer.startsWith("ANDROID_JPG_READY")){
//Obtain size for byte array
jpgSize = Integer.parseInt(fromServer.substring(18));
Log.e("JPG SIZE", String.valueOf(jpgSize));
//Create byte array of size jpgSize
buffer = new byte[jpgSize];
Log.e("BUFFER SIZE", String.valueOf(buffer.length));
//Send flag and receive image data from camcorder
ReceiverUtils.sendText(receiver, "ANDROID_JPG_SEND");
jpgData = ReceiverUtils.receiveData(receiver, buffer);
//Compress jpgData and write result to jpgBaos
jpgBaos = ReceiverUtils.compressFrame(imgRect, jpgData);
//Decode jpgData into Bitmap
image = ReceiverUtils.getFrame(jpgBaos.toByteArray(), jpgSize);
//image = ReceiverUtils.getFrame(jpgData, jpgSize);
//Set buffer and jpgData to null since we aren't using them
buffer = null;
jpgData = null;
//Draw Bitmap to canvas
ReceiverUtils.draw(image, holder, imgRect);
//break; (testing one run-through)
}
//Receive 'ready' flag from camcorder
fromServer = ReceiverUtils.receiveText(receiver);
if(isItOK == false){
break;
}
}
//break; (testing one run-through)
}
}
This is the ReceiverUtils.compressFrame() called in the previous snippet.
/*----------------------------------------
* compressFrame(Rect imgRect, byte[] input): Creates new instance of YuvImage used to compress ARGB
* byte array data to jpeg. Returns ByteArrayOutputStream with data.
*/
public static ByteArrayOutputStream compressFrame(Rect imgRect, byte[] input){
boolean success = false;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(input, ImageFormat.NV21,
imgRect.width(), imgRect.height(), null);
Log.e("HEY", "HEY");
success = yuvImage.compressToJpeg(imgRect, 80, baos);
Log.e("Compression Success:", String.valueOf(success));
return baos;
}
Finally, this is the output I'm getting from my LogCat. (Beware some obviously hastily written debug logs.)
07-03 15:01:19.754: E/From server:(1634): ANDROID_JPG_READY_26907
07-03 15:01:19.754: E/JPG SIZE(1634): 26907
07-03 15:01:19.764: E/BUFFER SIZE(1634): 26907
07-03 15:01:19.764: E/To server:(1634): ANDROID_JPG_SEND
07-03 15:01:19.834: E/jpgIn(1634): Data received successfully.
07-03 15:01:19.834: E/HEY(1634): HEY
07-03 15:01:19.844: D/skia(1634): onFlyCompress
EDIT: ReceiverUtils.getFrame()
/*---------------------------------------
* getFrame(byte[] jpgData, int jpgSize): Decodes a byte array into a Bitmap and
* returns the Bitmap.
*/
public static Bitmap getFrame(byte[] jpgData, int jpgSize){
Bitmap res = null;
res = BitmapFactory.decodeByteArray(jpgData, 0, jpgSize);
Log.e("Decode success", String.valueOf(!(res == null)));
return res;
}
EDIT 2: Code before adding compression
//Compress jpgData and write result to jpgBaos
//jpgBaos = ReceiverUtils.compressFrame(imgRect, jpgData);
//Decode jpgData into Bitmap
//image = ReceiverUtils.getFrame(jpgBaos.toByteArray(), jpgSize);
image = ReceiverUtils.getFrame(jpgData, jpgSize);
EDIT 3: Image after saved to SD Card
The image that Android holds in 'jpgData' in the first code snippet is not the entire image. I can't post it due to being a new user. I added a 'writeToSD' method to test. I assume that the reason it cannot be decoded is because the majority of the image is just blank space, and only a portion of the image is there.

Getting the size of an image inputstream

I need to get the height and width of the image found in the inputstream. Here is what I did:
private Boolean testSize(InputStream inputStream){
BitmapFactory.Options Bitmp_Options = new BitmapFactory.Options();
Bitmp_Options.inJustDecodeBounds = true;
BitmapFactory.decodeResourceStream(getResources(), new TypedValue(), inputStream, new Rect(), Bitmp_Options);
int currentImageHeight = Bitmp_Options.outHeight;
int currentImageWidth = Bitmp_Options.outWidth;
Bitmp_Options.inJustDecodeBounds = false;
if(currentImageHeight < 200 || currentImageWidth < 200){
Object obj = map.remove(pageCounter);
Log.i("Page recycled", obj.toString());
return true;
}
return false;}
Skipping to the problem at point:
It change BitmapFactory.Options even if I forced it to be false after calculation on my second method below.
private Bitmap getBitmap(InputStream InpStream){
Bitmap originalBitmap = BitmapFactory.decodeStream(InpStream);//Null.
return originalBitmap;
}
Now to my question is there another way of getting the size and width of an image from an inputstream? I really need help on this any help is greatly appreciated.
ZipInputStream zip = null;
zip = new ZipInputStream(new FileInputStream(getFileLocation()));
for(ZipEntry zip_e = zip.getNextEntry(); zip_e != null ; zip_e = zip.getNextEntry()){
if(zip_e.isDirectory()) {
continue;
}
String file_zip = zip_e.getName();
String comparison = map.get(pageCounter).getHref();
if(file_zip.endsWith(comparison)){
SpannableString Spanable_String = new SpannableString("abc");
if(testSize(zip)){
map.remove(pageCounter);
return false;
}
Bitmap bitmap = getBitmap(zip);
if(bitmap == null){
map.remove(pageCounter);
return false;
}
image_page.put(zip_e.getName(), zip);
Drawable drawable_image = new FastBitmapDrawable(bitmap);
drawable_image.setBounds(0,0,drawable_image.getIntrinsicWidth(), drawable_image.getIntrinsicHeight());
ImageSpan imageSpan = new ImageSpan(drawable_image, ImageSpan.ALIGN_BASELINE);
Spanable_String.setSpan(imageSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(Spanable_String);
return false;
}
}
The main problem is not with the BitmapFactory.Options, but with the InputStream. The stream is sequential, therefore when you read the stream to decode the image for size only, the pointer in the stream is moving. Next, when you're calling decode again to actually decode the bitmap, the stream pointer is no longer at the beginning of the bitmap, and you get null because the partial stream cannot be decode.
You would need to reset the stream. Depending on what that stream is, you might be able to use .mark and .reset on it. Essentially, you'd do something like this:
private Boolean testSize(InputStream inputStream) {
BitmapFactory.Options Bitmp_Options = new BitmapFactory.Options();
Bitmp_Options.inJustDecodeBounds = true;
inputStream.mark(inputSteram.available());
BitmapFactory.decodeResourceStream(getResources(), new TypedValue(), inputStream, new Rect(), Bitmp_Options);
inputSteram.reset();
int currentImageHeight = Bitmp_Options.outHeight;
int currentImageWidth = Bitmp_Options.outWidth;
Bitmp_Options.inJustDecodeBounds = false;
if(currentImageHeight < 200 || currentImageWidth < 200){
Object obj = map.remove(pageCounter);
Log.i("Page recycled", obj.toString());
return true;
}
return false;
}
You can check whether your InputStream supports this or not by calling markSupported on it. If that returns true then you can use the above technique. If it returns false, then the above method will not work and you will actually need to close and reopen the stream again before decoding the full bitmap.

Categories

Resources