From 5b298b4a043989ac1ae097792819a1d0d4d841d7 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Fri, 10 May 2019 12:16:19 -0300 Subject: [PATCH] Resize image in attempts to get it to fit into the maxImageSize bytes. Fixes #8803 --- .../securesms/util/BitmapUtil.java | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java index c73ce7c8f7..1fd89864b0 100644 --- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -10,14 +10,15 @@ import android.graphics.Rect; import android.graphics.YuvImage; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.support.annotation.*; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.media.ExifInterface; -import org.thoughtcrime.securesms.logging.Log; import android.util.Pair; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.MediaConstraints; @@ -26,6 +27,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; @@ -42,9 +44,10 @@ public class BitmapUtil { private static final int MIN_COMPRESSION_QUALITY = 45; private static final int MAX_COMPRESSION_ATTEMPTS = 5; private static final int MIN_COMPRESSION_QUALITY_DECREASE = 5; + private static final int MAX_IMAGE_HALF_SCALES = 3; @WorkerThread - public static ScaleResult createScaledBytes(Context context, T model, MediaConstraints constraints) + public static ScaleResult createScaledBytes(@NonNull Context context, @NonNull T model, @NonNull MediaConstraints constraints) throws BitmapDecodingException { return createScaledBytes(context, model, @@ -54,7 +57,24 @@ public class BitmapUtil { } @WorkerThread - public static ScaleResult createScaledBytes(Context context, T model, int maxImageWidth, int maxImageHeight, int maxImageSize) + public static ScaleResult createScaledBytes(@NonNull Context context, + @NonNull T model, + final int maxImageWidth, + final int maxImageHeight, + final int maxImageSize) + throws BitmapDecodingException + { + return createScaledBytes(context, model, maxImageWidth, maxImageHeight, maxImageSize, 1, 0); + } + + @WorkerThread + private static ScaleResult createScaledBytes(@NonNull Context context, + @NonNull T model, + final int maxImageWidth, + final int maxImageHeight, + final int maxImageSize, + final int sizeAttempt, + int totalAttempts) throws BitmapDecodingException { try { @@ -75,15 +95,17 @@ public class BitmapUtil { throw new BitmapDecodingException("Unable to decode image"); } - Log.i(TAG, "Initial scaled bitmap has size of " + scaledBitmap.getByteCount() + " bytes."); + Log.i(TAG, String.format(Locale.US,"Initial scaled bitmap has size of %d bytes.", scaledBitmap.getByteCount())); + Log.i(TAG, String.format(Locale.US, "Max dimensions %d x %d, %d bytes", maxImageWidth, maxImageHeight, maxImageSize)); try { do { + totalAttempts++; ByteArrayOutputStream baos = new ByteArrayOutputStream(); scaledBitmap.compress(CompressFormat.JPEG, quality, baos); bytes = baos.toByteArray(); - Log.d(TAG, "iteration with quality " + quality + " size " + (bytes.length / 1024) + "kb"); + Log.d(TAG, "iteration with quality " + quality + " size " + bytes.length + " bytes."); if (quality == MIN_COMPRESSION_QUALITY) break; int nextQuality = (int)Math.floor(quality * Math.sqrt((double)maxImageSize / bytes.length)); @@ -95,14 +117,22 @@ public class BitmapUtil { while (bytes.length > maxImageSize && attempts++ < MAX_COMPRESSION_ATTEMPTS); if (bytes.length > maxImageSize) { - throw new BitmapDecodingException("Unable to scale image below: " + bytes.length); + if (sizeAttempt <= MAX_IMAGE_HALF_SCALES) { + scaledBitmap.recycle(); + scaledBitmap = null; + + Log.i(TAG, "Halving dimensions and retrying."); + return createScaledBytes(context, model, maxImageWidth / 2, maxImageHeight / 2, maxImageSize, sizeAttempt + 1, totalAttempts); + } else { + throw new BitmapDecodingException("Unable to scale image below " + bytes.length + " bytes."); + } } if (bytes.length <= 0) { throw new BitmapDecodingException("Decoding failed. Bitmap has a length of " + bytes.length + " bytes."); } - Log.i(TAG, "createScaledBytes(" + model.toString() + ") -> quality " + Math.min(quality, MAX_COMPRESSION_QUALITY) + ", " + attempts + " attempt(s)"); + Log.i(TAG, String.format(Locale.US, "createScaledBytes(%s) -> quality %d, %d attempt(s) over %d sizes.", model.getClass().getName(), quality, totalAttempts, sizeAttempt)); return new ScaleResult(bytes, scaledBitmap.getWidth(), scaledBitmap.getHeight()); } finally {