Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.6+6

* Improved Bitmap resizing.

## 0.8.6+5

* Fixes case when file extension returned from the OS does not match its real mime type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,8 +31,10 @@ class ImageResizer {
*/
String resizeImageIfNeeded(
String imagePath, @Nullable Double maxWidth, @Nullable Double maxHeight, int 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 = maxWidth != null || maxHeight != null || imageQuality < 100;
Expand All @@ -41,7 +44,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) {
Expand All @@ -55,6 +69,16 @@ private File resizedImage(
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);
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;

Expand Down Expand Up @@ -90,10 +114,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) {
Expand All @@ -112,14 +133,28 @@ 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) {
return Bitmap.createScaledBitmap(bmp, width, height, filter);
}

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -78,4 +87,32 @@ public void onResizeImageIfNeeded_whenParentDirectoryDoesNotExists_shouldNotCras
String outputFile = invalidResizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, 100);
assertThat(outputFile, equalTo(nonExistentDirectory.getPath() + "/scaled_pngImage.png"));
}

@Test
public void onResizeImageIfNeeded_whenResizeIsNotNecessary_shouldOnlyQueryBitmap() {
try (MockedStatic<BitmapFactory> mockBitmapFactory =
mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) {
String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 100);
ArgumentCaptor<BitmapFactory.Options> 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<BitmapFactory> mockBitmapFactory =
mockStatic(BitmapFactory.class, Mockito.CALLS_REAL_METHODS)) {
String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, 50.0, 100);
ArgumentCaptor<BitmapFactory.Options> argument =
ArgumentCaptor.forClass(BitmapFactory.Options.class);
mockBitmapFactory.verify(
() -> BitmapFactory.decodeFile(anyString(), argument.capture()), times(2));
List<BitmapFactory.Options> capturedOptions = argument.getAllValues();
assertTrue(capturedOptions.get(0).inJustDecodeBounds);
assertFalse(capturedOptions.get(1).inJustDecodeBounds);
}
}
}
2 changes: 1 addition & 1 deletion packages/image_picker/image_picker_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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+5
version: 0.8.6+6

environment:
sdk: ">=2.17.0 <3.0.0"
Expand Down