2011-08-13 12 views
10

Mam aplikację na Androida, która jest bardzo intensywna pod względem obrazu. Obecnie używam Bitmap.createScaledBitmap(), aby przeskalować obraz do pożądanego rozmiaru. Jednak ta metoda wymaga, że ​​mam już oryginalną bitmapę w pamięci, która może być całkiem spora.Jak skalować bitmapę strumieniową w miejscu bez uprzedniego przeczytania całego obrazu?

Jak skalować bitmapę, którą pobierałem, bez uprzedniego zapisania całej rzeczy w pamięci lokalnej lub systemie plików?

Odpowiedz

23

Ta metoda odczyta informacje nagłówka z obrazu w celu ustalenia jego rozmiaru, a następnie odczyta obraz i przeskaluje go do wymaganego rozmiaru w miejscu bez przydzielania pamięci dla pełnego obrazu o oryginalnym rozmiarze.

Używa również BitmapFactory.Options.inPurgeable, która wydaje się być słabo udokumentowaną, ale pożądaną opcją, aby zapobiec wyjątkom OoM podczas korzystania z dużej ilości bitmap. UPDATE: nie używa inPurgeable patrz this note od Romain

Działa przy użyciu BufferedInputStream przeczytać informacje nagłówka dla obrazu przed odczytaniem całego obrazu w poprzez InputStream.

/** 
* Read the image from the stream and create a bitmap scaled to the desired 
* size. Resulting bitmap will be at least as large as the 
* desired minimum specified dimensions and will keep the image proportions 
* correct during scaling. 
*/ 
protected Bitmap createScaledBitmapFromStream(InputStream s, int minimumDesiredBitmapWith, int minimumDesiredBitmapHeight) { 

    final BufferedInputStream is = new BufferedInputStream(s, 32*1024); 
    try { 
     final Options decodeBitmapOptions = new Options(); 
     // For further memory savings, you may want to consider using this option 
     // decodeBitmapOptions.inPreferredConfig = Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel 

     if(minimumDesiredBitmapWidth >0 && minimumDesiredBitmapHeight >0) { 
      final Options decodeBoundsOptions = new Options(); 
      decodeBoundsOptions.inJustDecodeBounds = true; 
      is.mark(32*1024); // 32k is probably overkill, but 8k is insufficient for some jpgs 
      BitmapFactory.decodeStream(is,null,decodeBoundsOptions); 
      is.reset(); 

      final int originalWidth = decodeBoundsOptions.outWidth; 
      final int originalHeight = decodeBoundsOptions.outHeight; 

      // inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings 
      decodeBitmapOptions.inSampleSize= Math.max(1,Math.min(originalWidth/minimumDesiredBitmapWidth, originalHeight/minimumDesiredBitmapHeight)); 

     } 

     return BitmapFactory.decodeStream(is,null,decodeBitmapOptions); 

    } catch(IOException e) { 
     throw new RuntimeException(e); // this shouldn't happen 
    } finally { 
     try { 
      is.close(); 
     } catch(IOException ignored) {} 
    } 

} 
+1

Być może trzeba dokonać wyraźnego połączenia GC przed wywołaniem BitmapFactory.decodeStream, jak na to odpowiedź: http://stackoverflow.com/questions/6718855/using-caching-to-improve-scrolling-performance -in-an-android-listview-with-images/7245318 # 7245318 – emmby

+0

Prawdopodobnie miałoby sens użycie nowego/zamknięcia zamiast znaku/resetowania w FileInputStream. Ale pomysł jest dobry. – kellogs

+1

Dlaczego otrzymuję 'java.io.IOException: Mark został unieważniony' na' is.reset(); ' – sancho21

1

Oto moja wersja, w oparciu o rozwiązania @emmby człowieka (dzięki!) podaję drugi etap, w którym objęcie obniżoną bitmapę i skalować go ponownie, aby dopasować dokładnie żądane wymiary. Moja wersja pobiera ścieżkę pliku zamiast strumienia.

protected Bitmap createScaledBitmap(String filePath, int desiredBitmapWith, int desiredBitmapHeight) throws IOException, FileNotFoundException { 
    BufferedInputStream imageFileStream = new BufferedInputStream(new FileInputStream(filePath)); 
    try { 
     // Phase 1: Get a reduced size image. In this part we will do a rough scale down 
     int sampleSize = 1; 
     if (desiredBitmapWith > 0 && desiredBitmapHeight > 0) { 
      final BitmapFactory.Options decodeBoundsOptions = new BitmapFactory.Options(); 
      decodeBoundsOptions.inJustDecodeBounds = true; 
      imageFileStream.mark(64 * 1024); 
      BitmapFactory.decodeStream(imageFileStream, null, decodeBoundsOptions); 
      imageFileStream.reset(); 
      final int originalWidth = decodeBoundsOptions.outWidth; 
      final int originalHeight = decodeBoundsOptions.outHeight; 
      // inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings 
      sampleSize = Math.max(1, Math.max(originalWidth/desiredBitmapWith, originalHeight/desiredBitmapHeight)); 
     } 
     BitmapFactory.Options decodeBitmapOptions = new BitmapFactory.Options(); 
     decodeBitmapOptions.inSampleSize = sampleSize; 
     decodeBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel 

     // Get the roughly scaled-down image 
     Bitmap bmp = BitmapFactory.decodeStream(imageFileStream, null, decodeBitmapOptions); 

     // Phase 2: Get an exact-size image - no dimension will exceed the desired value 
     float ratio = Math.min((float)desiredBitmapWith/ (float)bmp.getWidth(), (float)desiredBitmapHeight/ (float)bmp.getHeight()); 
     int w =(int) ((float)bmp.getWidth() * ratio); 
     int h =(int) ((float)bmp.getHeight() * ratio); 
     return Bitmap.createScaledBitmap(bmp, w,h, true); 

    } catch (IOException e) { 
     throw e; 
    } finally { 
     try { 
      imageFileStream.close(); 
     } catch (IOException ignored) { 
     } 
    } 
} 
Powiązane problemy