Canoe

有关GIF图片上传

2017.06.10

需求

实现gif图片和普通静态图的混合上传,并且在小图界面需要显示gif动画
效果如图:

思路

用到的参数

  • data类型的图片 ——> 上传到服务器
  • image ——> 用于显示
  • assset (图片的各种参数信息PHAsset or ALAsset) ——> 用于获取原图以及判断是否为gif图片

用到的核心方法

  • 根据asset获取原图的data数据
  • GIF图片 data转换成image类型
  • GIF图片 image类型转换成data类型

图片上传其实说透了很简单,只需要将图片data数据传给服务器,麻烦的是数据和页面显示的处理,我一向喜欢将复杂的问题简单化,所以简化来实际上需要的就是两个东西,不管是gif还是普通图片,一个是image用于界面显示,一个是data数据用于上传。

思路清晰了那么直接开始实现。

步骤

1.从相册获取图片

这里需要注意的是在iOS9之前使用的是AssetsLibrary,在iOS9之后使用的是PhotoKit框架。有关于这两个框架可以参考iOS照片开发之PhotoKit - 贵大头的博客
网上有很多从相册获取图片的方式,如果想详细了解可以查看这个Demo:GGPhotoManager,或者直接使用一些比较成熟的三方库TZImagePickerController,这里不再详细说明。
从相册中我们可以获取到图片的两个参数,一个是image缩略图,一个是asset(PHAsset或者ALAsset)。

2.判断gif图片,获取gif的UIImage类型图片

需求是能够看到动图的效果,所以我们必须获取到UIImage类型的gif,但是不能在选择的时候直接获取原图,因为原图获取是一个异步的线程,并且需要一定时间,所以获取到缩略图之后直接显示到界面上,然后通过获取原图的方法获取原始gif图片再次刷新界面,显示动态图片。

  • 根据asset判断图片格式
if ([asset isKindOfClass:[PHAsset class]]) {
        PHAsset *phAsset = (PHAsset *)asset;
        if (phAsset.mediaType == PHAssetMediaTypeVideo)      type = Video;
        else if (phAsset.mediaType == PHAssetMediaTypeAudio) type = Audio;
        else if (phAsset.mediaType == PHAssetMediaTypeImage) {
            if (iOS9_1Later) {
                // if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) type = LivePhoto;
            }
            // Gif
            if ([[phAsset valueForKey:@"filename"] hasSuffix:@"GIF"]) {
                type = Gif;
            }
        }
    } else {
        if ([[asset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) {
            type = Video;
        }
    }
  • 根据asset获取原图的data数据
- (void)getOriginalPhotoDataWithAsset:(id)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion {
    if ([asset isKindOfClass:[PHAsset class]]) {
        PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
        option.networkAccessAllowed = YES;
        option.resizeMode = PHImageRequestOptionsResizeModeFast;
        [[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
            BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
            if (downloadFinined && imageData) {
                if (completion) completion(imageData,info,NO);
            }
        }];
    } else if ([asset isKindOfClass:[ALAsset class]]) {
        ALAsset *alAsset = (ALAsset *)asset;
        ALAssetRepresentation *assetRep = [alAsset defaultRepresentation];
        Byte *imageBuffer = (Byte *)malloc(assetRep.size);
        NSUInteger bufferSize = [assetRep getBytes:imageBuffer fromOffset:0.0 length:assetRep.size error:nil];
        NSData *imageData = [NSData dataWithBytesNoCopy:imageBuffer length:bufferSize freeWhenDone:YES];
        if (completion) completion(imageData,nil,NO);
    }
}
  • data数据转换成gif
NSString * const AnimatedGIFImageErrorDomain = @"com.compuserve.gif.image.error";

__attribute__((overloadable)) UIImage * UIImageWithAnimatedGIFData(NSData *data) {
    return UIImageWithAnimatedGIFData(data, [[UIScreen mainScreen] scale], 0.0f, nil);
}

__attribute__((overloadable)) UIImage * UIImageWithAnimatedGIFData(NSData *data, CGFloat scale, NSTimeInterval duration, NSError * __autoreleasing *error) {
    if (!data) {
        return nil;
    }

    NSDictionary *userInfo = nil;
    {
        NSMutableDictionary *mutableOptions = [NSMutableDictionary dictionary];
        [mutableOptions setObject:@(YES) forKey:(NSString *)kCGImageSourceShouldCache];
        [mutableOptions setObject:(NSString *)kUTTypeGIF forKey:(NSString *)kCGImageSourceTypeIdentifierHint];

        CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)mutableOptions);

        size_t numberOfFrames = CGImageSourceGetCount(imageSource);
        NSMutableArray *mutableImages = [NSMutableArray arrayWithCapacity:numberOfFrames];

        NSTimeInterval calculatedDuration = 0.0f;
        for (size_t idx = 0; idx < numberOfFrames; idx++) {
            CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, idx, (__bridge CFDictionaryRef)mutableOptions);

            NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, idx, NULL);
            calculatedDuration += [[[properties objectForKey:(__bridge NSString *)kCGImagePropertyGIFDictionary] objectForKey:(__bridge  NSString *)kCGImagePropertyGIFDelayTime] doubleValue];

            [mutableImages addObject:[UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]];

            CGImageRelease(imageRef);
        }

        CFRelease(imageSource);

        if (numberOfFrames == 1) {
            return [mutableImages firstObject];
        } else {
            return [UIImage animatedImageWithImages:mutableImages duration:(duration <= 0.0f ? calculatedDuration : duration)];
        }
    }
    _error: {
        if (error) {
            *error = [[NSError alloc] initWithDomain:AnimatedGIFImageErrorDomain code:-1 userInfo:userInfo];
        }
        return nil;
    }
}

3.上传gif图片,格式转换

现在已经能够显示动态图片了,接下来需要上传图片到服务器,这里使用了AFNetworking的上传图片接口。


到这里可以看到动态图的类和普通图片的类有区别,他会显示动画时间以及图片张数,通过这个我们可以根据image来区分动态图和基本图,如果需要压缩也可以想到将单张图片压缩之后再重新拼接在一起,这里需求不需要做压缩。

上传图片时需要Data数据,现在最大的问题是如何将gif类型的图片转化成Data类型的数据,使用UIImageJPEGRepresentation()方法会将GIF转化成单张图片,很明显此路不通。
通过谷歌和查看文档,我找到了将gif转成data的方法。

  • gif转成data
__attribute__((overloadable)) NSData * UIImageAnimatedGIFRepresentation(UIImage *image) {
    return UIImageAnimatedGIFRepresentation(image, 0.0f, 0, nil);
}

__attribute__((overloadable)) NSData * UIImageAnimatedGIFRepresentation(UIImage *image, NSTimeInterval duration, NSUInteger loopCount, NSError * __autoreleasing *error) {
    if (!image.images) {
        return nil;
    }
    NSDictionary *userInfo = nil;
    {
        size_t frameCount = image.images.count;
        NSTimeInterval frameDuration = (duration <= 0.0 ? image.duration / frameCount : duration / frameCount);
        NSDictionary *frameProperties = @{
                                          (__bridge NSString *)kCGImagePropertyGIFDictionary: @{
                                                  (__bridge NSString *)kCGImagePropertyGIFDelayTime: @(frameDuration)
                                                  }
                                          };
        NSMutableData *mutableData = [NSMutableData data];
        CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)mutableData, kUTTypeGIF, frameCount, NULL);
        NSDictionary *imageProperties = @{ (__bridge NSString *)kCGImagePropertyGIFDictionary: @{
                                              (__bridge NSString *)kCGImagePropertyGIFLoopCount: @(loopCount)
                                            }
                                    };
        CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)imageProperties);
        for (size_t idx = 0; idx < image.images.count; idx++) {
            CGImageDestinationAddImage(destination, [[image.images objectAtIndex:idx] CGImage], (__bridge CFDictionaryRef)frameProperties);
        }
        BOOL success = CGImageDestinationFinalize(destination);
        CFRelease(destination);
        if (!success) {
            userInfo = @{
                         NSLocalizedDescriptionKey: NSLocalizedString(@"Could not finalize image destination", nil)
                        };
            goto _error;
        }
        return [NSData dataWithData:mutableData];
    }
    _error: {
        if (error) {
            *error = [[NSError alloc] initWithDomain:AnimatedGIFImageErrorDomain code:-1 userInfo:userInfo];
        }
        return nil;
    }
}
  • 上传图片处理
- (id<AFMultipartFormData>)appendPartWithImageArray:(NSMutableArray *)imageArray formData:(id<AFMultipartFormData>)formData name:(NSString *)name
{
    for (NSInteger i = 0; i < imageArray.count; i++) {
        NSData *imageData = [NSData data];
        // 可以在上传时使用当前的系统事件作为文件名
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        // 设置时间格式
        formatter.dateFormat = @"yyyyMMddHHmmss";
        NSString *str = [formatter stringFromDate:[NSDate date]];
        NSString *imageName = [NSString stringWithFormat:@"%@%@@%ld.png",str,name,i];
    
        //gif的处理
        if ([imageArray[i] images].count > 1) {
            imageData = UIImageAnimatedGIFRepresentation(imageArray[i]);
            [formData appendPartWithFileData:imageData name:name fileName:[NSString stringWithFormat:@"%@%@@%ld.gif",str,name,i] mimeType:@"image/gif"];
        }else
        {
            //图片压缩处理
            imageData = [UIImage compressImage:imageArray[i] toMaxLength:512000];
            [formData appendPartWithFileData:imageData name:name fileName:imageName mimeType:@"image/png"];
        }
    }
    return formData;
}

至此,gif图片上传完成,但是还有一些地方可以优化,例如gif图片压缩等,有什么建议或者错误欢迎指正。

Comments
Write a Comment