I am trying to find a solution to be able to download an image from Url into the Filesystem and resize it without the need to decode it to a bitmap first.
The only solution I could find yet was the approach to decode it to a bitmap with specific SampleSize. However, the problem here is that a lot of memory is consumed when creating the bitmap which would limit the maximum size of the image depending on the free memory available on the device. Another Problem is that it takes a lot of time to decode it to a Bitmap and also to compress it back to an output stream.
My current code looks like this (options1 contains the original dimensions, calculateSampleSizeForImageSize() calculates the sampleSize for smaller required Imagesize):
if (maximumImageSize < Runtime.getRuntime().maxMemory() / 4f) {
//enough memory free to reduce size
//reduce size
BitmapFactory.Options options2 = new BitmapFactory.Options();
options2.inSampleSize = calculateSampleSizeForImageSize(options1, maximumImageSize);
options2.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeStream(is, null, options2);
try {
//write reduced size bitmap to picture file
os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
os.flush();
bitmap.recycle();
return 0;
} catch (IOException e) {
Log.d(TAG, "failed to fetch image", e);
throw new IOException(e);
} finally {
closeStream(os);
closeStream(is);
}
So is there a way to bypass the bitmap and directly write the input stream in reduced size to the output stream?
Related
This question already has answers here:
Decrease image size without losing its quality in android
(6 answers)
Closed 4 years ago.
When the user selects an image from the gallery, I convert it into a base64 string. Then I use an API. The image size must be less than 2MB, so if the image from the gallery is bigger than 2MB, I need to reduce its size.
How should I do that?
That's my code:
// I pass the URI of the image to the method
#Override
protected Integer doInBackground(Uri... uris) {
InputStream inputStream = null;
try {
inputStream = getContentResolver().openInputStream(uris[0]);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
byte[] buffer = new byte[8192];
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
bytes = output.toByteArray();
String encodedImage = Base64.encodeToString(bytes, Base64.NO_WRAP);
You can create a Bitmap object using
BitmapFactory.decodeByteArray
Decode an immutable bitmap from the specified byte array.
and then you can use
bitmap.compress
Write a compressed version of the bitmap to the specified
outputstream. If this returns true, the bitmap can be reconstructed by
passing a corresponding inputstream to BitmapFactory.decodeStream().
Note: not all Formats support all bitmap configs directly, so it is
possible that the returned bitmap from BitmapFactory could be in a
different bitdepth, and/or may have lost per-pixel alpha (e.g. JPEG
only supports opaque pixels).
This method may take several seconds to complete, so it should only be
called from a worker thread.
and/or
Bitmap.createScaledBitmap
Creates a new bitmap, scaled from an existing bitmap, when possible.
If the specified width and height are the same as the current width
and height of the source bitmap, the source bitmap is returned and no
new bitmap is created.
In my project I created two utility functions that might help you understand how to use the functions I mentioned above to fit your needs
public static Bitmap base64ToBitmap(String encodedImage) {
byte[] decodedString = Base64.decode(encodedImage, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
}
public static String bitmapToBase64(Bitmap bitmap, int quality, int percentScale) {
if (quality < 0 || quality > 100) {
quality = IMAGE_QUALITY_DEFAULT_VALUE;
}
if (percentScale < 0 || percentScale > 100) {
percentScale = IMAGE_SCALE_DEFAULT_VALUE;
}
float scale = (float) percentScale / 100;
int width = Math.round(bitmap.getWidth() * scale);
int height = Math.round(bitmap.getHeight() * scale);
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
byte[] byteArray = byteArrayOutputStream.toByteArray();
return Base64.encodeToString(byteArray, Base64.NO_WRAP);
}
One way to easily check the image and store it is according to the developer docs
https://developer.android.com/topic/performance/graphics/load-bitmap#java
Which gives a solution to your answer explained properly and another way is to change the size of the image which can be found here and then use it accordingly
https://developer.android.com/reference/android/graphics/drawable/ScaleDrawable
I am coding an app where I take a picture and I pass the picture into an image recognition model. The problem is, the recognition only takes 150528 bytes, and the number of bytes in the image depends on the image quality. Is there a way in Java to crop the top and bottom of the image as evenly as possible depending on the quality to make it a set number of bytes each time?
This is my code for this part so far.
int width = 100;
int height = 200;
final ImageReader reader = ImageReader.newInstance(width,height,ImageFormat.JPEG,1);
List<Surface> outputSurface = new ArrayList<>(2);
outputSurface.add(reader.getSurface());
outputSurface.add(new Surface(textureView.getSurfaceTexture()));
As you can see, I'm restricting the size of the image captured, but the number of bytes depends on the quality so I need to actually just crop the image. How can I do this?
You can crop/ resize image with the following
byte[] thumb_byte_data;
Uri resultUri = ImageUri;
//getting imageUri and store in file. and compress to bitmap
File file_path = new File(resultUri.getPath());
try {
Bitmap thumb_bitmap = new Compressor(this)
.setMaxHeight(200)
.setMaxWidth(200)
.setQuality(75)
.compressToBitmap(file_path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
thumb_bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
thumb_byte_data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
I am not new to bitmaps nor new to java. I am trying to convert High resolution bitmaps to byte array in a loop. Please find code here:
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream);
imageByteArray = stream.toByteArray();
When i am using the above approach I cam able to convert 5 images in 1 second. But I need it to be even faster. I tried ByteBuffer approach also like this:
Bitmap bmp = intent.getExtras().get("data");
int size = bmp.getRowBytes() * bmp.getHeight();
ByteBuffer b = ByteBuffer.allocate(size);
bmp.copyPixelsToBuffer(b);
byte[] bytes = new byte[size];
try {
b.get(bytes, 0, bytes.length);
} catch (BufferUnderflowException e) {
// always happens
}
But this is very slow (Slower then previous) :(
Please, can somebody give a faster method? Guide Me...
The first solution is the right one.
But two things can happen here:
The image is maybe not of JPEG type, so conversion occurs, which takes time
The image is compressed 50%, which takes time
That aside, if it's taking some time, I doubt it could go faster (being the right solution).
You must consider the fact that the speed of processing is tightly tied to the speed of the device you are testing on( since this is tagged android I'm presuming you're using a mobile device ).
You should take a look at android developer on how to handle large bitmaps effectively Android developers . Since processing 5 high resolution images per second is slow to you I can presume you are having some kind of gallery or previews? If that's the case you shouldn't handle the high resolution images and should indeed take a look at the link above.
Also as a side-note your second code can be optimised this way:
int bytes = bmp.getByteCount();
ByteBuffer buffer = ByteBuffer.allocate(bytes);
bmp.copyPixelsToBuffer(buffer);
byte[] array = buffer.array();
Otherwise the most efficient way of copying bytes that I know is copy() taken from Commons-IO:
public static int copy(InputStream input, OutputStream output) throws IOException {
int n, count = 0;
byte[] buffer = new byte[4 * 1024];
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
you can try as follows
Bitmap bitmap = intent.getExtras().get("data");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap .compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
hope it may work good for you!!
check for line bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream); it may cause problem..as you are using JPEG format with rate 50.
I am using the following code for encoding my bitmap:
public static String convertBitmapToBase64String(Bitmap bitmap) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String encodedImage = null;
try {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] byteArrayImage = baos.toByteArray();
encodedImage = Base64
.encodeToString(byteArrayImage, Base64.DEFAULT);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return encodedImage;
}
But this method returns a very long string and therefore I can't upload my image to the server.
Please help.
Thanks in advance.
There is nothing base64 can do for the actual size. The encoding will inevitably increase the size by a fixed percentage (about 37% IIRC).
The only thing you can do is make the image data smaller. You are using an unreasonable quality setting here:
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
100% quality JPEG is overkill, 83-90% is more reasonable for most images. If you need lossless image quality, try PNG. JPEG isn't designed for 100% quality, the resulting file will very likely be larger than if you used a lossless image format in the first place.
if your image is really too big you can always use Bitmap.createScaledBitmap() to resize it smaller
Hello im testing code for avoid memory crashes when loading a lot of bitmaps. And I have a little question, about the following code
Bitmap bmp;
long sizepreload, sizepostload,bmppixels;
sizepreload= Debug.getNativeHeapAllocatedSize();
bmp=getImageBitmapFromUrl("http://www.google.com/intl/en_com/images/srpr/logo2w.png");
sizepostload=Debug.getNativeHeapAllocatedSize();
bmppixels=bmp.getHeight()*bmp.getWidth();
Log.d("bmp test","pre="+sizepreload+"\n" +
"post="+sizepostload+"\n" +
"bmppixels="+bmppixels+"\n"+
"ratio="+(sizepostload-sizepreload)/bmppixels
);
The return of that snippet is:
pre=4260464
post=4333112
bmppixels=26125
ratio=2
So a bitmap takes 2 bytes per pixel. 16 bits per pixel is constant for all bitmaps or it is variable (depending of screen density or image quality). If its variable how can I get that ratio from the Bitmap class?
thanks in advance
Thats the code for download from a url:
public static Bitmap getImageBitmapFromUrl(String url){
URL aURL;
Bitmap result = null;
try {
aURL = new URL(url);
URLConnection conn = aURL.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
result= BitmapFactory.decodeStream(is);
is.close();
} catch (Exception e) {
result=null;
e.printStackTrace();
}
return result;
}
Bitmaps can be up to 4 bytes per pixel, this depends on the bitmap's format. It is not variable for the screen density. However when drawing the bitmap, it may need to be resized for the screen density, and this would affect your heap temporarily.