Redimensionner un gros fichier bitmap en fichier de sortie mis à l'échelle sur Android

218

J'ai une grande image bitmap (disons 3888x2592) dans un fichier. Maintenant, je veux redimensionner ce bitmap en 800x533 et l'enregistrer dans un autre fichier. Normalement, je redimensionnerais le bitmap en appelant la Bitmap.createBitmapméthode, mais 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 (voir ici , par exemple).

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

Je voudrais également éviter de lire l'image en utilisant inSampleSize avec, par exemple, 972x648 dans une première étape, puis la redimensionner exactement à 800x533 dans une deuxième étape, car la qualité serait médiocre par rapport à un redimensionnement direct de l'image d'origine.

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

J'ai également essayé BitmapFactory.decodeFile(file, options)et défini manuellement les valeurs Options.outHeight et Options.outWidth sur 800 et 533, mais cela ne fonctionne pas de cette façon.

Manuel
la source
Non, outHeight et outWidth sont des paramètres de sortie de la méthode de décodage. Cela étant dit, j'ai le même problème que vous et je ne suis jamais très satisfait de l'approche en deux étapes.
rds
souvent, Dieu merci, vous pouvez utiliser une seule ligne de code .. stackoverflow.com/a/17733530/294884
Fattie
Lecteurs, veuillez noter cet AQ absolument critique !!! stackoverflow.com/a/24135522/294884
Fattie
1
Veuillez noter que cette question a maintenant 5 ans et que la solution complète est .. stackoverflow.com/a/24135522/294884 A bientôt!
Fattie
2
Il existe maintenant une documentation officielle sur ce sujet: developer.android.com/training/displaying-bitmaps/…
Vince

Réponses:

146

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

Voici les étapes pour toute personne naviguant:

  1. Calculez le maximum possible inSampleSizequi donne toujours une image plus grande que votre cible.
  2. Chargez l'image à l'aide de BitmapFactory.decodeFile(file, options), en passant inSampleSize en option.
  3. Redimensionnez aux dimensions souhaitées en utilisant Bitmap.createScaledBitmap().
Justin
la source
J'ai essayé d'éviter ça. Il n'y a donc aucun moyen de redimensionner directement une grande image en une seule étape?
Manuel
2
Pas à ma connaissance, mais cela ne vous empêche pas d'explorer cela plus avant.
Justin
D'accord, je vais prendre cela pour ma réponse acceptée jusqu'à présent. Si je découvre d'autres méthodes, je vous le ferai savoir.
Manuel
Comme PSIXO l'a mentionné dans une réponse, vous pouvez également utiliser android: largeHeap si vous rencontrez toujours des problèmes après avoir utilisé inSampleSize.
user276648
variable bitmap devenait vide
Prasad
99

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);

    // Decode image size
    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, "scale = " + scale + ", orig-width: " + options.outWidth + ", 
       orig-height: " + options.outHeight);

    Bitmap resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // resize to desired dimensions
        int height = resultBitmap.getHeight();
        int width = resultBitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + 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, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " + 
       resultBitmap.getHeight());
    return resultBitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}
Ofir
la source
15
Rend difficile à lire lorsque vous utilisez des variables comme "b" mais bonne réponse néanmoins.
Oliver Dixon
@Ofir: getImageUri (chemin); ce que je dois passer dans cette méthode?
Biginner
1
Au lieu de (w h) /Math.pow (scale, 2), il est plus efficace d'utiliser (w h) >> scale.
david.perez
2
System.gc()
N'appelez
Merci @Ofir mais cette transformation ne conserve pas l'orientation de l'image: - /
JoeCoolman
43

Il s'agit des solutions «combinées» de Mojo Risin et «Ofir». Cela vous donnera une image redimensionnée proportionnellement avec les limites de la largeur maximale et de la hauteur maximale.

  1. Il lit uniquement les métadonnées pour obtenir la taille d'origine (options.inJustDecodeBounds)
  2. Il utilise un redimensionnement rought pour économiser de la mémoire (itmap.createScaledBitmap)
  3. Il utilise une image redimensionnée avec précision basée sur le bitamp brut créé précédemment.

Pour moi, il a bien fonctionné sur les images de 5 mégapixels et ci-dessous.

try
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // decode image size (decode metadata only, not the whole image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // save width and height
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // decode full image pre-resized
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calc rought re-size (this is no exact resize)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // decode full image
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calc exact destination size
    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);

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

    // save image
    try
    {
        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);
}
blubl
la source
23

Pourquoi ne pas utiliser l'API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Bostone
la source
21
Parce que cela ne résoudrait pas mon problème. Ce 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." Donc, je ne peux pas non plus transmettre un bitmap à la méthode .createScaledBitmap, car il me faudrait tout d'abord charger une grande image dans un objet Bitmap.
Manuel
2
Droite. J'ai relu votre question et, fondamentalement (si je comprends bien), elle se résume à "puis-je redimensionner l'image aux dimensions exactes sans charger le fichier d'origine dans la mémoire?" Si c'est le cas - je ne connais pas suffisamment les subtilités du traitement d'image pour y répondre, mais quelque chose me dit que 1. ce n'est pas disponible à partir de l'API, 2. ce ne sera pas 1 ligne. Je vais marquer cela comme favori - il serait intéressant de voir si vous (ou quelqu'un d'autre) allez résoudre ce problème.
Bostone
cela a fonctionné pour moi parce que je reçois uri et convertis en bitmap, donc les mettre à l'échelle est facile pour moi 1+ pour le plus simple.
Hamza
22

Reconnaissant l'autre excellente réponse jusqu'à présent, le meilleur code que j'ai vu à ce jour se trouve dans la documentation de l'outil de prise de photo.

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

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

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

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();
    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}
Alex
la source
1
D'abord, vous divisez des nombres entiers pour déterminer le résultat. Deuxièmement, le code plante avec targetW ou targetH à 0 (bien que cela n'ait pas beaucoup de sens, je sais). Third inSampleSize devrait être une puissance de 2.
cybergen
Ne vous méprenez pas. Cela va certainement charger une image, mais si le revêtement de sol des encres est indenté, il ne semble pas. Et ce n'est certainement pas non plus la bonne réponse car l'image ne sera pas mise à l'échelle comme prévu. Il ne fera rien tant que la vue de l'image ne sera pas la moitié de la taille de l'image ou plus petite. Ensuite, rien ne se produit jusqu'à ce que la vue de l'image soit 1/4 de la taille de l'image. Et ainsi de suite avec des pouvoirs de deux!
cybergen
18

Après avoir lu ces réponses et la documentation Android, voici le code pour redimensionner le bitmap sans le charger en mémoire:

public Bitmap getResizedBitmap(int targetW, int targetH,  String imagePath) {

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    //inJustDecodeBounds = true <-- will not load the bitmap into memory
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    return(bitmap);
}
penduDev
la source
Veuillez noter que bmOptions.inPurgeable = true; est obsolète.
Ravit
6

Lorsque j'ai de grandes images bitmap et que je veux les décoder redimensionnées, j'utilise ce qui suit

BitmapFactory.Options options = new BitmapFactory.Options();
InputStream is = null;
is = new FileInputStream(path_to_file);
BitmapFactory.decodeStream(is,null,options);
is.close();
is = new FileInputStream(path_to_file);
// here w and h are the desired width and height
options.inSampleSize = Math.max(options.outWidth/w, options.outHeight/h);
// bitmap is the resized bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);
Mojo Risin
la source
1
Étant donné que inSampleSize est un entier, vous n'obtiendrez que très rarement la largeur et la hauteur de pixel exactes que vous souhaitez obtenir. Vous pouvez vous approcher parfois, mais vous pouvez également en être loin, en fonction des décimales.
Manuel
Matin, j'ai essayé votre code (poste ci-dessus dans ce fil), mais ne semble pas fonctionner, où ai-je fait de mal? Tous les suggestions sont les bienvenues :-)
RRTW
5

Cela peut être utile pour quelqu'un d'autre qui examine cette question. J'ai réécrit le code de Justin pour permettre à la méthode de recevoir également l'objet de taille cible requis. Cela fonctionne très bien lors de l'utilisation de Canvas. Tout le mérite devrait revenir à JUSTIN pour son excellent code initial.

    private Bitmap getBitmap(int path, Canvas canvas) {

        Resources resource = null;
        try {
            final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
            resource = getResources();

            // Decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(resource, path, options);

            int scale = 1;
            while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
                  IMAGE_MAX_SIZE) {
               scale++;
            }
            Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

            Bitmap pic = null;
            if (scale > 1) {
                scale--;
                // scale to max possible inSampleSize that still yields an image
                // larger than target
                options = new BitmapFactory.Options();
                options.inSampleSize = scale;
                pic = BitmapFactory.decodeResource(resource, path, options);

                // resize to desired dimensions
                int height = canvas.getHeight();
                int width = canvas.getWidth();
                Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

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

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

                System.gc();
            } else {
                pic = BitmapFactory.decodeResource(resource, path);
            }

            Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
            return pic;
        } catch (Exception e) {
            Log.e("TAG", e.getMessage(),e);
            return null;
        }
    }

Le code de Justin est TRÈS efficace pour réduire la surcharge de travail avec de grands bitmaps.

Music Monkey
la source
4

Je ne sais pas si ma solution est la meilleure pratique, mais j'ai réussi à charger une image bitmap avec la mise à l'échelle souhaitée en utilisant les options inDensityet inTargetDensity. inDensityest 0initialement lorsque vous ne chargez pas une ressource pouvant être dessinée, cette approche consiste donc à charger des images sans ressource.

Les variables imageUri, maxImageSideLengthet contextsont des paramètres de ma méthode. J'ai publié uniquement l'implémentation de la méthode sans le wrapper AsyncTask pour plus de clarté.

            ContentResolver resolver = context.getContentResolver();
            InputStream is;
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Options opts = new Options();
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, opts);

            // scale the image
            float maxSideLength = maxImageSideLength;
            float scaleFactor = Math.min(maxSideLength / opts.outWidth, maxSideLength / opts.outHeight);
            // do not upscale!
            if (scaleFactor < 1) {
                opts.inDensity = 10000;
                opts.inTargetDensity = (int) ((float) opts.inDensity * scaleFactor);
            }
            opts.inJustDecodeBounds = false;

            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);
            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }

            return bitmap;
cybergen
la source
2
Très agréable! Utiliser inDensity au lieu de Bitmap.createScaledBitmap m'a fait économiser beaucoup de mémoire. Encore mieux combiné avec inSamplesize.
Ostkontentitan
2

Étant donné que vous souhaitez redimensionner à la taille exacte et que vous souhaitez conserver autant de qualité que nécessaire, je pense que vous devriez essayer cela.

  1. Découvrez la taille de l'image redimensionnée avec l'appel de BitmapFactory.decodeFile et en fournissant checkSizeOptions.inJustDecodeBounds
  2. Calculez le maximum possible dans inSampleSize que vous pouvez utiliser sur votre appareil pour ne pas dépasser la mémoire. bitmapSizeInBytes = 2 * largeur * hauteur; Généralement, pour votre image, inSampleSize = 2 serait bien puisque vous n'aurez besoin que de 2 * 1944x1296) = 4,8 Mo qui devraient être en mémoire
  3. Utilisez BitmapFactory.decodeFile avec inSampleSize pour charger le bitmap
  4. Redimensionnez le bitmap à la taille exacte.

Motivation: la mise à l'échelle en plusieurs étapes peut vous donner une image de meilleure qualité, mais il n'y a aucune garantie que cela fonctionnera mieux que d'utiliser une taille inSampleSize élevée. En fait, je pense que vous pouvez également utiliser inSampleSize comme 5 (pas une puissance de 2) pour avoir une mise à l'échelle directe en une seule opération. Ou utilisez simplement 4 et vous pouvez simplement utiliser cette image dans l'interface utilisateur. si vous l'envoyez au serveur - vous pouvez faire une mise à l'échelle à la taille exacte côté serveur qui vous permet d'utiliser des techniques de mise à l'échelle avancées.

Remarques: si le bitmap chargé à l'étape 3 est au moins 4 fois plus grand (donc le 4 * targetWidth <width), vous pouvez probablement utiliser plusieurs redimensionnements pour obtenir une meilleure qualité. au moins cela fonctionne en java générique, dans android vous n'avez pas la possibilité de spécifier l'interpolation utilisée pour la mise à l'échelle http://today.java.net/pub/a/today/2007/04/03/perils-of- image-getscaledinstance.html

Andrey Chorniy
la source
2

J'ai utilisé du code comme celui-ci:

  String filePath=Environment.getExternalStorageDirectory()+"/test_image.jpg";
  BitmapFactory.Options options=new BitmapFactory.Options();
  InputStream is=new FileInputStream(filePath);
  BitmapFactory.decodeStream(is, null, options);
  is.close();
  is=new FileInputStream(filePath);
  // here w and h are the desired width and height
  options.inSampleSize=Math.max(options.outWidth/460, options.outHeight/288); //Max 460 x 288 is my desired...
  // bmp is the resized bitmap
  Bitmap bmp=BitmapFactory.decodeStream(is, null, options);
  is.close();
  Log.d(Constants.TAG, "Scaled bitmap bytes, "+bmp.getRowBytes()+", width:"+bmp.getWidth()+", height:"+bmp.getHeight());

J'ai essayé l'image d'origine est 1230 x 1230, et le bitmap dit est 330 x 330.
Et si essayé 2590 x 3849, je vais avoir OutOfMemoryError.

Je l'ai tracé, il lance toujours OutOfMemoryError sur la ligne "BitmapFactory.decodeStream (is, null, options);", si le bitmap d'origine est trop grand ...

RRTW
la source
2

Le code ci-dessus a rendu un peu plus propre. InputStreams a finalement fermé l'habillage pour s'assurer qu'il se ferme également:

* Remarque
Entrée: InputStream est, int w, int h
Sortie: Bitmap

    try
    {

        final int inWidth;
        final int inHeight;

        final File tempFile = new File(temp, System.currentTimeMillis() + is.toString() + ".temp");

        {

            final FileOutputStream tempOut = new FileOutputStream(tempFile);

            StreamUtil.copyTo(is, tempOut);

            tempOut.close();

        }



        {

            final InputStream in = new FileInputStream(tempFile);
            final BitmapFactory.Options options = new BitmapFactory.Options();

            try {

                // decode image size (decode metadata only, not the whole image)
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            // save width and height
            inWidth = options.outWidth;
            inHeight = options.outHeight;

        }

        final Bitmap roughBitmap;

        {

            // decode full image pre-resized
            final InputStream in = new FileInputStream(tempFile);

            try {

                final BitmapFactory.Options options = new BitmapFactory.Options();
                // calc rought re-size (this is no exact resize)
                options.inSampleSize = Math.max(inWidth/w, inHeight/h);
                // decode full image
                roughBitmap = BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            tempFile.delete();

        }

        float[] values = new float[9];

        {

            // calc exact destination size
            Matrix m = new Matrix();
            RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
            RectF outRect = new RectF(0, 0, w, h);
            m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
            m.getValues(values);

        }

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

        return resizedBitmap;

    }
    catch (IOException e) {

        logger.error("Error:" , e);
        throw new ResourceException("could not create bitmap");

    }
user1327738
la source
1

Pour redimensionner l'image de la manière "correcte", sans sauter aucun pixel, vous devez vous connecter au décodeur d'image pour effectuer le sous-échantillonnage ligne par ligne. Android (et la bibliothèque Skia qui le sous-tend) ne fournit pas de tels crochets, vous devez donc rouler le vôtre. En supposant que vous parlez d'images jpeg, votre meilleur pari serait d'utiliser libjpeg directement, en C.

Compte tenu des complexités impliquées, l'utilisation du sous-échantillon en deux étapes puis de la mise à l'échelle est probablement la meilleure pour les applications de type prévisualisation d'image.

D0SBoots
la source
1

Si vous voulez absolument faire un redimensionnement en une étape, vous pouvez probablement charger l'intégralité du bitmap si android: largeHeap = true mais comme vous pouvez le voir, ce n'est pas vraiment conseillé.

De docs: android: largeHeap Si les processus de votre application doivent être créés avec un grand tas Dalvik. Cela s'applique à tous les processus créés pour l'application. Elle ne s'applique qu'à la première application chargée dans un processus; si vous utilisez un ID utilisateur partagé pour autoriser plusieurs applications à utiliser un processus, elles doivent toutes utiliser cette option de manière cohérente sinon elles auront des résultats imprévisibles. La plupart des applications ne devraient pas en avoir besoin et devraient plutôt se concentrer sur la réduction de leur utilisation globale de la mémoire pour de meilleures performances. L'activation de cela ne garantit pas non plus une augmentation fixe de la mémoire disponible, car certains appareils sont limités par leur mémoire totale disponible.

Igor Čordaš
la source
0

Cela a fonctionné pour moi. La fonction obtient un chemin d'accès à un fichier sur la carte SD et renvoie un bitmap dans la taille maximale affichable. Le code est d'Ofir avec quelques changements comme le fichier image sur sd au lieu d'une Ressource et le witdth et heigth sont obtenus à partir de l'objet d'affichage.

private Bitmap makeBitmap(String path) {

    try {
        final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
        //resource = getResources();

        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        int scale = 1;
        while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
                IMAGE_MAX_SIZE) {
            scale++;
        }
        Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

        Bitmap pic = null;
        if (scale > 1) {
            scale--;
            // scale to max possible inSampleSize that still yields an image
            // larger than target
            options = new BitmapFactory.Options();
            options.inSampleSize = scale;
            pic = BitmapFactory.decodeFile(path, options);

            // resize to desired dimensions

            Display display = getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int width = size.y;
            int height = size.x;

            //int height = imageView.getHeight();
            //int width = imageView.getWidth();
            Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

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

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

            System.gc();
        } else {
            pic = BitmapFactory.decodeFile(path);
        }

        Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
        return pic;

    } catch (Exception e) {
        Log.e("TAG", e.getMessage(),e);
        return null;
    }

}
Penta
la source
0

Voici le code que j'utilise qui n'a aucun problème à décoder de grandes images en mémoire sur Android. J'ai pu décoder des images plus grandes que 20 Mo tant que mes paramètres d'entrée sont d'environ 1024x1024. Vous pouvez enregistrer le bitmap renvoyé dans un autre fichier. En dessous de cette méthode se trouve une autre méthode que j'utilise également pour redimensionner les images sur un nouveau bitmap. N'hésitez pas à utiliser ce code à votre guise.

/*****************************************************************************
 * public decode - decode the image into a Bitmap
 * 
 * @param xyDimension
 *            - The max XY Dimension before the image is scaled down - XY =
 *            1080x1080 and Image = 2000x2000 image will be scaled down to a
 *            value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image - a value of "null" if there is an issue decoding
 *         image dimension
 * 
 * @throws FileNotFoundException
 *             - If the image has been removed while this operation is
 *             taking place
 */
public Bitmap decode( int xyDimension, Bitmap.Config bitmapConfig ) throws FileNotFoundException
{
    // The Bitmap to return given a Uri to a file
    Bitmap bitmap = null;
    File file = null;
    FileInputStream fis = null;
    InputStream in = null;

    // Try to decode the Uri
    try
    {
        // Initialize scale to no real scaling factor
        double scale = 1;

        // Get FileInputStream to get a FileDescriptor
        file = new File( this.imageUri.getPath() );

        fis = new FileInputStream( file );
        FileDescriptor fd = fis.getFD();

        // Get a BitmapFactory Options object
        BitmapFactory.Options o = new BitmapFactory.Options();

        // Decode only the image size
        o.inJustDecodeBounds = true;
        o.inPreferredConfig = bitmapConfig;

        // Decode to get Width & Height of image only
        BitmapFactory.decodeFileDescriptor( fd, null, o );
        BitmapFactory.decodeStream( null );

        if( o.outHeight > xyDimension || o.outWidth > xyDimension )
        {
            // Change the scale if the image is larger then desired image
            // max size
            scale = Math.pow( 2, (int) Math.round( Math.log( xyDimension / (double) Math.max( o.outHeight, o.outWidth ) ) / Math.log( 0.5 ) ) );
        }

        // Decode with inSampleSize scale will either be 1 or calculated value
        o.inJustDecodeBounds = false;
        o.inSampleSize = (int) scale;

        // Decode the Uri for real with the inSampleSize
        in = new BufferedInputStream( fis );
        bitmap = BitmapFactory.decodeStream( in, null, o );
    }
    catch( OutOfMemoryError e )
    {
        Log.e( DEBUG_TAG, "decode : OutOfMemoryError" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "decode : NullPointerException" );
        e.printStackTrace();
    }
    catch( RuntimeException e )
    {
        Log.e( DEBUG_TAG, "decode : RuntimeException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "decode : FileNotFoundException" );
        e.printStackTrace();
    }
    catch( IOException e )
    {
        Log.e( DEBUG_TAG, "decode : IOException" );
        e.printStackTrace();
    }

    // Save memory
    file = null;
    fis = null;
    in = null;

    return bitmap;

} // decode

REMARQUE: les méthodes n'ont rien à voir les unes avec les autres, sauf la méthode de décodage des appels createScaledBitmap ci-dessus. La largeur et la hauteur des notes peuvent changer par rapport à l'image d'origine.

/*****************************************************************************
 * public createScaledBitmap - Creates a new bitmap, scaled from an existing
 * bitmap.
 * 
 * @param dstWidth
 *            - Scale the width to this dimension
 * @param dstHeight
 *            - Scale the height to this dimension
 * @param xyDimension
 *            - The max XY Dimension before the original image is scaled
 *            down - XY = 1080x1080 and Image = 2000x2000 image will be
 *            scaled down to a value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image scaled - a value of "null" if there is an issue
 * 
 */
public Bitmap createScaledBitmap( int dstWidth, int dstHeight, int xyDimension, Bitmap.Config bitmapConfig )
{
    Bitmap scaledBitmap = null;

    try
    {
        Bitmap bitmap = this.decode( xyDimension, bitmapConfig );

        // Create an empty Bitmap which will contain the new scaled bitmap
        // This scaled bitmap should be the size we want to scale the
        // original bitmap too
        scaledBitmap = Bitmap.createBitmap( dstWidth, dstHeight, bitmapConfig );

        float ratioX = dstWidth / (float) bitmap.getWidth();
        float ratioY = dstHeight / (float) bitmap.getHeight();
        float middleX = dstWidth / 2.0f;
        float middleY = dstHeight / 2.0f;

        // Used to for scaling the image
        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale( ratioX, ratioY, middleX, middleY );

        // Used to do the work of scaling
        Canvas canvas = new Canvas( scaledBitmap );
        canvas.setMatrix( scaleMatrix );
        canvas.drawBitmap( bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint( Paint.FILTER_BITMAP_FLAG ) );
    }
    catch( IllegalArgumentException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : IllegalArgumentException" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : NullPointerException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : FileNotFoundException" );
        e.printStackTrace();
    }

    return scaledBitmap;
} // End createScaledBitmap
user560663
la source
le calcul de la puissance de l'échelle est tout simplement faux ici; utilisez simplement le calcul sur la page doco android.
Fattie
0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

ou:

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);
Vaishali Sutariya
la source
0

J'utilise Integer.numberOfLeadingZerospour calculer la meilleure taille d'échantillon, de meilleures performances.

Code complet en kotlin:

@Throws(IOException::class)
fun File.decodeBitmap(options: BitmapFactory.Options): Bitmap? {
    return inputStream().use {
        BitmapFactory.decodeStream(it, null, options)
    }
}

@Throws(IOException::class)
fun File.decodeBitmapAtLeast(
        @androidx.annotation.IntRange(from = 1) width: Int,
        @androidx.annotation.IntRange(from = 1) height: Int
): Bitmap? {
    val options = BitmapFactory.Options()

    options.inJustDecodeBounds = true
    decodeBitmap(options)

    val ow = options.outWidth
    val oh = options.outHeight

    if (ow == -1 || oh == -1) return null

    val w = ow / width
    val h = oh / height

    if (w > 1 && h > 1) {
        val p = 31 - maxOf(Integer.numberOfLeadingZeros(w), Integer.numberOfLeadingZeros(h))
        options.inSampleSize = 1 shl maxOf(0, p)
    }
    options.inJustDecodeBounds = false
    return decodeBitmap(options)
}
lymoge
la source
-2

Redimensionnez le bitmap à l'aide du code suivant

    public static Bitmap decodeFile(File file, int reqWidth, int reqHeight){

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;        
    BitmapFactory.decodeFile(file.getPath(), options);

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

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(file.getPath(), options);
   }

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

La même chose est également expliquée dans l'astuce / astuce suivante

http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory

Amol
la source