[image_picker] Support reading WebP images for iOS (#4448)

diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index 382798f..9ab762a 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.4+10
+
+* iOS: allows picking images with WebP format.
+
 ## 0.8.4+9
 
 * Internal code cleanup for stricter analysis options.
diff --git a/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
index f8fe668..2847bfd 100644
--- a/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
@@ -18,6 +18,11 @@
 		680049382280F2B9006DD6AB /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; };
 		680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; };
 		6801C8392555D726009DAF8D /* ImagePickerFromGalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6801C8382555D726009DAF8D /* ImagePickerFromGalleryUITests.m */; };
+		86430DF9272D71E9002D9D6C /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; };
+		86E9A893272754860017E6E0 /* PickerSaveImageToPathOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */; };
+		86E9A894272754A30017E6E0 /* webpImage.webp in Resources */ = {isa = PBXBuildFile; fileRef = 86E9A88F272747B90017E6E0 /* webpImage.webp */; };
+		86E9A895272769130017E6E0 /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; };
+		86E9A896272769150017E6E0 /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -79,6 +84,8 @@
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
 		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		86E9A88F272747B90017E6E0 /* webpImage.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = webpImage.webp; sourceTree = "<group>"; };
+		86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PickerSaveImageToPathOperationTests.m; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -132,6 +139,7 @@
 				F78AF3172342D9D7008449C7 /* ImagePickerTestImages.h */,
 				F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */,
 				68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */,
+				86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */,
 				334733F62668136400DCC49E /* Info.plist */,
 			);
 			path = RunnerTests;
@@ -140,6 +148,7 @@
 		680049282280E33D006DD6AB /* TestImages */ = {
 			isa = PBXGroup;
 			children = (
+				86E9A88F272747B90017E6E0 /* webpImage.webp */,
 				9FC8F0E8229FA49E00C8D58F /* gifImage.gif */,
 				680049362280F2B8006DD6AB /* jpgImage.jpg */,
 				680049352280F2B8006DD6AB /* pngImage.png */,
@@ -352,6 +361,10 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				86430DF9272D71E9002D9D6C /* gifImage.gif in Resources */,
+				86E9A894272754A30017E6E0 /* webpImage.webp in Resources */,
+				86E9A895272769130017E6E0 /* pngImage.png in Resources */,
+				86E9A896272769150017E6E0 /* jpgImage.jpg in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -456,6 +469,7 @@
 			files = (
 				334733FD266813F100DCC49E /* MetaDataUtilTests.m in Sources */,
 				334733FF266813FA00DCC49E /* ImagePickerTestImages.m in Sources */,
+				86E9A893272754860017E6E0 /* PickerSaveImageToPathOperationTests.m in Sources */,
 				334733FC266813EE00DCC49E /* ImageUtilTests.m in Sources */,
 				33473400266813FD00DCC49E /* ImagePickerPluginTests.m in Sources */,
 				334733FE266813F400DCC49E /* PhotoAssetUtilTests.m in Sources */,
diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m
new file mode 100644
index 0000000..f94db83
--- /dev/null
+++ b/packages/image_picker/image_picker/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m
@@ -0,0 +1,100 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <OCMock/OCMock.h>
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
+
+@import image_picker;
+@import image_picker.Test;
+@import XCTest;
+
+@interface PickerSaveImageToPathOperationTests : XCTestCase
+
+@end
+
+@implementation PickerSaveImageToPathOperationTests
+
+- (void)testSaveWebPImage API_AVAILABLE(ios(14)) {
+  NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"webpImage"
+                                                             withExtension:@"webp"];
+  NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
+  PHPickerResult *result = [self createPickerResultWithProvider:itemProvider
+                                                 withIdentifier:UTTypeWebP.identifier];
+
+  [self verifySavingImageWithPickerResult:result];
+}
+
+- (void)testSavePNGImage API_AVAILABLE(ios(14)) {
+  NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage"
+                                                             withExtension:@"png"];
+  NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
+  PHPickerResult *result = [self createPickerResultWithProvider:itemProvider
+                                                 withIdentifier:UTTypeWebP.identifier];
+
+  [self verifySavingImageWithPickerResult:result];
+}
+
+- (void)testSaveJPGImage API_AVAILABLE(ios(14)) {
+  NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"jpgImage"
+                                                             withExtension:@"jpg"];
+  NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
+  PHPickerResult *result = [self createPickerResultWithProvider:itemProvider
+                                                 withIdentifier:UTTypeWebP.identifier];
+
+  [self verifySavingImageWithPickerResult:result];
+}
+
+- (void)testSaveGIFImage API_AVAILABLE(ios(14)) {
+  NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"gifImage"
+                                                             withExtension:@"gif"];
+  NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
+  PHPickerResult *result = [self createPickerResultWithProvider:itemProvider
+                                                 withIdentifier:UTTypeWebP.identifier];
+
+  [self verifySavingImageWithPickerResult:result];
+}
+
+/**
+ * Creates a mock picker result using NSItemProvider.
+ *
+ * @param itemProvider an item provider that will be used as picker result
+ * @param identifier local identifier of the asset
+ */
+- (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvider
+                                    withIdentifier:(NSString *)identifier API_AVAILABLE(ios(14)) {
+  PHPickerResult *result = OCMClassMock([PHPickerResult class]);
+
+  OCMStub([result itemProvider]).andReturn(itemProvider);
+  OCMStub([result assetIdentifier]).andReturn(identifier);
+
+  return result;
+}
+
+/**
+ * Validates a saving process of FLTPHPickerSaveImageToPathOperation.
+ *
+ * FLTPHPickerSaveImageToPathOperation is responsible for saving a picked image to the disk for
+ * later use. It is expected that the saving is always successful.
+ *
+ * @param result the picker result
+ */
+- (void)verifySavingImageWithPickerResult:(PHPickerResult *)result API_AVAILABLE(ios(14)) {
+  XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"];
+
+  FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
+           initWithResult:result
+                maxHeight:@100
+                 maxWidth:@100
+      desiredImageQuality:@100
+           savedPathBlock:^(NSString *savedPath) {
+             if ([[NSFileManager defaultManager] fileExistsAtPath:savedPath]) {
+               [pathExpectation fulfill];
+             }
+           }];
+
+  [operation start];
+  [self waitForExpectations:@[ pathExpectation ] timeout:30];
+}
+
+@end
diff --git a/packages/image_picker/image_picker/example/ios/TestImages/webpImage.webp b/packages/image_picker/image_picker/example/ios/TestImages/webpImage.webp
new file mode 100644
index 0000000..ab7d40d
--- /dev/null
+++ b/packages/image_picker/image_picker/example/ios/TestImages/webpImage.webp
Binary files differ
diff --git a/packages/image_picker/image_picker/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
index 5a084c4..9e7e7e8 100644
--- a/packages/image_picker/image_picker/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
+++ b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerSaveImageToPathOperation.m
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
+
 #import "FLTPHPickerSaveImageToPathOperation.h"
 
 API_AVAILABLE(ios(14))
@@ -82,46 +84,24 @@
   }
   if (@available(iOS 14, *)) {
     [self setExecuting:YES];
+
+    if ([self.result.itemProvider hasItemConformingToTypeIdentifier:UTTypeWebP.identifier]) {
+      [self.result.itemProvider
+          loadDataRepresentationForTypeIdentifier:UTTypeWebP.identifier
+                                completionHandler:^(NSData *_Nullable data,
+                                                    NSError *_Nullable error) {
+                                  UIImage *image = [[UIImage alloc] initWithData:data];
+                                  [self processImage:image];
+                                }];
+      return;
+    }
+
     [self.result.itemProvider
         loadObjectOfClass:[UIImage class]
         completionHandler:^(__kindof id<NSItemProviderReading> _Nullable image,
                             NSError *_Nullable error) {
           if ([image isKindOfClass:[UIImage class]]) {
-            __block UIImage *localImage = image;
-            PHAsset *originalAsset =
-                [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:self.result];
-
-            if (self.maxWidth != nil || self.maxHeight != nil) {
-              localImage = [FLTImagePickerImageUtil scaledImage:localImage
-                                                       maxWidth:self.maxWidth
-                                                      maxHeight:self.maxHeight
-                                            isMetadataAvailable:originalAsset != nil];
-            }
-            __block NSString *savedPath;
-            if (!originalAsset) {
-              // Image picked without an original asset (e.g. User pick image without permission)
-              savedPath =
-                  [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil
-                                                                  image:localImage
-                                                           imageQuality:self.desiredImageQuality];
-              [self completeOperationWithPath:savedPath];
-            } else {
-              [[PHImageManager defaultManager]
-                  requestImageDataForAsset:originalAsset
-                                   options:nil
-                             resultHandler:^(
-                                 NSData *_Nullable imageData, NSString *_Nullable dataUTI,
-                                 UIImageOrientation orientation, NSDictionary *_Nullable info) {
-                               // maxWidth and maxHeight are used only for GIF images.
-                               savedPath = [FLTImagePickerPhotoAssetUtil
-                                   saveImageWithOriginalImageData:imageData
-                                                            image:localImage
-                                                         maxWidth:self.maxWidth
-                                                        maxHeight:self.maxHeight
-                                                     imageQuality:self.desiredImageQuality];
-                               [self completeOperationWithPath:savedPath];
-                             }];
-            }
+            [self processImage:image];
           }
         }];
   } else {
@@ -129,4 +109,41 @@
   }
 }
 
+/**
+ * Processes the image.
+ */
+- (void)processImage:(UIImage *)localImage API_AVAILABLE(ios(14)) {
+  PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:self.result];
+
+  if (self.maxWidth != nil || self.maxHeight != nil) {
+    localImage = [FLTImagePickerImageUtil scaledImage:localImage
+                                             maxWidth:self.maxWidth
+                                            maxHeight:self.maxHeight
+                                  isMetadataAvailable:originalAsset != nil];
+  }
+  if (originalAsset) {
+    [[PHImageManager defaultManager]
+        requestImageDataForAsset:originalAsset
+                         options:nil
+                   resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI,
+                                   UIImageOrientation orientation, NSDictionary *_Nullable info) {
+                     // maxWidth and maxHeight are used only for GIF images.
+                     NSString *savedPath = [FLTImagePickerPhotoAssetUtil
+                         saveImageWithOriginalImageData:imageData
+                                                  image:localImage
+                                               maxWidth:self.maxWidth
+                                              maxHeight:self.maxHeight
+                                           imageQuality:self.desiredImageQuality];
+                     [self completeOperationWithPath:savedPath];
+                   }];
+  } else {
+    // Image picked without an original asset (e.g. User pick image without permission)
+    NSString *savedPath =
+        [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil
+                                                        image:localImage
+                                                 imageQuality:self.desiredImageQuality];
+    [self completeOperationWithPath:savedPath];
+  }
+}
+
 @end
diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml
index 1d280f1..4774711 100755
--- a/packages/image_picker/image_picker/pubspec.yaml
+++ b/packages/image_picker/image_picker/pubspec.yaml
@@ -3,7 +3,7 @@
   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+9
+version: 0.8.4+10
 
 environment:
   sdk: ">=2.14.0 <3.0.0"