24
24
25
25
#import < Accelerate/Accelerate.h>
26
26
27
+ // / Calculate the actual thumnail pixel size
28
+ static CGSize SDCalculateThumbnailSize (CGSize fullSize, BOOL preserveAspectRatio, CGSize thumbnailSize) {
29
+ CGFloat width = fullSize.width ;
30
+ CGFloat height = fullSize.height ;
31
+ CGFloat resultWidth;
32
+ CGFloat resultHeight;
33
+
34
+ if (width == 0 || height == 0 || thumbnailSize.width == 0 || thumbnailSize.height == 0 || (width <= thumbnailSize.width && height <= thumbnailSize.height )) {
35
+ // Full Pixel
36
+ resultWidth = width;
37
+ resultHeight = height;
38
+ } else {
39
+ // Thumbnail
40
+ if (preserveAspectRatio) {
41
+ CGFloat pixelRatio = width / height;
42
+ CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height ;
43
+ if (pixelRatio > thumbnailRatio) {
44
+ resultWidth = thumbnailSize.width ;
45
+ resultHeight = ceil (thumbnailSize.width / pixelRatio);
46
+ } else {
47
+ resultHeight = thumbnailSize.height ;
48
+ resultWidth = ceil (thumbnailSize.height * pixelRatio);
49
+ }
50
+ } else {
51
+ resultWidth = thumbnailSize.width ;
52
+ resultHeight = thumbnailSize.height ;
53
+ }
54
+ }
55
+
56
+ return CGSizeMake (resultWidth, resultHeight);
57
+ }
58
+
27
59
#ifndef SD_LOCK
28
60
#define SD_LOCK (lock ) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
29
61
#endif
@@ -68,6 +100,8 @@ @implementation SDImageWebPCoder {
68
100
CGFloat _canvasHeight;
69
101
dispatch_semaphore_t _lock;
70
102
NSUInteger _currentBlendIndex;
103
+ BOOL _preserveAspectRatio;
104
+ CGSize _thumbnailSize;
71
105
}
72
106
73
107
- (void )dealloc {
@@ -133,6 +167,22 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
133
167
}
134
168
}
135
169
170
+ CGSize thumbnailSize = CGSizeZero;
171
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
172
+ if (thumbnailSizeValue != nil ) {
173
+ #if SD_MAC
174
+ thumbnailSize = thumbnailSizeValue.sizeValue ;
175
+ #else
176
+ thumbnailSize = thumbnailSizeValue.CGSizeValue ;
177
+ #endif
178
+ }
179
+
180
+ BOOL preserveAspectRatio = YES ;
181
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
182
+ if (preserveAspectRatioValue != nil ) {
183
+ preserveAspectRatio = preserveAspectRatioValue.boolValue ;
184
+ }
185
+
136
186
// for animated webp image
137
187
WebPIterator iter;
138
188
// libwebp's index start with 1
@@ -141,11 +191,15 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
141
191
WebPDemuxDelete (demuxer);
142
192
return nil ;
143
193
}
144
- CGColorSpaceRef colorSpace = [self sd_colorSpaceWithDemuxer: demuxer];
194
+ CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer: demuxer];
195
+ int canvasWidth = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_WIDTH);
196
+ int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT);
197
+ // Check whether we need to use thumbnail
198
+ CGSize scaledSize = SDCalculateThumbnailSize (CGSizeMake (canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
145
199
146
200
if (!hasAnimation || decodeFirstFrame) {
147
201
// first frame for animated webp image
148
- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpace];
202
+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpace scaledSize: scaledSize ];
149
203
CGColorSpaceRelease (colorSpace);
150
204
#if SD_UIKIT || SD_WATCH
151
205
UIImage *firstFrameImage = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
@@ -159,9 +213,6 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
159
213
return firstFrameImage;
160
214
}
161
215
162
- int loopCount = WebPDemuxGetI (demuxer, WEBP_FF_LOOP_COUNT);
163
- int canvasWidth = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_WIDTH);
164
- int canvasHeight = WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT);
165
216
BOOL hasAlpha = flags & ALPHA_FLAG;
166
217
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host ;
167
218
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst ;
@@ -172,14 +223,16 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
172
223
return nil ;
173
224
}
174
225
226
+ int loopCount = WebPDemuxGetI (demuxer, WEBP_FF_LOOP_COUNT);
175
227
NSMutableArray <SDImageFrame *> *frames = [NSMutableArray array ];
176
228
177
229
do {
178
230
@autoreleasepool {
179
- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: canvas iterator: iter colorSpace: colorSpace];
231
+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: canvas iterator: iter colorSpace: colorSpace scaledSize: scaledSize ];
180
232
if (!imageRef) {
181
233
continue ;
182
234
}
235
+
183
236
#if SD_UIKIT || SD_WATCH
184
237
UIImage *image = [[UIImage alloc ] initWithCGImage: imageRef scale: scale orientation: UIImageOrientationUp];
185
238
#else
@@ -221,6 +274,22 @@ - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)optio
221
274
}
222
275
}
223
276
_scale = scale;
277
+ CGSize thumbnailSize = CGSizeZero;
278
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
279
+ if (thumbnailSizeValue != nil ) {
280
+ #if SD_MAC
281
+ thumbnailSize = thumbnailSizeValue.sizeValue ;
282
+ #else
283
+ thumbnailSize = thumbnailSizeValue.CGSizeValue ;
284
+ #endif
285
+ }
286
+ _thumbnailSize = thumbnailSize;
287
+ BOOL preserveAspectRatio = YES ;
288
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
289
+ if (preserveAspectRatioValue != nil ) {
290
+ preserveAspectRatio = preserveAspectRatioValue.boolValue ;
291
+ }
292
+ _preserveAspectRatio = preserveAspectRatio;
224
293
}
225
294
return self;
226
295
}
@@ -317,7 +386,7 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
317
386
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
318
387
CGContextClearRect (canvas, imageRect);
319
388
} else {
320
- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef];
389
+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef scaledSize: CGSizeZero ];
321
390
if (!imageRef) {
322
391
return ;
323
392
}
@@ -331,16 +400,18 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
331
400
}
332
401
}
333
402
334
- - (nullable CGImageRef)sd_drawnWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
335
- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef];
403
+ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas : (CGContextRef)canvas iterator : (WebPIterator)iter colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef scaledSize : (CGSize) scaledSize CF_RETURNS_RETAINED {
404
+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: colorSpaceRef scaledSize: CGSizeZero ];
336
405
if (!imageRef) {
337
406
return nil ;
338
407
}
339
408
409
+ size_t canvasWidth = CGBitmapContextGetWidth (canvas);
340
410
size_t canvasHeight = CGBitmapContextGetHeight (canvas);
341
411
CGFloat tmpX = iter.x_offset ;
342
412
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset ;
343
413
CGRect imageRect = CGRectMake (tmpX, tmpY, iter.width , iter.height );
414
+
344
415
BOOL shouldBlend = iter.blend_method == WEBP_MUX_BLEND;
345
416
346
417
// If not blend, cover the target image rect. (firstly clear then draw)
@@ -351,15 +422,26 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
351
422
CGImageRef newImageRef = CGBitmapContextCreateImage (canvas);
352
423
353
424
CGImageRelease (imageRef);
354
-
425
+
355
426
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
356
427
CGContextClearRect (canvas, imageRect);
357
428
}
358
429
430
+ // Check whether we need to use thumbnail
431
+ if (!CGSizeEqualToSize (CGSizeMake (canvasWidth, canvasHeight), scaledSize)) {
432
+ // Important: For Animated WebP thumbnail generation, we can not just use a scaled small canvas and draw each thumbnail frame
433
+ // This works **On Theory**. However, image scale down loss details. Animated WebP use the partial pixels with blend mode / dispose method with offset, to cover previous canvas status
434
+ // Because of this reason, even each frame contains small zigzag, the final animation contains visible glitch, this is not we want.
435
+ // So, always create the full pixels canvas (even though this consume more RAM), after drawn on the canvas, re-scale again with the final size
436
+ CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled: newImageRef size: scaledSize];
437
+ CGImageRelease (newImageRef);
438
+ newImageRef = scaledImageRef;
439
+ }
440
+
359
441
return newImageRef;
360
442
}
361
443
362
- - (nullable CGImageRef)sd_createWebpImageWithData : (WebPData)webpData colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
444
+ - (nullable CGImageRef)sd_createWebpImageWithData : (WebPData)webpData colorSpace : (nonnull CGColorSpaceRef)colorSpaceRef scaledSize : (CGSize) scaledSize CF_RETURNS_RETAINED {
363
445
WebPDecoderConfig config;
364
446
if (!WebPInitDecoderConfig (&config)) {
365
447
return nil ;
@@ -377,24 +459,26 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
377
459
config.options .use_threads = 1 ;
378
460
config.output .colorspace = MODE_bgrA;
379
461
462
+ // Use scaling for thumbnail
463
+ if (scaledSize.width != 0 && scaledSize.height != 0 ) {
464
+ config.options .use_scaling = 1 ;
465
+ config.options .scaled_width = scaledSize.width ;
466
+ config.options .scaled_height = scaledSize.height ;
467
+ }
468
+
380
469
// Decode the WebP image data into a RGBA value array
381
470
if (WebPDecode (webpData.bytes , webpData.size , &config) != VP8_STATUS_OK) {
382
471
return nil ;
383
472
}
384
473
385
- int width = config.input .width ;
386
- int height = config.input .height ;
387
- if (config.options .use_scaling ) {
388
- width = config.options .scaled_width ;
389
- height = config.options .scaled_height ;
390
- }
391
-
392
474
// Construct a UIImage from the decoded RGBA value array
393
475
CGDataProviderRef provider =
394
476
CGDataProviderCreateWithData (NULL , config.output .u .RGBA .rgba , config.output .u .RGBA .size , FreeImageData);
395
477
size_t bitsPerComponent = 8 ;
396
478
size_t bitsPerPixel = 32 ;
397
479
size_t bytesPerRow = config.output .u .RGBA .stride ;
480
+ size_t width = config.output .width ;
481
+ size_t height = config.output .height ;
398
482
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault ;
399
483
CGImageRef imageRef = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL , NO , renderingIntent);
400
484
@@ -414,7 +498,7 @@ - (NSTimeInterval)sd_frameDurationWithIterator:(WebPIterator)iter {
414
498
}
415
499
416
500
// Create and return the correct colorspace by checking the ICC Profile
417
- - (nonnull CGColorSpaceRef)sd_colorSpaceWithDemuxer : (nonnull WebPDemuxer *)demuxer CF_RETURNS_RETAINED {
501
+ - (nonnull CGColorSpaceRef)sd_createColorSpaceWithDemuxer : (nonnull WebPDemuxer *)demuxer CF_RETURNS_RETAINED {
418
502
// WebP contains ICC Profile should use the desired colorspace, instead of default device colorspace
419
503
// See: https://developers.google.com/speed/webp/docs/riff_container#color_profile
420
504
@@ -681,6 +765,22 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(nullable SDIma
681
765
scale = 1 ;
682
766
}
683
767
}
768
+ CGSize thumbnailSize = CGSizeZero;
769
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
770
+ if (thumbnailSizeValue != nil ) {
771
+ #if SD_MAC
772
+ thumbnailSize = thumbnailSizeValue.sizeValue ;
773
+ #else
774
+ thumbnailSize = thumbnailSizeValue.CGSizeValue ;
775
+ #endif
776
+ }
777
+ _thumbnailSize = thumbnailSize;
778
+ BOOL preserveAspectRatio = YES ;
779
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
780
+ if (preserveAspectRatioValue != nil ) {
781
+ preserveAspectRatio = preserveAspectRatioValue.boolValue ;
782
+ }
783
+ _preserveAspectRatio = preserveAspectRatio;
684
784
_scale = scale;
685
785
_demux = demuxer;
686
786
_imageData = data;
@@ -804,15 +904,17 @@ - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
804
904
- (UIImage *)safeStaticImageFrame {
805
905
UIImage *image;
806
906
if (!_colorSpace) {
807
- _colorSpace = [self sd_colorSpaceWithDemuxer : _demux];
907
+ _colorSpace = [self sd_createColorSpaceWithDemuxer : _demux];
808
908
}
809
909
// Static WebP image
810
910
WebPIterator iter;
811
911
if (!WebPDemuxGetFrame (_demux, 1 , &iter)) {
812
912
WebPDemuxReleaseIterator (&iter);
813
913
return nil ;
814
914
}
815
- CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: _colorSpace];
915
+ // Check whether we need to use thumbnail
916
+ CGSize scaledSize = SDCalculateThumbnailSize (CGSizeMake (_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
917
+ CGImageRef imageRef = [self sd_createWebpImageWithData: iter.fragment colorSpace: _colorSpace scaledSize: scaledSize];
816
918
if (!imageRef) {
817
919
return nil ;
818
920
}
@@ -837,7 +939,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
837
939
_canvas = canvas;
838
940
}
839
941
if (!_colorSpace) {
840
- _colorSpace = [self sd_colorSpaceWithDemuxer : _demux];
942
+ _colorSpace = [self sd_createColorSpaceWithDemuxer : _demux];
841
943
}
842
944
843
945
SDWebPCoderFrame *frame = _frames[index];
@@ -887,7 +989,9 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
887
989
_currentBlendIndex = index;
888
990
889
991
// Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image.
890
- CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace];
992
+ // Check whether we need to use thumbnail
993
+ CGSize scaledSize = SDCalculateThumbnailSize (CGSizeMake (_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
994
+ CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas: _canvas iterator: iter colorSpace: _colorSpace scaledSize: scaledSize];
891
995
if (!imageRef) {
892
996
return nil ;
893
997
}
0 commit comments