UIImage+WebP.m 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #ifdef SD_WEBP
  9. #import "UIImage+WebP.h"
  10. #import "webp/decode.h"
  11. #import "webp/mux_types.h"
  12. #import "webp/demux.h"
  13. #import "NSImage+WebCache.h"
  14. #import "objc/runtime.h"
  15. // Callback for CGDataProviderRelease
  16. static void FreeImageData(void *info, const void *data, size_t size) {
  17. free((void *)data);
  18. }
  19. @implementation UIImage (WebP)
  20. - (NSInteger)sd_webpLoopCount
  21. {
  22. NSNumber *value = objc_getAssociatedObject(self, @selector(sd_webpLoopCount));
  23. return value.integerValue;
  24. }
  25. + (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data {
  26. if (!data) {
  27. return nil;
  28. }
  29. WebPData webpData;
  30. WebPDataInit(&webpData);
  31. webpData.bytes = data.bytes;
  32. webpData.size = data.length;
  33. WebPDemuxer *demuxer = WebPDemux(&webpData);
  34. if (!demuxer) {
  35. return nil;
  36. }
  37. uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
  38. if (!(flags & ANIMATION_FLAG)) {
  39. // for static single webp image
  40. UIImage *staticImage = [self sd_rawWebpImageWithData:webpData];
  41. WebPDemuxDelete(demuxer);
  42. return staticImage;
  43. }
  44. WebPIterator iter;
  45. if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
  46. WebPDemuxReleaseIterator(&iter);
  47. WebPDemuxDelete(demuxer);
  48. return nil;
  49. }
  50. #if SD_UIKIT || SD_WATCH
  51. int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
  52. int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
  53. #endif
  54. int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
  55. int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
  56. CGBitmapInfo bitmapInfo;
  57. if (!(flags & ALPHA_FLAG)) {
  58. bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
  59. } else {
  60. bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
  61. }
  62. CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
  63. if (!canvas) {
  64. WebPDemuxReleaseIterator(&iter);
  65. WebPDemuxDelete(demuxer);
  66. return nil;
  67. }
  68. NSMutableArray<UIImage *> *images = [NSMutableArray array];
  69. #if SD_UIKIT || SD_WATCH
  70. NSTimeInterval totalDuration = 0;
  71. int durations[frameCount];
  72. #endif
  73. do {
  74. UIImage *image;
  75. if (iter.blend_method == WEBP_MUX_BLEND) {
  76. image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter];
  77. } else {
  78. image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter];
  79. }
  80. if (!image) {
  81. continue;
  82. }
  83. [images addObject:image];
  84. #if SD_MAC
  85. break;
  86. #else
  87. int duration = iter.duration;
  88. if (duration <= 10) {
  89. // 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
  90. // Some animated WebP images also created without duration, we should keep compatibility
  91. duration = 100;
  92. }
  93. totalDuration += duration;
  94. size_t count = images.count;
  95. durations[count - 1] = duration;
  96. #endif
  97. } while (WebPDemuxNextFrame(&iter));
  98. WebPDemuxReleaseIterator(&iter);
  99. WebPDemuxDelete(demuxer);
  100. CGContextRelease(canvas);
  101. UIImage *finalImage = nil;
  102. #if SD_UIKIT || SD_WATCH
  103. NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:totalDuration];
  104. finalImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.0];
  105. if (finalImage) {
  106. objc_setAssociatedObject(finalImage, @selector(sd_webpLoopCount), @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  107. }
  108. #elif SD_MAC
  109. finalImage = images.firstObject;
  110. #endif
  111. return finalImage;
  112. }
  113. + (nullable UIImage *)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
  114. UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
  115. if (!image) {
  116. return nil;
  117. }
  118. size_t canvasWidth = CGBitmapContextGetWidth(canvas);
  119. size_t canvasHeight = CGBitmapContextGetHeight(canvas);
  120. CGSize size = CGSizeMake(canvasWidth, canvasHeight);
  121. CGFloat tmpX = iter.x_offset;
  122. CGFloat tmpY = size.height - iter.height - iter.y_offset;
  123. CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
  124. CGContextDrawImage(canvas, imageRect, image.CGImage);
  125. CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
  126. #if SD_UIKIT || SD_WATCH
  127. image = [UIImage imageWithCGImage:newImageRef];
  128. #elif SD_MAC
  129. image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
  130. #endif
  131. CGImageRelease(newImageRef);
  132. if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
  133. CGContextClearRect(canvas, imageRect);
  134. }
  135. return image;
  136. }
  137. + (nullable UIImage *)sd_nonblendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
  138. UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
  139. if (!image) {
  140. return nil;
  141. }
  142. size_t canvasWidth = CGBitmapContextGetWidth(canvas);
  143. size_t canvasHeight = CGBitmapContextGetHeight(canvas);
  144. CGSize size = CGSizeMake(canvasWidth, canvasHeight);
  145. CGFloat tmpX = iter.x_offset;
  146. CGFloat tmpY = size.height - iter.height - iter.y_offset;
  147. CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
  148. CGContextClearRect(canvas, imageRect);
  149. CGContextDrawImage(canvas, imageRect, image.CGImage);
  150. CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
  151. #if SD_UIKIT || SD_WATCH
  152. image = [UIImage imageWithCGImage:newImageRef];
  153. #elif SD_MAC
  154. image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
  155. #endif
  156. CGImageRelease(newImageRef);
  157. if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
  158. CGContextClearRect(canvas, imageRect);
  159. }
  160. return image;
  161. }
  162. + (nullable UIImage *)sd_rawWebpImageWithData:(WebPData)webpData {
  163. WebPDecoderConfig config;
  164. if (!WebPInitDecoderConfig(&config)) {
  165. return nil;
  166. }
  167. if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) {
  168. return nil;
  169. }
  170. config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
  171. config.options.use_threads = 1;
  172. // Decode the WebP image data into a RGBA value array
  173. if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) {
  174. return nil;
  175. }
  176. int width = config.input.width;
  177. int height = config.input.height;
  178. if (config.options.use_scaling) {
  179. width = config.options.scaled_width;
  180. height = config.options.scaled_height;
  181. }
  182. // Construct a UIImage from the decoded RGBA value array
  183. CGDataProviderRef provider =
  184. CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
  185. CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
  186. CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
  187. size_t components = config.input.has_alpha ? 4 : 3;
  188. CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
  189. CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
  190. CGDataProviderRelease(provider);
  191. #if SD_UIKIT || SD_WATCH
  192. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
  193. #else
  194. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef size:NSZeroSize];
  195. #endif
  196. CGImageRelease(imageRef);
  197. return image;
  198. }
  199. + (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
  200. {
  201. // [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
  202. // divide the total duration to implement per frame duration for animated WebP
  203. NSUInteger count = images.count;
  204. if (!count) {
  205. return nil;
  206. }
  207. if (count == 1) {
  208. return images;
  209. }
  210. int const gcd = gcdArray(count, durations);
  211. NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
  212. [images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
  213. int duration = durations[idx];
  214. int repeatCount;
  215. if (gcd) {
  216. repeatCount = duration / gcd;
  217. } else {
  218. repeatCount = 1;
  219. }
  220. for (int i = 0; i < repeatCount; ++i) {
  221. [animatedImages addObject:image];
  222. }
  223. }];
  224. return animatedImages;
  225. }
  226. static CGColorSpaceRef SDCGColorSpaceGetDeviceRGB() {
  227. static CGColorSpaceRef space;
  228. static dispatch_once_t onceToken;
  229. dispatch_once(&onceToken, ^{
  230. space = CGColorSpaceCreateDeviceRGB();
  231. });
  232. return space;
  233. }
  234. static int gcdArray(size_t const count, int const * const values) {
  235. int result = values[0];
  236. for (size_t i = 1; i < count; ++i) {
  237. result = gcd(values[i], result);
  238. }
  239. return result;
  240. }
  241. static int gcd(int a,int b) {
  242. int c;
  243. while (a != 0) {
  244. c = a;
  245. a = b % a;
  246. b = c;
  247. }
  248. return b;
  249. }
  250. @end
  251. #endif