From 3ce17806214b6c58af8d5d88c5f26ea0e96f5537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Wed, 8 Mar 2023 16:28:23 -0300 Subject: [PATCH 01/11] Improved Bitmap resize on Android --- .../image_picker_android/CHANGELOG.md | 1 + .../plugins/imagepicker/ImageResizer.java | 52 +++++++++++++++---- .../plugins/imagepicker/ImageResizerTest.java | 37 +++++++++++++ 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index bc0c6c46388..5bc13e50e77 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.8.5+10 +* Improved Bitmap resizing. * Clarifies explanation of endorsement in README. * Aligns Dart and Flutter SDK constraints. diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java index 2a93785678a..9c230ed581d 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java @@ -6,6 +6,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Point; import android.util.Log; import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; @@ -33,8 +34,10 @@ String resizeImageIfNeeded( @Nullable Double maxWidth, @Nullable Double maxHeight, @Nullable Integer imageQuality) { - Bitmap bmp = decodeFile(imagePath); - if (bmp == null) { + BitmapFactory.Options queryOptions = new BitmapFactory.Options(); + queryOptions.inJustDecodeBounds = true; + decodeFile(imagePath, queryOptions); + if (queryOptions.outWidth == -1 || queryOptions.outHeight == -1) { return null; } boolean shouldScale = @@ -45,7 +48,18 @@ String resizeImageIfNeeded( try { String[] pathParts = imagePath.split("/"); String imageName = pathParts[pathParts.length - 1]; - File file = resizedImage(bmp, maxWidth, maxHeight, imageQuality, imageName); + Point size = + calculateSize( + Double.valueOf(queryOptions.outWidth), + Double.valueOf(queryOptions.outHeight), + maxWidth, + maxHeight); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = calculateInSampleSize(options, size.x, size.y); + options.inJustDecodeBounds = false; + File file = + resizedImage( + decodeFile(imagePath, options), maxWidth, maxHeight, imageQuality, imageName); copyExif(imagePath, file.getPath()); return file.getPath(); } catch (IOException e) { @@ -63,6 +77,15 @@ private File resizedImage( imageQuality = 100; } + Point size = calculateSize(originalWidth, originalHeight, maxWidth, maxHeight); + Bitmap scaledBmp = createScaledBitmap(bmp, size.x, size.y, false); + File file = + createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality); + return file; + } + + private Point calculateSize( + Double originalWidth, Double originalHeight, Double maxWidth, Double maxHeight) { boolean hasMaxWidth = maxWidth != null; boolean hasMaxHeight = maxHeight != null; @@ -98,10 +121,7 @@ private File resizedImage( } } - Bitmap scaledBmp = createScaledBitmap(bmp, width.intValue(), height.intValue(), false); - File file = - createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality); - return file; + return new Point(width.intValue(), height.intValue()); } private File createFile(File externalFilesDirectory, String child) { @@ -120,8 +140,8 @@ private void copyExif(String filePathOri, String filePathDest) { exifDataCopier.copyExif(filePathOri, filePathDest); } - private Bitmap decodeFile(String path) { - return BitmapFactory.decodeFile(path); + private Bitmap decodeFile(String path, @Nullable BitmapFactory.Options opts) { + return BitmapFactory.decodeFile(path, opts); } private Bitmap createScaledBitmap(Bitmap bmp, int width, int height, boolean filter) { @@ -132,6 +152,20 @@ private boolean isImageQualityValid(Integer imageQuality) { return imageQuality != null && imageQuality > 0 && imageQuality < 100; } + private 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 halfHeight = height / 2; + final int halfWidth = width / 2; + while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } + return inSampleSize; + } + private File createImageOnExternalDirectory(String name, Bitmap bitmap, int imageQuality) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java index a3710038379..aa09bf52273 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java @@ -6,16 +6,25 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.io.File; import java.io.IOException; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -78,4 +87,32 @@ public void onResizeImageIfNeeded_WhenParentDirectoryDoesNotExists_ShouldNotCras String outoutFile = invalidResizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null); assertThat(outoutFile, equalTo(nonExistentDirectory.getPath() + "/scaled_pngImage.png")); } + + @Test + public void onResizeImageIfNeeded_WhenResizeIsNotNecessary_ShouldOnlyQueryBitmap() { + try (MockedStatic mockBitmapFactory = + mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { + String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null); + ArgumentCaptor argument = + ArgumentCaptor.forClass(BitmapFactory.Options.class); + mockBitmapFactory.verify(() -> BitmapFactory.decodeFile(anyString(), argument.capture())); + BitmapFactory.Options capturedOptions = argument.getValue(); + assertTrue(capturedOptions.inJustDecodeBounds); + } + } + + @Test + public void onResizeImageIfNeeded_WhenResizeIsNecessary_ShouldAllocateBitmap() { + try (MockedStatic mockBitmapFactory = + mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { + String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, null); + ArgumentCaptor argument = + ArgumentCaptor.forClass(BitmapFactory.Options.class); + mockBitmapFactory.verify( + () -> BitmapFactory.decodeFile(anyString(), argument.capture()), times(2)); + List capturedOptions = argument.getAllValues(); + assertTrue(capturedOptions.get(0).inJustDecodeBounds); + assertFalse(capturedOptions.get(1).inJustDecodeBounds); + } + } } From ceb3512774ee5f1e0fe54754b044c70142dec842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Wed, 8 Mar 2023 17:57:07 -0300 Subject: [PATCH 02/11] Update CHANGELOG --- packages/image_picker/image_picker_android/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 5bc13e50e77..04b7b30bf7e 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,6 +1,9 @@ -## 0.8.5+10 +## NEXT * Improved Bitmap resizing. + +## 0.8.5+10 + * Clarifies explanation of endorsement in README. * Aligns Dart and Flutter SDK constraints. From 3ce3a65caee2421ac2751c96396d09d4eb3b1f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Wed, 8 Mar 2023 18:07:33 -0300 Subject: [PATCH 03/11] Bump version --- packages/image_picker/image_picker_android/CHANGELOG.md | 2 +- packages/image_picker/image_picker_android/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 04b7b30bf7e..6b6f6089d27 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,4 +1,4 @@ -## NEXT +## 0.8.5+11 * Improved Bitmap resizing. diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 30b95c3ff17..4745e03bb7f 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+10 +version: 0.8.5+11 environment: sdk: ">=2.17.0 <3.0.0" From 68894c643286236f8ed07feb6f877a26301acabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Sun, 19 Mar 2023 13:23:21 -0300 Subject: [PATCH 04/11] Refactor tests --- .../io/flutter/plugins/imagepicker/ImageResizerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java index af4359bf37a..f4cd91ce58d 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java @@ -89,10 +89,10 @@ public void onResizeImageIfNeeded_whenParentDirectoryDoesNotExists_shouldNotCras } @Test - public void onResizeImageIfNeeded_WhenResizeIsNotNecessary_ShouldOnlyQueryBitmap() { + public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmap() { try (MockedStatic mockBitmapFactory = mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { - String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null); + String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null); ArgumentCaptor argument = ArgumentCaptor.forClass(BitmapFactory.Options.class); mockBitmapFactory.verify(() -> BitmapFactory.decodeFile(anyString(), argument.capture())); @@ -102,10 +102,10 @@ public void onResizeImageIfNeeded_WhenResizeIsNotNecessary_ShouldOnlyQueryBitmap } @Test - public void onResizeImageIfNeeded_WhenResizeIsNecessary_ShouldAllocateBitmap() { + public void onResizeImageIfNeeded_whenResizeIsNecessary_shouldAllocateBitmap() { try (MockedStatic mockBitmapFactory = mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { - String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, null); + String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, null); ArgumentCaptor argument = ArgumentCaptor.forClass(BitmapFactory.Options.class); mockBitmapFactory.verify( From 02ccb00824a1faca0cc5a9f8e8c2c1a36777c28f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Sun, 19 Mar 2023 13:32:00 -0300 Subject: [PATCH 05/11] Fix tests --- .../java/io/flutter/plugins/imagepicker/ImageResizerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java index f4cd91ce58d..368e8ab1cff 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java @@ -92,7 +92,7 @@ public void onResizeImageIfNeeded_whenParentDirectoryDoesNotExists_shouldNotCras public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmap() { try (MockedStatic mockBitmapFactory = mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { - String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null); + String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 100); ArgumentCaptor argument = ArgumentCaptor.forClass(BitmapFactory.Options.class); mockBitmapFactory.verify(() -> BitmapFactory.decodeFile(anyString(), argument.capture())); @@ -105,7 +105,7 @@ public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmap public void onResizeImageIfNeeded_whenResizeIsNecessary_shouldAllocateBitmap() { try (MockedStatic mockBitmapFactory = mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { - String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, null); + String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, 100); ArgumentCaptor argument = ArgumentCaptor.forClass(BitmapFactory.Options.class); mockBitmapFactory.verify( From ec95f406fbd61caffeb4fe1739049c36a0a8f50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Tue, 21 Mar 2023 07:49:37 -0300 Subject: [PATCH 06/11] Perform requested changes --- .../image_picker_android/CHANGELOG.md | 2 +- .../plugins/imagepicker/ImageResizer.java | 65 +++++++++++-------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 834ce3683ad..1d088e36358 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.8.6+6 -* Improved Bitmap resizing. +* Improves Bitmap resizing. ## 0.8.6+5 diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java index fb07df1573b..8541936e947 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java @@ -6,9 +6,9 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Point; import android.util.Log; import androidx.annotation.Nullable; +import androidx.core.util.SizeFCompat; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -31,10 +31,8 @@ class ImageResizer { */ String resizeImageIfNeeded( String imagePath, @Nullable Double maxWidth, @Nullable Double maxHeight, int imageQuality) { - BitmapFactory.Options queryOptions = new BitmapFactory.Options(); - queryOptions.inJustDecodeBounds = true; - decodeFile(imagePath, queryOptions); - if (queryOptions.outWidth == -1 || queryOptions.outHeight == -1) { + SizeFCompat originalSize = readFileDimensions(imagePath); + if (originalSize.getWidth() == -1 || originalSize.getHeight() == -1) { return null; } boolean shouldScale = maxWidth != null || maxHeight != null || imageQuality < 100; @@ -44,18 +42,22 @@ String resizeImageIfNeeded( try { String[] pathParts = imagePath.split("/"); String imageName = pathParts[pathParts.length - 1]; - Point size = - calculateSize( - Double.valueOf(queryOptions.outWidth), - Double.valueOf(queryOptions.outHeight), + SizeFCompat targetSize = + calculateTargetSize( + (double) originalSize.getWidth(), + (double) originalSize.getHeight(), maxWidth, maxHeight); BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = calculateInSampleSize(options, size.x, size.y); - options.inJustDecodeBounds = false; + options.inSampleSize = + calculateSampleSize(options, (int) targetSize.getWidth(), (int) targetSize.getHeight()); File file = resizedImage( - decodeFile(imagePath, options), maxWidth, maxHeight, imageQuality, imageName); + decodeFile(imagePath, options), + (int) targetSize.getWidth(), + (int) targetSize.getHeight(), + imageQuality, + imageName); copyExif(imagePath, file.getPath()); return file.getPath(); } catch (IOException e) { @@ -64,19 +66,15 @@ String resizeImageIfNeeded( } private File resizedImage( - Bitmap bmp, Double maxWidth, Double maxHeight, int imageQuality, String outputImageName) + Bitmap bmp, int width, int height, int imageQuality, String outputImageName) throws IOException { - double originalWidth = bmp.getWidth() * 1.0; - double originalHeight = bmp.getHeight() * 1.0; - - Point size = calculateSize(originalWidth, originalHeight, maxWidth, maxHeight); - Bitmap scaledBmp = createScaledBitmap(bmp, size.x, size.y, false); + Bitmap scaledBmp = createScaledBitmap(bmp, width, height, false); File file = createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality); return file; } - private Point calculateSize( + private SizeFCompat calculateTargetSize( Double originalWidth, Double originalHeight, Double maxWidth, Double maxHeight) { boolean hasMaxWidth = maxWidth != null; @@ -114,7 +112,7 @@ private Point calculateSize( } } - return new Point(width.intValue(), height.intValue()); + return new SizeFCompat(width.floatValue(), height.floatValue()); } private File createFile(File externalFilesDirectory, String child) { @@ -133,6 +131,13 @@ private void copyExif(String filePathOri, String filePathDest) { exifDataCopier.copyExif(filePathOri, filePathDest); } + private SizeFCompat readFileDimensions(String path) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + decodeFile(path, options); + return new SizeFCompat(options.outWidth, options.outHeight); + } + private Bitmap decodeFile(String path, @Nullable BitmapFactory.Options opts) { return BitmapFactory.decodeFile(path, opts); } @@ -141,18 +146,26 @@ private Bitmap createScaledBitmap(Bitmap bmp, int width, int height, boolean fil return Bitmap.createScaledBitmap(bmp, width, height, filter); } - private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + /** + * Calculates the largest sample size value that is a power of two based on a target width and + * height. + * + *

This value is necessary to tell the Bitmap decoder to subsample the original image, + * returning a smaller image to save memory. + */ + private int calculateSampleSize( + BitmapFactory.Options options, int targetWidth, int targetHeight) { final int height = options.outHeight; final int width = options.outWidth; - int inSampleSize = 1; - if (height > reqHeight || width > reqWidth) { + int sampleSize = 1; + if (height > targetHeight || width > targetWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; - while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { - inSampleSize *= 2; + while ((halfHeight / sampleSize) >= targetHeight && (halfWidth / sampleSize) >= targetWidth) { + sampleSize *= 2; } } - return inSampleSize; + return sampleSize; } private File createImageOnExternalDirectory(String name, Bitmap bitmap, int imageQuality) From bb04c944951749c238a8449fb93c65d9276c993f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Tue, 21 Mar 2023 08:41:46 -0300 Subject: [PATCH 07/11] Rename tests --- .../java/io/flutter/plugins/imagepicker/ImageResizerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java index 368e8ab1cff..2a66b434047 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java @@ -89,7 +89,7 @@ public void onResizeImageIfNeeded_whenParentDirectoryDoesNotExists_shouldNotCras } @Test - public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmap() { + public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmapDimensions() { try (MockedStatic mockBitmapFactory = mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 100); @@ -102,7 +102,7 @@ public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmap } @Test - public void onResizeImageIfNeeded_whenResizeIsNecessary_shouldAllocateBitmap() { + public void onResizeImageIfNeeded_whenResizeIsNecessary_shouldDecodeBitmapPixels() { try (MockedStatic mockBitmapFactory = mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) { String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, 100); From c5b88d7c52f5d5c8e6f5c08cd2e05f9d4c2c0155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Fri, 14 Apr 2023 08:34:58 -0300 Subject: [PATCH 08/11] Update CHANGELOG.md --- packages/image_picker/image_picker_android/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index ef704d9b55c..4e1bdb98050 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.8.6+7 * Updates minimum Flutter version to 3.3. -* Improves image resizing performance by decoding the Bitmap only when scaling is needed, and using `BitmapFactory.Options.inSampleSize` on the decoding step. +* Improves image resizing performance by decoding Bitmap only when needed. ## 0.8.6+6 From 22a13745887111ab74a6b6d0a66944b089ba6958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Fri, 14 Apr 2023 18:05:52 -0300 Subject: [PATCH 09/11] Update version --- packages/image_picker/image_picker_android/CHANGELOG.md | 5 ++++- packages/image_picker/image_picker_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 2a41f35a6d3..90c4cd70324 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,8 +1,11 @@ +## 0.8.6+8 + +* Improves image resizing performance by decoding Bitmap only when needed. + ## 0.8.6+7 * Fixes handling of non-bitmap image types. * Updates minimum Flutter version to 3.3. -* Improves image resizing performance by decoding Bitmap only when needed. ## 0.8.6+6 diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index b1dad503864..349cacc8b35 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+7 +version: 0.8.6+8 environment: sdk: ">=2.18.0 <4.0.0" From b032b44730014601cb77f0ae9f8c5fbccb1066d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Fri, 14 Apr 2023 19:02:01 -0300 Subject: [PATCH 10/11] Update AUTHORS --- packages/image_picker/image_picker_android/AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/image_picker/image_picker_android/AUTHORS b/packages/image_picker/image_picker_android/AUTHORS index 493a0b4ef9c..57d4f75a1d3 100644 --- a/packages/image_picker/image_picker_android/AUTHORS +++ b/packages/image_picker/image_picker_android/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +André Sousa From 316ba793be8a2f849002fdcd3244e4287414ee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sousa?= Date: Fri, 5 May 2023 17:22:40 -0300 Subject: [PATCH 11/11] Apply suggested changes --- .../plugins/imagepicker/ImageResizer.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java index 3a857fc0bb6..9ec4c0aa3ba 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java @@ -7,6 +7,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.SizeFCompat; import java.io.ByteArrayOutputStream; @@ -51,11 +52,15 @@ String resizeImageIfNeeded( BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = calculateSampleSize(options, (int) targetSize.getWidth(), (int) targetSize.getHeight()); + Bitmap bmp = decodeFile(imagePath, options); + if (bmp == null) { + return imagePath; + } File file = resizedImage( - decodeFile(imagePath, options), - (int) targetSize.getWidth(), - (int) targetSize.getHeight(), + bmp, + (double) targetSize.getWidth(), + (double) targetSize.getHeight(), imageQuality, imageName); copyExif(imagePath, file.getPath()); @@ -66,16 +71,19 @@ String resizeImageIfNeeded( } private File resizedImage( - Bitmap bmp, int width, int height, int imageQuality, String outputImageName) + Bitmap bmp, Double width, Double height, int imageQuality, String outputImageName) throws IOException { - Bitmap scaledBmp = createScaledBitmap(bmp, width, height, false); + Bitmap scaledBmp = createScaledBitmap(bmp, width.intValue(), height.intValue(), false); File file = createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality); return file; } private SizeFCompat calculateTargetSize( - Double originalWidth, Double originalHeight, Double maxWidth, Double maxHeight) { + @NonNull Double originalWidth, + @NonNull Double originalHeight, + @Nullable Double maxWidth, + @Nullable Double maxHeight) { boolean hasMaxWidth = maxWidth != null; boolean hasMaxHeight = maxHeight != null; @@ -152,6 +160,10 @@ private Bitmap createScaledBitmap(Bitmap bmp, int width, int height, boolean fil * *

This value is necessary to tell the Bitmap decoder to subsample the original image, * returning a smaller image to save memory. + * + * @see + * Loading Large Bitmaps Efficiently */ private int calculateSampleSize( BitmapFactory.Options options, int targetWidth, int targetHeight) {