Android: Resize an JPEG from SD Card without losing quality - android

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.

Related

How can I make a copy of an image on disk as it appears in the ImageView?

I'm loading an image in to a number of different ImageViews, each with different ScaleTypes. Additionally, some images can be rotated using a Glide transformation before being displayed.
I'd like to be able to make a copy of the original image and crop, rotate, resize it in the same way as it is displayed in the ImageViews.
At the moment I'm grabbing a Bitmap from the ImageView itself but the quality is obviously much lower than the original source image.
Is there a way that I could use the properties of this bitmap and apply them to the original image?
For saving file you can do this
FileOutputStream out = null;
try {
out = new FileOutputStream(filename);
bmp.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap instance
// PNG is a lossless format, the compression factor (100) is ignored
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
the original answer is here
For your rotating, cropping, resizing image, you can use canvas and matrix Android APIs.
Here is an answer
And here is Canvas API
If you are doing that too often in your app, then maybe you are using the wrong library. Please refer this post for more info. https://medium.com/#multidots/glide-vs-picasso-930eed42b81d

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 compressing with different quality doesn't work

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.

Android Bitmaps always saved with 72dpi

I'm making bitmap (that will be printed on paper, later) and using canvas to draw on it.
But after saving it always have 72 dpi resolution. I tried to use bitmap.setDensity(96);but it does not seems to work.
This is how I make bitmap and save it, nothing fancy
Bitmap outBitmap = Bitmap.createBitmap(378,559,Bitmap.Config.RGB_565);
OutputStream outStream = null;
File file = new File(Environment.getExternalStorageDirectory(),
"96dpiBitmap.png");
try {
outStream = new FileOutputStream(file);
outBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
outStream.flush();
outStream.close();
} catch (FileNotFoundException e) {
// doh
} catch (IOException e) {
// doh
}
So. How do I save bitmap with dpi > 72?
you have to create scaled bitmap from the original one for ex :
MyNewBitmap = Bitmap.createScaledBitmap(myOldOne,612,612,false);
where 612 width and 612 height the result will be image squared. i'm using this method to prevent instagram from scaling or cutting my image so its perfectly fit into instagram image cropping :).
anyway you have to find the proper way to scale your image to fit 72dpi. i guess 800x600 will do the trick. try to create new bitmap and scale the old one and then save the newBitmap.
good luck

Android: BitmapFactory.decodeByteArray gives pixelated bitmap

I am working on an Android app that displays photos which are downloaded from Flickr. I obtain a bitmap object from a byte array, which in turn is read from the relevant Flickr URL, as follows:
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inDither = true;
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opt);
I then draw the bitmap onto a canvas in the onDraw method of a View object:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bitmap, 0, 0, paint);
The problem is that the resulting picture is pixelated and I can't figure out why; I have tried a number of variations of the opt and paint objects with no luck. The difference between the picture displayed in my app and the picture at the original URL is roughly demonstrated by the following:
Bad image, see pixelation in top left corner http://homepages.inf.ed.ac.uk/s0677975/bad.jpg
Good picture, this is the expected result http://homepages.inf.ed.ac.uk/s0677975/good.jpg
Look e.g. at the clouds in the top-left corner to see the difference.
Note that JPEG pictures which are loaded from the project resources and drawn in a similar way display just fine, i.e. have no pixelation.
Can anybody give me a hint as to why this is happening?
To elaborate a little, the byte array is obtained from Flickr as follows; this is based on code from the Photostream app by Romain Guy:
InputStream in = new BufferedInputStream(url.openStream(), IO_BUFFER_SIZE);
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE);
copy(in, out);
out.flush();
final byte[] data = dataStream.toByteArray();
PS: I also posted a variant of this question on the android.developer Google group.
Thanks a lot for your suggestion -- now I am really puzzled! I did as you suggested and found that the image resulting directly from the downloaded byte array is indeed pixelated. However, this is downloaded from exactly the same URL which, when accessed on my computer, is NOT pixelated. Here is the corresponding Flickr URL:
http://farm3.static.flickr.com/2678/4315351421_54e8cdb8e5.jpg
Even stranger, when I run the same app in the simulator rather than on my phone (a HTC Hero), there is no pixelation.
How on earth is this possible?
Below is the code I use for loading a bitmap from a URL -- it is based on the Photostream app by Romain Guy, and it incorporates Will's suggestion to write the raw byte array to file:
Bitmap loadPhotoBitmap(URL url) {
Bitmap bitmap = null;
InputStream in = null;
BufferedOutputStream out = null;
try {
FileOutputStream fos = new FileOutputStream("/sdcard/photo-tmp.jpg");
BufferedOutputStream bfs = new BufferedOutputStream(fos);
in = new BufferedInputStream(url.openStream(),
IO_BUFFER_SIZE);
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE);
copy(in, out);
out.flush();
final byte[] data = dataStream.toByteArray();
bfs.write(data, 0, data.length);
bfs.flush();
BitmapFactory.Options opt = new BitmapFactory.Options();
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opt);
} catch (IOException e) {
android.util.Log.e(LOG_TAG, "Could not load photo: " + this, e);
} finally {
closeStream(in);
closeStream(out)
closeStream(bfs);
}
return bitmap;
}
private static void copy(InputStream in, OutputStream out) throws IOException {
byte[] b = new byte[IO_BUFFER_SIZE];
int read;
while ((read = in.read(b)) != -1) {
out.write(b, 0, read);
}
}
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
android.util.Log.e(LOG_TAG, "Could not close stream", e);
}
}
}
Am I going crazy here?
Best,
Michael.
Ok, so I finally get it: it appears that my mobile network does image compression to save bandwidth.
Hence a picture downloaded from my phone is of lower quality than the same picture downloaded from my computer.
That's a bummer for my app, but I don't suppose there is anything I can do about it. Sigh.
Thanks again for your input though!
Best,
Michael.
Write the raw bytes fetched from the URL to /sdcard/tmp.jpg, and view on your PC.
JPEG images are compressed in 8x8 (or 16x16) tiles. The 'pixelation' as you describe it is actually in these tiles, suggesting that the 'bad' image is a JPEG that is more aggressively compressed than the other.
So I'd anticipate that the actual issue is that the image being downloaded is a very low-quality version, e.g. one intended for thumbnailing/preview use-cases.
Some version of Android have a bug in Bitmap class and convert the Bitmap to RGB_565 upon some operations. This would manifest itself in artifacts similar to those on your picture. This would also explain the banding of the blue sky.
Also, have in mind that android attempts to "optimize" image by converting them to rgb_565 upon loading and even compiling in resource files. Take a look at:
http://android.nakatome.net/2010/04/bitmap-basics.html

Categories

Resources