From df099ced20a48231ff1e843c764d390f01f79a27 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 5 Aug 2017 16:09:59 +0800 Subject: [PATCH] Performance enhancement related to single WebP image and animated WebP decoding 1. Change the code to not decode WebP images created in SD because it has been decoded 2. Add @autoreleasepool for animated WebP decoding do-while loop to reduce memory peak --- SDWebImage/SDImageCache.m | 6 ++ SDWebImage/SDWebImageDownloaderOperation.m | 16 +++- SDWebImage/UIImage+WebP.m | 85 ++++++++++++++-------- 3 files changed, 73 insertions(+), 34 deletions(-) diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index acd3f2b2..813cec63 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -347,6 +347,12 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { if (data) { UIImage *image = [UIImage sd_imageWithData:data]; image = [self scaledImageForKey:key image:image]; +#ifdef SD_WEBP + SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data]; + if (imageFormat == SDImageFormatWebP) { + return image; + } +#endif if (self.config.shouldDecompressImages) { image = [UIImage decodedImageWithImage:image]; } diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index 6c171428..89d75ab9 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -446,8 +446,20 @@ didReceiveResponse:(NSURLResponse *)response NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; image = [self scaledImageForKey:key image:image]; - // Do not force decoding animated GIFs - if (!image.images) { + BOOL shouldDecode = YES; + // Do not force decoding animated GIFs and WebPs + if (image.images) { + shouldDecode = NO; + } else { +#ifdef SD_WEBP + SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:self.imageData]; + if (imageFormat == SDImageFormatWebP) { + shouldDecode = NO; + } +#endif + } + + if (shouldDecode) { if (self.shouldDecompressImages) { if (self.options & SDWebImageDownloaderScaleDownLargeImages) { #if SD_UIKIT || SD_WATCH diff --git a/SDWebImage/UIImage+WebP.m b/SDWebImage/UIImage+WebP.m index 2a25169d..117bbad1 100644 --- a/SDWebImage/UIImage+WebP.m +++ b/SDWebImage/UIImage+WebP.m @@ -43,14 +43,6 @@ static void FreeImageData(void *info, const void *data, size_t size) { return nil; } - uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); - if (!(flags & ANIMATION_FLAG)) { - // for static single webp image - UIImage *staticImage = [self sd_rawWebpImageWithData:webpData]; - WebPDemuxDelete(demuxer); - return staticImage; - } - WebPIterator iter; if (!WebPDemuxGetFrame(demuxer, 1, &iter)) { WebPDemuxReleaseIterator(&iter); @@ -58,6 +50,8 @@ static void FreeImageData(void *info, const void *data, size_t size) { return nil; } + uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); + #if SD_UIKIT || SD_WATCH int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT); int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT); @@ -77,6 +71,30 @@ static void FreeImageData(void *info, const void *data, size_t size) { return nil; } + if (!(flags & ANIMATION_FLAG)) { + // for static single webp image + UIImage *staticImage = [self sd_rawWebpImageWithData:webpData]; + if (staticImage) { + // draw on CGBitmapContext can reduce memory usage + CGImageRef imageRef = staticImage.CGImage; + size_t width = CGImageGetWidth(imageRef); + size_t height = CGImageGetHeight(imageRef); + CGContextDrawImage(canvas, CGRectMake(0, 0, width, height), imageRef); + CGImageRef newImageRef = CGBitmapContextCreateImage(canvas); +#if SD_UIKIT || SD_WATCH + staticImage = [[UIImage alloc] initWithCGImage:newImageRef]; +#else + staticImage = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize]; +#endif + CGImageRelease(newImageRef); + } + WebPDemuxReleaseIterator(&iter); + WebPDemuxDelete(demuxer); + CGContextRelease(canvas); + return staticImage; + } + + // for animated webp image NSMutableArray *images = [NSMutableArray array]; #if SD_UIKIT || SD_WATCH NSTimeInterval totalDuration = 0; @@ -84,33 +102,36 @@ static void FreeImageData(void *info, const void *data, size_t size) { #endif do { - UIImage *image; - if (iter.blend_method == WEBP_MUX_BLEND) { - image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter]; - } else { - image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter]; - } - - if (!image) { - continue; - } - - [images addObject:image]; - + @autoreleasepool { + UIImage *image; + if (iter.blend_method == WEBP_MUX_BLEND) { + image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter]; + } else { + image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter]; + } + + if (!image) { + continue; + } + + [images addObject:image]; + #if SD_MAC - break; + break; #else - - int duration = iter.duration; - if (duration <= 10) { - // WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms - // Some animated WebP images also created without duration, we should keep compatibility - duration = 100; - } - totalDuration += duration; - size_t count = images.count; - durations[count - 1] = duration; + + int duration = iter.duration; + if (duration <= 10) { + // WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms + // Some animated WebP images also created without duration, we should keep compatibility + duration = 100; + } + totalDuration += duration; + size_t count = images.count; + durations[count - 1] = duration; #endif + } + } while (WebPDemuxNextFrame(&iter)); WebPDemuxReleaseIterator(&iter);