Il modo più efficiente per la memoria per ridimensionare le bitmap su Android?


115

Sto creando un'app social ad alta intensità di immagini in cui le immagini vengono inviate dal server al dispositivo. Quando il dispositivo ha risoluzioni dello schermo più piccole, è necessario ridimensionare le bitmap, sul dispositivo, in modo che corrispondano alle dimensioni di visualizzazione previste.

Il problema è che l'utilizzo di createScaledBitmap mi fa incorrere in molti errori di memoria insufficiente dopo aver ridimensionato un'orda di immagini in miniatura.

Qual è il modo più efficiente in termini di memoria per ridimensionare le bitmap su Android?


7
Il tuo server non può inviare la dimensione corretta in modo da risparmiare RAM e larghezza di banda del cliente !?
James

2
Questo è valido solo se possedevo la risorsa del server, aveva un componente di elaborazione disponibile con essa e, in tutti i casi, poteva prevedere le dimensioni esatte delle immagini per proporzioni che non aveva ancora visto. Quindi, se stai caricando il contenuto di una risorsa da un CDN di terze parti (come me) non funziona :(
Colt McAnlis

Risposte:


168

Questa risposta è riassunta da Caricamento efficiente di bitmap di grandi dimensioni che spiega come utilizzare inSampleSize per caricare una versione bitmap ridotta.

In particolare , le bitmap di pre-ridimensionamento spiegano i dettagli di vari metodi, come combinarli e quali sono i più efficienti in termini di memoria.

Esistono tre modi principali per ridimensionare una bitmap su Android che hanno proprietà di memoria diverse:

API createScaledBitmap

Questa API accetterà una bitmap esistente e creerà una NUOVA bitmap con le dimensioni esatte che hai selezionato.

Tra i lati positivi, puoi ottenere esattamente le dimensioni dell'immagine che stai cercando (indipendentemente da come appare). Ma lo svantaggio è che questa API richiede una bitmap esistente per funzionare . Significa che l'immagine dovrebbe essere caricata, decodificata e creata una bitmap prima di poter creare una nuova versione più piccola. Questo è l'ideale in termini di ottenere le dimensioni esatte, ma orribile in termini di sovraccarico di memoria aggiuntivo. In quanto tale, questo è un po 'un problema per la maggior parte degli sviluppatori di app che tendono ad essere attenti alla memoria

inSampleSize flag

BitmapFactory.Optionsha una proprietà annotata in quanto inSampleSizeridimensionerà l'immagine durante la decodifica, per evitare la necessità di decodificarla in una bitmap temporanea. Questo valore intero usato qui caricherà un'immagine con una dimensione ridotta di 1 / x. Ad esempio, l'impostazione inSampleSizesu 2 restituisce un'immagine che è la metà delle dimensioni e l'impostazione su 4 restituisce un'immagine che è 1/4 della dimensione. Fondamentalmente le dimensioni dell'immagine saranno sempre un po 'più piccole della dimensione della sorgente.

Dal punto di vista della memoria, l'utilizzo inSampleSizeè un'operazione molto veloce. In effetti, decodificherà solo ogni X pixel dell'immagine nella bitmap risultante. Ci sono due problemi principali con inSampleSizeperò:

  • Non ti dà risoluzioni esatte . Riduce la dimensione della tua bitmap solo di una potenza di 2.

  • Non produce il ridimensionamento della migliore qualità . La maggior parte dei filtri di ridimensionamento produce immagini di bell'aspetto leggendo blocchi di pixel e quindi pesandoli per produrre il pixel ridimensionato in questione. inSampleSizeevita tutto questo leggendo solo pochi pixel. Il risultato è abbastanza performante e poca memoria, ma la qualità ne risente.

Se hai a che fare solo con la riduzione della tua immagine di alcune dimensioni pow2 e il filtro non è un problema, non puoi trovare un metodo più efficiente in termini di memoria (o prestazioni) di inSampleSize.

Flag inScaled, inDensity, inTargetDensity

Se è necessario ridimensionare l'immagine a una dimensione che non è uguale a una potenza di due, allora è necessario il inScaled, inDensitye inTargetDensitybandiere di BitmapOptions. Quando il inScaledflag è stato impostato, il sistema ricaverà il valore di ridimensionamento da applicare alla bitmap dividendo il inTargetDensityper i inDensityvalori.

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'utilizzo di questo metodo ridimensionerà l'immagine e applicherà anche un "filtro di ridimensionamento", ovvero il risultato finale avrà un aspetto migliore perché durante la fase di ridimensionamento sono stati presi in considerazione alcuni calcoli aggiuntivi. Ma attenzione: questo passaggio di filtro aggiuntivo richiede tempo di elaborazione aggiuntivo e può sommarsi rapidamente per immagini di grandi dimensioni, con conseguente ridimensionamento lento e allocazioni di memoria extra per il filtro stesso.

In genere non è una buona idea applicare questa tecnica a un'immagine che è significativamente più grande della dimensione desiderata, a causa del sovraccarico di filtraggio extra.

Combinazione magica

Dal punto di vista della memoria e delle prestazioni, puoi combinare queste opzioni per ottenere i migliori risultati. (impostando il inSampleSize, inScaled, inDensitye inTargetDensityle bandiere)

inSampleSizeverrà prima applicato all'immagine, portandola alla successiva potenza di due MAGGIORE rispetto alla dimensione di destinazione. Quindi, inDensity& inTargetDensityvengono utilizzati per ridimensionare il risultato alle dimensioni esatte desiderate, applicando un'operazione di filtro per ripulire l'immagine.

La combinazione di questi due è un'operazione molto più veloce, poiché il inSampleSizepassaggio ridurrà il numero di pixel su cui il passaggio basato sulla densità risultante dovrà applicare il filtro di ridimensionamento.

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

Se devi adattare un'immagine a dimensioni specifiche, e un filtro più gradevole, questa tecnica è il miglior ponte per ottenere la dimensione giusta, ma eseguita con un'operazione di impronta di memoria rapida e ridotta.

Ottenere le dimensioni dell'immagine

Ottenere le dimensioni dell'immagine senza decodificare l'intera immagine Per ridimensionare la tua bitmap, dovrai conoscere le dimensioni in arrivo. Puoi utilizzare il inJustDecodeBoundsflag per ottenere le dimensioni dell'immagine, senza dover decodificare effettivamente i dati dei 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

È possibile utilizzare questo flag per decodificare prima la dimensione, quindi calcolare i valori corretti per il ridimensionamento alla risoluzione di destinazione.


1
sarebbe stato fantastico se potessi dirci cos'è dstWidth?
k0sh

@ k0sh dstWIdth è la larghezza di ImageView per la sua destinazione, ovvero destination widthdstWidth in breve
tyczj

@tyczj grazie per la risposta, so di cosa si tratta, ma alcuni potrebbero non saperlo e dal momento che Colt che ha effettivamente risposto a questa domanda, forse potrebbe spiegarlo in modo che le persone non si confondano.
k0sh

Bel post ... non conoscevo i flag inScaled, inDensity, inTargetDensity in precedenza ...
maveroid

Ho guardato la serie di performance pattern di Android e ho imparato molto!
Anis

13

Per quanto bella (e accurata) sia questa risposta, è anche molto complicata. Piuttosto che reinventare la ruota, considera librerie come Glide , Picasso , UIL , Ion o qualsiasi altra che implementa questa logica complessa e soggetta a errori per te.

Lo stesso Colt consiglia persino di dare un'occhiata a Glide e Picasso nel video Pre-scaling Bitmaps Performance Patterns .

Utilizzando le librerie, puoi ottenere tutta l'efficienza menzionata nella risposta di Colt, ma con API molto più semplici che funzionano in modo coerente su ogni versione di Android.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.