Android: Bitmap resizing using better resampling algorithm than bilinear (like Lanczos3)
The most promising IMO is to use libswscale (from FFmpeg), it offers Lanczos and many other filters. To access Bitmap
buffer from native code you can use jnigraphics. This approach guarantees nice performance and reliable results.
EDIT
Here you can find rough demo app, that uses proposed approach. At the moment performance is frustratingly bad, so it should be investigated to decide if we to do something to improve it.
Unfortunately Android uses android.graphics.Bitmap which does not exist in javawhile java uses java.awt.image.BufferedImage that does not exist in android :-(
I donot have a ready to use library for android
but a path how to port a java-awt specific lib to a platform independat java lib with platfrom specific handlers for android and awt/j2se
In the java rescale lib you have to hide all java-awt specific classes (like BufferedImage) behind an interface IBitmap
and implement that interface for j2se and independantly for Android.
I have done this successfully for exif/icc/ipc metadata processing and implemented interface pixymeta-lib/.../IBitmap.java with implementation for j2se pixymeta-j2se-lib/.../j2se/BitmapNative.java and android pixymeta-android-lib/.../android/BitmapNative.java
So I have these packages
- pixymeta-lib
- transformed platform independant lib where all awt-references are replaced by
IBitmap
interface
- transformed platform independant lib where all awt-references are replaced by
- pixymeta-j2se-lib
- awt/j2se implementation of
IBitmap
- awt/j2se implementation of
- pixymeta-android-lib
- android implementation of
IBitmap
- android implementation of
I recently wrote this to scale/crop an image to a specific resolution and compress it with quality:
public static void scaleImageToResolution(Context context, File image, int dstWidth, int dstHeight) { if (dstHeight > 0 && dstWidth > 0 && image != null) { Bitmap result = null; try { //Get Image Properties BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(image.getAbsolutePath(), bmOptions); int photoH = bmOptions.outHeight; int photoW = bmOptions.outWidth; bmOptions.inJustDecodeBounds = false; bmOptions.inPurgeable = true; //Smaller Image Size in Memory with Config bmOptions.inPreferredConfig = Bitmap.Config.RGB_565; //Is resolution not the same like 16:9 == 4:3 then crop otherwise fit ScalingLogic scalingLogic = getScalingLogic(photoW, photoH,dstWidth, dstHeight); //Get Maximum automatic downscaling that it's still bigger then this requested resolution bmOptions.inSampleSize = calculateScalingSampleSize(photoW, photoH, dstWidth, dstHeight, scalingLogic); //Get unscaled Bitmap result = BitmapFactory.decodeFile(image.getAbsolutePath(), bmOptions); //Scale Bitmap to requested Resolution result = scaleImageToResolution(context, result, scalingLogic); if (result != null) { //Save Bitmap with quality saveImageWithQuality(context, result, image); } } finally { //Clear Memory if (result != null) result.recycle(); } }}public static void saveImageWithQuality(Bitmap bitmap, String path, int compressQuality) { try { FileOutputStream fOut; fOut = new FileOutputStream(path); bitmap.compress(Bitmap.CompressFormat.JPEG, compressQuality, fOut); fOut.flush(); fOut.close(); } catch (IOException ex) { if (Logger.getRootLogger() != null) Logger.getRootLogger().error(ex); else Log.e("saveImageWithQuality", "Error while saving compressed Picture: " + ex.getMessage() + StringUtils.newLine() + ex.getStackTrace().toString()); }}public static void saveImageWithQuality(Context context, Bitmap bitmap, File file) { saveImageWithQuality(bitmap, file.getAbsolutePath(), getCompressQuality());}public static void saveImageWithQuality(Context context, Bitmap bitmap, String path) { saveImageWithQuality(bitmap, path, getCompressQuality());}private static int calculateScalingSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.FIT) { final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect > dstAspect) { return srcWidth / dstWidth; } else { return srcHeight / dstHeight; } } else { final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect > dstAspect) { return srcHeight / dstHeight; } else { return srcWidth / dstWidth; } }}private static Bitmap scaleImageToResolution(Context context, Bitmap unscaledBitmap, ScalingLogic scalingLogic, int dstWidth, int dstHeight) { //Do Rectangle of original picture when crop Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); //Do Rectangle to fit in the source rectangle Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); //insert source rectangle into new one Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(scaledBitmap); canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG)); //Recycle the unscaled Bitmap afterwards unscaledBitmap.recycle(); return scaledBitmap;}private static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.CROP) { if (srcWidth >= srcHeight) { //Horizontal final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { final int srcRectHeight = (int) (srcWidth / dstAspect); final int scrRectTop = (srcHeight - srcRectHeight) / 2; return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight); } else { final int srcRectWidth = (int) (srcHeight * dstAspect); final int srcRectLeft = (srcWidth - srcRectWidth) / 2; return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight); } } else { //Vertikal final float srcAspect = (float) srcHeight / (float) srcWidth; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { final int srcRectWidth = (int) (srcHeight / dstAspect); final int srcRectLeft = (srcWidth - srcRectWidth) / 2; return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight); } else { final int srcRectHeight = (int) (srcWidth * dstAspect); final int scrRectTop = (srcHeight - srcRectHeight) / 2; return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight); } } } else { return new Rect(0, 0, srcWidth, srcHeight); }}private static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.FIT) { if (srcWidth > srcHeight) { //Vertikal final float srcAspect = (float) srcWidth / (float) srcHeight; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { return new Rect(0, 0, (int) (dstHeight * srcAspect), dstHeight); } else { return new Rect(0, 0, dstWidth, (int) (dstWidth / srcAspect)); } } else { //Horizontal final float srcAspect = (float) srcHeight / (float) srcWidth; final float dstAspect = (float) dstWidth / (float) dstHeight; if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) { return new Rect(0, 0, (int) (dstHeight / srcAspect), dstHeight); } else { return new Rect(0, 0, dstWidth, (int) (dstWidth * srcAspect)); } } } else { if (srcWidth >= srcHeight) return new Rect(0, 0, dstWidth, dstHeight); else return new Rect(0, 0, dstHeight, dstWidth); }}private static ScalingLogic getScalingLogic(int imageWidth, int imageHeight, int dstResolutionWidth, int dstResolutionHeight) { if (imageWidth >= imageHeight) { //Bild horizontal final float srcAspect = (float) imageWidth / (float) imageHeight; final float dstAspect = (float) dstResolutionWidth / (float) dstResolutionHeight; if (!isResolutionEqual(srcAspect, dstAspect)) { return ScalingLogic.CROP; } else { return ScalingLogic.FIT; } } else { //Bild vertikal final float srcAspect = (float) imageHeight / (float) imageWidth; final float dstAspect = (float) dstResolutionWidth / (float) dstResolutionHeight; if (!isResolutionEqual(srcAspect, dstAspect)) { return ScalingLogic.CROP; } else { return ScalingLogic.FIT; } }}public enum PictureQuality { High, Medium, Low}public enum ScalingLogic { CROP, FIT}//Does resolution matchprivate static boolean isResolutionEqual(float v1, float v2) { // Falls a 1.999999999999 and b = 2.000000000000 return v1 == v2 || Math.abs(v1 - v2) / Math.max(Math.abs(v1), Math.abs(v2)) < 0.01;}public int getCompressQuality() { if (Quality == PictureQuality.High) return 100; else if (Quality == PictureQuality.Medium) return 50; else if (Quality == PictureQuality.Low) return 25; else return 0;}
it's not using the libraries you mentioned but it works and I am happy with it. Maybe you are, too.