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

[image_picker] Support reading WebP images for iOS #4448

Merged
merged 18 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all 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/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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; };
Expand Down Expand Up @@ -132,6 +139,7 @@
F78AF3172342D9D7008449C7 /* ImagePickerTestImages.h */,
F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */,
68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */,
86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */,
334733F62668136400DCC49E /* Info.plist */,
);
path = RunnerTests;
Expand All @@ -140,6 +148,7 @@
680049282280E33D006DD6AB /* TestImages */ = {
isa = PBXGroup;
children = (
86E9A88F272747B90017E6E0 /* webpImage.webp */,
9FC8F0E8229FA49E00C8D58F /* gifImage.gif */,
680049362280F2B8006DD6AB /* jpgImage.jpg */,
680049352280F2B8006DD6AB /* pngImage.png */,
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -82,51 +84,66 @@ - (void)start {
}
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 {
[self setFinished:YES];
}
}

/**
* 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
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+9
version: 0.8.4+10

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