Android compressing with different quality doesn't work - android

When I change quality from 100 to 0 (any value) bitmap size before compressing and after always has the same size. Why? And how to really compress it?
public static void checkSize(Bitmap tempBitmap, Context context) {
Log.d("IMAGES_", "SIZE before = " + tempBitmap.getByteCount());
Bitmap newBit = compress(tempBitmap, Bitmap.CompressFormat.PNG, 3);
Log.d("IMAGES_", "SIZE after = " + newBit.getByteCount());
}
public static Bitmap compress(Bitmap src, Bitmap.CompressFormat format,
int quality) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
src.compress(format, quality, os);
byte[] array = os.toByteArray();
return BitmapFactory.decodeByteArray(array, 0, array.length);
}
Result:
D/IMAGES_: SIZE before = 1228800
D/IMAGES_: SIZE after = 1228800

You are measuring wrong. The compressed size is given by array.length. You are then decoding it back into its full size and pixel encoding. If you want to have a smaller memory footprint, you would need to:
resize the image.
decode with a different pixel format.
all of the above.

The quality parameter doesn't work for PNG format which is loseless, you need to change it to JPG to make it take effect.
Even you changed it to JPG, the before and after bitmaps will still have the same dimensions, but different qualities. I think what you really need is to call Bitmap.createScaledBitmap() to get a smaller bitmap and then store it.

Related

Bitmap compress grainy image after saving

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);

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.

How to save Bitmap image in Native memory using ByteBuffer and recover it

I'm getting direct stream from camera and I need to save a Bitmap into a ByteBuffer and recover it. Here is my code:
YuvImage yuv = new YuvImage(data.getExtractImageData(), previewFormat, width, height, null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, width, height), 50, out);
byte[] bytes = out.toByteArray();
Bitmap image = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Bitmap imageResult = RotateImage(image, 4 - rotation);
imageResult = cropBitmap(imageResult, data.getRect());
int nBytes = imageResult.getByteCount();
ByteBuffer buffer = ByteBuffer.allocateDirect(nBytes);
imageResult.copyPixelsToBuffer(buffer);
return buffer.array();
Code to convert byte[] back to Bitmap:
Bitmap bitmap = BitmapFactory.decodeByteArray(images.getImage(), 0, images.getImage().length);
But then, bitmap is Null after conversion...
Any idea on what's wrong?
To clarify: I need to save the byte[] image in the Native memory, that's why I do a ByteBuffer.allocateDirect. I need to crop the image in a specific point, that's why I need the bitmap.
decodeByteArray() decodes a compressed image (e.g. a JPEG or PNG) stored in a byte array. However copyPixelsToBuffer() copies the contents of a Bitmap into a byte buffer "as is" (i.e. uncompressed), so it can't be decoded by decodeByteArray().
If you don't want to re-encode your bitmap, you can use copyPixelsToBuffer() like you are doing, and change your second code block to use copyPixelsFromBuffer() instead of decodeByteArray().
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(images.getImage()));
You'll need to save the width and height. Also make sure the Bitmap.Config is the same.
Basically, if you save it compressed then you have to load it compressed, and if you save it uncompressed then you have to load it uncompressed.
You should also set the byte order when you allocate the buffer because Java is big endian, the buffer is therefore big endian by default, android is little endian and the underlying cpu architecture can vary but is mostly little endian wrt android:
buffer.order( ByteOrder.nativeOrder() );

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 keep image quality same in BitmapFactory

I've converted an bitmap image into string to save it:
............
Bitmap photo = extras.getParcelable("data");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] b = baos.toByteArray();
String encodedImage = Base64.encodeToString(b, Base64.DEFAULT);
Then I retrieve the bitmap from string to set an activity's background just like that:
byte[] temp = Base64.decode(encodedImage, Base64.DEFAULT);
Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(temp, 0,
temp.length, options);
Drawable d = new BitmapDrawable(getResources(), bitmap);
getWindow().setBackgroundDrawable(d);
Everything works fine but the image quality reduces tremendously. How can I keep the image quality same as the original image? Did I do something wrong here that have reduced the quality?
JPEG is lossy, no matter what quality settings you use. If you want to keep the image unchanged, you have to use lossless compression. for example Bitmap.CompressFormat.PNG
You are having here a tradeoff situation between picture quality and memory usage. Take a look at this line:
photo.compress(Bitmap.CompressFormat.JPEG, 100, baos);
photo.compress is obviously decreasing your image resolution in a factor given by the second parameter, unfortunately, this is the best quality you can get, since between 0 - 100, 100 stands for the best quality you can get. Now, you have another option, depending on the original picture's size you can just save the image without compressing it, but be aware that most cases this doesn't work and Jalvik can throw an OutofMemoryException,
hope this helps.

Categories

Resources