what mean bitmap.compress() parameter 'quality'? - android

In my app, i send some image to server.
images max size is 300kb, i'll resize file.
this is my resize code.
public static void resize_filesize(Context context, Uri uri, int maxSize) {
int filesize;
int compressRate;
try {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
filesize = inputStream.available();
inputStream.close();
compressRate = ((100*maxSize) / filesize);
File storageDir = new File(Environment.getExternalStorageDirectory() + context.getString(R.string.photoPath));
File image = new File(storageDir,"resize.jpg");
FileOutputStream outputStream = new FileOutputStream(image);
Bitmap bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, compressRate, outputStream);
outputStream.close();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(context,
BuildConfig.APPLICATION_ID + ".provider", image);
} else {
uri = Uri.fromFile(image);
}
} catch (Exception e) {
e.printStackTrace();
}
}
i think this.
if original image size is 500kb, i need 300kb.
than i set quality:60, image will 60% compress and it become 300kb.
but i try it, so much compress work.
this is my log.
original : 831210(811kb)
maxsize : 307200(300kb)
rate : 35
final size : 95053(92kb)
what happen???
ofcource i want resize, but it's too small!
what did i do wrong?
'quality' isnt mean % ? than how can i get 'near' maxsize image?
in android document, just say it mean compress size...

i think this. if original image size is 500kb, i need 300kb. than i set quality:60, image will 60% compress and it become 300kb.
No.
in android document, just say it mean compress size...
No. The documentation has this for the quality parameter to compress():
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.
If you compress() to a PNG, the quality value is ignored. If you compress() to a JPEG, then it is used to control JPEG compression, which commonly uses a 0-100 range to express the desired quality. However, that does not mean that a value of 0 gives you a photo that takes no space.
than how can i get 'near' maxsize image?
There is no way of knowing the size of a JPEG compressed image without compressing it. Ideally, you do not worry about getting it "near" some particular compression ratio. Instead, either let the user choose the quality value, or just pick some quality value and move on.

Related

Android bitmap compress method is returning same size image even on different quality value

I am trying to scale and then compress an image file as PNG, the camera image format is in JPG and it is being scaled successfully and but it is still not being converted to PNG.
Secondly I think compress method returns different sized byteArray for different quality but I am getting same sizes even on changing the quality parameter value.
Here is the method I have written:
fun compressImage(file: File): File {
val outputBounds = 600
val scaleOptions = BitmapFactory.Options()
scaleOptions.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.path, scaleOptions)
val scaleFactor = if(scaleOptions.outHeight > outputBounds || scaleOptions.outWidth > outputBounds)
kotlin.math.max(scaleOptions.outWidth/outputBounds, scaleOptions.outHeight/outputBounds)
else 1
val outOptions = BitmapFactory.Options()
outOptions.inSampleSize = scaleFactor.roundToInt()
outOptions.inPreferredConfig = Config.RGB_565;
outOptions.inDither = true;
val decodedBMP = BitmapFactory.decodeFile(file.path, outOptions)
var byteOutStream = ByteArrayOutputStream()
val newFile = File(file.path)
val fileOutputStream = FileOutputStream(newFile, false)
var quality = 60
decodedBMP.compress(Bitmap.CompressFormat.PNG, quality, byteOutStream)
fileOutputStream.write(byteOutStream.toByteArray())
fileOutputStream.flush()
fileOutputStream.close()
return newFile
}
I passed a file as parameter here and I am overwriting the file with the compressed image.
Update
I tried using Bitmap.CompressFormat.JPEG as bitmap compression config and that is compressing the image size but with apparently losing quality (I mean picture looks like pixelated), I read Bitmap.CompressFormat.PNG gives you lossless compression but it is not working.
In the Android Developer reference docs it says:
JPEG
Added in API level 1
public static final Bitmap.CompressFormat JPEG
Compress to the JPEG format. quality of 0 means compress for the smallest size. 100 means compress for max visual quality.
PNG
Added in API level 1
public static final Bitmap.CompressFormat PNG
Compress to the PNG format. PNG is lossless, so quality is ignored.
Here is the answer. Since quality is ignored Compress to format.PNG is lossless, which means it will not compress.

Convert a bitmap to a fixed size

For example, I have a 10mb image; which I want to convert to 300kb. I have been through many examples
used
bmp.compress(Bitmap.CompressFormat.JPEG, 100, stream);
(here changing 100 to a lesser value will decrease the size but how would it result in a size close to 300-350kb)
and
BitmapFactory.decodeFile(filePath, options);
where I provided
options.inSampleSize = 5 /*sample*/;
But somehow I am missing something.
UPDATE
Settled with conversion 11mb to 2mb. Will update if I find a better way.
I think because PNG is lossless, the quality parameter has no effect. It's not going to "crunch" your PNGs. However this approach would work for jpg:
Trial and error, with a binary search will get you close very quickly, 3-4 attempts probably depending on the size of the acceptable range.
int minQuality = 10;
int maxQuality = 100;
long size = 0;
while(true) {
int mid = (maxQuality + minQuality)/2;
long size = compress(mid);
if (size > minSize) { //too large
if (maxQuality == minQuality){
throw new RuntimeException("Cannot compress this image down in to this size range.");
}
maxQuality = mid - 1;
continue;
}
if (size < maxSize) { //too small
if(maxQuality == 100){
break; //this means the image is smaller than the acceptable range even at 100
}
minQuality = mid + 1;
continue;
}
break;//done, falls in range
}
Two options available
Decrease the contrast of the image using image processing or some image processing api for android or do sampling using image processing api
Repeat the bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); for several times by storing the image outside and again reading in each time

Camera Intent Photos Upload

So (or SO), my requirements are:
Display 4 photo placeholders
Click one placeholder to take a photo
Display the 4 thumbnails of the taken pictures in a fragment
Upload 4 photos (one unique HTTP request, max size per photo 250kb, total 1 MB)
I initially thought this was a standard/quite easy task, but I finally changed
my mind. I'm proceeding this way:
When a user click on a placeholder:
file = createImageFile();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
startActivityForResult(takePictureIntent);
private File createImageFile() throws IOException {
// fileName is just a unique string...
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File file = File.createTempFile(fileName, ".jpg", storageDir);
return file;
}
From Google Docs
I set the thumbnail for the placeholder:
private void setThumbnail(File file, ImageView target) {
int targetWidth = target.getWidth();
int targetHeight = target.getHeight();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
int thumbWidth = options.outWidth;
int thumbHeight = options.outHeight;
int scaleFactor = Math.min(thumbWidth / targetWidth, thumbHeight
/ targetHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = scaleFactor;
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(),
options);
target.setImageBitmap(bitmap);
}
Server side my PHP API requires 4 JPEG files 250kb max each.
I'm using Loopj library (1.4.6) to upload them.
Loopj set request parameters whit method put, and we can use three
way to upload a file:
params.put("picture", file); // Upload a File
params.put("picture", inputStream); // Upload an InputStream
params.put("picture", new ByteArrayInputStream(bytes)); // Upload some bytes
The pictures saved from camera intent are too big for the memory reserved to the app and
I cannot just "load" them in RAM (or I'll get a OOM Exception).
Should I scale them down (or compress don't know) iteratively to 250kb (or some specific pixels size) and
then overwrite the saved files? Can you please suggest something to point me in the right direction?
Important: if possile I'd like to avoid rewriting a camera app, I'm just going with the intent, I've not enough time.
Did you try to add in your manifest.xml that code:
<application
android:largeHeap="true">
That allows you to manage properly huge photo by allocating more memory to your app.

Android Camera Parameter setPictureSize causes streaked picture

I am trying to take a picture using the Android camera. I have a requirement to capture a 1600 (w) x 1200 (h) image (3rd party vendor requirement). My code seems to work fine for many phone cameras but the setPictureSize causes a crash on some phones (Samsung Galaxy S4, Samsung Galaxy Note) and causes a streaked picture on others (Nexus 7 Tablet). On at least the Nexus the size I desire is showing up in the getSupportPictureSizes list.
I have tried specifying the orientation but it didn't help. Taking the picture with the default picture size works fine.
Here is an example of the streaking:
For my image capture I have a requirement of 1600x1200, jpg, 30% compression, so I am capturing a JPG file.
I think I have three choices:
1) Figure out how to capture the 1600x1200 size without a crash or streaking, or
2) Figure out how to change the size of the default picture size to a JPG that is 1600x1200.
3) Something else that is currently unknown to me.
I have found some other postings that have similar issues but not quite the same. I am in my 2nd day of trying things but am not finding a solution. Here is one posting that got close:
Camera picture to Bitmap results in messed up image (none of the suggestions helped me)
Here is the section of my code that worked fine for until I ran into the S4/Note/Nexus 7. I have added a bit of debugging code for now:
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null) {
int pictureWidth = 1600;
int pictureHeight = 1200;
// testing
Camera.Size test = parameters.getPictureSize();
List<Camera.Size> testSizes = parameters.getSupportedPictureSizes();
for ( int i = 0; i < testSizes.size(); i++ ) {
test = testSizes.get(i);
}
test = testSizes.get(3);
// get(3) is 1600 x 1200
pictureWidth = test.width;
pictureHeight = test.height;
parameters.setPictureFormat(ImageFormat.JPEG);
parameters.setPictureSize(pictureWidth, pictureHeight);
parameters.setJpegQuality(30);
parameters.setPreviewSize(size.width, size.height);
// catch any exception
try {
// make sure the preview is stopped
mCamera.stopPreview();
mCamera.setParameters(parameters);
didConfig = true;
catch(Exception e) {
// some error presentation was removed for brevity
// since didConfig not set to TRUE it will fail gracefully
}
}
Here is the section of my code that saves the JPG file:
PictureCallback jpegCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
if ( data.length > 0 ) {
String fileName = "image.jpg";
File file = new File(getFilesDir(), fileName);
String filePath = file.getAbsolutePath();
boolean goodWrite = false;
try {
OutputStream os = new FileOutputStream(file);
os.write(data);
os.close();
goodWrite = true;
} catch (IOException e) {
goodWrite = false;
}
if ( goodWrite ) {
// go on to the Preview
} else {
// TODO return an error to the calling activity
}
}
Log.d(TAG, "onPictureTaken - jpeg");
}
};
Any suggestions on how to correctly set up the camera parameters for taking photos or how to crop or resize the resulting photo would be great. Especially if it will work with older cameras (API level 8 or later)! Based on needing the full width of the picture I can only crop off the top.
Thanks!
EDIT: Here is what I ended up doing:
I started by processing the Camera.Parameters getSupportedPictureSizes to use the first one that had the height and width both greater than my desired size, AND the same width:height ratio. I set the Camera parameters to that picture size.
Then once the picture was taken:
BitmapFactory.Options options = new BitmapFactory.Options();;
options.inPurgeable = true;
// convert the byte array to a bitmap, taking care to allow for garbage collection
Bitmap original = BitmapFactory.decodeByteArray(input , 0, input.length, options);
// resize the bitmap to my desired scale
Bitmap resized = Bitmap.createScaledBitmap(original, 1600, 1200, true);
// create a new byte array and output the bitmap to a compressed JPG
ByteArrayOutputStream blob = new ByteArrayOutputStream();
resized.compress(Bitmap.CompressFormat.JPEG, 30, blob);
// recycle the memory since bitmaps seem to have slightly different garbage collection
original.recycle();
resized.recycle();
byte[] desired = blob.toByteArray();
Then I write out the desired jpg to a file for upload.
test = testSizes.get(3);
// get(3) is 1600 x 1200
There is no requirement that the array have 4+ elements, let alone that the fourth element be 1600x1200.
1) Figure out how to capture the 1600x1200 size without a crash or streaking
There is no guarantee that every device is capable of taking a picture with that exact resolution. You cannot specify arbitrary values for the resolution -- it must be one of the supported picture sizes. Some devices support arbitrary values, while other devices will give you corrupted output (as is the case here) or will flat-out crash.
2) Figure out how to change the size of the default picture size to a JPG that is 1600x1200
I am not aware that there is a "default picture size", and, beyond that, such a size will be immutable, since it is the default. Changing the picture size is your option #1 above.
3) Something else that is currently unknown to me.
For devices that support a resolution that is bigger on both axes, take a picture in that resolution, then crop to 1600x1200.
For all other devices, where one or both axes are smaller than desired, take a picture in whatever resolution suits you (largest, closest match to 4:3 aspect ratio, etc.), and then stretch/crop to get to 1600x1200.

Image from Android expansion file appears too small

I am in the process of implementing expansion files for my Android app.
So far, I've placed several images and audio files into the main expansion file (I am not using the patch expansion file). None of the files have been compressed.
Through the app, I am able to download the expansion file, and play the audio files without any problems. At the same time an audio file in the expansion file is played, I am also displaying an image from the expansion file. However, the image is considerably smaller than I expected.
The image is 320x400px. Before implementing the expansion files, it was displayed as expected in my app. However, after implementation, it looks like the image shrank to about 50px wide (the height shrank in proportion).
I then tried the solution offered in How to create a drawable from a stream without resizing it. While the image does appear slightly larger, it is still much smaller than what I want it to be (looks like it's about 100x125 px now). Currently, my code for displaying the image looks like this:
public void displayImageFromExpansionFile(){
Bitmap b = BitmapFactory.decodeStream(fileStream);
b.setDensity(Bitmap.DENSITY_NONE);
Drawable d = new BitmapDrawable(this.getResources(), b);
imageToDisplay.setImageDrawable(d);
}
public void showImg(int imgNum){
switch(imgNum){
case(1):
try{
if((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL){
fileStream = expansionFile.getInputStream("filepath inside expansion file for small image");
}
else if((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_NORMAL){
fileStream = expansionFile.getInputStream("filepath inside expansion file for normal image");
}
else if((getResources().getConfiguration().screenLayout &
Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE){
fileStream = expansionFile.getInputStream("filepath inside expansion file for large image");
}
else{
fileStream = expansionFile.getInputStream("filepath inside expansion file for xlarge image");
}
displayImageFromExpansionFile();
} catch (Exception e) {
e.printStackTrace();
}
break;
// more cases here
}
It still seems as if the image is not being displayed at its actual size. When I examine the image inside the expansion file, I can see that it is still at 320x400px. However, the app is not displaying the image at these dimensions.
What could I do to get the app to display the image at its correct dimensions?
Thanks!
---UPDATE---
I've also tried the code below, with no difference in results. It still looks to be about 100x125px, instead of 320x400px, like its original size.
public void displayImageFromExpansionFile(int bWidth, int bHeight){
BitmapFactory.Options bfo = new BitmapFactory.Options();
bfo.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap b = BitmapFactory.decodeStream(fileStream, null, bfo);
b.setDensity(Bitmap.DENSITY_NONE);
imageToDisplay.setImageBitmap(b);
}
The only thing that's worked so far is Bitmap.createScaledBitmap(b, bWidth, bHeight, true);
On my phone, doubling the image's original dimensions (to 640x800 px) using the above method brings the image up to its expected size, but I would imagine that the image might appear at different sizes on different phones (probably because of screen density/size). When I tried doubling the xlarge image dimensions and viewed it on my tablet, the image appears larger than it should.
In the end, I fiddled around with the layout XML file that was displaying the shrunken image. I changed the attributes of the imageView like so:
Width / Height: fill_parent (both)
Scale Type: fitCenter
Now, the images are slightly bigger than I initially expected, but I think it looks better this way. And now the images are of a consistent size. Problem solved!
If anyone else is having trouble with what I've been experiencing, hope this helps.
thats worked for me:
public static Bitmap getBitmapExt(Context context, String name){
Bitmap bitmap = null;
try {
if(expansionFile == null){
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, pInfo.versionCode, 0);
}
InputStream is = expansionFile.getInputStream("images/" + name + ".png");
bitmap = BitmapFactory.decodeStream(is);
}
catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
https://gist.github.com/uebi-belli/3c07323c8055a3b66ccac0d388b2b013
Hope that helps =)

Categories

Resources