Die speichereffizienteste Methode, um die Größe von Bitmaps auf Android zu ändern?

115

Ich erstelle eine bildintensive soziale App, in der Bilder vom Server an das Gerät gesendet werden. Wenn das Gerät kleinere Bildschirmauflösungen hat, muss ich die Größe der Bitmaps auf dem Gerät an die beabsichtigten Anzeigegrößen anpassen.

Das Problem ist, dass die Verwendung von createScaledBitmap dazu führt, dass ich nach dem Ändern der Größe einer Horde von Miniaturbildern auf viele Speicherfehler stoße .

Was ist die speichereffizienteste Methode, um die Größe von Bitmaps unter Android zu ändern?

Colt McAnlis
quelle
7
Kann Ihr Server nicht die richtige Größe senden, um RAM und Bandbreite Ihres Kunden zu sparen?
James
2
Dies ist nur gültig, wenn ich die Serverressource besitze, eine Rechenkomponente zur Verfügung habe und in allen Fällen die genauen Abmessungen von Bildern für Seitenverhältnisse vorhersagen kann, die noch nicht gesehen wurden. Wenn Sie also Ressourceninhalte von einem CDN eines Drittanbieters laden (wie ich), funktioniert dies nicht :(
Colt McAnlis

Antworten:

168

Diese Antwort wird unter Effizientes Laden großer Bitmaps zusammengefasst. Hier wird erläutert, wie Sie inSampleSize zum Laden einer verkleinerten Bitmap-Version verwenden.

In Bitmaps vor der Skalierung werden insbesondere die Details verschiedener Methoden erläutert, wie sie kombiniert werden und welche speichereffizientesten sind.

Es gibt drei Möglichkeiten, die Größe einer Bitmap unter Android zu ändern, die unterschiedliche Speichereigenschaften haben:

createScaledBitmap API

Diese API nimmt eine vorhandene Bitmap auf und erstellt eine NEUE Bitmap mit den genauen Dimensionen, die Sie ausgewählt haben.

Auf der positiven Seite können Sie genau die Bildgröße erhalten, die Sie suchen (unabhängig davon, wie es aussieht). Der Nachteil ist jedoch, dass für diese API eine vorhandene Bitmap um zu funktionieren . Das bedeutet, dass das Bild geladen, dekodiert und eine Bitmap erstellt werden muss, bevor eine neue, kleinere Version erstellt werden kann. Dies ist ideal, um Ihre genauen Abmessungen zu erhalten, aber schrecklich in Bezug auf zusätzlichen Speicheraufwand. Als solches ist dies für die meisten App-Entwickler, die dazu neigen, speicherbewusst zu sein, eine Art Deal Breaker

inSampleSize-Flag

BitmapFactory.Options hat eine Eigenschaft vermerkt als inSampleSize , die die Größe Ihres Bildes beim Dekodieren ändert, um zu vermeiden, dass eine temporäre Bitmap dekodiert werden muss. Dieser hier verwendete ganzzahlige Wert lädt ein Bild mit einer um 1 / x reduzierten Größe. Wenn Sie beispielsweise inSampleSizeauf 2 setzen, wird ein Bild zurückgegeben, das halb so groß ist, und wenn Sie es auf 4 setzen, wird ein Bild zurückgegeben, das 1/4 der Größe entspricht. Grundsätzlich sind die Bildgrößen immer um eine Zweierpotenz kleiner als Ihre Quellgröße.

Aus Speichersicht ist die Verwendung inSampleSizeeine sehr schnelle Operation. Tatsächlich wird nur jedes x-te Pixel Ihres Bildes in die resultierende Bitmap dekodiert. Es gibt jedoch zwei Hauptprobleme inSampleSize:

  • Es gibt Ihnen keine genauen Auflösungen . Die Größe Ihrer Bitmap wird nur um eine Zweierpotenz verringert.

  • Es wird nicht die beste Größenänderung erzeugt . Die meisten Größenänderungsfilter erzeugen gut aussehende Bilder, indem sie Pixelblöcke lesen und dann gewichten, um das fragliche Pixel mit geänderter Größe zu erzeugen. inSampleSizevermeidet dies alles, indem nur alle paar Pixel gelesen wird. Das Ergebnis ist ziemlich performant und hat wenig Speicher, aber die Qualität leidet.

Wenn Sie nur mit dem Verkleinern Ihres Bildes um eine bestimmte pow2-Größe zu tun haben und das Filtern kein Problem darstellt, können Sie keine speichereffizientere (oder leistungsfähigere) Methode finden als inSampleSize .

inScaled-, inDensity-, inTargetDensity-Flags

Wenn Sie ein Bild in eine Dimension skalieren , die zu einer Zweierpotenz ist nicht gleich sind , dann müssen Sie das inScaled, inDensityund inTargetDensityFlaggen von BitmapOptions. Wenn das inScaledFlag gesetzt wurde, leitet das System den Skalierungswert ab, der auf Ihre Bitmap angewendet werden soll, indem es inTargetDensitydurch die inDensityWerte dividiert wird .

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

Wenn Sie diese Methode verwenden, wird die Größe Ihres Bilds geändert und es wird auch ein "Größenänderungsfilter" darauf angewendet. Das Endergebnis sieht also besser aus, da beim Größenänderungsschritt einige zusätzliche Berechnungen berücksichtigt wurden. Aber seien Sie gewarnt: Dieser zusätzliche Filterschritt nimmt zusätzliche Verarbeitungszeit in Anspruch und kann sich schnell für große Bilder summieren, was zu langsamen Größenänderungen und zusätzlichen Speicherzuweisungen für den Filter selbst führt.

Aufgrund des zusätzlichen Filteraufwands ist es im Allgemeinen keine gute Idee, diese Technik auf ein Bild anzuwenden, das erheblich größer als die gewünschte Größe ist.

Magische Kombination

Aus Sicht des Speichers und der Leistung können Sie diese Optionen kombinieren, um die besten Ergebnisse zu erzielen. (Einstellung der inSampleSize, inScaled, inDensityund inTargetDensityFlags)

inSampleSizewird zuerst auf das Bild angewendet und erreicht die nächste Zweierpotenz, die größer ist als Ihre Zielgröße. Anschließend wird inDensity& inTargetDensityverwendet, um das Ergebnis auf die gewünschten Abmessungen zu skalieren und eine Filteroperation anzuwenden, um das Bild zu bereinigen.

Das Kombinieren dieser beiden Funktionen ist viel schneller, da durch den inSampleSizeSchritt die Anzahl der Pixel verringert wird, auf die der resultierende dichtebasierte Schritt angewendet werden muss, um den Größenänderungsfilter anzuwenden.

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

Wenn Sie ein Bild an bestimmte Abmessungen anpassen und eine bessere Filterung durchführen möchten, ist diese Technik die beste Brücke, um die richtige Größe zu erhalten, jedoch in einem schnellen Vorgang mit geringem Speicherbedarf.

Bildabmessungen abrufen

Abrufen der Bildgröße ohne Dekodierung des gesamten Bilds Um die Größe Ihrer Bitmap zu ändern, müssen Sie die eingehenden Abmessungen kennen. Sie können das inJustDecodeBoundsFlag verwenden, um die Abmessungen des Bildes zu ermitteln, ohne die Pixeldaten tatsächlich dekodieren zu müssen.

// 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

Mit diesem Flag können Sie zuerst die Größe dekodieren und dann die richtigen Werte für die Skalierung auf Ihre Zielauflösung berechnen.

Colt McAnlis
quelle
1
Es wäre toll, wenn Sie uns sagen könnten, was dstWidth ist.
k0sh
@ k0sh dstWIdth ist die Breite der ImageView, für die es geht, dh destination widthoder dstWidth für kurz
tyczj
@tyczj danke für die Antwort, ich weiß was es ist, aber es gibt einige, die es vielleicht nicht wissen und da Colt diese Frage tatsächlich beantwortet hat, könnte er es vielleicht erklären, damit die Leute nicht verwirrt werden.
k0sh
Netter Beitrag ... wusste vorher nichts über inScaled, inDensity, inTargetDensity Flags ...
Maveroid
Ich habe die Android Performance Pattern Serie gesehen und viel gelernt!
Anis
13

So schön (und genau) diese Antwort auch ist, sie ist auch sehr kompliziert. Anstatt das Rad neu zu erfinden, sollten Sie Bibliotheken wie Glide , Picasso , UIL , Ion in Betracht ziehen oder eine beliebige Anzahl anderer in , die diese komplexe und fehleranfällige Logik für Sie implementieren.

Colt selbst empfiehlt sogar, einen Blick auf Glide und Picasso im Video zu den Leistungsskalen vor der Skalierung von Bitmaps zu werfen .

Durch die Verwendung von Bibliotheken können Sie jede in Colts Antwort erwähnte Effizienz erzielen, jedoch mit erheblich einfacheren APIs, die in jeder Android-Version konsistent funktionieren.

Sam Judd
quelle