Le moyen le plus efficace de redimensionner les bitmaps sur Android?

115

Je suis en train de créer une application sociale à forte intensité d'images où les images sont envoyées du serveur à l'appareil. Lorsque l'appareil a des résolutions d'écran plus petites, je dois redimensionner les bitmaps, sur l'appareil, pour correspondre à leurs tailles d'affichage prévues.

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

Quel est le moyen le plus efficace de redimensionner les bitmaps sur Android?

Colt McAnlis
la source
7
Votre serveur ne peut-il pas envoyer la bonne taille pour économiser la RAM et la bande passante de votre client !?
James
2
Ce n'est valable que si je possédais la ressource serveur, un composant de calcul était disponible avec, et dans tous les cas, il pouvait prédire les dimensions exactes des images pour les rapports hauteur / largeur qu'il n'avait pas encore vus. Donc, si vous chargez du contenu de ressource à partir d'un CDN tiers (comme moi), cela ne fonctionne pas :(
Colt McAnlis

Réponses:

168

Cette réponse est résumée dans Chargement efficace de grandes images bitmap qui explique comment utiliser inSampleSize pour charger une version bitmap réduite.

En particulier, les bitmaps de pré-mise à l'échelle expliquent les détails des différentes méthodes, comment les combiner et quelles sont les plus efficaces en mémoire.

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

API createScaledBitmap

Cette API prendra un bitmap existant et créera un NOUVEAU bitmap avec les dimensions exactes que vous avez sélectionnées.

Du côté positif, vous pouvez obtenir exactement la taille d'image que vous recherchez (quel que soit son aspect). Mais l'inconvénient est que cette API nécessite un bitmap existant pour fonctionner . Cela signifie que l'image devrait ê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 supplémentaire. En tant que tel, c'est une sorte de rupture pour la plupart des développeurs d'applications qui ont tendance à être conscients de leur mémoire

Indicateur inSampleSize

BitmapFactory.Optionsa une propriété notée car inSampleSizecela redimensionnera votre image lors de son décodage, pour éviter d'avoir à décoder en une image bitmap temporaire. Cette valeur entière utilisée ici chargera une image à une taille réduite 1 / x. Par exemple, le réglage inSampleSizesur 2 renvoie une image qui fait la moitié de la taille et le réglage sur 4 renvoie une image qui fait 1 / 4ème de la taille. Fondamentalement, les tailles d'image seront toujours plus petites que la taille de votre source.

Du point de vue de la mémoire, l'utilisation inSampleSizeest une opération très rapide. En effet, il ne décodera que chaque Xème pixel de votre image dans votre bitmap résultant. Cependant, il y a deux problèmes principaux inSampleSize:

  • Cela ne vous donne pas de résolutions exactes . Cela ne réduit la taille de votre bitmap que d'une puissance de 2.

  • Il ne produit pas le meilleur redimensionnement de qualité . 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 simplement tous les quelques pixels. Le résultat est assez performant et la mémoire est faible, mais la qualité en souffre.

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

Indicateurs inScaled, inDensity, inTargetDensity

Si vous avez besoin de mettre à l'échelle une image à une dimension qui n'est pas égale à une puissance de deux, vous aurez besoin des indicateurs inScaled, inDensityet inTargetDensityde BitmapOptions. Une fois l' inScaledindicateur défini, le système dérivera la valeur de mise à l'échelle à appliquer à votre bitmap en divisant le inTargetDensitypar les inDensityvaleurs.

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 sera meilleur car des mathématiques supplémentaires ont été prises en compte lors de l'étape de redimensionnement. Mais attention: cette étape de filtrage supplémentaire prend du temps de traitement supplémentaire et peut rapidement s'additionner pour les grandes images, ce qui entraîne des redimensionnements lents et des allocations de mémoire supplémentaires pour le filtre lui-même.

Ce n'est généralement pas une bonne idée d'appliquer cette technique à une image beaucoup plus grande que la taille souhaitée, en raison de la surcharge 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 de la inSampleSize, inScaled, inDensityet inTargetDensitydrapeaux)

inSampleSizesera d'abord appliqué à l'image, l'amenant à la prochaine puissance de deux PLUS GRANDE que votre taille cible. Ensuite, inDensity& inTargetDensitysont utilisés pour mettre à l'échelle le résultat aux dimensions exactes que vous souhaitez, en appliquant une opération de filtre pour nettoyer l'image.

La combinaison de ces deux éléments est une opération beaucoup plus rapide, car l' inSampleSizeétape réduira le nombre de pixels dont l'étape basée sur la densité résultante aura besoin pour appliquer son 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 d' un filtrage plus agréable, cette technique est le meilleur pont pour obtenir la bonne taille, mais effectuée dans une opération rapide et à faible encombrement mémoire.

Obtenir les dimensions de l'image

Obtenir la taille de l'image sans décoder l'image entière Pour redimensionner votre bitmap, vous aurez besoin de connaître les dimensions entrantes. Vous pouvez utiliser l' inJustDecodeBoundsindicateur pour vous aider à obtenir les dimensions de l'image, sans avoir besoin de décoder réellement les données de pixel.

// 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 en fonction de votre résolution cible.

Colt McAnlis
la source
1
ça aurait été génial si vous pouviez nous dire ce qu'est dstWidth?
k0sh
@ k0sh dstWIdth est la largeur de l'ImageView pour savoir où il va ie destination widthou dstWidth pour faire court
tyczj
@tyczj merci pour la réponse, je sais ce que c'est, mais il y en a peut-être certains qui ne le savent peut-être pas et puisque Colt qui a répondu à cette question, il pourrait peut-être l'expliquer pour que les gens ne se trompent pas.
k0sh
Beau message ... je ne savais pas à propos des drapeaux inScaled, inDensity, inTargetDensity précédemment ...
Maveroid
J'ai regardé la série de modèles de performances Android et j'ai beaucoup appris!
Anis
13

Aussi agréable (et précise) que soit cette réponse, elle est également très compliquée. Plutôt que de réinventer la roue, pensez à des bibliothèques comme Glide , Picasso , UIL , Ion ou tout autre qui implémentent cette logique complexe et sujette aux erreurs pour vous.

Colt lui-même recommande même de jeter un coup d'œil à Glide et Picasso dans la vidéo des modèles de performances Bitmaps de pré-mise à l'échelle .

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 sur toutes les versions d'Android.

Sam Judd
la source