OutOfMemoryError while decoding and encoding Base64 String into Bitmap

前端 未结 7 1987
你的背包
你的背包 2020-12-14 14:49

I\'m trying to decode and encode a Bitmap image. On some devices it runs perfectly while on others it\'s not. I\'m uploading Bas

相关标签:
7条回答
  • 2020-12-14 15:13

    You are not base64 encoding a Bitmap instance (as you suggest in your post and even in the subject) but a byte array b in String imageEncoded = Base64.encodeToString(b, Base64.DEFAULT);. The byte array 'b' contains the bytes of what would be the content of a .jpg file if you would write these bytes to file.

    What you have to do is put the contents of the .jpg file which you want to upload directly in a byte array. Then encode that array.

    Google for android put file content in byte array. Code has been posted many times on this site

    0 讨论(0)
  • 2020-12-14 15:21

    I would suggest you to:

    1. If you receive data from a server using xml-based protocol - use SAX parser instead of DOM.

    2. Do not load the whole base64 string in to the memory. Use InputStream to load portion of data in to the byte[] array, decode Base64 from the array and append the result to a cache file via FileOutputStream.

    3. Use options.inJustDecodeBounds option to prevent a full-scale bitmap file loading in to the memory. You can evaluate the total amount of memory required and make a decision to scale it down, for example. UPD. I noticed you are using it already.

    4. Keep in mind object references you are holding and passing trough your methods flow. Strong references prevents memory from to be reclaimed by GC.

    Following your code: method decodeBase64 holds String input argument parameter in the stack, byte[] decodedBytereference and return BitmapFactory.decodeStream(new FileInputStream(f), null, o2) at the very end. Holding all these objects in the memory at the same time means big pressure on GC and memory allocation in general. As a simplest solution you can null references just after corresponding objects were used (string and byte array), so GC will be able to free up them.

    0 讨论(0)
  • 2020-12-14 15:22

    Just increase the heap size of your application in manifest file.

    android:largeHeap="true"
    
    eg:
    <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/Theme.AppTheme"
            android:largeHeap="true" >
    
    0 讨论(0)
  • 2020-12-14 15:22

    You can try this, it should solve your problem.

    Basically first saving the data to a file, using a buffer to avoid memory problems. After that we check and resize the file - loading directly a resized Bitmap, again to avoid memory problems.

    From what I can suppose, you are getting the encoded string from a server... so you can manage your code to directly write to the file.

    Writing the data to the file:

    private Bitmap getBitmapThumbnail(String base64StringReceive) {
            byte[] data = Base64.decode(base64StringReceive, Base64.DEFAULT);
            File file = new File(getFilesDir(), "testFile");
    
            try {
                BufferedOutputStream bos = new BufferedOutputStream(openFileOutput("testFile", Context.MODE_PRIVATE),1024);
                bos.write(data);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return getThumbFromFile(file, false);
        }
    

    Creating the thumbnail from the image file:

    private Bitmap getThumbFromFile(File file) {
        Bitmap tempImage = null;
        try {
            tempImage = decodeSampledBitmapFromFile(file.getAbsolutePath(), 50, 50); // this is in pixels - use your desired size
            return ThumbnailUtils.extractThumbnail(tempImage, 50, 50);
        } finally {
            if (tempImage != null) {
                tempImage.recycle();
            }
        }
    }
    

    Loading only a resized Bitmap:

    private Bitmap decodeSampledBitmapFromFile(String filePath, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
    
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    
        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath, options);
    }
    

    Calculating the sampling value - provided by Google, you can find it their tutorials:

    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
    
        if (height > reqHeight || width > reqWidth) {
    
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
    
            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight &&
                    (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
    
        return inSampleSize;
    }
    
    0 讨论(0)
  • 2020-12-14 15:30

    As greenapps suggested, it would be better to avoid using Bitmap and BitmapFactory in order to TRANSMIT the file:

    FileInputStream fis = new FileInputStream(imageFile);
    byte[] byteArray = inputStreamToByteArray(fis);
    String base64ImageSend = Base64.encodeToString(byteArray, Base64.NO_WRAP);
    

    where:

    /**
     * Convert an InputStream object into a byte array
     * @param inputStream The InputStream to convert
     * @return The byte[] representing the converted InputStream or null if the inputStream is null
     */
    public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {
        if(inputStream==null) {
            return null;
        }
        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];
    
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            byteBuffer.write(buffer, 0, len);
        }
        return byteBuffer.toByteArray();
    }
    

    Actually the outOfMemory problem, as stated by Kai, is due to the fact that you are decoding a full resolution and high quality image. The better way to avoid this is to downsampling your image:

    /**
     * Decode a file, representing an image, into a bitmap, trying to scale the original image if required
     * @param context The application context
     * @param file The image file
     * @param requestedWidth The requested width
     * @param requestedHeight The requested height
     * @return The decoded bitmap or null if it is not possible to decode the file parameter
     */
    public static Bitmap decodeImageFileIntoMutableBitmap(Context context, File file, int requestedWidth, int requestedHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // just compute size, don't create bitmap
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);
        float sampleSize = computeImageScaleCoefficient(options, requestedWidth, requestedHeight);
        if (sampleSize == -1) {
            return null;
        }
        else if (sampleSize <= 1) {
            options.inSampleSize = 1;
        }
        else {
            options.inSampleSize = (int) sampleSize;
        }
        options.inJustDecodeBounds = false; // compute size and create bitmap
        /*it is possible to reduce the memory when decoding through skipping ARGB_8888 and using RGB_565 instead and inDither to true to preserve image quality.*/
        //options.inPreferredConfig = Bitmap.Config.RGB_565;
        //options.inDither = true;
        Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
        if (bitmap == null) {
            return null;
        }
        return bitmap;
    }
    
    /**
     * Find the sample coefficient <code><b>s<sub>k</sub></b>=2<sup>k</sup></code>, with <code>k &isin &#8469;</code>, such that
     * <code>min<sub>k</sub>(requestedWidth-options.outWidth&times;<b>s<sub>k</sub></b>)&gt;0</code>
     * <code>&&</code>
     * <code>min<sub>k</sub>(requestedHeight-options.outHeight&times;<b>s<sub>k</sub></b>)&gt;0</code>
     * @param options The BitmapFactory.Options instance of the original image user wants to scale
     * @param requestedWidth The requested width
     * @param requestedHeight The requested height
     * @return -1 if if there is an error trying to decode the image
     * or the sample coefficient <code><b>s<sub>k</sub></b></code>,
     * &gt;1 if the image needs to be down-sampled,
     * &lt;1 if the image needs to be up-sampled
     */
    public static float computeImageScaleCoefficient(BitmapFactory.Options options, int requestedWidth, int requestedHeight) {
        float sampleCoefficient = 1;
        int imageWidth = options.outWidth;
        int imageHeight = options.outHeight;
        if (imageWidth == -1 || imageHeight == -1) {
            return -1;
        }
        double outWidth = imageWidth;
        double outHeight = imageHeight;
        if ((outWidth > requestedWidth) || (outHeight > requestedHeight)) {
            while ((outWidth > requestedWidth) || (outHeight > requestedHeight)) {
                outWidth = Math.floor(outWidth/2.0);
                outHeight = Math.floor(outHeight/2.0);
                sampleCoefficient *= 2.0;
            }
        }
        else {
            while ((outWidth < requestedWidth) && (outHeight < requestedHeight)) {
                outWidth *= 2;
                outHeight *= 2;
                if ((outWidth <= requestedWidth) && (outHeight <= requestedHeight)) {
                    sampleCoefficient /= 2.0;
                }
            }
        }
        return sampleCoefficient;
    }
    
    0 讨论(0)
  • 2020-12-14 15:32

    When showing Bitmap in ImageView from a file first decode it with the help of BitmapHelper.decodeFile(picturePath, 200, 200, true) this will return compressed Bitmap so that while encoding this Bitmap you can handle high resolution images as well as heavy size images upto 100 MB of file.

    After decoding file set it to your ImageView and to get Base64 String form above compressed image using BitmapHelper class; get Bitmap from your ImageView (i.e. already been compressed) and use ImageBase64.encodeTobase64(bitmap) this will again compress the file and make handle OutOfMemory exception while Encoding a Bitmap.

    For decoding first write your Base64String to file in your device memory. Then read Bytes in chunks and decode that ByteArray by this you will have your bitmap in your memory. After this scale down this bitmap using decodeFile() method then you will have compressed Bitmap from Base64String

    I have searched various blogs and post and from them I have taken best suitable code and combined them into one to make more reliable. Try below code to make encoding and decoding more faster with meory management.

    To decode Base64String

    Bitmap bitmap = ImageBase64.decodeBase64(base64ImageStr,MainActivity.this);
    imageView.setImageBitmap(bitmap);
    bitmap = null;
    System.gc();
    

    To encode Bitmap Try this:

    imageView.setImageBitmap(BitmapHelper.decodeFile(picturePath, 200, 200, true));
    
    Bitmap bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();              
    base64ImageStr = ImageBase64.encodeTobase64(bitmap);
    

    Try This code for ImageBase64

    public class ImageBase64 {
    
        private ImageBase64() {
            super();
        }
        private static Context appContext;
    
        public static String encodeTobase64(Bitmap image) {
    
             ByteArrayOutputStream baos=new  ByteArrayOutputStream();
             image.compress(Bitmap.CompressFormat.JPEG,100, baos);
                byte [] b=baos.toByteArray();
                String temp=null;
                try{
                System.gc();
                temp=Base64.encodeToString(b, Base64.DEFAULT);
                }catch(Exception e){
                    e.printStackTrace();
                }catch(OutOfMemoryError e){
                    baos=new  ByteArrayOutputStream();
                    image.compress(Bitmap.CompressFormat.JPEG,50, baos);
                    b=baos.toByteArray();
                    temp=Base64.encodeToString(b, Base64.DEFAULT);
                    Log.e("EWN", "Out of memory error catched");
                }
                return temp;
        }
    
    
        public static Bitmap decodeBase64(String input,Context context) {
            byte[] decodedByte = Base64.decode(input, 0);
    
            appContext = context;
            Boolean isSDPresent = android.os.Environment
                    .getExternalStorageState().equals(
                            android.os.Environment.MEDIA_MOUNTED);
    
            File sdCardDirectory;
            if (isSDPresent) {
                // yes SD-card is present
                 sdCardDirectory = new File(
                            Environment
                                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                            "IMG");
    
                    if (!sdCardDirectory.exists()) {
                        if (!sdCardDirectory.mkdirs()) {
                            Log.d("MySnaps", "failed to create directory");
    
                        }
                    }
            } else {
                // Sorry
                sdCardDirectory = new File(context.getCacheDir(),"");
            }
    
    
    
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
                    .format(new Date());
    
            Random rand = new Random();
    
            // nextInt is normally exclusive of the top value,
            // so add 1 to make it inclusive
            int randomNum = rand.nextInt((1000 - 0) + 1) + 0;
    
            String nw = "IMG_" + timeStamp + randomNum+".txt";
            File image = new File(sdCardDirectory, nw);
    
    
    
            // Encode the file as a PNG image.
            FileOutputStream outStream;
            try {
    
    
                outStream = new FileOutputStream(image);
                outStream.write(input.getBytes());
    
                outStream.flush();
                outStream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
            Log.i("Compress bitmap path", image.getPath());
            Bitmap bitmap;
            try{
            bitmap = BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length);
            }catch(OutOfMemoryError e){
                e.printStackTrace();
                InputStream is = context.getResources().openRawResource(R.drawable.default_profile_pic);
                bitmap  = BitmapFactory.decodeStream(is);  
    
            }catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
                bitmap=null;
            }
    
            return bitmap;//BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length);
            //return decodeFile(image); 
        }
    
        private static Bitmap decodeFile(File f){
            try {
                //Decode image size
                BitmapFactory.Options o = new BitmapFactory.Options();
                o.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(new FileInputStream(f),null,o);
    
                //The new size we want to scale to
                final int REQUIRED_SIZE=70;
    
                //Find the correct scale value. It should be the power of 2.
                int scale=1;
                while(o.outWidth/scale>=REQUIRED_SIZE && o.outHeight/scale>=REQUIRED_SIZE)
                    scale*=2;
    
                //Decode with inSampleSize
                BitmapFactory.Options o2 = new BitmapFactory.Options();
                o2.inSampleSize=scale;
    
                Bitmap bmp = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    
    
                Boolean isSDPresent = android.os.Environment
                        .getExternalStorageState().equals(
                                android.os.Environment.MEDIA_MOUNTED);
                File sdCardDirectory;
                if (isSDPresent) {
                    // yes SD-card is present
                     sdCardDirectory = new File(
                                Environment
                                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                                "IMG");
    
                        if (!sdCardDirectory.exists()) {
                            if (!sdCardDirectory.mkdirs()) {
                                Log.d("MySnaps", "failed to create directory");
    
                            }
                        }
                } else {
                    // Sorry
                    sdCardDirectory = new File(appContext.getCacheDir(),"");
                }
    
    
    
                String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
                        .format(new Date());
    
                Random rand = new Random();
    
                // nextInt is normally exclusive of the top value,
                // so add 1 to make it inclusive
                int randomNum = rand.nextInt((1000 - 0) + 1) + 0;
    
                String nw = "IMG_" + timeStamp + randomNum+".png";
                File image = new File(sdCardDirectory, nw);
    
    
    
                FileOutputStream out = null;
                try {
                    out = new FileOutputStream(image);
                    bmp.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap instance
                    // PNG is a lossless format, the compression factor (100) is ignored
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (out != null) {
                            out.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
                String pathNew =compressImage(image.getAbsolutePath());
                Uri uri = Uri.parse(pathNew);   
                Bitmap bitmap=null ;
                try {
                     bitmap     = MediaStore.Images.Media.getBitmap(appContext.getContentResolver(),uri);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
                return bitmap;
            } catch (FileNotFoundException e) {}
            return null;
        }
    
    
        public static String compressImage(String imageUri) {
    
            String filePath = imageUri;//getRealPathFromURI(imageUri);
            Bitmap scaledBitmap = null;
    
            BitmapFactory.Options options = new BitmapFactory.Options();
    
    //      by setting this field as true, the actual bitmap pixels are not loaded in the memory. Just the bounds are loaded. If
    //      you try the use the bitmap here, you will get null.
            options.inJustDecodeBounds = true;
            Bitmap bmp = BitmapFactory.decodeFile(filePath, options);
    
            int actualHeight = options.outHeight;
            int actualWidth = options.outWidth;
    
    //      max Height and width values of the compressed image is taken as 816x612
    
            float maxHeight = 816.0f;
            float maxWidth = 612.0f;
            float imgRatio = actualWidth / actualHeight;
            float maxRatio = maxWidth / maxHeight;
    
    //      width and height values are set maintaining the aspect ratio of the image
    
            if (actualHeight > maxHeight || actualWidth > maxWidth) {
                if (imgRatio < maxRatio) {               imgRatio = maxHeight / actualHeight;                actualWidth = (int) (imgRatio * actualWidth);               actualHeight = (int) maxHeight;             } else if (imgRatio > maxRatio) {
                    imgRatio = maxWidth / actualWidth;
                    actualHeight = (int) (imgRatio * actualHeight);
                    actualWidth = (int) maxWidth;
                } else {
                    actualHeight = (int) maxHeight;
                    actualWidth = (int) maxWidth;
    
                }
            }
    
    //      setting inSampleSize value allows to load a scaled down version of the original image
    
            options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight);
    
    //      inJustDecodeBounds set to false to load the actual bitmap
            options.inJustDecodeBounds = false;
    
    //      this options allow android to claim the bitmap memory if it runs low on memory
            options.inPurgeable = true;
            options.inInputShareable = true;
            options.inTempStorage = new byte[16 * 1024];
    
            try {
    //          load the bitmap from its path
                bmp = BitmapFactory.decodeFile(filePath, options);
            } catch (OutOfMemoryError exception) {
                exception.printStackTrace();
    
            }
            try {
                scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight,Bitmap.Config.ARGB_8888);
            } catch (OutOfMemoryError exception) {
                exception.printStackTrace();
            }
    
            float ratioX = actualWidth / (float) options.outWidth;
            float ratioY = actualHeight / (float) options.outHeight;
            float middleX = actualWidth / 2.0f;
            float middleY = actualHeight / 2.0f;
    
            Matrix scaleMatrix = new Matrix();
            scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
    
            Canvas canvas = new Canvas(scaledBitmap);
            canvas.setMatrix(scaleMatrix);
            canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
    
    //      check the rotation of the image and display it properly
            ExifInterface exif;
            try {
                exif = new ExifInterface(filePath);
    
                int orientation = exif.getAttributeInt(
                        ExifInterface.TAG_ORIENTATION, 0);
                Log.d("EXIF", "Exif: " + orientation);
                Matrix matrix = new Matrix();
                if (orientation == 6) {
                    matrix.postRotate(90);
                    Log.d("EXIF", "Exif: " + orientation);
                } else if (orientation == 3) {
                    matrix.postRotate(180);
                    Log.d("EXIF", "Exif: " + orientation);
                } else if (orientation == 8) {
                    matrix.postRotate(270);
                    Log.d("EXIF", "Exif: " + orientation);
                }
                scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0,
                        scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix,
                        true);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            FileOutputStream out = null;
            String filename = getFilename();
            try {
                out = new FileOutputStream(filename);
    
    //          write the compressed bitmap at the destination specified by filename.
                scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
    
            return filename;
    
        }
    
        public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
            final int height = options.outHeight;
            final int width = options.outWidth;
            int inSampleSize = 1;
    
            if (height > reqHeight || width > reqWidth) {
                final int heightRatio = Math.round((float) height/ (float) reqHeight);
                final int widthRatio = Math.round((float) width / (float) reqWidth);
                inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;      }       final float totalPixels = width * height;       final float totalReqPixelsCap = reqWidth * reqHeight * 2;       while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
    
            return inSampleSize;
        }
    
        public static String getFilename() {
            /*File file = new File(Environment.getExternalStorageDirectory().getPath(), "IMG/Images");
            if (!file.exists()) {
                file.mkdirs();
            }
            String uriSting = (file.getAbsolutePath() + "/" + System.currentTimeMillis() + ".jpg");
            */
    
    
            Boolean isSDPresent = android.os.Environment
                    .getExternalStorageState().equals(
                            android.os.Environment.MEDIA_MOUNTED);
    
            File sdCardDirectory;
            if (isSDPresent) {
                // yes SD-card is present
                 sdCardDirectory = new File(
                            Environment
                                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath(),
                            "IMG/Images");
    
                    if (!sdCardDirectory.exists()) {
                        if (!sdCardDirectory.mkdirs()) {
                            Log.d("MySnaps", "failed to create directory");
    
                        }
                    }
            } else {
                // Sorry
                sdCardDirectory = new File(appContext.getCacheDir(),"");
            }
    
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
            .format(new Date());
    
            Random rand = new Random();
    
    // nextInt is normally exclusive of the top value,
    // so add 1 to make it inclusive
            int randomNum = rand.nextInt((1000 - 0) + 1) + 0;
    
            String nw = "img_" + timeStamp + randomNum+".jpg";
            File image = new File(sdCardDirectory, nw);
    
             String uriSting1 = (sdCardDirectory.getAbsolutePath() + "/" + nw);//System.currentTimeMillis() + ".jpg");
    
            return uriSting1;
    
        }
    }
    

    Add this class to handle heavy images

    public class BitmapHelper
    {
    
        //decodes image and scales it to reduce memory consumption
        public static Bitmap decodeFile(String bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
        {
            try
            {
                //Decode image size
                BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
                bitmapSizeOptions.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
    
                // load image using inSampleSize adapted to required image size
                BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
                bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
                bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
                bitmapDecodeOptions.inPurgeable = true;
                bitmapDecodeOptions.inDither = !quickAndDirty;
                bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
    
                Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
    
                // scale bitmap to mathc required size (and keep aspect ratio)
    
                float srcWidth = (float) bitmapDecodeOptions.outWidth;
                float srcHeight = (float) bitmapDecodeOptions.outHeight;
    
                float dstWidth = (float) requiredWidth;
                float dstHeight = (float) requiredHeight;
    
                float srcAspectRatio = srcWidth / srcHeight;
                float dstAspectRatio = dstWidth / dstHeight;
    
                // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
                // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
                // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
                // I do not excatly understand why, but this way it's OK
    
                boolean recycleDecodedBitmap = false;
    
                Bitmap scaledBitmap = decodedBitmap;
                if (srcAspectRatio < dstAspectRatio)
                {
                    scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                    // will recycle recycleDecodedBitmap
                    recycleDecodedBitmap = true;
                }
                else if (srcAspectRatio > dstAspectRatio)
                {
                    scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                    recycleDecodedBitmap = true;
                }
    
                // crop image to match required image size
    
                int scaledBitmapWidth = scaledBitmap.getWidth();
                int scaledBitmapHeight = scaledBitmap.getHeight();
    
                Bitmap croppedBitmap = scaledBitmap;
    
                if (scaledBitmapWidth > requiredWidth)
                {
                    int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                    croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                    scaledBitmap.recycle();
                }
                else if (scaledBitmapHeight > requiredHeight)
                {
                    int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                    croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                    scaledBitmap.recycle();
                }
    
                if (recycleDecodedBitmap)
                {
                    decodedBitmap.recycle();
                }
                decodedBitmap = null;
    
                scaledBitmap = null;
                return croppedBitmap;
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
            return null;
        }
    
        /**
         * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
         * 
         * @param requiredWidth
         * @param requiredHeight
         * @param powerOf2
         *            weither we want a power of 2 sclae or not
         * @return
         */
        public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
        {
            int inSampleSize = 1;
    
            // Raw height and width of image
            final int srcHeight = options.outHeight;
            final int srcWidth = options.outWidth;
    
            if (powerOf2)
            {
                //Find the correct scale value. It should be the power of 2.
    
                int tmpWidth = srcWidth, tmpHeight = srcHeight;
                while (true)
                {
                    if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                        break;
                    tmpWidth /= 2;
                    tmpHeight /= 2;
                    inSampleSize *= 2;
                }
            }
            else
            {
                // Calculate ratios of height and width to requested height and width
                final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
                final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
    
                // Choose the smallest ratio as inSampleSize value, this will guarantee
                // a final image with both dimensions larger than or equal to the
                // requested height and width.
                inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
            }
    
            return inSampleSize;
        }
    
        public static Bitmap drawableToBitmap(Drawable drawable)
        {
            if (drawable instanceof BitmapDrawable)
            {
                return ((BitmapDrawable) drawable).getBitmap();
            }
    
            Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
    
            return bitmap;
        }
    
        public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
        {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            float scaleWidth = ((float) newWidth) / width;
            float scaleHeight = ((float) newHeight) / height;
    
            // CREATE A MATRIX FOR THE MANIPULATION
            Matrix matrix = new Matrix();
            // RESIZE THE BIT MAP
            matrix.postScale(scaleWidth, scaleHeight);
    
            // RECREATE THE NEW BITMAP
            Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
            return resizedBitmap;
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题