0

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

Louis
  • 1,913
  • 2
  • 28
  • 41
  • JPEG is already a **compressed** format. Moreover, it uses a **lossy** compression (meaning that every time you save it it will loose some quality). So, what's the point of recompressing and lowering the quality of your pictures? – Phantômaxx Dec 08 '17 at 15:49
  • @NoiseGenerator because the server which I am uploading this JPG file to has a fixed maximum size (otherwise my request will fail), and also because I do not need a super high quality for this picture. – Louis Dec 08 '17 at 16:11
  • Your best bet: change server. Or set up your own. – Phantômaxx Dec 08 '17 at 17:37
  • @NoiseGenerator The server is part of the constraints :) – Louis Dec 11 '17 at 10:41

6 Answers6

2

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.

Louis
  • 1,913
  • 2
  • 28
  • 41
1

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.

user3344003
  • 20,574
  • 3
  • 26
  • 62
0

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.

lmaooooo
  • 3,364
  • 4
  • 19
  • 24
  • IM 6 is rather forgiving. But proper IM 6 syntax would read the input image first, then the other options, then the output. IM 7 is not as forgiving. So best to start using proper syntax. – fmw42 Dec 08 '17 at 18:24
  • @Makz Why is it better to tweak the quality parameter of ImageMagick rather than the compression parameter of the Bitmap ? I feel like it is the same thing. – Louis Dec 11 '17 at 10:40
  • @Louis yes it's probably the same thing. You may want to use Bitmap over ImageMagick. – lmaooooo Dec 12 '17 at 11:57
0

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

Shivam Mathur
  • 111
  • 1
  • 3
0

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.

Bonzo
  • 5,169
  • 1
  • 19
  • 27
  • `jpeg:extent` was introduced in version 6.5.8-1. It uses binary search, so it should find the correct quality setting within 8 tries. Versions 6.9.1-0 to 6.9.2-4 had a bug where you had to add `-quality 100` as well (ignored on higher versions). By the way, `kB` or `KB` means 1000 bytes; `KiB` means 1024 bytes. – Silas S. Brown Sep 05 '18 at 13:06
-1

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
    });
}    
dlemstra
  • 7,813
  • 2
  • 27
  • 43