web-dev-qa-db-fra.com

La plupart des moyens efficaces de la mémoire pour redimensionner les bitmaps sur Android?

Je construis une application sociale à forte intensité d'image dans laquelle les images sont envoyées du serveur à l'appareil. Lorsque le périphérique présente une résolution d'écran inférieure, je dois redimensionner les bitmaps, sur le périphérique, afin de les adapter à la taille d'affichage souhaitée.

Le problème est que l'utilisation de createScaledBitmap me cause de nombreuses erreurs de mémoire insuffisante après le redimensionnement d'une horde de images miniatures.

Quel est le moyen le plus efficace en termes de mémoire pour redimensionner les bitmaps sur Android?

113
Colt McAnlis

Cette réponse est résumée à partir de Chargement efficace de bitmaps volumineux , qui explique comment utiliser inSampleSize pour charger une version bitmap réduite.

En particulier, bitmaps de pré-dimensionnement explique en détail les différentes méthodes, comment les combiner et quelles sont celles qui utilisent le moins la mémoire.

Il existe trois méthodes principales pour redimensionner une image bitmap sur Android, lesquelles ont des propriétés de mémoire différentes:

API createScaledBitmap

Cette API utilisera une bitmap existante et créera une nouvelle bitmap avec les dimensions exactes sélectionnées.

Du côté positif, vous pouvez obtenir exactement la taille d’image que vous recherchez (quelle que soit son apparence). Mais l'inconvénient, est que cette API nécessite un bitmap existant pour fonctionner. Cela signifie que l'image devra être chargée, décodée et un bitmap créé avant de pouvoir créer une nouvelle version plus petite. C'est idéal pour obtenir vos dimensions exactes, mais horrible en termes de surcharge de mémoire. En tant que tel, c’est une sorte de compromis pour la plupart des développeurs d’app qui ont tendance à être conscients de la mémoire

indicateur inSampleSize

BitmapFactory.Options possède une propriété notée inSampleSize qui redimensionnera votre image lors du décodage, afin d’éviter le décodage en bitmap temporaire. Cette valeur entière utilisée ici chargera une image à une taille réduite de 1/x. Par exemple, si vous définissez inSampleSize sur 2, vous obtenez une image dont la taille est réduite de moitié, et si vous définissez cette valeur sur 4, vous obtenez une image dont la taille est 1/4. Fondamentalement, la taille des images sera toujours inférieure à la taille de votre source.

Du point de vue de la mémoire, utiliser inSampleSize est une opération très rapide. Effectivement, il ne décodera que le Xième pixel de votre image dans le bitmap obtenu. inSampleSize présente deux problèmes principaux:

  • Il ne vous donne pas les résolutions exactes . Cela réduit seulement la taille de votre bitmap par une puissance de 2.

  • Il ne produit pas la meilleure qualité de redimensionnement . La plupart des filtres de redimensionnement produisent de belles images en lisant des blocs de pixels, puis en les pondérant pour produire le pixel redimensionné en question. inSampleSize évite tout cela en lisant seulement tous les quelques pixels. Le résultat est assez performant et peu de mémoire, mais la qualité en souffre.

Si vous ne faites que réduire la taille de votre image à une taille pow2 et si le filtrage n'est pas un problème, vous ne pouvez pas trouver une méthode plus efficace en termes de mémoire (ou de performances) que inSampleSize.

indicateurs inScaled, inDensity, inTargetDensity

Si vous devez redimensionner une image selon une dimension qui n’est pas égale à une puissance égale à deux, vous aurez besoin des drapeaux inScaled, inDensity et inTargetDensity de BitmapOptions. Lorsque le drapeau inScaled est défini, le système dérive la valeur de mise à l'échelle à appliquer à votre image bitmap en divisant le inTargetDensity par les valeurs inDensity.

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

L’utilisation de cette méthode redimensionnera votre image et lui appliquera également un ‘filtre de redimensionnement’, c’est-à-dire que le résultat final aura une meilleure apparence, car certaines opérations mathématiques supplémentaires ont été prises en compte lors de l’opération de redimensionnement. Mais soyez averti: cette étape de filtre supplémentaire, prend un temps de traitement supplémentaire et peut s'additionner rapidement pour les grandes images, ce qui entraîne des redimensionnements lents et une allocation de mémoire supplémentaire pour le filtre lui-même.

En règle générale, il n’est pas judicieux d’appliquer cette technique à une image dont la taille est nettement supérieure à celle souhaitée, en raison de la charge de filtrage supplémentaire.

Combinaison magique

Du point de vue de la mémoire et des performances, vous pouvez combiner ces options pour obtenir les meilleurs résultats. (réglage des drapeaux inSampleSize, inScaled, inDensity et inTargetDensity)

inSampleSize sera d'abord appliqué à l'image, ce qui le rendra plus grand que la taille cible suivante. Ensuite, inDensity & inTargetDensity permettent de redimensionner le résultat aux dimensions exactes souhaitées, en appliquant une opération de filtrage pour nettoyer l’image.

La combinaison de ces deux est une opération beaucoup plus rapide, car l’étape inSampleSize réduira le nombre de pixels sur lesquels l’étape basée sur la densité obtenue devra appliquer le filtre de redimensionnement.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

Si vous avez besoin d’adapter une image à des dimensions spécifiques, et un filtrage plus agréable, cette technique est le meilleur moyen d’obtenir la bonne taille, mais avec une empreinte de mémoire rapide et réduite. opération.

Obtenir les dimensions de l'image

Obtenir la taille de l’image sans décoder l’ensemble de l’image Pour redimensionner votre bitmap, vous devez connaître les dimensions entrantes. Vous pouvez utiliser le drapeau inJustDecodeBounds pour vous aider à obtenir les dimensions de l’image, sans avoir besoin de décoder les données de pixels.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

Vous pouvez utiliser cet indicateur pour décoder d’abord la taille, puis calculer les valeurs appropriées pour la mise à l’échelle à votre résolution cible.

162
Colt McAnlis

Aussi gentil (et précis) que soit cette réponse, c'est aussi très compliqué. Plutôt que de réinventer la roue, considérons des bibliothèques telles que Glide , Picasso , UIL , - Ion , ou un nombre quelconque d’autres qui implémentent pour vous cette logique complexe et sujette aux erreurs.

Colt lui-même recommande même de jeter un coup d'œil sur Glide et Picasso dans la vidéo Vidéo de pré-dimensionnement des performances de bitmaps .

En utilisant des bibliothèques, vous pouvez obtenir toute l'efficacité mentionnée dans la réponse de Colt, mais avec des API beaucoup plus simples qui fonctionnent de manière cohérente dans toutes les versions d'Android.

13
Sam Judd