Android: Bitmap resizing using better resampling algorithm than bilinear (like Lanczos3) Android: Bitmap resizing using better resampling algorithm than bilinear (like Lanczos3) android android

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
  • pixymeta-j2se-lib
    • awt/j2se implementation of IBitmap
  • pixymeta-android-lib
    • android implementation of IBitmap


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.