How to reduce image size into 1MB

10,547

Solution 1

you can use this code to resize a bitmap and for image size < 1MB i recommend use resolution of 480x640

public Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
        int width = bm.getWidth();
        int height = bm.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
        return Bitmap.createBitmap(
                bm, 0, 0, width, height, matrix, false);
    }

Solution 2

If you really want the Bitmap that scales down to the Bitmap that is the closest to a given amount of bytes, heres the method I use. (It does not uses a while loop)

NOTE: This method only works if passed bitmap is in ARGB_8888 configuration. See: Compress bitmap to a specific byte size in Android for the conversion method.

/**
 * Method to scale the Bitmap to respect the max bytes
 *
 * @param input    the Bitmap to scale if too large
 * @param maxBytes the amount of bytes the Image may be
 * @return The scaled bitmap or the input if already valid
 * @Note: The caller of this function is responsible for recycling once the input is no longer needed
 */
public static Bitmap scaleBitmap(final Bitmap input, final long maxBytes) {
    final int currentWidth = input.getWidth();
    final int currentHeight = input.getHeight();
    final int currentPixels = currentWidth * currentHeight;
    // Get the amount of max pixels:
    // 1 pixel = 4 bytes (R, G, B, A)
    final long maxPixels = maxBytes / 4; // Floored
    if (currentPixels <= maxPixels) {
        // Already correct size:
        return input;
    }
    // Scaling factor when maintaining aspect ratio is the square root since x and y have a relation:
    final double scaleFactor = Math.sqrt(maxPixels / (double) currentPixels);
    final int newWidthPx = (int) Math.floor(currentWidth * scaleFactor);
    final int newHeightPx = (int) Math.floor(currentHeight * scaleFactor);
    Timber.i("Scaled bitmap sizes are %1$s x %2$s when original sizes are %3$s x %4$s and currentPixels %5$s and maxPixels %6$s and scaled total pixels are: %7$s",
            newWidthPx, newHeightPx, currentWidth, currentHeight, currentPixels, maxPixels, (newWidthPx * newHeightPx));
    final Bitmap output = Bitmap.createScaledBitmap(input, newWidthPx, newHeightPx, true);
    return output;
}

Where the Sample use would look something like:

// (1 MB)
final long maxBytes = 1024 * 1024; 
// Scale it
final Bitmap scaledBitmap = BitmapUtils.scaleBitmap(yourBitmap, maxBytes);
if(scaledBitmap != yourBitmap){
    // Recycle the bitmap since we can use the scaled variant:
    yourBitmap.recycle();
} 
// ... do something with the scaled bitmap

Solution 3

I've tried to comment it but the comment become too big. So, I've tested many solutions and it seems like there are only TWO solutions for this problem, which give some results.

Lets discuss Koen solution first. What it actually does is creates a scaled JPG

Bitmap.createScaledBitmap(input, newWidthPx, newHeightPx, true)

Seems like it does not compress it at all but just cuts off the resolution.

I've tested this code and when I pass MAX_IMAGE_SIZE = 1024000 it gives me 350kb compressed image out of 2.33Mb original image. Bug? Also it lacks quality. I was unable to recognize a text on A4 sheet of paper photo made by Google Pixel.

There is another solution to this problem, which gives good quality, but lacks in speed. A WHILE LOOP! Basically you just loop through image size, until you get the desired size

private fun scaleBitmap() {
    if (originalFile.length() > MAX_IMAGE_SIZE) {
        var streamLength = MAX_IMAGE_SIZE
        var compressQuality = 100
        val bmpStream = ByteArrayOutputStream()
        while (streamLength >= MAX_IMAGE_SIZE) {
            bmpStream.use {
                it.flush()
                it.reset()
            }

            compressQuality -= 8
            val bitmap = BitmapFactory.decodeFile(originalFile.absolutePath, BitmapFactory.Options())
            bitmap.compress(Bitmap.CompressFormat.JPEG, compressQuality, bmpStream)
            streamLength = bmpStream.toByteArray().size
        }

        FileOutputStream(compressedFile).use {
            it.write(bmpStream.toByteArray())
        }
    }
}

I think that this approach will consume exponential time depending on image resolution. 9mb image takes up to 12 seconds to compress down to 1mb. Quality is good. You can tweak this by reducing the original bitmap resolution(which seems like a constant operation), by doing:

options.inSampleSize = 2;

What we need to do is to somehow calculate compressQuality for any image. There should be a math around this, so we can determinate compressQuality from original image size or width + height.

Share:
10,547
Admin
Author by

Admin

Updated on June 07, 2022

Comments

  • Admin
    Admin almost 2 years

    I want my application to upload image with no size limit, but in the code, I want to resize the image into 1MB if the image size exceeds. I have tried many ways but I couldn't find any code for the requirement I have mentioned above.

    For once, I have tried this:

    public void scaleDown() {
        int width = stdImageBmp.getWidth();
        int height = stdImageBmp.getHeight();
        Matrix matrix = new Matrix();
        float scaleWidth = ((float) MAX_WIDTH) / width;
        float scaleHeight = ((float) MAX_HEIGHT) / height;
    
    
        matrix.postScale(scaleWidth, scaleHeight);
        stdImageBmp = Bitmap.createBitmap(stdImageBmp, 0, 0, width, height, matrix, true);
    
        File Image = new File("path");
    
    
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //compress bmp
        stdImageBmp.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
        byte[] byteArray = byteArrayOutputStream.toByteArray();
    
    
        imgViewStd.setImageBitmap(stdImageBmp);
        Log.d("resizedBitmap", stdImageBmp.toString());
    
        width = stdImageBmp.getWidth();
        height = stdImageBmp.getHeight();
        System.out.println("imgWidth" + width);
        System.out.println("imgHeight" + height);
    }