Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b1c0e17
Reland: [e2e] Replaces the check for ActivityTestRule (#2633)
ened Apr 17, 2020
7670c96
Merge branch 'master' of github.com:ened/plugins
ened May 23, 2020
aeb0a3a
Merge branch 'master' of https://github.com/flutter/plugins
ened May 23, 2020
a93b935
Merge branch 'master' of github.com:ened/plugins
May 28, 2020
ac8de30
Merge branch 'master' of https://github.com/flutter/plugins
ened Jun 6, 2020
8a07d76
Merge branch 'master' of https://github.com/flutter/plugins
ened Jun 8, 2020
e6d9fc8
Determine the mimeType using ContentResolver
ened Jun 8, 2020
2abb07f
Bump version, add changelog
ened Jun 8, 2020
cef9a60
Clarify the comment
ened Jun 8, 2020
bcf05d1
Merge branch 'master' of https://github.com/flutter/plugins
Jul 9, 2020
38f5cfe
Merge branch 'master' of https://github.com/flutter/plugins
Jul 14, 2020
d4badb0
Merge branch 'master' of https://github.com/flutter/plugins
Jul 16, 2020
ff51d61
Merge branch 'master' of https://github.com/flutter/plugins
Jul 27, 2020
26930f8
Merge branch 'master' of https://github.com/flutter/plugins
Aug 28, 2020
5a8f350
Merge branch 'master' of https://github.com/flutter/plugins
Sep 2, 2020
6080f0d
Merge branch 'master' of github.com:ened/plugins into image_picker_gif
ened Sep 9, 2020
9873b3c
Merge branch 'master' of github.com:ened/plugins into master
ened Sep 9, 2020
60c4a5f
Merge branch 'master' of https://github.com/flutter/plugins into master
ened Sep 9, 2020
76e5364
Merge branch 'master' into image_picker_gif
ened Sep 9, 2020
e48fe36
Merge branch 'master' of https://github.com/flutter/plugins into master
Oct 23, 2020
4b4cfd4
Merge branch 'master' of github.com:ened/plugins into master
Oct 23, 2020
e709bed
Merge branch 'master' of https://github.com/flutter/plugins
ened Dec 4, 2020
e9f5296
Merge branch 'master' of https://github.com/flutter/plugins
ened Feb 19, 2021
246d688
Merge branch 'master' into image_picker_gif
ened Feb 19, 2021
c3677fb
revert non-extension related changes
ened Feb 19, 2021
f82882a
Merge branch 'master' of https://github.com/flutter/plugins
ened Mar 25, 2021
a19f248
Merge branch 'master' into image_picker_gif
ened Mar 25, 2021
de13769
Merge branch 'master' of https://github.com/flutter/plugins
ened Oct 19, 2021
bcd85ee
Merge branch 'master' into image_picker_gif
ened Oct 19, 2021
40e4268
Move format check to ImageResizer. Add test. Fix some typos along the…
ditman Oct 20, 2021
888f5b8
Merge branch 'master' into image_picker_gif
ditman Oct 20, 2021
b1f6cb4
Copy-paste is bad.
ditman Oct 20, 2021
161ded1
Handle gif, apng and svg extensions. Perform faster checks earlier.
ditman Oct 21, 2021
bb82d8d
Format check.
ditman Oct 21, 2021
3038282
Merge branch 'master' of https://github.com/flutter/plugins
ened Dec 30, 2021
8df136e
Swap the mirroring order. (#4650)
godofredoc Jan 6, 2022
5129308
Merge branch 'master' of https://github.com/flutter/plugins
ened Jan 7, 2022
aef3422
Merge branch 'main' of https://github.com/flutter/plugins
ened Jan 9, 2022
e9d23a2
Merge branch 'main' of https://github.com/flutter/plugins
ened Jan 11, 2022
2d2cdf0
Merge branch 'main' of https://github.com/flutter/plugins
ened Feb 6, 2022
93b6615
Merge branch 'main' into image_picker_gif
ened Feb 6, 2022
2133a7f
Merge branch 'image_picker_gif' of github.com:ened/plugins into image…
ened Feb 6, 2022
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/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.5

* Android: Disable image resizing for GIF images.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be updated to reflect the actual set of extensions in the PR.


## 0.8.4+6

* Fixes minor type issues in iOS implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

class ImageResizer {
/** Extensions that the ImageResizer cannot resize. */
private static final Set<String> nonResizeableExtensions =
new HashSet<>(Arrays.asList("gif", "svg", "apng"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applying this to all gif files seems like a large hammer; not all gifs are animated. Shouldn't we be examining the file's header to determine whether it's an animated gif?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'll look into implementing this; however I think the header doesn't contain info about animation, the file needs to be scanned to look for "local image descriptors" (> 1)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a promising "Movie" class that might be useful to see if an image is animated or not. I'll use it to check both GIF and PNG (potentially animated), and leave the extensions for APNG (definitely animated) and SVG (vector).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding to what Stuart has commented:

For non-animated gif, we should scale it as it works as is.
For animated gif, we can scale it by scale all the frames of the git then combine them again. See: flutter/flutter#34134 (comment)

We can make this PR to exclude file extensions that cannot be scaled, which doesn't include gifs. And create another PR to scale gif.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cyanglaz in theory there is more than 1 animated file format available on Android:

Animated GIFs, WebP and HEIF via list of Android formats

IMHO, the MR currently resolves a issue in that the GIF file looses animation, which is the No. 1 purpose of using a GIF file. I understand your point to "do it right", but think this would delay the product iteration by a lot. Why not merging this change with determined behaviour (as breaking change) and consider the next step after community feedback?

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, the MR currently resolves a issue in that the GIF file looses animation, which is the No. 1 purpose of using a GIF file.

And the only reason to pass max width/height is to cause the image to be resized. You are proposing breaking something that currently works in order to fix something that is sometimes currently broken. That is not a clear improvement.

I understand your point to "do it right", but think this would delay the product iteration by a lot.

Doing things correctly sometimes takes longer, yes. Rapidly moving between different broken states isn't something clients of software generally view as a feature.

Why not merging this change with determined behaviour (as breaking change) and consider the next step after community feedback?

If we are going to do an interim fix, it would be something along the lines of my non-breaking suggestion, so that we do not:

  • actively regress people
  • introduce a breaking change for something that is not necessarily a net improvement, and which would need another breaking change to change again later to improve

Breaking changes are disruptive; we do not want to set ourselves up to make several when there's a perfectly good way not to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stuartmorgan OK understood.


private final File externalFilesDirectory;
private final ExifDataCopier exifDataCopier;

Expand All @@ -33,15 +40,21 @@ String resizeImageIfNeeded(
@Nullable Double maxWidth,
@Nullable Double maxHeight,
@Nullable Integer imageQuality) {
Bitmap bmp = decodeFile(imagePath);
if (bmp == null) {
return null;
}
boolean shouldScale =
maxWidth != null || maxHeight != null || isImageQualityValid(imageQuality);
if (!shouldScale) {
return imagePath;
}
// This class cannot resize certain extensions, so skip those.
String extension = imagePath.substring(imagePath.lastIndexOf(".") + 1).toLowerCase();
boolean canScale = !nonResizeableExtensions.contains(extension);
if (!canScale) {
return imagePath;
}
Bitmap bmp = decodeFile(imagePath);
if (bmp == null) {
return null;
}
try {
String[] pathParts = imagePath.split("/");
String imageName = pathParts[pathParts.length - 1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ public class ImageResizerTest {

ImageResizer resizer;
File imageFile;
File gifImageFile;
File externalDirectory;
Bitmap originalImageBitmap;

@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
imageFile = new File(getClass().getClassLoader().getResource("pngImage.png").getFile());
gifImageFile = new File(getClass().getClassLoader().getResource("dash-fainting.gif").getFile());
originalImageBitmap = BitmapFactory.decodeFile(imageFile.getPath());
TemporaryFolder temporaryFolder = new TemporaryFolder();
temporaryFolder.create();
Expand All @@ -40,34 +42,40 @@ public void setUp() throws IOException {
}

@Test
public void onResizeImageIfNeeded_WhenQualityIsNull_ShoultNotResize_ReturnTheUnscaledFile() {
String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null);
assertThat(outoutFile, equalTo(imageFile.getPath()));
public void onResizeImageIfNeeded_WhenQualityIsNull_ShouldNotResize_ReturnTheUnscaledFile() {
String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null);
assertThat(outputFile, equalTo(imageFile.getPath()));
}

@Test
public void onResizeImageIfNeeded_WhenQualityIsNotNull_ShoulResize_ReturnResizedFile() {
String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 50);
assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
public void onResizeImageIfNeeded_WhenQualityIsNotNull_ShouldResize_ReturnResizedFile() {
String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 50);
assertThat(outputFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
}

@Test
public void onResizeImageIfNeeded_WhenWidthIsNotNull_ShoulResize_ReturnResizedFile() {
String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, null, null);
assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
public void onResizeImageIfNeeded_WhenImageIsGif_ShouldNotResize_ReturnUnscaledFile() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This is the test covering the new feature of not resizing .gif files)

String outputFilePath = resizer.resizeImageIfNeeded(gifImageFile.getPath(), null, null, 50);
assertThat(outputFilePath, equalTo(gifImageFile.getPath()));
}

@Test
public void onResizeImageIfNeeded_WhenHeightIsNotNull_ShoulResize_ReturnResizedFile() {
String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null);
assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
public void onResizeImageIfNeeded_WhenWidthIsNotNull_ShouldResize_ReturnResizedFile() {
String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, null, null);
assertThat(outputFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
}

@Test
public void onResizeImageIfNeeded_WhenHeightIsNotNull_ShouldResize_ReturnResizedFile() {
String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null);
assertThat(outputFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png"));
}

@Test
public void onResizeImageIfNeeded_WhenParentDirectoryDoesNotExists_ShouldNotCrash() {
File nonExistentDirectory = new File(externalDirectory, "/nonExistent");
ImageResizer invalidResizer = new ImageResizer(nonExistentDirectory, new ExifDataCopier());
String outoutFile = invalidResizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null);
assertThat(outoutFile, equalTo(nonExistentDirectory.getPath() + "/scaled_pngImage.png"));
String outputFile = invalidResizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null);
assertThat(outputFile, equalTo(nonExistentDirectory.getPath() + "/scaled_pngImage.png"));
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/image_picker/image_picker/lib/image_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class ImagePicker {
/// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked,
/// a warning message will be logged.
///
/// GIF images picked from the gallery will only be scaled on iOS. They'll be returned as-is on Android and the Web.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change, and would need to be versioned accordingly.

I think a better option would probably be a new, optional boolean to apply resizing only if we're confident that we can do it without destroying information, defaulting to false. That makes this a non-breaking change (and would make things like the animated vs non-animated gif issue something we could punt on).

Copy link
Contributor Author

@ened ened Oct 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, given the current pickImage method signature:

Future<XFile?> pickImage({
    required ImageSource source,
    double? maxWidth,
    double? maxHeight,
    int? imageQuality,
    CameraDevice preferredCameraDevice = CameraDevice.rear,
  }) {
    return platform.getImage(
      source: source,
      maxWidth: maxWidth,
      maxHeight: maxHeight,
      imageQuality: imageQuality,
      preferredCameraDevice: preferredCameraDevice,
    );
  }

The new boolean would be called safeResizeOnly: false and be described as follows:

When [safeResizeOnly] is set to true and a resize would incur a loss of information, the original image will be returned. This applies to animated formats like GIF or APNG, which can not be resized on Android without loosing the animation.

WDYT, @stuartmorgan (naming variables is not easy ;-)).

Side note: When you said:

only if we're confident that we can do it without destroying information

.. I started to wonder what happens to the EXIF metadata on resize. It was on the radar previously https://github.com/flutter/flutter/issues?q=is%3Aissue+exif+image_picker+is%3Aopen so may warrant another check in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new boolean would be called safeResizeOnly: false and be described as follows:

When [safeResizeOnly] is set to true and a resize would incur a loss of information, the original image will be returned. This applies to animated formats like GIF or APNG, which can not be resized on Android without loosing the animation.

That sounds good, except s/would/could/, and I would explicitly say that it's a heuristic that may be adjusted over time. I think your wording would be misleading about, e.g., the initial planned handling of GIFs (where it will fail to resize them even when not animated).

I started to wonder what happens to the EXIF metadata on resize

You could say something like "fundamentally change the image" instead of "incur a loss of information", to make it clearer that it's not just any loss of information.

That's probably a good idea anyway, because as currently written any resize down would actually be forbidden because it loses information.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever the change ends up being, the doc change will need to be applied to all the methods that take a size.

///
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
/// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if
Expand Down
2 changes: 1 addition & 1 deletion packages/image_picker/image_picker/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image
library, and taking new pictures with the camera.
repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
version: 0.8.4+6
version: 0.8.5

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