Search code examples
androidbitmapbytejpeg

Compressing a JPG image to a given size in KB?


I have a byte array returned by the camera in input.

I need to scale down my image to make its size equal to 500KB. I am trying to achieve this using a Bitmap, but I cannot find how to get the proper compression value.

public static byte[] compressCapture(byte[] capture) {

    // How to get the right compression value ?
    int compression = 50;

    Bitmap bitmap  = BitmapFactory.decodeByteArray(capture, 0, capture.length);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, compression, outputStream);
    return outputStream.toByteArray();
}

The problem is that the compression is not proportional to the image size, meaning that compression equals to 50 will not divide the image size by 2.

Is there a way to get a fixed image size in output that does not depend on the focus, or the smartphone model ?

EDIT :

I do not want to use a File (I work with confidential data).


Solution

  • UPDATE 01/16/2018

    The simplest approach is to use BitmapFactory.Options.inSampleSize to decode the byte array and compress it at the same time. Here is the doc

     BitmapFactory.Options options = new BitmapFactory.Options();
     options.inSampleSize = 4; // If you want an image four times smaller than the original
     Bitmap decoded = BitmapFactory.decodeByteArray(data, 0, data.length, options);
    

    OLD ANSWER, PLEASE DON'T USE THIS

    Since there is apparently no way to achieve this in one shot, I implemented an iterative process to reach a given size in KB.

    I start with a compression coefficient equal to 80, and if it is not enough I decrease this coefficient and I retry until I get a size below my threshold.

    static COMPRESSION_PERCENTAGE_START = 80;
    static IMAGE_COMPRESSION_EXPECTED_MAX_ITERATIONS = 3;
    static IMAGE_COMPRESSION_STEP_PERCENT = 5;
    
    // For logging
    static IMAGE_COMPRESSION_EXPECTED_MAX_ITERATIONS = 5;
    
    static byte[] compressCapture(byte[] capture, int maxSizeKB) {
        long maxSizeByte = ((long) maxSizeKB) * 1_000;
    
        if (capture.length <= maxSizeByte) return capture;
    
        byte[] compressed = capture;
    
        // Chosen arbitrarily so that we can compress multiple times to reach the expected size.
        int compression = COMPRESSION_PERCENTAGE_START;
    
        Bitmap bitmap = BitmapFactory.decodeByteArray(capture, 0, capture.length);
        ByteArrayOutputStream outputStream;
        int iterations = 0;
        while (compressed.length > maxSizeByte) {
            // Just a counter
            iterations++;
    
            compression -= IMAGE_COMPRESSION_STEP_PERCENT;
    
            outputStream = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, compression, outputStream);
            compressed = outputStream.toByteArray();
        }
    
        if (iterations > IMAGE_COMPRESSION_EXPECTED_MAX_ITERATIONS) {
            Timber.w("Compression process has iterated more than expected, with " + iterations + " iterations.");
        }
    
        return compressed;
    }
    

    Here is the output size for an original size of 1_871_058 bytes

    • Iteration #1 (compression equal to 80) : 528_593 bytes
    • Iteration #2 (compression equal to 75) : 456_591 bytes

    It does the job for me but please be careful if you use that, it certainly needs some fine tuning and I just tested it on a given smartphone model.