How do I scale a streaming bitmap in-place without reading the whole image first?

前端 未结 2 2127
甜味超标
甜味超标 2020-12-01 05:00

I have an Android application that is very image intensive. I\'m currently using Bitmap.createScaledBitmap() to scale the image to a desired size. However, this method requi

相关标签:
2条回答
  • 2020-12-01 05:24

    Here is my version, based on @emmby solution (thanks man!) I've included a second phase where you take the reduced bitmap and scale it again to match exactly your desired dimensions. My version takes a file path rather than a stream.

    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) {
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:29

    This method will read the header information from the image to determine its size, then read the image and scale it to the desired size in place without allocating memory for the full original sized image.

    It also uses BitmapFactory.Options.inPurgeable, which seems to be a sparsely documented but desirable option to prevent OoM exceptions when using lots of bitmaps. UPDATE: no longer uses inPurgeable, see this note from Romain

    It works by using a BufferedInputStream to read the header information for the image before reading the entire image in via the 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 ) {}
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题