I have a high resolution image Uri and I would like to reduce the image quality and size since i'm uploading it to a server and then load it afterwards as a smaller version of the original image.
I tried this:
Picasso.with(context).load(image).resize(100,100).get();
But it didn't work cause you need to do this from a separate Thread and I didn't find a listener that tells you when the resizing is over.
Does anyone has a solution that can reduce the image quality to get a smaller file size?
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 50;
Bitmap bmpSample = BitmapFactory.decodeFile(fileUri.getPath(), options);
ByteArrayOutputStream out = new ByteArrayOutputStream();
bmpSample.compress(Bitmap.CompressFormat.JPEG, 1, out);
byte[] byteArray = out.toByteArray();
return convertToUri(byteArray);
Thank you Android: Compress Bitmap from Uri
Step #1: Call openInputStream() on a ContentResolver, passing in your Uri.
Step #2: Create a BitmapFactory.Options object, with an inSampleSize to control the degree of downsampling that you want to do, to create a lower-resolution Bitmap.
Step #3: Pass the stream and the BitmapFactory.Options object to decodeStream() on BitmapFactory, to get a downsampled Bitmap.
Step #4: Upload that Bitmap, where the details will vary a bit by whatever API you are using to do the upload (byte[], File, etc.).
Step #5: Do steps #1-4 on a background thread, as you should not be doing disk I/O, bitmap downsampling, and network operations on the main application thread.
Related
Firstly, I understand questions regarding java.lang.OutOfMemoryError and Bitmaps have already been asked numerous times before. I have also checked out the Displaying Bitmaps Efficiently page.
My use case:
I am storing two different sized Bitmaps as Strings in an SQLite database. The first size of the Bitmaps is 50% of the screen width, and the second size is 100% of the screen width.
I am using a RecyclerView which displays the images in ImageViews which are either 50% or 100% of the screen width, so the Bitmaps being loaded are no bigger than they need to be, and they are appropriately sized before the images are retrieved from the Database.
I am also loading the Bitmaps using an AsyncTask.
I have over 180 different items in the RecyclerView so I have a total of over 360 Bitmaps (i.e. numberOfimages * theDifferentSizesOfEachImage) being created. I am coverting the String versions of the images into byte arrays via this code: byte [] byteArray = Base64.decode(encodedString, Base64.DEFAULT);
The Problem
The Activity was able to load over around 170 different images without encurring the java.lang.OutOfMemoryError, unless I restarted the same Activity (e.g. load the Activity, then recreate the Activity by clicking on it again in the Navigation Drawer and then repeating that process) and incurred the java.lang.OutOfMemoryError whilst converting the Strings into byte arrays.
I am converting the byte array to a Bitmap using the Glide library using the following code:
Bitmap bitmap = Glide.with(context).load(byteArray).asBitmap()
.dontTransform().dontAnimate().skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE).into(-1, -1).get();
My Question in a nutshell
How do I avoid the java.lang.OutOfMemoryError occurring whilst am I converting the Strings into byte arrays?
Note
After creating a Bitmap using Glide I am calling recycle() on the given Bitmap and then setting it to null.
I am also already using android:largeHeap="true" in my Android Manifest
Thanks in advance
Edit
Here is how I am creating the Bitmap Strings:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.WEBP,100, baos);
byte [] b =baos.toByteArray();
String bitmapString = Base64.encodeToString(b, Base64.DEFAULT);
What i would suggest you to drop your approach, this would just not work with large set of image and you are already putting to much work on your thread to handle. One should never store image like that in the sqllite.
You should just convert your bitmap to a file having unique name or could be same (depends upon your use case) then you can just save this file inside the app directory and save the file path in database. Here is some code to help you.
File pictureFile = getOutputMediaFile(getActivity(), MEDIA_TYPE_IMAGE);
if (pictureFile == null) {
return;
}
Bitmap bitmap =BitmapFactory.decodeByteArray(data,0,data.length);
FileOutputStream fos = new FileOutputStream(pictureFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
private File getOutputMediaFile(Context context, int m) {
File mediaStorageDir = context.getFilesDir();
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d("Fade", "failed to create directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
File mediaFile=new File(mediaStorageDir.getPath()+File.separator
+ "IMG_" + timeStamp + ".JPG");
return mediaFile;
}
Now you have the file and now you can just store the file path in the database and when its needed you can always get your file from the storage using glide. This would also make your database fast to queries.
This way you wont need any changes in gradle or anywhere else. Try this.
I am reading a gif image from internet url.
// URL of a sample animated gif, needs to be wrapped in try-catch block
URL imageUrl = new Url("http://4.bp.blogspot.com/-CTUfMbxRZWg/URi_3Sp-vKI/AAAAAAAAAa4/a2n_9dUd2Hg/s1600/Kei_Run.gif");
// reads the image from url and stores in BufferedImage object.
BufferedImage bImage = ImageIO.read(imageUrl);
// creates a new `java.io.File` object with image name
File imageFile = new File("download.gif");
// ImageIO writes BufferedImage into File Object
ImageIO.write(bImage, "gif", imageFile);
The code executes successfully. But, the saved image is not animated as the source image is.
I have looked at many of the stack-overflow questions/answers, but i am not able to get through this. Most of them do it by BufferedImage frame by frame which alters frame-rate. I don't want changes to the source image. I want to download it as it is with same size, same resolution and same frame-rate.
Please keep in mind that i want to avoid using streams and unofficial-libraries as much as i can(if it can't be done without them, i will use them).
If there is an alternative to ImageIO or the way i read image from url and it gets the thing done, please point me in that direction.
There is no need to decode the image and then re-encode it.
Just read the bytes of the image, and write the bytes, as is, to the file:
try (InputStream in = imageUrl.openStream()) {
Files.copy(in, new File("download.gif").toPath());
}
I have an int array of color r,g and b values. And I would like to encode them in a image file. Is there an easy method in android to write this data to an image? Also which image format should I use for this, png?
Create a bitmap using your int array like this using Bitmap.createBitmap:
int[] array; // array of int RGB values e.g. 0x00ff0000 = red
Bitmap bitmap = Bitmap.createBitmap(array, width, height, Bitmap.Config.ARGB_8888);
Then write it out using Bitmap.compress:
outStream = new FileOutputStream(filepath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
You can call Environment.getExternalStorageDirectory() to get a folder on external storage where you can save the file, if that's where you want to save it. You can get the path with get File.getAbsolutePath(), e.g:
String filepath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/image.png";
You need the WRITE_EXTERNAL_STORAGE permission defined in your AndroidManifest.xml to be able to write to files on external storage.
There are pure java implementations for reading and writting images:
Image processing library for Android and Java
Probably some will work in android out of the box
I believe ImageIO is available in Android. The ImageIO API provides methods to read the source image and to write the image in the new file format.
To read the image, simply provide the ImageIO.read() method a File object for the source image. This will return a BufferedImage.
//Create file for the source
File input = new File("c:/temp/image.bmp");
//Read the file to a BufferedImage
BufferedImage image = ImageIO.read(input);
Once you have the BufferedImage, you can write the image as a PNG. You will need to create a File object for the destination image. When calling the write() method, specify the type string as "png".
//Create a file for the output
File output = new File("c:/temp/image.png");
//Write the image to the destination as a PNG
ImageIO.write(image, "png", output);
Is there a Java library that can read regions of very large image (e.g. JPEG) files (> 10,000 x 10,000 pixels) without keeping the whole image in memory.
Or alternatively, which Java library is capable of handling very large image files with a minimum of overhead.
Standard ImageIO allows you to read regions of (large) images without reading the entire image into memory first.
Rectangle sourceRegion = new Rectangle(x, y, w, h); // The region you want to extract
ImageInputStream stream = ImageIO.createImageInputStream(input); // File or input stream
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (readers.hasNext()) {
ImageReader reader = readers.next();
reader.setInput(stream);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(sourceRegion); // Set region
BufferedImage image = reader.read(0, param); // Will read only the region specified
}
You can use, for example, RandomAccessFile to read from the middle of the file:
but the issue is that whole jpeg image is compressed after DCT quantization (http://www.fileformat.info/mirror/egff/ch09_06.htm), so I don't think that it is possible to read a fragment without reading whole file to memory.
You can use a BufferedImage to do what you need.
// Set these variables according to your requirements
int regionX, regionY, regionWidth, regionHeight;
BufferedImage image = ImageIO.read(new File("/path/to/image.jpg"));
BufferedImage region = image.getSubimage(regionX, regionY, regionWidth, regionHeight);
And then you can process the region subimage however you want.
I'm new to all the memory management subject, so there are a lot of things I don't understand.
I'm trying to cache an image in my app, but I'm having troubles with its memory consumption:
All of the Bitmap Chaching code is pretty much copy-pasted from here: http://developer.android.com/training/displaying-bitmaps/index.html
I debugged the code and checked the heap size in the DDMS view in eclipse, and there is about 15mb jump after these code lines:
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
in the "decodeSampledBitmapFromResource" method.
The image is 1024x800, 75kb jpg file. According to what I've already seen on the internet, the amount of memory this image is supposed to take is about 1024*800*4(Bytes per pixel)=3.125mb
All of the threads regarding this subject don't say why it's taking much more memory than it should. Is there a way to cache one image with a reasonable amount of memory?
EDIT
I tried using the decodeFile method suggested on #ArshadParwez's answer below. Using this method, after the BitmapFactory.decodeStream method the memory is increased by only 3.5mb - problem solved, sort of, but I want to cache bitmaps directly from the resource.
I noticed that during the decodeResource method there are 2 memory "jumps" - one of about 3.5mb - which is reasonable, and another strange one of 14mb. What are those 14mb used for and why does this happen?
Images are also scaled according to the density so they can use a lot of memory.
For example, if the image file is in the drawable folder (which is mdpi density) and you run it on an xhdpi device, both the width and the height would double. Maybe this link could help you, or this one.
So in your example the bytes the image file would take are :
(1024*2)*(800*2)*4 = 13,107,200 bytes.
It would be even worse if you ran it on an xxhdpi device (like the HTC one and Galaxy S4) .
What can you do? Either put the image file in the correct density folder (drawable-xhdpi or drawable-xxhdpi) or put it in drawable-nodpi (or in the assets folder) and downscale the image according to your needs.
BTW you don't have to set options.inJustDecodeBounds = false since it's the default behavior. In fact you can set null for the bitmap options.
About down scaling you can use either google's way or my way each has its own advantages and disadvantages.
About caching there are many ways to do it. The most common one is LRU cache. There is also an alternative I've created recently (link here or here) that allows you to cache a lot more images and avoid having OOM but it gives you a lot of responsibility.
You can use this method to pass the image and get a bitmap out of it :
public Bitmap decodeFile(File f) {
Bitmap b = null;
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int IMAGE_MAX_SIZE = 1000;
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int) Math.pow(
2,
(int) Math.round(Math.log(IMAGE_MAX_SIZE
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
return b;
}
#Ori Wasserman: As per your request I used a method to get images from the resource folder and that too I used a 7 MB image. I put the 7 MB image in the "res->drawable" folder and with the following code it didn't crash and the image was shown in the imageview:
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.image_7mb);
loBitmap = Bitmap.createScaledBitmap(image, width_of_screen , height_of_screen, true);
imageview.setImageBitmap(loBitmap);