Skip to content

Commit f6ff82b

Browse files
committed
The Animated WebP should not use the scaled canvas size, which will cause the draw frame contains Jagged and buggy. Instead, use the full pixels canvas to draw, scale down each frame after drawn (sadlly)
1 parent 72d1968 commit f6ff82b

File tree

1 file changed

+34
-53
lines changed

1 file changed

+34
-53
lines changed

SDWebImageWebPCoder/Classes/SDImageWebPCoder.m

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ @implementation SDImageWebPCoder {
9696
BOOL _hasAnimation;
9797
BOOL _hasAlpha;
9898
BOOL _finished;
99-
CGFloat _canvasWidth; // Full Size without thumbnail scale
100-
CGFloat _canvasHeight; // Full Size without thumbnail scale
99+
CGFloat _canvasWidth;
100+
CGFloat _canvasHeight;
101101
dispatch_semaphore_t _lock;
102102
NSUInteger _currentBlendIndex;
103103
BOOL _preserveAspectRatio;
@@ -192,10 +192,14 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
192192
return nil;
193193
}
194194
CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer];
195+
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
196+
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
195197

196198
if (!hasAnimation || decodeFirstFrame) {
197199
// first frame for animated webp image
198-
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpace preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
200+
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(canvasWidth, canvasHeight), preserveAspectRatio, thumbnailSize);
201+
// Create thumbnail if need
202+
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpace scaledSize:scaledSize];
199203
CGColorSpaceRelease(colorSpace);
200204
#if SD_UIKIT || SD_WATCH
201205
UIImage *firstFrameImage = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
@@ -209,15 +213,10 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
209213
return firstFrameImage;
210214
}
211215

212-
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
213-
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
214216
BOOL hasAlpha = flags & ALPHA_FLAG;
215217
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
216218
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
217-
218-
CGSize canvasFullSize = CGSizeMake(canvasWidth, canvasHeight);
219-
CGSize canvasSize = SDCalculateThumbnailSize(canvasFullSize, preserveAspectRatio, thumbnailSize);
220-
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasSize.width, canvasSize.height, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
219+
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
221220
if (!canvas) {
222221
WebPDemuxDelete(demuxer);
223222
CGColorSpaceRelease(colorSpace);
@@ -229,7 +228,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
229228

230229
do {
231230
@autoreleasepool {
232-
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize canvasFullSize:canvasFullSize];
231+
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter colorSpace:colorSpace];
233232
if (!imageRef) {
234233
continue;
235234
}
@@ -377,22 +376,16 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
377376
return image;
378377
}
379378

380-
- (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize canvasFullSize:(CGSize)canvasFullSize {
381-
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
379+
- (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef {
382380
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
383-
CGFloat xScale = canvasWidth / canvasFullSize.width;
384-
CGFloat yScale = canvasHeight / canvasFullSize.height;
385-
386-
CGFloat tmpX = iter.x_offset * xScale;
387-
CGFloat tmpY = (canvasFullSize.height - iter.height - iter.y_offset) * yScale;
388-
CGFloat tmpWidth = iter.width * xScale;
389-
CGFloat tmpHeight = iter.height * yScale;
390-
CGRect imageRect = CGRectMake(tmpX, tmpY, tmpWidth, tmpHeight);
381+
CGFloat tmpX = iter.x_offset;
382+
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset;
383+
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
391384

392385
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
393386
CGContextClearRect(canvas, imageRect);
394387
} else {
395-
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
388+
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef scaledSize:CGSizeZero];
396389
if (!imageRef) {
397390
return;
398391
}
@@ -406,22 +399,17 @@ - (void)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)
406399
}
407400
}
408401

409-
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize canvasFullSize:(CGSize)canvasFullSize CF_RETURNS_RETAINED {
410-
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
402+
- (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef CF_RETURNS_RETAINED {
403+
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:colorSpaceRef scaledSize:CGSizeZero];
411404
if (!imageRef) {
412405
return nil;
413406
}
414407

415-
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
416408
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
417-
CGFloat xScale = canvasWidth / canvasFullSize.width;
418-
CGFloat yScale = canvasHeight / canvasFullSize.height;
419-
420-
CGFloat tmpX = iter.x_offset * xScale;
421-
CGFloat tmpY = (canvasFullSize.height - iter.height - iter.y_offset) * yScale;
422-
CGFloat tmpWidth = iter.width * xScale;
423-
CGFloat tmpHeight = iter.height * yScale;
424-
CGRect imageRect = CGRectMake(tmpX, tmpY, tmpWidth, tmpHeight);
409+
CGFloat tmpX = iter.x_offset;
410+
CGFloat tmpY = canvasHeight - iter.height - iter.y_offset;
411+
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
412+
425413
BOOL shouldBlend = iter.blend_method == WEBP_MUX_BLEND;
426414

427415
// If not blend, cover the target image rect. (firstly clear then draw)
@@ -432,15 +420,15 @@ - (nullable CGImageRef)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator
432420
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
433421

434422
CGImageRelease(imageRef);
435-
423+
436424
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
437425
CGContextClearRect(canvas, imageRect);
438426
}
439427

440428
return newImageRef;
441429
}
442430

443-
- (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize CF_RETURNS_RETAINED {
431+
- (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:(nonnull CGColorSpaceRef)colorSpaceRef scaledSize:(CGSize)scaledSize CF_RETURNS_RETAINED {
444432
WebPDecoderConfig config;
445433
if (!WebPInitDecoderConfig(&config)) {
446434
return nil;
@@ -458,34 +446,28 @@ - (nullable CGImageRef)sd_createWebpImageWithData:(WebPData)webpData colorSpace:
458446
config.options.use_threads = 1;
459447
config.output.colorspace = MODE_bgrA;
460448

461-
int width = config.input.width;
462-
int height = config.input.height;
463-
CGSize resultSize = SDCalculateThumbnailSize(CGSizeMake(width, height), preserveAspectRatio, thumbnailSize);
464-
if (resultSize.width != width || resultSize.height != height) {
465-
// Use scaling
449+
// Use scaling for thumbnail
450+
if (scaledSize.width != 0 && scaledSize.height != 0) {
466451
config.options.use_scaling = 1;
467-
config.options.scaled_width = resultSize.width;
468-
config.options.scaled_height = resultSize.height;
452+
config.options.scaled_width = scaledSize.width;
453+
config.options.scaled_height = scaledSize.height;
469454
}
470455

471456
// Decode the WebP image data into a RGBA value array
472457
if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) {
473458
return nil;
474459
}
475460

476-
if (config.options.use_scaling) {
477-
width = config.options.scaled_width;
478-
height = config.options.scaled_height;
479-
}
480-
481461
// Construct a UIImage from the decoded RGBA value array
482462
CGDataProviderRef provider =
483463
CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
484464
size_t bitsPerComponent = 8;
485465
size_t bitsPerPixel = 32;
486466
size_t bytesPerRow = config.output.u.RGBA.stride;
467+
size_t width = config.output.width;
468+
size_t height = config.output.height;
487469
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
488-
CGImageRef imageRef = CGImageCreate(resultSize.width, resultSize.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
470+
CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
489471

490472
CGDataProviderRelease(provider);
491473

@@ -917,7 +899,8 @@ - (UIImage *)safeStaticImageFrame {
917899
WebPDemuxReleaseIterator(&iter);
918900
return nil;
919901
}
920-
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:_colorSpace preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize];
902+
CGSize scaledSize = SDCalculateThumbnailSize(CGSizeMake(_canvasWidth, _canvasHeight), _preserveAspectRatio, _thumbnailSize);
903+
CGImageRef imageRef = [self sd_createWebpImageWithData:iter.fragment colorSpace:_colorSpace scaledSize:scaledSize];
921904
if (!imageRef) {
922905
return nil;
923906
}
@@ -935,12 +918,10 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
935918
if (!_colorSpace) {
936919
_colorSpace = [self sd_createColorSpaceWithDemuxer:_demux];
937920
}
938-
CGSize canvasFullSize = CGSizeMake(_canvasWidth, _canvasHeight);
939921
if (!_canvas) {
940922
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
941923
bitmapInfo |= _hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
942-
CGSize canvasSize = SDCalculateThumbnailSize(canvasFullSize, _preserveAspectRatio, _thumbnailSize);
943-
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasSize.width, canvasSize.height, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
924+
CGContextRef canvas = CGBitmapContextCreate(NULL, _canvasWidth, _canvasHeight, 8, 0, [SDImageCoderHelper colorSpaceGetDeviceRGB], bitmapInfo);
944925
if (!canvas) {
945926
return nil;
946927
}
@@ -981,7 +962,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
981962
if (endIndex > startIndex) {
982963
do {
983964
@autoreleasepool {
984-
[self sd_blendWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize canvasFullSize:canvasFullSize];
965+
[self sd_blendWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
985966
}
986967
} while ((size_t)iter.frame_num < endIndex && WebPDemuxNextFrame(&iter));
987968
}
@@ -994,7 +975,7 @@ - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
994975
_currentBlendIndex = index;
995976

996977
// Now the canvas is ready, which respects of dispose method behavior. Just do normal decoding and produce image.
997-
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize canvasFullSize:canvasFullSize];
978+
CGImageRef imageRef = [self sd_drawnWebpImageWithCanvas:_canvas iterator:iter colorSpace:_colorSpace];
998979
if (!imageRef) {
999980
return nil;
1000981
}

0 commit comments

Comments
 (0)