Skip to content

Commit

Permalink
Fix WebP animation blending and default support for animated WebP ima…
Browse files Browse the repository at this point in the history
…ges (#507)

* WebP blending is hard

* Fix rendering WebP images when cached previous frame doesn't exist

* Use CGColorRef instead of UIColor

* Update CHANGELOG
  • Loading branch information
garrettmoon committed Jul 9, 2019
1 parent ef10415 commit 310d931
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 9 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## master
* Add your own contributions to the next release on the line below this with your name.
- [new] Add PINRemoteImageManagerConfiguration configuration object [#492](https://github.com/pinterest/PINRemoteImage/pull/492) [rqueue](https://github.com/rqueue)
- [new] Add PINRemoteImageManagerConfiguration configuration object. [#492](https://github.com/pinterest/PINRemoteImage/pull/492) [rqueue](https://github.com/rqueue)
- [fixed] Fixes blending in animated WebP images. [#507](https://github.com/pinterest/PINRemoteImage/pull/507) [garrettmoon](https://github.com/garrettmoon)
- [fixed] Fixes support in PINAnimatedImageView for WebP animated images. [#507](https://github.com/pinterest/PINRemoteImage/pull/507) [garrettmoon](https://github.com/garrettmoon)

## 3.0.0 Beta 14
- [fixed] Re-enable warnings check [#506](https://github.com/pinterest/PINRemoteImage/pull/506) [garrettmoon](https://github.com/garrettmoon)
Expand Down
80 changes: 72 additions & 8 deletions Source/Classes/AnimatedImages/PINWebPAnimatedImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ @interface PINWebPAnimatedImage ()
BOOL _hasAlpha;
size_t _frameCount;
size_t _loopCount;
CGColorRef _backgroundColor;
CFTimeInterval *_durations;
NSError *_error;
}
Expand Down Expand Up @@ -54,6 +55,16 @@ - (instancetype)initWithAnimatedImageData:(NSData *)animatedImageData
uint32_t flags = WebPDemuxGetI(_demux, WEBP_FF_FORMAT_FLAGS);
_hasAlpha = flags & ALPHA_FLAG;
_durations = malloc(sizeof(CFTimeInterval) * _frameCount);

uint32_t backgroundColorInt = WebPDemuxGetI(_demux, WEBP_FF_BACKGROUND_COLOR);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[4];
components[0] = (CGFloat)(((backgroundColorInt & 0xFF000000) >> 24)/255.0);
components[1] = (CGFloat)(((backgroundColorInt & 0x00FF0000) >> 16)/255.0);
components[2] = (CGFloat)(((backgroundColorInt & 0x0000FF00) >> 8)/255.0);
components[3] = (CGFloat)((backgroundColorInt & 0x000000FF)/255.0);
_backgroundColor = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);

// Iterate over the frames to gather duration
WebPIterator iter;
Expand Down Expand Up @@ -85,6 +96,9 @@ - (void)dealloc
if (_durations) {
free(_durations);
}
if (_backgroundColor) {
CGColorRelease(_backgroundColor);
}
}

- (NSData *)data
Expand Down Expand Up @@ -127,7 +141,13 @@ - (CFTimeInterval)durationAtIndex:(NSUInteger)index
return _durations[index];
}

- (CGImageRef)canvasWithPreviousFrame:(CGImageRef)previousFrame image:(CGImageRef)image atRect:(CGRect)rect
- (CGImageRef)canvasWithPreviousFrame:(CGImageRef)previousFrame
previousFrameRect:(CGRect)previousFrameRect
clearPreviousFrame:(BOOL)clearPreviousFrame
backgroundColor:(CGColorRef)backgroundColor
image:(CGImageRef)image
clearCurrentFrame:(BOOL)clearCurrentFrame
atRect:(CGRect)rect
{
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
Expand All @@ -137,13 +157,23 @@ - (CGImageRef)canvasWithPreviousFrame:(CGImageRef)previousFrame image:(CGImageRe
0,
colorSpaceRef,
_hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone);
if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
}

if (previousFrame) {
CGContextDrawImage(context, CGRectMake(0, 0, _width, _height), previousFrame);
if (clearPreviousFrame) {
CGContextFillRect(context, previousFrameRect);
}
}

if (image) {
CGContextDrawImage(context, CGRectMake(rect.origin.x, _height - rect.size.height - rect.origin.y, rect.size.width, rect.size.height), image);
CGRect currentRect = CGRectMake(rect.origin.x, _height - rect.size.height - rect.origin.y, rect.size.width, rect.size.height);
if (clearCurrentFrame) {
CGContextFillRect(context, currentRect);
}
CGContextDrawImage(context, currentRect, image);
}

CGImageRef canvas = CGBitmapContextCreateImage(context);
Expand Down Expand Up @@ -234,13 +264,30 @@ - (CGImageRef)imageAtIndex:(NSUInteger)index cacheProvider:(nullable id<PINCache
// Output will be the same size as the canvas, just return it directly.
canvas = imageRef;
} else {
canvas = [self canvasWithPreviousFrame:nil image:imageRef atRect:CGRectMake(iterator.x_offset, iterator.y_offset, iterator.width, iterator.height)];
canvas = [self canvasWithPreviousFrame:nil
previousFrameRect:CGRectZero
clearPreviousFrame:NO
backgroundColor:_backgroundColor
image:imageRef
clearCurrentFrame:iterator.blend_method == WEBP_MUX_NO_BLEND
atRect:CGRectMake(iterator.x_offset, iterator.y_offset, iterator.width, iterator.height)];
}
} else {
// If we have a cached image provider, try to get the last frame from them
CGImageRef previousFrame = [cacheProvider cachedFrameImageAtIndex:index - 1];
if (previousFrame) {
canvas = [self canvasWithPreviousFrame:previousFrame image:imageRef atRect:CGRectMake(iterator.x_offset, iterator.y_offset, iterator.width, iterator.height)];
// We need an iterator from the previous frame to dispose to background if
// necessary.
WebPDemuxReleaseIterator(&previousIterator);
WebPDemuxGetFrame(_demux, (int)index, &previousIterator);
CGRect previousFrameRect = CGRectMake(previousIterator.x_offset, _height - previousIterator.height - previousIterator.y_offset, previousIterator.width, previousIterator.height);
canvas = [self canvasWithPreviousFrame:previousFrame
previousFrameRect:previousFrameRect
clearPreviousFrame:previousIterator.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND
backgroundColor:_backgroundColor
image:imageRef
clearCurrentFrame:iterator.blend_method == WEBP_MUX_NO_BLEND
atRect:CGRectMake(iterator.x_offset, iterator.y_offset, iterator.width, iterator.height)];
} else if (index > 0) {
// Sadly, we need to draw *all* the frames from the previous key frame previousIterator to the current one :(
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
Expand All @@ -251,15 +298,32 @@ - (CGImageRef)imageAtIndex:(NSUInteger)index cacheProvider:(nullable id<PINCache
0,
colorSpaceRef,
_hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone);
CGContextSetFillColorWithColor(context, _backgroundColor);

while (previousIterator.frame_num < iterator.frame_num) {
while (previousIterator.frame_num <= iterator.frame_num) {
CGImageRef previousFrame = [self rawImageWithIterator:previousIterator];
if (previousFrame) {
CGContextDrawImage(context, CGRectMake(previousIterator.x_offset, _height - previousIterator.height - previousIterator.y_offset, previousIterator.width, iterator.height), previousFrame);
WebPDemuxNextFrame(&previousIterator);
CGRect previousFrameRect = CGRectMake(previousIterator.x_offset, _height - previousIterator.height - previousIterator.y_offset, previousIterator.width, previousIterator.height);
if (previousIterator.blend_method == WEBP_MUX_NO_BLEND) {
CGContextFillRect(context, previousFrameRect);
}

if (previousIterator.frame_num == iterator.frame_num) {
CGContextDrawImage(context, previousFrameRect, previousFrame);
// We have to break here because we're not getting the next frame! Basically
// the while loop is a sham and only here to illustrate what we want to iterate.
break;
} else {
if (previousIterator.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
CGContextFillRect(context, previousFrameRect);
} else {
CGContextDrawImage(context, previousFrameRect, previousFrame);
}
WebPDemuxNextFrame(&previousIterator);
}
}
}

canvas = CGBitmapContextCreateImage(context);
if (canvas) {
CFAutorelease(canvas);
Expand Down
6 changes: 6 additions & 0 deletions Source/Classes/PINAlternateRepresentationProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ @implementation PINAlternateRepresentationProvider

- (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageManagerDownloadOptions)options
{
#if PIN_WEBP
if ([data pin_isAnimatedGIF] || [data pin_isAnimatedWebP]) {
return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:data];
}
#else
if ([data pin_isAnimatedGIF]) {
return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:data];
}
#endif
return nil;
}

Expand Down

0 comments on commit 310d931

Please sign in to comment.