Jak skalować bitmapę strumieniową na miejscu bez uprzedniego odczytania całego obrazu?

[1]}mam aplikację na Androida, która jest bardzo obrazochłonna. Obecnie używam Bitmap.createScaledBitmap() aby przeskalować obraz do pożądanego rozmiaru. Metoda ta wymaga jednak, abym miał już w pamięci oryginalną bitmapę, która może być dość spora.

Jak skalować pobieraną bitmapę bez uprzedniego zapisania całości do pamięci lokalnej lub systemu plików?

Author: emmby, 2011-08-13

2 answers

Ta metoda odczytuje informacje nagłówka z obrazu, aby określić jego rozmiar, a następnie odczytuje obraz i skaluje go do żądanego rozmiaru w miejscu bez przydzielania pamięci dla pełnego oryginalnego rozmiaru obrazu.

Używa również BitmapFactory.Opcje.inpureable , co wydaje się być słabo udokumentowaną, ale pożądaną opcją, aby zapobiec wyjątkom OoM podczas korzystania z wielu bitmap. UPDATE: nie używa już inpuriable, zobacz tę notkę od Romain

To działa za pomocą BufferedInputStream odczytać informacje nagłówka obrazu przed odczytaniem całego obrazu za pośrednictwem strumienia wejściowego.

/**
 * 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 ) {}
    }

}
 23
Author: emmby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:01:43

Oto moja wersja, oparta na rozwiązaniu @ emmby (dzięki człowieku!) Włączyłem drugą fazę, w której bierzesz zmniejszoną bitmapę i skalujesz ją ponownie, aby dokładnie dopasować pożądane wymiary. Moja wersja pobiera ścieżkę do pliku, a nie strumień.

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) {
        }
    }
}
 1
Author: Assaf S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-04 12:14:23