Louis
Louis

Reputation: 2023

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).

Upvotes: 0

Views: 2979

Answers (6)

Louis
Louis

Reputation: 2023

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.

Upvotes: 2

Bonzo
Bonzo

Reputation: 5299

Did not notice the reply from @dlemstra

Imagemagick has a file size option but it is slower as from memory it is trying different options for the output size and is not exact:

convert input -define jpeg:extent=500kb output.jpg

Of course -define may not be supported on your phone.

Upvotes: 0

Shivam Mathur
Shivam Mathur

Reputation: 111

For better picture quality first, save the file to storage and then compress it. It helps the user to have the best quality image for there own use. In some of my projects, I am using this library https://android-arsenal.com/details/1/3758 this is a very good library and it compresses well. If you need a File then use this

compressedImage = new Compressor(this)
        .setMaxWidth(640)
        .setMaxHeight(480)
        .setQuality(75)
        .setCompressFormat(Bitmap.CompressFormat.WEBP)
        .setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory(
          Environment.DIRECTORY_PICTURES).getAbsolutePath())
        .compressToFile(actualImage);

or if you need a bitmap than this

compressedImageBitmap = new Compressor(this).compressToBitmap(actualImageFile);

Happy Coding :)

Upvotes: 0

user3344003
user3344003

Reputation: 21627

The problem you face is that the compression depends upon the nature of the image. My guess is that there is academic research that looks at the image and selects the parameters to reach a given size.

In JPEG you have several settings to reduce size:

  1. The inclusion or exclusion of COM and APPn markers.
  2. Whether optimized or off-the-shelf Huffman tables are used.
  3. The selection of quantization tables.
  4. Subsampling of Cb and Cr components.
  5. Progressive or Sequential encoding (if the former, you have an plethora of scan settings at your disposal).

Still, it's mostly trial and error.

Upvotes: 1

dlemstra
dlemstra

Reputation: 8153

You could use Magick.NET (https://github.com/dlemstra/Magick.NET) for this:

using (var image = new MagickImage(capture))
{
    return image.ToByteArray(new JpegWriteDefines()
    {
        // Will make the maximum size 500KB (it might be smaller)
        Extent = 500
    });
}    

Upvotes: -1

lmaooooo
lmaooooo

Reputation: 3666

It's hard to compress to a given size, I suggest using ImageMagick and tweaking the options until you get to a size you feel comfortable using.

I use always ImageMagick. Consider this command

convert -strip -interlace Plane -gaussian-blur 0.05 -quality 85% source.jpg result.jpg

The command uses these options:

  • quality in 85
  • progressive (comprobed compression)
  • a very tiny gausssian blur to optimize the size (0.05 or 0.5 of radius) depends on the quality and size of the picture, this notably optimizes the size of the jpeg.
  • Strip any comment or exif tag

Source

Try tweaking the quality parameter. If you decrease it, it increases compression and reduces file size.

Upvotes: 0

Related Questions