web-dev-qa-db-fra.com

Android: redimensionnement bitmap utilisant un meilleur algorithme de ré-échantillonnage que bilinéaire (comme Lanczos3)

Existe-t-il un moyen ou une bibliothèque externe permettant de redimensionner une image à l’aide de Lanczos (idéalement) ou au moins bicubic alg. sous Android ? (plus vite c'est mieux, mais la qualité est la priorité, le temps de traitement est secondaire)}

Tout ce que j'ai jusqu'à présent est le suivant:

Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

Cependant, il utilise un filtre bilinéaire et la qualité de sortie est terrible. Surtout si vous voulez conserver des détails (comme des lignes fines ou des textes lisibles).

Il existe de nombreuses bonnes bibliothèques pour Java, comme indiqué par exemple ici: Java - redimensionner l'image sans perte de qualité

Cependant, cela dépend toujours de classes awt Java telles que Java.awt.image.BufferedImage, de sorte qu'il ne peut pas être utilisé dans Android.

Existe-t-il un moyen de changer le filtre (bilinéaire) par défaut dans la méthode Bitmap.createScaledBitmap() ou une bibliothèque telle que liben de Morten Nobel pouvant fonctionner avec la classe Android.graphics.Bitmap (ou avec une représentation brute, comme l'a souligné @Tron dans le commentaire en dehors)?

20
Jolinar

L’OMI la plus prometteuse consiste à utiliser libswscale (de FFmpeg), qui propose Lanczos et de nombreux autres filtres. Pour accéder à la mémoire tampon Bitmap à partir du code natif, vous pouvez utiliser jnigraphics . Cette approche garantit des performances agréables et des résultats fiables.

EDIT

Ici vous pouvez trouver une application de démonstration approximative utilisant l'approche proposée. Pour le moment, les performances sont terriblement mauvaises, il faudrait donc enquêter pour décider si nous devons faire quelque chose pour les améliorer.

5
Sergio

Malheureusement, Android utilise Android.graphics.Bitmap qui n’existe pas en Java Alors que Java utilise Java.awt.image.BufferedImage qui n’existe pas sous Android :-(

Je n'ai pas a ready to use library for Android mais un chemin pour porter une librairie spécifique Java-awt sur une librairie Java indépendante de la plate-forme avec des gestionnaires spécifiques à platfrom pour Android et awt/j2se

Dans la bibliothèque Java rescale, vous devez masquer toutes les classes spécifiques à Java-awt (telles que BufferedImage) derrière une interface IBitmap et mettre en œuvre cette interface pour j2se et indépendamment pour Android.

Je l'ai fait avec succès pour le traitement des métadonnées exif/icc/ipc et l'interface implémentée pixymeta-lib /.../ IBitmap.Java avec l'implémentation pour j2se pixymeta-j2se-lib /.../ j2se/BitmapNative .Java et Android pixymeta-Android-lib /.../ Android/BitmapNative.Java

J'ai donc ces paquets

  • pixymeta-lib
    • plate-forme transformée indépendante lib où toutes les références-awt sont remplacées par l'interface IBitmap
  • pixymeta-j2se-lib
    • awt/j2se implémentation de IBitmap
  • pixymeta-Android-lib
    • Implémentation Android de IBitmap
5
k3b

J'ai récemment écrit ceci pour redimensionner/rogner une image à une résolution spécifique et la compresser avec la qualité:

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 match
private 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;
}

il n'utilise pas les bibliothèques que vous avez mentionnées, mais cela fonctionne et j'en suis heureux. Peut-être que vous l'êtes aussi.

0
Code.IT

Si vous souhaitez simplement rééchantillonner l’image de manière optimisée pour l’affichage, vous pouvez utiliser ce joli petit support qui m’a bien servi.

Bitmap bitmap = new BitmapDrawable(getResources(), yourBitmap).getBitmap();

Cette ligne de code peut sembler étrange car vous convertissez une image bitmap en BitmapDrawable, puis vous revenez à une image bitmap, mais BitmapDrawable utilise par défaut la densité de pixels du périphérique (sauf si vous utilisez un constructeur différent).

Si vous avez besoin de redimensionner également, séparez-le simplement en deux lignes et utilisez setBounds avant de reconvertir BitmapDrawable en bitmap comme ceci:

BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), yourBitmap);
bitmapDrawable.setBounds(left, top, right, bottom); //Make it a new size in pixels.
yourBitmap = bitmapDrawable.getBitmap(); //Convert it back to a bitmap optimised for display purposes.

Les images bitmap susceptibles d’être dessinées peuvent être répertoriées comme étant privées mais ce n’est pas le cas, seuls certains constructeurs sont privés et le constructeur de cet exemple ci-dessus ne l’est pas. Cela fonctionnera également avec API 4

Alternativement, les documents Android ont un exemple téléchargeable à cet effet ici: https://developer.Android.com/topic/performance/graphics/load-bitmap.html

J'espère que cela t'aides.

0
user2288580

Voici le code que j'ai utilisé pour redimensionner l'image ..

Bitmap photo1 ;
private byte[] imageByteArray1 ;


BitmapFactory.Options opt1 = new BitmapFactory.Options();
opt1.inJustDecodeBounds=true;
BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),opt1);

// The new size we want to scale to
final int REQUIRED_SIZE=320;

// Find the correct scale value. It should be the power of 2.
int width_tmp=opt1.outWidth,height_tmp=opt1.outHeight;
int scale=2;
while(true){
    if(width_tmp>REQUIRED_SIZE||height_tmp>REQUIRED_SIZE)
        break;
    width_tmp/=2;
    height_tmp/=2;
    scale*=2;
}
// Decode with inSampleSize
BitmapFactory.Options o2=new BitmapFactory.Options();
o2.inSampleSize=scale;
o2.inJustDecodeBounds=false;
photo1=BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),o2);

ByteArrayOutputStream baos1=new ByteArrayOutputStream();
photo1.compress(Bitmap.CompressFormat.JPEG,60,baos1);
imageByteArray1=baos1.toByteArray();

J'espère que cela vous aidera .. 

0
Preet_Android