blob: d71175da1e924fb0c6a2a95ea48135026294cc8c [file] [log] [blame]
// 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 "FLTImagePickerImageUtil.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface GIFInfo ()
@property(strong, nonatomic, readwrite) NSArray<UIImage *> *images;
@property(assign, nonatomic, readwrite) NSTimeInterval interval;
@end
@implementation GIFInfo
- (instancetype)initWithImages:(NSArray<UIImage *> *)images interval:(NSTimeInterval)interval;
{
self = [super init];
if (self) {
self.images = images;
self.interval = interval;
}
return self;
}
@end
@implementation FLTImagePickerImageUtil : NSObject
static UIImage *FLTImagePickerDrawScaledImage(UIImage *imageToScale, double width, double height) {
if (imageToScale == nil || width == 0 || height == 0) {
return nil;
}
UIGraphicsImageRenderer *imageRenderer =
[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(width, height)
format:imageToScale.imageRendererFormat];
return [imageRenderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
CGContextRef cgContext = rendererContext.CGContext;
// Flip vertically to translate between UIKit and Quartz.
CGContextTranslateCTM(cgContext, 0, height);
CGContextScaleCTM(cgContext, 1, -1);
CGContextDrawImage(cgContext, CGRectMake(0, 0, width, height), imageToScale.CGImage);
}];
}
+ (UIImage *)scaledImage:(UIImage *)image
maxWidth:(NSNumber *)maxWidth
maxHeight:(NSNumber *)maxHeight
isMetadataAvailable:(BOOL)isMetadataAvailable {
double originalWidth = image.size.width;
double originalHeight = image.size.height;
BOOL hasMaxWidth = maxWidth != nil;
BOOL hasMaxHeight = maxHeight != nil;
if ((originalWidth == maxWidth.doubleValue && originalHeight == maxHeight.doubleValue) ||
(!hasMaxWidth && !hasMaxHeight)) {
// Nothing to scale.
return image;
}
double aspectRatio = originalWidth / originalHeight;
double width = hasMaxWidth ? MIN(round([maxWidth doubleValue]), originalWidth) : originalWidth;
double height =
hasMaxHeight ? MIN(round([maxHeight doubleValue]), originalHeight) : originalHeight;
bool shouldDownscaleWidth = hasMaxWidth && [maxWidth doubleValue] < originalWidth;
bool shouldDownscaleHeight = hasMaxHeight && [maxHeight doubleValue] < originalHeight;
bool shouldDownscale = shouldDownscaleWidth || shouldDownscaleHeight;
if (shouldDownscale) {
double widthForMaxHeight = height * aspectRatio;
double heightForMaxWidth = width / aspectRatio;
if (heightForMaxWidth > height) {
width = round(widthForMaxHeight);
} else {
height = round(heightForMaxWidth);
}
}
if (!isMetadataAvailable) {
UIImage *imageToScale = [UIImage imageWithCGImage:image.CGImage
scale:1
orientation:image.imageOrientation];
return FLTImagePickerDrawScaledImage(imageToScale, width, height);
}
// Scaling the image always rotate itself based on the current imageOrientation of the original
// Image. Set to orientationUp for the orignal image before scaling, so the scaled image doesn't
// mess up with the pixels.
UIImage *imageToScale = [UIImage imageWithCGImage:image.CGImage
scale:1
orientation:UIImageOrientationUp];
// The image orientation is manually set to UIImageOrientationUp which swapped the aspect ratio in
// some scenarios. For example, when the original image has orientation left, the horizontal
// pixels should be scaled to `width` and the vertical pixels should be scaled to `height`. After
// setting the orientation to up, we end up scaling the horizontal pixels to `height` and vertical
// to `width`. Below swap will solve this issue.
if ([image imageOrientation] == UIImageOrientationLeft ||
[image imageOrientation] == UIImageOrientationRight ||
[image imageOrientation] == UIImageOrientationLeftMirrored ||
[image imageOrientation] == UIImageOrientationRightMirrored) {
double temp = width;
width = height;
height = temp;
}
return FLTImagePickerDrawScaledImage(imageToScale, width, height);
}
+ (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((__bridge CFDataRef)data, (__bridge 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, (__bridge CFDictionaryRef)options);
NSDictionary *properties = (NSDictionary *)CFBridgingRelease(
CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL));
NSDictionary *gifProperties = properties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delay = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delay == nil) {
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 isMetadataAvailable:YES];
[images addObject:image];
CGImageRelease(imageRef);
}
CFRelease(imageSource);
GIFInfo *info = [[GIFInfo alloc] initWithImages:images interval:interval];
return info;
}
@end