diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 04bb4dfdf890..b75e6333b94e 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.8.6+7 +* Fixes issue where GIF file would not animate without `Photo Library Usage` permissions. Fixes issue where PNG and GIF files were converted to JPG, but only when they are do not have `Photo Library Usage` permissions. * Updates minimum Flutter version to 3.0. ## 0.8.6+6 diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m index 091755ca163b..027e287d1586 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m @@ -21,7 +21,7 @@ - (void)testSaveWebPImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSavePNGImage API_AVAILABLE(ios(14)) { @@ -30,7 +30,7 @@ - (void)testSavePNGImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"png"]; } - (void)testSaveJPGImage API_AVAILABLE(ios(14)) { @@ -39,7 +39,7 @@ - (void)testSaveJPGImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveGIFImage API_AVAILABLE(ios(14)) { @@ -48,7 +48,39 @@ - (void)testSaveGIFImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + NSData *dataGIF = [NSData dataWithContentsOfURL:imageURL]; + CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil); + size_t numberOfFrames = CGImageSourceGetCount(imageSource); + + XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; + XCTestExpectation *operationExpectation = + [self expectationWithDescription:@"Operation completed"]; + + FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] + initWithResult:result + maxHeight:@100 + maxWidth:@100 + desiredImageQuality:@100 + fullMetadata:NO + savedPathBlock:^(NSString *savedPath, FlutterError *error) { + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); + + // Ensure gif is animated. + XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, @"gif"); + NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPath]; + CGImageSourceRef newImageSource = + CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil); + size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); + XCTAssertEqual(numberOfFrames, newNumberOfFrames); + [pathExpectation fulfill]; + }]; + operation.completionBlock = ^{ + [operationExpectation fulfill]; + }; + + [operation start]; + [self waitForExpectationsWithTimeout:30 handler:nil]; + XCTAssertTrue(operation.isFinished); } - (void)testSaveBMPImage API_AVAILABLE(ios(14)) { @@ -57,7 +89,7 @@ - (void)testSaveBMPImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveHEICImage API_AVAILABLE(ios(14)) { @@ -66,7 +98,7 @@ - (void)testSaveHEICImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveICNSImage API_AVAILABLE(ios(14)) { @@ -75,7 +107,7 @@ - (void)testSaveICNSImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveICOImage API_AVAILABLE(ios(14)) { @@ -84,7 +116,7 @@ - (void)testSaveICOImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveProRAWImage API_AVAILABLE(ios(14)) { @@ -93,7 +125,7 @@ - (void)testSaveProRAWImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveSVGImage API_AVAILABLE(ios(14)) { @@ -102,7 +134,7 @@ - (void)testSaveSVGImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) { @@ -110,7 +142,7 @@ - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) { withExtension:@"tiff"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testNonexistentImage API_AVAILABLE(ios(14)) { @@ -176,7 +208,7 @@ - (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) { PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; OCMReject([photoAssetUtil fetchAssetsWithLocalIdentifiers:OCMOCK_ANY options:OCMOCK_ANY]); - [self verifySavingImageWithPickerResult:result fullMetadata:NO]; + [self verifySavingImageWithPickerResult:result fullMetadata:NO withExtension:@"png"]; OCMVerifyAll(photoAssetUtil); } @@ -204,7 +236,8 @@ - (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvide * @param result the picker result */ - (void)verifySavingImageWithPickerResult:(PHPickerResult *)result - fullMetadata:(BOOL)fullMetadata API_AVAILABLE(ios(14)) { + fullMetadata:(BOOL)fullMetadata + withExtension:(NSString *)extension API_AVAILABLE(ios(14)) { XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; XCTestExpectation *operationExpectation = [self expectationWithDescription:@"Operation completed"]; @@ -217,6 +250,7 @@ - (void)verifySavingImageWithPickerResult:(PHPickerResult *)result fullMetadata:fullMetadata savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); + XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, extension); [pathExpectation fulfill]; }]; operation.completionBlock = ^{ diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m index 11fedfb73846..efcbdbeec897 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m @@ -98,8 +98,7 @@ - (void)start { completionHandler:^(NSData *_Nullable data, NSError *_Nullable error) { if (data != nil) { - UIImage *image = [[UIImage alloc] initWithData:data]; - [self processImage:image]; + [self processImage:data]; } else { FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_image" @@ -122,7 +121,9 @@ - (void)start { /** * Processes the image. */ -- (void)processImage:(UIImage *)localImage API_AVAILABLE(ios(14)) { +- (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) { + UIImage *localImage = [[UIImage alloc] initWithData:pickerImageData]; + PHAsset *originalAsset; // Only if requested, fetch the full "PHAsset" metadata, which requires "Photo Library Usage" // permissions. @@ -172,10 +173,13 @@ - (void)processImage:(UIImage *)localImage API_AVAILABLE(ios(14)) { } } else { // Image picked without an original asset (e.g. User pick image without permission) + // maxWidth and maxHeight are used only for GIF images. NSString *savedPath = - [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil - image:localImage - imageQuality:self.desiredImageQuality]; + [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:pickerImageData + image:localImage + maxWidth:self.maxWidth + maxHeight:self.maxHeight + imageQuality:self.desiredImageQuality]; [self completeOperationWithPath:savedPath error:nil]; } } diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 5bfb8852d2cc..1f4e2af4cb96 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+6 +version: 0.8.6+7 environment: sdk: ">=2.14.0 <3.0.0"