1

I have an app that needs to resize an image and then save it to jpg. My test image is a photo with very smooth gradients in the sky. I'm trying to save it to jpeg after a resize using this code:

dstBmp = Bitmap.createBitmap(srcBmp, cropX, 0, tWidth, srcBmp.getHeight());
if (android.os.Build.VERSION.SDK_INT > 12) {
    Log.w("General", "Calling setHasAlpha");
    dstBmp.setHasAlpha(true);
}
dstBmp = Bitmap.createScaledBitmap(dstBmp, scaledSmaller, scaledLarger, true);
OutputStream out = null;
File f = new File(directory + "/f"+x+".jpg");
try {
    f.createNewFile();
    out = new FileOutputStream(f);
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

dstBmp.compress(CompressFormat.JPEG, jpegQuality, out);

The problem is that excessive banding occurs in the gradients of the image unless I bump the quality up to around 95. However, at quality level 95, the resulting file is over 150kb. When I perform these same functions in photoshop and do a "Save for web", I can avoid banding all the way down to quality level 40 with an image size of 50kb. Using ImageCR on my web server I can accomplish the same at 30kb.

Is there any way in Java to compress an image into jpeg more efficiently, or is there a separate library or api I can use to do so? I'm loading a lot of images into memory and at this rate the app is threatening OOM errors on older devices. I'd be happy to allocate more time to image manipulation if that will help the final result.

Nicholas
  • 1,974
  • 4
  • 20
  • 46

1 Answers1

3

Try this code from http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

In short, you have 2 static methods

the first for calculating the new size of the image

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {

    // Calculate ratios of height and width to requested height and width
    final int heightRatio = Math.round((float) height / (float) reqHeight);
    final int widthRatio = Math.round((float) width / (float) reqWidth);

    // Choose the smallest ratio as inSampleSize value, this will guarantee
    // a final image with both dimensions larger than or equal to the
    // requested height and width.
    inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}

return inSampleSize;
}

second for loading the scaled size image into memory:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);

// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}

and You can call this function like this:

Bitmap b = decodeSampledBitmapFromResource(getResources(), R.id.myimage, 128, 128));

You can modify the second method to take input parametar String (the path if the image is file on sdcard) instead of resource, and instead of decodeResource use:

BitmapFactory.decodeFile(path, options);

I always use this code when loading big images.

Riste
  • 143
  • 1
  • 8
  • Thanks. I'm trying to understand the code you've posted; still pretty new to Java. Could you please explain how this affects the quality or size of the final, saved jpg? – Nicholas Aug 09 '13 at 14:36
  • This code is useful when u need to load/scale bitmap (either for displaying or saving). When you use bitmap the Android OS allocates memory and if there isn't enough memory available in runtime it can result with OutOfMemoryException. These methods checks the size of the image with the option options.inJustDecodeBounds = true; without loading the image into memory and then scales it. The final jpg will be in my example above 128x128 px (not precisely) and smaller in size and quality. U then can use this scaled bitmap to save it into memory or whatever. – Riste Aug 12 '13 at 09:36
  • This seems like it would reduce the memory footprint of the scaling process, but it seems like if given the same dimensions the resulting file saved to the SD card would be the same size. That jpg size is what I'm concerned about reducing without loss of resolution or quality. – Nicholas Aug 12 '13 at 19:18