Bitmap compress grainy image after saving - android

I'm performing a transformation on an image via Glide transformation, setting it as a background to a view with a black background, then saving it the device as a PNG. The view looks like this:
After compressing it and saving it as a PNG, it looks like this:
Ignoring the size and background difference, notice the graininess around the edges. This is persisted after the save. The PNG is the correct size as the original image so no scaling was performed. How do I prevent this from happening? The code to compress is:
File file = new File(context.getFilesDir().getPath() + "/" + String.valueOf(num) + ".png");
OutputStream os = null;
try {
os = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
Properties set to the bitmap were:
Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
b.setHasAlpha(true);
Things I've tried:
Using WEBP; same issue.
Saving the view and building a drawing cache,
then saving that bitmap. Again, same issue.
Can't use JPEG since I need the alpha layer.
It seems like the compression to PNG can't handle bright colors.
Update:
Using RGB_565 instead of ARGB_8888 removes the graininess but adds black to the outside, since there's no alpha layer. Seems like the compression can't handle it if there are any alpha pixels.

Try using BitmapFactory.decodeFile
String filePath = context.getFilesDir().getPath() + "/" + String.valueOf(num) + ".png";
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDither = false;
BitmapFactory.decodeFile(filePath, options);
Alternatively, you can use createScaledBitmap method, it has a flag where you can set if the scaled image should be filtered or not. That flag improves the quality of the bitmap.
boolean shouldFilter = false;
bitmap.createScaledBitmap(bitmap, desiredWidth, desiredHeight, shouldFilter);

Related

How to write rotated bitmap into specific file without compress android

I want to rotate and save rotated bitmap to specific file path without compress it. Before i have used the below code for rotate, compress and store it to a specific file. Now i dnt want to compress my bitmap. Please suggest me an idea to rotate and save the bitmap into specified path.
public static void compressToStandard(String file) {
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, bmOptions);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = getInSampleSize(bmOptions);
try {
Bitmap bitmap = BitmapFactory.decodeFile(file, bmOptions);
bitmap = ExifUtils.rotateBitmap(file, bitmap);
Log.i("ImageUtils", "compressed bitmap size:" + bitmap.getWidth() + "x" + bitmap.getHeight());
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
when i call this method. I will pass my image path to this method.
PNG is lossless so you can use Bitmap.CompressFormat.PNG
Compresion
Hint to the compressor, 0-100. 0 meaning compress for
small size, 100 meaning compress for max quality. Some
formats, like PNG which is lossless, will ignore the
quality setting

Android: Resize an JPEG from SD Card without losing quality

Usually I find everything I need using the search function of StackO. But now (success less) I’m really trying hard to resize a JPEG from SD Card without getting bad quality. As follows, you can see that the original Image is clean and perfectly readable. After resizing I will always get a blurred result.
Original: https://www.dropbox.com/s/l5h4cdkz29vkapw/signature.jpg?dl=0
Scaled: https://www.dropbox.com/s/tobijbu5hisf9rz/signature_small.jpg?dl=0
At the following code passage you can see everything I tried without success (I hope I am wrong, but I think this passage includes all the really useable answers from StackO.) :
public void saveSignature(View view) throws IOException {
image = signature.getImage();
File sd = Environment.getExternalStorageDirectory();
File final_location = new File(sd, "signature.jpg");
try {
if (sd.canWrite()) {
final_location.createNewFile();
OutputStream os = new FileOutputStream(final_location);
image.compress(Bitmap.CompressFormat.JPEG, 100, os);
os.close();
//Bitmap resized = Bitmap.createScaledBitmap(image,(int)(image.getWidth()*0.4),(int)(image.getHeight()*0.4), false);
//Bitmap resized = Bitmap.createScaledBitmap(image, 300,75, false);
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
// "works but Color ist gray from sampling" bmOptions.inSampleSize = 5;
final_location = new File(sd, "signature_small.jpg");
final_location.createNewFile();
os = new FileOutputStream(final_location);
Bitmap b= BitmapFactory.decodeFile(sd +"/signature.jpg",bmOptions);
Bitmap out = Bitmap.createScaledBitmap(b, 349, 86, true); //also tyed false (without any Change)
out.compress(Bitmap.CompressFormat.JPEG, 100, os);
os.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
It would be nice to get some answers or links to samples for Android (4.0 and higher).
Thanks for your help in advance,
Tough!!
How do you expect to scale down an image without losing quality?
Anyway, to scale down correctly you need to blur the image first, preferably with a Gaussian kernel, but a box blur will probably be fine. The kernel size should match the scale you wish to scale to. After that you can scale down the image with any interpolation technique. Lanzcos, bilinear or even nearest neighbor will be good enough. An alternative approach is to use some kind of area sampling, where each pixel in the target image is a mean of an area of pixels in the source image. Another alternative is to use some kind of supersampling.
If you don't do any of these, you'll end up with crappy images. But even if you do it correctly, you will lose quality. Because the scaled down image will have less information than the original.

Dynamically creating a compressed Bitmap

I'm writing a custom printing app in Android and I'm looking for ways to save on memory. I have three basic rectangles I need to print on a full page. Currently I'm creating a base Bitmap the size of the page:
_baseBitmap = Bitmap.createBitmap(width/_scale, height/_scale, Bitmap.Config.ARGB_8888);
The print process requests a Rect portion of that page. I cannot predetermine the dimensions of this Rect.
newBitmap = Bitmap.createBitmap(fullPageBitmap, rect.left/_scale, rect.top/_scale, rect.width()/_scale, rect.height()/_scale);
return Bitmap.createScaledBitmap(newBitmap, rect.width(), rect.height(), true);
Using bitmap config ARGB_8888 _baseBitmap is about 28MB (8.5"x11" # 300dpi = 2250*3300*4bytes). Even at 50% scaling (used above), my image is over 7MB. Scaling any smaller than this and image quality is too poor.
I've attempted to create _baseBitmap using Bitmap.Config.RGB_565, which does greatly reduce the full image size, but then when I overlay an image (jpegs) I get funny results. The image is compressed in width, duplicated next to itself, and all the color is green.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap myBitmap = BitmapFactory.decodeStream(input, null, options);
input.close();
return myBitmap;
....
private static Bitmap overlay(Bitmap bmp1, Bitmap bmp2, float left, float top) {
Canvas canvas = new Canvas(bmp1);
canvas.drawBitmap(bmp2, left, top, null);
return bmp1;
}
I know I can compress an image of these dimensions down to a reasonable size. I've looked into Bitmap.compress, but for some reason beyond my understanding I'm getting the same size image back:
ByteArrayOutputStream os = new ByteArrayOutputStream();
_baseBitmap.compress(Bitmap.CompressFormat.JPEG, 3, os);
byte[] array = os.toByteArray();
Bitmap newBitmap = BitmapFactory.decodeByteArray(array, 0, array.length);
_baseBitmap.getAllocationByteCount() == newBitmap.getAllocationByteCount()
It would be better to create it compressed than to create a large one and then compress it. Is there any way to create a compressed Bitmap? Any advice is greatly appreciated.
note: Not an Android expert. I'm not necessarily familiar with the platform specific terms you may use to respond. Please be gentle.
Try something like this, if you have a target size in mind.
private static final int MAX_BYTES_IMAGE = 4194304; // 4MB
//...
ByteArrayOutputStream out;
int quality = 90;
do
{
out = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out);
quality -= 10;
} while (out.size() > MAX_BYTES_IMAGE_FILESIZE);
out.close();

How to avoid "out of memory exception" when doing Bitmap processing?

In onPictureTaken, I want to do the following:
Bitmap decodedPicture = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.preScale(-1.0f, 1.0f);
Bitmap picture = Bitmap.createBitmap(decodedPicture, 0, 0, decodedPicture.getWidth(), decodedPicture.getHeight(), matrix, false);
View v1 = mainLayout.getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap screenshot = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Bitmap scaledPicture = Bitmap.createScaledBitmap(picture, screenshot.getWidth(), screenshot.getHeight(), true);
Bitmap compos = Bitmap.createBitmap(scaledPicture.getWidth(), scaledPicture.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(compos);
canvas.drawBitmap(scaledPicture, new Matrix(), null);
canvas.drawBitmap(screenshot, new Matrix(), null);
MediaStore.Images.Media.insertImage(getContentResolver(), compos, "name" , "description");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
My only requirement is that I'd like to save a high-quality photo... Seems I might have to sacrifice that.
On my Nexus 4 and newer devices, this code runs fine and as expected. But on older devices that have less memory, I'm running out of RAM! :(
How do I do the same image manipulation without running up against the memory limit?? I'm not trying to display these images on screen, so the solutions that have to do with a scaled down image don't really apply here...
you need to read the bitmap in with an increased sample size. the trick is finding the correct sample size that won't result in reduced resolution when you ultimately scale the image. i wrote a blog entry about it here that includes a nice utility class for scaling,
http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/
you could probably simplify that class quite a bit depending on your specific needs.
the jist is to read just the size of the bitmap. calculate the optimal sample size based on your desired scaled size, read the bitmap in using that sample size, then fine-scale it to exactly the size you want.
You have so many Bitmap object lying around. try recycling/reusing some of this.
Not exactly sure what is your requirement is but i can see you can save some memory by simply doing this.
Bitmap decodedPicture = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.preScale(-1.0f, 1.0f);
Bitmap picture = Bitmap.createBitmap(decodedPicture, 0, 0, decodedPicture.getWidth(), decodedPicture.getHeight(), matrix, false);
decodedPicture.recycle();
decodedPicture=null;
View v1 = mainLayout.getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap screenshot = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Bitmap scaledPicture = Bitmap.createScaledBitmap(picture, screenshot.getWidth(), screenshot.getHeight(), true);
picture.recycle();
picture=null;
Bitmap compos = Bitmap.createBitmap(scaledPicture.getWidth(), scaledPicture.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(compos);
canvas.drawBitmap(scaledPicture, new Matrix(), null);
canvas.drawBitmap(screenshot, new Matrix(), null);
MediaStore.Images.Media.insertImage(getContentResolver(), compos, "name" , "description");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
Also look into your memory footprint, make sure device wise memory you are using are is not too big.
FYI, on post honycomb devices bitmap pixel image allocated on native layer. You need recycle() or finalizer() to restore memory
Considering you don't want to resize your bitmap and don't want to display it, I'd do something like this:
Load the Bitmap with inJustDecodeBounds to see its original height and width (code from here)
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Depending on the size and memory you have, you can directly process it from there (i.e. load the Bitmap) or proceed to load a number of chunks of said Bitmap with the Bitmap.createBitmap method that allows you to only load a chunk of data. Optionally: consider converting it into a byte array (see code below) and null+ recycle() before you process the chunk.
code
ByteArrayOutputStream byteArrayBitmapStream = new ByteArrayOutputStream();
bitmapPicture.compress(Bitmap.CompressFormat.PNG, COMPRESSION_QUALITY, byteArrayBitmapStream);
byte[] b = byteArrayBitmapStream.toByteArray();
I use for work with Bitmap class WeakReference and after I always call recycle on the instance object WeakReference, the snippet code for rotate image:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPurgeable = true;
options.inInputShareable = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
WeakReference<Bitmap> imageBitmapReference = new WeakReference<Bitmap>(BitmapFactory.decodeByteArray(params[0], 0, params[0].length, options));
Matrix mat = new Matrix();
mat.postRotate(90.0f);
imageBitmapReference = new WeakReference<Bitmap (Bitmap.createBitmap(imageBitmapReference.get(), 0, 0, resolution[0], resolution[1], mat, true));
FileOutputStream fos = new FileOutputStream(filename);
imageBitmapReference.get().compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
imageBitmapReference.get().recycle();
And second solution how work with Bitmap and don't get OutOfMemory Exception is use library Universal Image Loader
(Of course is so the third solution set in your AndroidManifest property android:largeHeap="true" and really DON'T USE THIS property).
The perfect material is on the http://developer.android.com/training/displaying-bitmaps/index.html and video https://www.youtube.com/watch?v=_CruQY55HOk

Error:Bitmap size Exceed VM budget, when converting image to ARGB_8888

Here is my code:
File file = new File(Path to Jpeg File size is 700kb);
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
}
catch (Exception e) {
// TODO: handle exception
}
bitmap =BitmapFactory.decodeStream(in);
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
Please help i get error in this copy line i want to make its ARGB_8888 image.Need Help :(
You need to reduce the memory usage.
From you code, you first decode stream to one bitmap, and then copy it, which means you create two large bitmap objects.
You don't need to decode and then copy it, you can try
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888
// You can try value larger than 1
options.inSampleSize = 2 // If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
// Decode bitmap
bitmap = BitmapFactory.decodeStream(in, null, options)
In this case, there's only one bitmap created. And you set inSampleSize to large values to reduce the loaded bitmap size.

Categories

Resources