224 votes

Redimensionner un fichier bitmap volumineux pour un fichier de sortie mis à l'échelle sur Android

J'ai une grande bitmap (par exemple 3888x2592) dans un fichier. Maintenant, je veux redimensionner cette bitmap en 800x533 et l'enregistrer dans un autre fichier. Normalement, je redimensionnerais la bitmap en appelant la méthode Bitmap.createBitmap mais elle nécessite une bitmap source en tant que premier argument, que je ne peux pas fournir car charger l'image originale dans un objet Bitmap dépasserait bien sûr la mémoire (voir ici, par exemple).

Je ne peux pas non plus lire la bitmap avec, par exemple, BitmapFactory.decodeFile(file, options), en fournissant un BitmapFactory.Options.inSampleSize, car je veux la redimensionner à une largeur et une hauteur exactes. Utiliser inSampleSize redimensionnerait la bitmap en 972x648 (si j'utilise inSampleSize=4) ou en 778x518 (si j'utilise inSampleSize=5, qui n'est même pas une puissance de 2).

J'aimerais également éviter de lire l'image en utilisant inSampleSize avec, par exemple, 972x648 dans une première étape, puis en la redimensionnant exactement en 800x533 dans une deuxième étape, car la qualité serait médiocre par rapport à une redimension directe de l'image d'origine.

Pour résumer ma question : y a-t-il un moyen de lire un fichier image de grande taille avec 10 MP ou plus et de l'enregistrer dans un nouveau fichier image, redimensionné à une nouvelle largeur et hauteur spécifique, sans obtenir d'exception OutOfMemory ?

J'ai également essayé BitmapFactory.decodeFile(file, options) et en définissant manuellement les valeurs Options.outHeight et Options.outWidth sur 800 et 533, mais cela ne fonctionne pas de cette manière.

0 votes

Non, les outHeight et outWidth sont des paramètres sortants de la méthode de décodage. Cela étant dit, j'ai le même problème que vous, et je ne suis pas très satisfait de l'approche en 2 étapes.

0 votes

Souvent, Dieu merci, vous pouvez utiliser une seule ligne de code .. stackoverflow.com/a/17733530/294884

0 votes

Lecteurs, veuillez noter ce QA absolument essentiel !!! stackoverflow.com/a/24135522/294884

151voto

Justin Points 9636

Non. J'aimerais bien que quelqu'un me corrige, mais j'ai accepté l'approche de chargement/redimensionnement que tu as essayée comme compromis.

Voici les étapes pour ceux qui naviguent :

  1. Calculer la valeur maximale possible de inSampleSize qui donne toujours une image plus grande que votre cible.
  2. Charger l'image en utilisant BitmapFactory.decodeFile(file, options), en passant inSampleSize comme option.
  3. Redimensionner aux dimensions désirées en utilisant Bitmap.createScaledBitmap().

0 votes

J'ai essayé d'éviter ça. Donc il n'y a aucun moyen de redimensionner directement une grande image en seulement une étape?

2 votes

Pas à ma connaissance, mais ne laissez pas cela vous empêcher d'explorer davantage.

0 votes

D'accord, je vais prendre ceci comme ma réponse acceptée jusqu'à présent. Si je découvre d'autres méthodes, je vous tiendrai informé.

100voto

Ofir Points 521

La réponse de Justin traduite en code (fonctionne parfaitement pour moi) :

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Décode la taille de l'image
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();

    int scale = 1;
    while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) {
       scale++;
    }
    Log.d(TAG, "échelle = " + scale + ", largeur d'origine : " + options.outWidth + ", 
       hauteur d'origine : " + options.outHeight);

    Bitmap resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // Mise à l'échelle au maximum possible de inSampleSize qui produit toujours une image
        // plus grande que la cible
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // Redimensionner aux dimensions souhaitées
        int height = resultBitmap.getHeight();
        int width = resultBitmap.getWidth();
        Log.d(TAG, "dimensions de l'opération de mise à l'échelle - largeur : " + width + ",
           hauteur : " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(resultBitmap, (int) x, 
           (int) y, true);
        resultBitmap.recycle();
        resultBitmap = scaledBitmap;

        System.gc();
    } else {
        resultBitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "taille du bitmap - largeur : " +resultBitmap.getWidth() + ", hauteur : " + 
       resultBitmap.getHeight());
    return resultBitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}

15 votes

Rend difficile la lecture lorsque vous utilisez des variables telles que "b", mais bonne réponse néanmoins.

0 votes

@Ofir : getImageUri(chemin); que dois-je passer dans cette méthode?

1 votes

Au lieu de (w_h)/Math.pow(scale, 2) il est plus efficace d'utiliser (w_h) >> scale.

45voto

blubl Points 181

Ceci est la solution "Mojo Risin's et 'Ofir's" "combinée". Cela vous donnera une image redimensionnée de manière proportionnelle avec les limites de la largeur maximale et de la hauteur maximale.

  1. Il lit seulement les métadonnées pour obtenir la taille originale (options.inJustDecodeBounds)
  2. Il utilise un redimensionnement approximatif pour économiser de la mémoire (itmap.createScaledBitmap)
  3. Il utilise une image redimensionnée de manière précise basée sur le Bitamp approximatif créé précédemment.

Pour moi, cela a bien fonctionné sur des images de 5 mégapixels et en dessous.

essayer
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // décoder la taille de l'image (décoder uniquement les métadonnées, pas toute l'image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // sauvegarder la largeur et la hauteur
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // décoder l'image complète pré-redimensionnée
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calculer le redimensionnement approximatif (ce n'est pas un redimensionnement exact)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // décoder l'image complète
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calculer la taille de destination exacte
    Matrix m = new Matrix();
    RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
    RectF outRect = new RectF(0, 0, dstWidth, dstHeight);
    m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
    float[] values = new float[9];
    m.getValues(values);

    // redimensionner le bitmap
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

    // sauvegarder l'image
    essayer
    {
        FileOutputStream out = new FileOutputStream(pathOfOutputImage);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    }
    catch (Exception e)
    {
        Log.e("Image", e.getMessage(), e);
    }
}
catch (IOException e)
{
    Log.e("Image", e.getMessage(), e);
}

24voto

Bostone Points 14208

Pourquoi ne pas utiliser l'API?

int h = 48; // hauteur en pixels
int w = 48; // largeur en pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);

23 votes

Parce que cela ne résoudrait pas mon problème. Qui est : "... il a besoin d'un bitmap source comme premier argument, que je ne peux pas fournir car le chargement de l'image d'origine dans un objet Bitmap dépasserait bien sûr la mémoire disponible." Donc, je ne peux pas passer un Bitmap à la méthode .createScaledBitmap non plus, car j'aurais quand même besoin de charger une grande image dans un objet Bitmap d'abord.

2 votes

D'accord. J'ai relu votre question et en gros (si je l'ai bien compris) il s'agit de "pouvez-vous redimensionner une image aux dimensions exactes sans charger le fichier original en mémoire ?" Si c'est le cas - je ne connais pas assez les subtilités du traitement d'image pour y répondre mais quelque chose me dit que 1. ce n'est pas disponible depuis l'API, 2. ce ne sera pas en une seule ligne. Je vais marquer ceci comme favori - ce serait intéressant de voir si vous (ou quelqu'un d'autre) résoudrez cela.

0 votes

Cela a fonctionné pour moi parce que je reçois uri et les convertis en bitmap, donc les mettre à l'échelle est facile pour moi 1+ pour le plus simple.

22voto

Alex Points 3933

Reconnaissant l'autre excellente réponse jusqu'à présent, le meilleur code que j'ai vu pour cela figure dans la documentation de l'outil de prise de photos.

Voir la section intitulée "Décoder une image mise à l'échelle".

http://developer.android.com/training/camera/photobasics.html

La solution qu'il propose est une solution de redimensionnement puis mise à l'échelle comme les autres ici, mais c'est assez astucieux.

J'ai copié le code ci-dessous comme une fonction prête à l'emploi pour plus de commodité.

private void setPic(String imagePath, ImageView destination) {
    int targetW = destination.getWidth();
    int targetH = destination.getHeight();
    // Obtenir les dimensions du bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Déterminer combien réduire l'image en échelle
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Décoder le fichier image dans un Bitmap de taille pour remplir la vue
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}

1 votes

Tout d'abord, vous divisez des entiers ce qui plantera le résultat. Deuxièmement, le code plante lorsque targetW ou targetH est égal à 0 (même si cela n'a pas beaucoup de sens je sais). Troisièmement, inSampleSize doit être une puissance de 2.

0 votes

Ne me méprenez pas. Cela chargera certainement une image mais si l'arrondi des entiers est prévu, cela ne semble pas être le cas. Et ce n'est définitivement pas la bonne réponse car l'image ne sera pas mise à l'échelle comme prévu. Rien ne se passera tant que l'aperçu de l'image ne sera pas à moitié de la taille de l'image ou plus petite. Ensuite, rien ne se passera jusqu'à ce que l'aperçu de l'image soit 1/4 de la taille de l'image. Et ainsi de suite avec les puissances de deux!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X