From c2173c1e759d8bb5a70f88f13f48d64fcd5f1534 Mon Sep 17 00:00:00 2001 From: jiangliancheng Date: Fri, 15 Jan 2016 23:47:25 +0800 Subject: [PATCH] add animated webP support --- SDWebImage.xcodeproj/project.pbxproj | 12 ++++ SDWebImage/UIImage+WebP.m | 97 +++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index c570bc2c..f563d8e1 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -173,6 +173,7 @@ 5D5B9145188EE8DD006D06BD /* NSData+ImageContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */; }; 5D5B9146188EE8DD006D06BD /* NSData+ImageContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */; }; 5D5B9147188EE8DD006D06BD /* NSData+ImageContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */; }; + 750C1B8E1C494AB000DC00B9 /* demux.c in Sources */ = {isa = PBXBuildFile; fileRef = 750C1B8A1C49491900DC00B9 /* demux.c */; }; A18A6CC7172DC28500419892 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CC5172DC28500419892 /* UIImage+GIF.h */; }; A18A6CC8172DC28500419892 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CC5172DC28500419892 /* UIImage+GIF.h */; }; A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; }; @@ -316,6 +317,7 @@ 53FB894814D35E9E0020B787 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 5D5B9140188EE8DD006D06BD /* NSData+ImageContentType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+ImageContentType.h"; sourceTree = ""; }; 5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+ImageContentType.m"; sourceTree = ""; }; + 750C1B8A1C49491900DC00B9 /* demux.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = demux.c; sourceTree = ""; }; A18A6CC5172DC28500419892 /* UIImage+GIF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+GIF.h"; sourceTree = ""; }; A18A6CC6172DC28500419892 /* UIImage+GIF.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+GIF.m"; sourceTree = ""; }; AB615301192DA24600A2D8E9 /* UIView+WebCacheOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+WebCacheOperation.h"; sourceTree = ""; }; @@ -571,6 +573,14 @@ name = Utils; sourceTree = ""; }; + 750C1B891C49491900DC00B9 /* demux */ = { + isa = PBXGroup; + children = ( + 750C1B8A1C49491900DC00B9 /* demux.c */, + ); + path = demux; + sourceTree = ""; + }; DA577C121998E60B007367ED /* libwebp */ = { isa = PBXGroup; children = ( @@ -583,6 +593,7 @@ DA577C4F1998E60B007367ED /* src */ = { isa = PBXGroup; children = ( + 750C1B891C49491900DC00B9 /* demux */, DA577D5A1998E6B2007367ED /* dec */, DA577C651998E60B007367ED /* dsp */, DA577CA71998E60B007367ED /* utils */, @@ -1134,6 +1145,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 750C1B8E1C494AB000DC00B9 /* demux.c in Sources */, DA577D101998E60B007367ED /* upsampling_neon.c in Sources */, DA577D151998E60B007367ED /* yuv_sse2.c in Sources */, DA577D111998E60B007367ED /* upsampling_sse2.c in Sources */, diff --git a/SDWebImage/UIImage+WebP.m b/SDWebImage/UIImage+WebP.m index f27a1b6f..f5effec7 100644 --- a/SDWebImage/UIImage+WebP.m +++ b/SDWebImage/UIImage+WebP.m @@ -11,8 +11,12 @@ #if !COCOAPODS #import "webp/decode.h" +#import "webp/mux_types.h" +#import "webp/demux.h" #else #import "libwebp/webp/decode.h" +#import "libwebp/webpc/mux_types.h" +#import "libwebp/webp/demux.h" #endif // Callback for CGDataProviderRelease @@ -24,12 +28,101 @@ static void FreeImageData(void *info, const void *data, size_t size) @implementation UIImage (WebP) + (UIImage *)sd_imageWithWebPData:(NSData *)data { + if (!data) { + return nil; + } + + WebPData webpData; + WebPDataInit(&webpData); + webpData.bytes = data.bytes; + webpData.size = data.length; + WebPDemuxer *demuxer = WebPDemux(&webpData); + if (!demuxer) { + return nil; + } + + uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); + if (!(flags & ANIMATION_FLAG)) { + // for static single webp image + UIImage *staticImage = [self sd_rawWepImageWithData:webpData]; + WebPDemuxDelete(demuxer); + return staticImage; + } + + WebPIterator iter; + if (!WebPDemuxGetFrame(demuxer, 1, &iter)) { + WebPDemuxReleaseIterator(&iter); + WebPDemuxDelete(demuxer); + return nil; + } + + NSMutableArray *images = [NSMutableArray array]; + NSTimeInterval duration = 0; + + do { + UIImage *image; + if (iter.blend_method == WEBP_MUX_BLEND) { + image = [self sd_blendWebpImageWithOriginImage:[images lastObject] iterator:iter]; + } else { + image = [self sd_rawWepImageWithData:iter.fragment]; + } + + if (!image) { + continue; + } + + [images addObject:image]; + duration += iter.duration / 1000.0f; + + } while (WebPDemuxNextFrame(&iter)); + + WebPDemuxReleaseIterator(&iter); + WebPDemuxDelete(demuxer); + UIImage *animateImage = [UIImage animatedImageWithImages:images duration:duration]; + return animateImage; +} + + ++ (UIImage *)sd_blendWebpImageWithOriginImage:(UIImage *)originImage iterator:(WebPIterator)iter +{ + if (!originImage) { + return nil; + } + + CGSize size = originImage.size; + CGFloat tmpX = iter.x_offset; + CGFloat tmpY = size.height - iter.height - iter.y_offset; + CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height); + + UIImage *image = [self sd_rawWepImageWithData:iter.fragment]; + if (!image) { + return nil; + } + + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + uint32_t bitmapInfo = iter.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0; + CGContextRef blendCanvas = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpaceRef, bitmapInfo); + CGContextDrawImage(blendCanvas, CGRectMake(0, 0, size.width, size.height), originImage.CGImage); + CGContextDrawImage(blendCanvas, imageRect, image.CGImage); + CGImageRef newImageRef = CGBitmapContextCreateImage(blendCanvas); + + image = [UIImage imageWithCGImage:newImageRef]; + + CGImageRelease(newImageRef); + CGContextRelease(blendCanvas); + CGColorSpaceRelease(colorSpaceRef); + + return image; +} + ++ (UIImage *)sd_rawWepImageWithData:(WebPData)webpData +{ WebPDecoderConfig config; if (!WebPInitDecoderConfig(&config)) { return nil; } - if (WebPGetFeatures(data.bytes, data.length, &config.input) != VP8_STATUS_OK) { + if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) { return nil; } @@ -37,7 +130,7 @@ static void FreeImageData(void *info, const void *data, size_t size) config.options.use_threads = 1; // Decode the WebP image data into a RGBA value array. - if (WebPDecode(data.bytes, data.length, &config) != VP8_STATUS_OK) { + if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) { return nil; }