[image_picker] ios: support GIF animation and the scaling (#1638)
diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 455ef52..ae67398 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md
@@ -1,3 +1,7 @@ +## 0.6.0+10 + +* iOS: support picking GIF from gallery. + ## 0.6.0+9 * Add missing template type parameter to `invokeMethod` calls.
diff --git a/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj index cbe546c..bb359cc 100644 --- a/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
@@ -24,6 +24,9 @@ 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; + 9FC8F0EC229FA68500C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; + 9FC8F0EE229FB90B00C8D58F /* ImageUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */; }; F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC32F6993F4529982D9519F1 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ @@ -77,6 +80,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifImage.gif; sourceTree = "<group>"; }; + 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImageUtilTests.m; sourceTree = "<group>"; }; EC32F6993F4529982D9519F1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -105,6 +110,7 @@ isa = PBXGroup; children = ( 6800491B2280D368006DD6AB /* Info.plist */, + 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */, 680049252280D736006DD6AB /* MetaDataUtilTests.m */, 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */, ); @@ -114,6 +120,7 @@ 680049282280E33D006DD6AB /* TestImages */ = { isa = PBXGroup; children = ( + 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */, 680049362280F2B8006DD6AB /* jpgImage.jpg */, 680049352280F2B8006DD6AB /* pngImage.png */, ); @@ -291,6 +298,7 @@ buildActionMask = 2147483647; files = ( 680049272280D79A006DD6AB /* Assets.xcassets in Resources */, + 9FC8F0EC229FA68500C8D58F /* gifImage.gif in Resources */, 680049382280F2B9006DD6AB /* pngImage.png in Resources */, 680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */, ); @@ -301,6 +309,7 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */, 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, @@ -382,6 +391,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9FC8F0EE229FB90B00C8D58F /* ImageUtilTests.m in Sources */, 680049262280D736006DD6AB /* MetaDataUtilTests.m in Sources */, 68F4B464228B3AB500C25614 /* PhotoAssetUtilTests.m in Sources */, );
diff --git a/packages/image_picker/example/ios/TestImages/gifImage.gif b/packages/image_picker/example/ios/TestImages/gifImage.gif new file mode 100644 index 0000000..5f989fc --- /dev/null +++ b/packages/image_picker/example/ios/TestImages/gifImage.gif Binary files differ
diff --git a/packages/image_picker/example/ios/image_picker_exampleTests/ImageUtilTests.m b/packages/image_picker/example/ios/image_picker_exampleTests/ImageUtilTests.m new file mode 100644 index 0000000..b554f4d --- /dev/null +++ b/packages/image_picker/example/ios/image_picker_exampleTests/ImageUtilTests.m
@@ -0,0 +1,48 @@ +// Copyright 2019 The Chromium 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 <XCTest/XCTest.h> +#import "FLTImagePickerImageUtil.h" + +@interface ImageUtilTests : XCTestCase + +@property(strong, nonatomic) NSBundle *testBundle; + +@end + +@implementation ImageUtilTests + +- (void)setUp { + self.testBundle = [NSBundle bundleForClass:self.class]; +} + +- (void)testScaledImage_ShouldBeScaled { + NSData *data = [NSData dataWithContentsOfFile:[self.testBundle pathForResource:@"jpgImage" + ofType:@"jpg"]]; + UIImage *image = [UIImage imageWithData:data]; + UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image maxWidth:@3 maxHeight:@2]; + + XCTAssertEqual(newImage.size.width, 3); + XCTAssertEqual(newImage.size.height, 2); +} + +- (void)testScaledGIFImage_ShouldBeScaled { + // gif image that frame size is 3 and the duration is 1 second. + NSData *data = [NSData dataWithContentsOfFile:[self.testBundle pathForResource:@"gifImage" + ofType:@"gif"]]; + GIFInfo info = [FLTImagePickerImageUtil scaledGIFImage:data maxWidth:@3 maxHeight:@2]; + + NSArray<UIImage *> *images = info.images; + NSTimeInterval duration = info.interval; + + XCTAssertEqual(images.count, 3); + XCTAssertEqual(duration, 1); + + for (UIImage *newImage in images) { + XCTAssertEqual(newImage.size.width, 3); + XCTAssertEqual(newImage.size.height, 2); + } +} + +@end
diff --git a/packages/image_picker/example/ios/image_picker_exampleTests/MetaDataUtilTests.m b/packages/image_picker/example/ios/image_picker_exampleTests/MetaDataUtilTests.m index 351f3f2..bc2fa79 100644 --- a/packages/image_picker/example/ios/image_picker_exampleTests/MetaDataUtilTests.m +++ b/packages/image_picker/example/ios/image_picker_exampleTests/MetaDataUtilTests.m
@@ -29,6 +29,12 @@ ofType:@"png"]]; XCTAssertEqual([FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:dataPNG], FLTImagePickerMIMETypePNG); + + // test gif + NSData *dataGIF = [NSData dataWithContentsOfFile:[self.testBundle pathForResource:@"gifImage" + ofType:@"gif"]]; + XCTAssertEqual([FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:dataGIF], + FLTImagePickerMIMETypeGIF); } - (void)testSuffixFromType { @@ -40,6 +46,10 @@ XCTAssertEqualObjects( [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypePNG], @".png"); + // test gif + XCTAssertEqualObjects( + [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypeGIF], @".gif"); + // test other XCTAssertNil([FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypeOther]); }
diff --git a/packages/image_picker/example/ios/image_picker_exampleTests/PhotoAssetUtilTests.m b/packages/image_picker/example/ios/image_picker_exampleTests/PhotoAssetUtilTests.m index e0eaf21..f0ff04d 100644 --- a/packages/image_picker/example/ios/image_picker_exampleTests/PhotoAssetUtilTests.m +++ b/packages/image_picker/example/ios/image_picker_exampleTests/PhotoAssetUtilTests.m
@@ -24,7 +24,9 @@ ofType:@"jpg"]]; UIImage *imageJPG = [UIImage imageWithData:dataJPG]; NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataJPG - image:imageJPG]; + image:imageJPG + maxWidth:nil + maxHeight:nil]; XCTAssertNotNil(savedPathJPG); XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4], @".jpg"); @@ -38,7 +40,9 @@ ofType:@"png"]]; UIImage *imagePNG = [UIImage imageWithData:dataPNG]; NSString *savedPathPNG = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataPNG - image:imagePNG]; + image:imagePNG + maxWidth:nil + maxHeight:nil]; XCTAssertNotNil(savedPathPNG); XCTAssertEqualObjects([savedPathPNG substringFromIndex:savedPathPNG.length - 4], @".png"); @@ -80,4 +84,57 @@ @"aNote"); } +- (void)testSaveImageWithOriginalImageData_ShouldSaveAsGifAnimation { + // test gif + NSData *dataGIF = [NSData dataWithContentsOfFile:[self.testBundle pathForResource:@"gifImage" + ofType:@"gif"]]; + UIImage *imageGIF = [UIImage imageWithData:dataGIF]; + CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil); + + size_t numberOfFrames = CGImageSourceGetCount(imageSource); + + NSNumber *nilSize = (NSNumber *)[NSNull null]; + NSString *savedPathGIF = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataGIF + image:imageGIF + maxWidth:nilSize + maxHeight:nilSize]; + XCTAssertNotNil(savedPathGIF); + XCTAssertEqualObjects([savedPathGIF substringFromIndex:savedPathGIF.length - 4], @".gif"); + + NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF]; + + CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil); + + size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); + + XCTAssertEqual(numberOfFrames, newNumberOfFrames); +} + +- (void)testSaveImageWithOriginalImageData_ShouldSaveAsScalledGifAnimation { + // test gif + NSData *dataGIF = [NSData dataWithContentsOfFile:[self.testBundle pathForResource:@"gifImage" + ofType:@"gif"]]; + UIImage *imageGIF = [UIImage imageWithData:dataGIF]; + + CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil); + + size_t numberOfFrames = CGImageSourceGetCount(imageSource); + + NSString *savedPathGIF = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataGIF + image:imageGIF + maxWidth:@3 + maxHeight:@2]; + NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF]; + UIImage *newImage = [[UIImage alloc] initWithData:newDataGIF]; + + XCTAssertEqual(newImage.size.width, 3); + XCTAssertEqual(newImage.size.height, 2); + + CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil); + + size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); + + XCTAssertEqual(numberOfFrames, newNumberOfFrames); +} + @end
diff --git a/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.h b/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.h new file mode 100644 index 0000000..d9abec8 --- /dev/null +++ b/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.h
@@ -0,0 +1,29 @@ +// Copyright 2019 The Chromium 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 <Foundation/Foundation.h> +#import <UIKit/UIKit.h> + +NS_ASSUME_NONNULL_BEGIN + +typedef struct GIFInfo { + // frames of animation + NSArray<UIImage *> *images; + NSTimeInterval interval; +} GIFInfo; + +@interface FLTImagePickerImageUtil : NSObject + ++ (UIImage *)scaledImage:(UIImage *)image + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight; + +// Resize all gif animation frames. ++ (GIFInfo)scaledGIFImage:(NSData *)data + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight; + +@end + +NS_ASSUME_NONNULL_END
diff --git a/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m b/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m new file mode 100644 index 0000000..cd4d1ad --- /dev/null +++ b/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m
@@ -0,0 +1,108 @@ +// Copyright 2019 The Chromium 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 "FLTImagePickerImageUtil.h" +#import <MobileCoreServices/MobileCoreServices.h> + +@implementation FLTImagePickerImageUtil : NSObject + ++ (UIImage *)scaledImage:(UIImage *)image + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight { + double originalWidth = image.size.width; + double originalHeight = image.size.height; + + bool hasMaxWidth = maxWidth != (id)[NSNull null]; + bool hasMaxHeight = maxHeight != (id)[NSNull null]; + + double width = hasMaxWidth ? MIN([maxWidth doubleValue], originalWidth) : originalWidth; + double height = hasMaxHeight ? MIN([maxHeight doubleValue], originalHeight) : originalHeight; + + bool shouldDownscaleWidth = hasMaxWidth && [maxWidth doubleValue] < originalWidth; + bool shouldDownscaleHeight = hasMaxHeight && [maxHeight doubleValue] < originalHeight; + bool shouldDownscale = shouldDownscaleWidth || shouldDownscaleHeight; + + if (shouldDownscale) { + double downscaledWidth = floor((height / originalHeight) * originalWidth); + double downscaledHeight = floor((width / originalWidth) * originalHeight); + + if (width < height) { + if (!hasMaxWidth) { + width = downscaledWidth; + } else { + height = downscaledHeight; + } + } else if (height < width) { + if (!hasMaxHeight) { + height = downscaledHeight; + } else { + width = downscaledWidth; + } + } else { + if (originalWidth < originalHeight) { + width = downscaledWidth; + } else if (originalHeight < originalWidth) { + height = downscaledHeight; + } + } + } + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); + [image drawInRect:CGRectMake(0, 0, width, height)]; + + UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return scaledImage; +} + ++ (GIFInfo)scaledGIFImage:(NSData *)data + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight { + NSMutableDictionary<NSString *, id> *options = [NSMutableDictionary dictionary]; + options[(NSString *)kCGImageSourceShouldCache] = @(YES); + options[(NSString *)kCGImageSourceTypeIdentifierHint] = (NSString *)kUTTypeGIF; + + CGImageSourceRef imageSource = + CGImageSourceCreateWithData((CFDataRef)data, (CFDictionaryRef)options); + + size_t numberOfFrames = CGImageSourceGetCount(imageSource); + NSMutableArray<UIImage *> *images = [NSMutableArray arrayWithCapacity:numberOfFrames]; + + NSTimeInterval interval = 0.0; + for (size_t index = 0; index < numberOfFrames; index++) { + CGImageRef imageRef = + CGImageSourceCreateImageAtIndex(imageSource, index, (CFDictionaryRef)options); + + NSDictionary *properties = (NSDictionary *)CFBridgingRelease( + CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL)); + NSDictionary *gifProperties = properties[(NSString *)kCGImagePropertyGIFDictionary]; + + NSNumber *delay = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; + if (!delay) { + delay = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; + } + + if (interval == 0.0) { + interval = [delay doubleValue]; + } + + UIImage *image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationUp]; + image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; + + [images addObject:image]; + + CGImageRelease(imageRef); + } + + CFRelease(imageSource); + + GIFInfo info; + info.images = images; + info.interval = interval; + + return info; +} + +@end
diff --git a/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.h b/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.h index 93a1af7..bc12f46 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.h +++ b/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.h
@@ -10,6 +10,7 @@ typedef enum : NSUInteger { FLTImagePickerMIMETypePNG, FLTImagePickerMIMETypeJPEG, + FLTImagePickerMIMETypeGIF, FLTImagePickerMIMETypeOther, } FLTImagePickerMIMEType;
diff --git a/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m b/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m index 8cb4e4a..5123dec 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m +++ b/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m
@@ -7,6 +7,7 @@ static const uint8_t kFirstByteJPEG = 0xFF; static const uint8_t kFirstBytePNG = 0x89; +static const uint8_t kFirstByteGIF = 0x47; NSString *const kFLTImagePickerDefaultSuffix = @".jpg"; const FLTImagePickerMIMEType kFLTImagePickerMIMETypeDefault = FLTImagePickerMIMETypeJPEG; @@ -21,6 +22,8 @@ return FLTImagePickerMIMETypeJPEG; case kFirstBytePNG: return FLTImagePickerMIMETypePNG; + case kFirstByteGIF: + return FLTImagePickerMIMETypeGIF; } return FLTImagePickerMIMETypeOther; } @@ -31,6 +34,8 @@ return @".jpg"; case FLTImagePickerMIMETypePNG: return @".png"; + case FLTImagePickerMIMETypeGIF: + return @".gif"; default: return nil; }
diff --git a/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h b/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h index 52b78d4..c03f1d5 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h +++ b/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h
@@ -5,6 +5,8 @@ #import <Foundation/Foundation.h> #import <Photos/Photos.h> +#import "FLTImagePickerImageUtil.h" + NS_ASSUME_NONNULL_BEGIN @interface FLTImagePickerPhotoAssetUtil : NSObject @@ -12,7 +14,11 @@ + (PHAsset *)getAssetFromImagePickerInfo:(NSDictionary *)info; // Save image with correct meta data and extention copied from the original asset. -+ (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData image:(UIImage *)image; +// maxWidth and maxHeight are used only for GIF images. ++ (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData + image:(UIImage *)image + maxWidth:(nullable NSNumber *)maxWidth + maxHeight:(nullable NSNumber *)maxHeight; // Save image with correct meta data and extention copied from image picker result info. + (NSString *)saveImageWithPickerInfo:(nullable NSDictionary *)info image:(UIImage *)image;
diff --git a/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.m b/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.m index e13436c..b5b0723 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.m +++ b/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.m
@@ -3,8 +3,11 @@ // found in the LICENSE file. #import "FLTImagePickerPhotoAssetUtil.h" +#import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" +@import MobileCoreServices; + @implementation FLTImagePickerPhotoAssetUtil + (PHAsset *)getAssetFromImagePickerInfo:(NSDictionary *)info { @@ -17,7 +20,10 @@ return result.firstObject; } -+ (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData image:(UIImage *)image { ++ (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData + image:(UIImage *)image + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight { NSString *suffix = kFLTImagePickerDefaultSuffix; FLTImagePickerMIMEType type = kFLTImagePickerMIMETypeDefault; NSDictionary *metaData = nil; @@ -28,7 +34,15 @@ [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:type] ?: kFLTImagePickerDefaultSuffix; metaData = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:originalImageData]; } - return [self saveImageWithMetaData:metaData image:image suffix:suffix type:type]; + if (type == FLTImagePickerMIMETypeGIF) { + GIFInfo gifInfo = [FLTImagePickerImageUtil scaledGIFImage:originalImageData + maxWidth:maxWidth + maxHeight:maxHeight]; + + return [self saveImageWithMetaData:metaData gifInfo:gifInfo suffix:suffix]; + } else { + return [self saveImageWithMetaData:metaData image:image suffix:suffix type:type]; + } } + (NSString *)saveImageWithPickerInfo:(nullable NSDictionary *)info image:(UIImage *)image { @@ -40,6 +54,13 @@ } + (NSString *)saveImageWithMetaData:(NSDictionary *)metaData + gifInfo:(GIFInfo)gifInfo + suffix:(NSString *)suffix { + NSString *path = [self temporaryFilePath:suffix]; + return [self saveImageWithMetaData:metaData gifInfo:gifInfo path:path]; +} + ++ (NSString *)saveImageWithMetaData:(NSDictionary *)metaData image:(UIImage *)image suffix:(NSString *)suffix type:(FLTImagePickerMIMEType)type { @@ -57,11 +78,64 @@ data = [FLTImagePickerMetaDataUtil updateMetaData:metaData toImage:data]; } + return [self createFile:data suffix:suffix]; +} + ++ (NSString *)saveImageWithMetaData:(NSDictionary *)metaData + gifInfo:(GIFInfo)gifInfo + path:(NSString *)path { + CGImageDestinationRef destination = CGImageDestinationCreateWithURL( + (CFURLRef)[NSURL fileURLWithPath:path], kUTTypeGIF, gifInfo.images.count, NULL); + + NSDictionary *frameProperties = [NSDictionary + dictionaryWithObject:[NSDictionary + dictionaryWithObject:[NSNumber numberWithFloat:gifInfo.interval] + forKey:(NSString *)kCGImagePropertyGIFDelayTime] + forKey:(NSString *)kCGImagePropertyGIFDictionary]; + + NSMutableDictionary *gifMetaProperties = [NSMutableDictionary dictionaryWithDictionary:metaData]; + NSMutableDictionary *gifProperties = + (NSMutableDictionary *)gifMetaProperties[(NSString *)kCGImagePropertyGIFDictionary]; + if (gifMetaProperties == nil) { + gifProperties = [NSMutableDictionary dictionary]; + } + + gifProperties[(NSString *)kCGImagePropertyGIFLoopCount] = [NSNumber numberWithFloat:0]; + + CGImageDestinationSetProperties(destination, (CFDictionaryRef)gifMetaProperties); + + CGImagePropertyOrientation orientation = (CGImagePropertyOrientation)[metaData[( + __bridge NSString *)kCGImagePropertyOrientation] integerValue]; + + for (NSInteger index = 0; index < gifInfo.images.count; index++) { + UIImage *image = (UIImage *)[gifInfo.images objectAtIndex:index]; + UIImage *newImage = [UIImage + imageWithCGImage:[image CGImage] + scale:1.0 + orientation: + [FLTImagePickerMetaDataUtil + getNormalizedUIImageOrientationFromCGImagePropertyOrientation:orientation]]; + + CGImageDestinationAddImage(destination, newImage.CGImage, (CFDictionaryRef)frameProperties); + } + + CGImageDestinationFinalize(destination); + CFRelease(destination); + + return path; +} + ++ (NSString *)temporaryFilePath:(NSString *)suffix { NSString *fileExtension = [@"image_picker_%@" stringByAppendingString:suffix]; NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; NSString *tmpFile = [NSString stringWithFormat:fileExtension, guid]; NSString *tmpDirectory = NSTemporaryDirectory(); NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; + return tmpPath; +} + ++ (NSString *)createFile:(NSData *)data suffix:(NSString *)suffix { + NSString *tmpPath = [self temporaryFilePath:suffix]; if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { return tmpPath; } else {
diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.m b/packages/image_picker/ios/Classes/ImagePickerPlugin.m index d4d5d7b..741e41f 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.m +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.m
@@ -9,6 +9,7 @@ #import <Photos/Photos.h> #import <UIKit/UIKit.h> +#import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" @@ -249,7 +250,7 @@ NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; + image = [FLTImagePickerImageUtil scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; } PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info]; @@ -263,7 +264,11 @@ options:nil resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, UIImageOrientation orientation, NSDictionary *_Nullable info) { - [weakSelf saveImageWithOriginalImageData:imageData image:image]; + // maxWidth and maxHeight are used only for GIF images. + [weakSelf saveImageWithOriginalImageData:imageData + image:image + maxWidth:maxWidth + maxHeight:maxHeight]; }]; } } @@ -278,59 +283,15 @@ _arguments = nil; } -- (UIImage *)scaledImage:(UIImage *)image - maxWidth:(NSNumber *)maxWidth - maxHeight:(NSNumber *)maxHeight { - double originalWidth = image.size.width; - double originalHeight = image.size.height; - - bool hasMaxWidth = maxWidth != (id)[NSNull null]; - bool hasMaxHeight = maxHeight != (id)[NSNull null]; - - double width = hasMaxWidth ? MIN([maxWidth doubleValue], originalWidth) : originalWidth; - double height = hasMaxHeight ? MIN([maxHeight doubleValue], originalHeight) : originalHeight; - - bool shouldDownscaleWidth = hasMaxWidth && [maxWidth doubleValue] < originalWidth; - bool shouldDownscaleHeight = hasMaxHeight && [maxHeight doubleValue] < originalHeight; - bool shouldDownscale = shouldDownscaleWidth || shouldDownscaleHeight; - - if (shouldDownscale) { - double downscaledWidth = floor((height / originalHeight) * originalWidth); - double downscaledHeight = floor((width / originalWidth) * originalHeight); - - if (width < height) { - if (!hasMaxWidth) { - width = downscaledWidth; - } else { - height = downscaledHeight; - } - } else if (height < width) { - if (!hasMaxHeight) { - height = downscaledHeight; - } else { - width = downscaledWidth; - } - } else { - if (originalWidth < originalHeight) { - width = downscaledWidth; - } else if (originalHeight < originalWidth) { - height = downscaledHeight; - } - } - } - - UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); - [image drawInRect:CGRectMake(0, 0, width, height)]; - - UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return scaledImage; -} - -- (void)saveImageWithOriginalImageData:(NSData *)originalImageData image:(UIImage *)image { +- (void)saveImageWithOriginalImageData:(NSData *)originalImageData + image:(UIImage *)image + maxWidth:(NSNumber *)maxWidth + maxHeight:(NSNumber *)maxHeight { NSString *savedPath = - [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:originalImageData image:image]; + [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:originalImageData + image:image + maxWidth:maxWidth + maxHeight:maxHeight]; [self handleSavedPath:savedPath]; }
diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml index 9b75a8f..6cddfa1 100755 --- a/packages/image_picker/pubspec.yaml +++ b/packages/image_picker/pubspec.yaml
@@ -5,7 +5,7 @@ - Flutter Team <flutter-dev@googlegroups.com> - Rhodes Davis Jr. <rody.davis.jr@gmail.com> homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.6.0+9 +version: 0.6.0+10 flutter: plugin: