We discussed earlier in Part 1 of this tutorial how Zingoo team wants to deliver the best UX possible to the users, & because WhatsApp is doing a great job with images, so we watched what they are doing, like:
- Photos are cached, so no need to load them every time you open the app.
- They first show a very small thumbnail (about 10KB or less) until the real image is loaded, & this is the real pro-tip for their better UX.
- Photos sizes range around 100KB, which loads the images pretty fast on most common mobile-network speeds.
First 2 points are already discussed in part 1, so question here in part 2 is how could we compress images to be about 100KB to be sent easily over common mobile-networks.
How is it done?
After taking the picture and saving it to a file with path imagePath
, we start compressing it to be ready for sending over network:
ImageCompressionAsyncTask imageCompression = new ImageCompressionAsyncTask() { @Override protected void onPostExecute(byte[] imageBytes) { // image here is compressed & ready to be sent to the server } }; imageCompression.execute(imagePath);// imagePath as a string
& here is what we do in ImageCompressionAsyncTask
:
public abstract class ImageCompressionAsyncTask extends AsyncTask<String, Void, byte[]> { @Override protected byte[] doInBackground(String... strings) { if(strings.length == 0 || strings[0] == null) return null; return ImageUtils.compressImage(strings[0]); } protected abstract void onPostExecute(byte[] imageBytes) ; }
It is clear that the real juice exists in ImageUtils.compressImage()
. Thanks to Ambalika Saha & her brilliant post that enabled me from using this solution in Zingoo, and even writing this post right here 🙂 . And here is my version of doing it:
public class ImageUtils { private static final float maxHeight = 1280.0f; private static final float maxWidth = 1280.0f; public static byte[] compressImage(String imagePath) { Bitmap scaledBitmap = null; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bmp = BitmapFactory.decodeFile(imagePath, options); int actualHeight = options.outHeight; int actualWidth = options.outWidth; float imgRatio = (float) actualWidth / (float) actualHeight; float maxRatio = maxWidth / maxHeight; if (actualHeight > maxHeight || actualWidth > maxWidth) { if (imgRatio < maxRatio) { imgRatio = maxHeight / actualHeight; actualWidth = (int) (imgRatio * actualWidth); actualHeight = (int) maxHeight; } else if (imgRatio > maxRatio) { imgRatio = maxWidth / actualWidth; actualHeight = (int) (imgRatio * actualHeight); actualWidth = (int) maxWidth; } else { actualHeight = (int) maxHeight; actualWidth = (int) maxWidth; } } options.inSampleSize = ImageUtils.calculateInSampleSize(options, actualWidth, actualHeight); options.inJustDecodeBounds = false; options.inDither = false; options.inPurgeable = true; options.inInputShareable = true; options.inTempStorage = new byte[16 * 1024]; try { bmp = BitmapFactory.decodeFile(imagePath, options); } catch (OutOfMemoryError exception) { exception.printStackTrace(); } try { scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888); } catch (OutOfMemoryError exception) { exception.printStackTrace(); } float ratioX = actualWidth / (float) options.outWidth; float ratioY = actualHeight / (float) options.outHeight; float middleX = actualWidth / 2.0f; float middleY = actualHeight / 2.0f; Matrix scaleMatrix = new Matrix(); scaleMatrix.setScale(ratioX, ratioY, middleX, middleY); Canvas canvas = new Canvas(scaledBitmap); canvas.setMatrix(scaleMatrix); canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG)); ExifInterface exif; try { exif = new ExifInterface(imagePath); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); Matrix matrix = new Matrix(); if (orientation == 6) { matrix.postRotate(90); } else if (orientation == 3) { matrix.postRotate(180); } else if (orientation == 8) { matrix.postRotate(270); } scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); } catch (IOException e) { e.printStackTrace(); } ByteArrayOutputStream out = new ByteArrayOutputStream(); scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 85, out); return out.toByteArray(); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } final float totalPixels = width * height; final float totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { inSampleSize++; } return inSampleSize; } }
I hope you like it.