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?
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:
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
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.
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.