Reputation: 2023
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
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
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
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
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
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:
Still, it's mostly trial and error.
Upvotes: 1
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
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
Try tweaking the quality parameter. If you decrease it, it increases compression and reduces file size.
Upvotes: 0