Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Conversation

@ened
Copy link
Contributor

@ened ened commented Jun 8, 2020

Description

Reenables image picking for .GIF images on Android instead of converting anything to JPG straight away.

Related Issues

Closes flutter/flutter#34134.
Closes flutter/flutter#52419.
Closes flutter/flutter#88461.

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (flutter analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy.
  • I updated CHANGELOG.md to add a description of the change.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (please indicate a breaking change in CHANGELOG.md and increment major revision).
  • No, this is not a breaking change.

@ened
Copy link
Contributor Author

ened commented Jun 9, 2020

This works a little different on Android than on iOS. There seems to be a gif scaler on iOS, whereas I'm not sure it exists anywhere in the Android SDK.

@cyanglaz do you have a suggestion how to test this PR? There are different paths for GIF and other image formats. I was thinking to just extend the FileUtilTest and perhaps check if the response from one of the methods matches the original path, if that is somehow possible to determine.

@ened ened requested review from cyanglaz and ditman September 9, 2020 05:49
@ditman
Copy link
Member

ditman commented Sep 9, 2020

Is this related to #2982? They seem similar?

@ditman ditman mentioned this pull request Sep 9, 2020
13 tasks
@ened
Copy link
Contributor Author

ened commented Sep 10, 2020

@ditman yes - 2982 solves one problem (the returned file extension being jpg for any file) while this PR solves two:

  • file extension always jpg
  • GIF images are resized which stops the animation on Android

We can either merge 2982 first or confirm this PR. @ponnamkarthik do you think this PR contains the same fix as yours from a technical point of view?

@ponnamkarthik
Copy link
Contributor

ponnamkarthik commented Sep 11, 2020

@ditman you can merge @ened PR

This PR contains the same fix.

@ditman
Copy link
Member

ditman commented Sep 16, 2020

I found another implementation of this fix in #2860 (only the JPG extension part), can we merge these two/grab ideas from both? I've noticed the extension detection seems a little bit more thorough in 2860 than here.

(Also, adding some unit tests here would be nice!)

@balvinderz
Copy link
Contributor

balvinderz commented Oct 20, 2021

i think this can be fixed too in this PR. We just need to add one more condition for svg images.
flutter/flutter#88461

@ened
Copy link
Contributor Author

ened commented Oct 20, 2021

i think this can be fixed too in this PR. We just need to add one more condition for svg images.
flutter/flutter#88461

@balvinderz very interesting. Do you know how iOS / Android differ when it comes to SVG? I seem to remember that iOS also supports PDF images?

@balvinderz
Copy link
Contributor

I am not able to pick svg images on ios.

@ditman
Copy link
Member

ditman commented Oct 20, 2021

I suppose there's many images that can't be resized by this code. I'm sure this code can't resize any animated format, like APNG:

Animated PNG wikimedia example

(Sometimes you can't tell APNG from its extension, though!)

@ened
Copy link
Contributor Author

ened commented Oct 21, 2021

@ditman lgtm

@@ -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.

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.

/// 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.

/// 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.

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

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.

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.

@ditman
Copy link
Member

ditman commented Oct 22, 2021

[...] For animated gif, we can scale it by scale all the frames of the git then combine them again. [...]

@cyanglaz everything that is an image/video can be scaled, right? 😜 we just need to be able to uncompress every frame from every possible format, resize each frame, and then recompose the scaled output.

I'm not sure I want to be in the business of knowing how to open and resize every possible image/video format that exists. Do we have a list of the formats that are/aren't supported?

Copy link
Contributor

@cyanglaz cyanglaz left a comment

Choose a reason for hiding this comment

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

Do we have a list of the formats that are/aren't supported?

I don't think we have such list. I think, Ideally, scaling all format should be supported. The priority of supporting each format would vary based on requirements. For example, gif might be a higher priority at the moment.

@ditman
Copy link
Member

ditman commented Nov 1, 2021

Hey all, I tried to detect if an image is animated or not, but I broke one of my tests (which should pass after my change) and couldn't setup a decent Android environment to debug the test. Here's my latest change in case somebody wants to keep at it:

ditman@e0bddc5

@stuartmorgan-g
Copy link
Contributor

Are you still planning on updating this PR based on the last round of discussion?

This is to make main the default branch.
@godofredoc godofredoc changed the base branch from master to main January 6, 2022 22:15
@ditman
Copy link
Member

ditman commented Jan 7, 2022

@stuartmorgan I haven't had time to look at this in a few months, somebody with more android experience should tackle it as suggested by @cyanglaz, probably!

@stuartmorgan-g
Copy link
Contributor

@ened Are you planning on updating this per #2821 (comment) and #2821 (comment) ?

It seems like that's a way forward here without tackling the larger problem of trying to fully support resizing of animated formats (as in the last bit of discussion).

@stuartmorgan-g
Copy link
Contributor

@ened I see you have updated the branch, but it doesn't look like you've addressed the comments. Is this still something you're planning on finishing?

@stuartmorgan-g
Copy link
Contributor

Thank you for your contribution. I'm going to close this PR for now since there are outstanding comments, just to get this off our PR review queue. Please don't hesitate to submit a new PR if you have the time to address the review comments. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

10 participants