Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animated WebP support #605

Merged
merged 15 commits into from
Oct 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [ASDisplayNode] Add attributed versions of a11y label, hint and value. [#554](https://github.com/TextureGroup/Texture/pull/554) [Alexander Hüllmandel](https://github.com/fruitcoder)
- [ASCornerRounding] Introduce .cornerRoundingType: CALayer, Precomposited, or Clip Corners. [Scott Goodson](https://github.com/appleguy) [#465](https://github.com/TextureGroup/Texture/pull/465)
- [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [Scott Goodson](https://github.com/appleguy)
- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon)

## 2.5

Expand Down
4 changes: 2 additions & 2 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "pinterest/PINRemoteImage" "3.0.0-beta.12"
github "pinterest/PINCache" "3.0.1-beta.5"
github "pinterest/PINRemoteImage" "3.0.0-beta.13"
github "pinterest/PINCache"
2 changes: 1 addition & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ target :'AsyncDisplayKitTests' do
pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master'

# Only for buck build
pod 'PINRemoteImage', '3.0.0-beta.10'
pod 'PINRemoteImage', '3.0.0-beta.13'
end

#TODO CocoaPods plugin instead?
Expand Down
63 changes: 51 additions & 12 deletions Source/ASImageNode+AnimatedImage.mm
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,17 @@ - (void)_locked_setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
};
}

animatedImage.playbackReadyCallback = ^{
// In this case the lock is already gone we have to call the unlocked version therefore
[weakSelf setShouldAnimate:YES];
};
if (animatedImage.playbackReady) {
[self _locked_setShouldAnimate:YES];
} else {
animatedImage.playbackReadyCallback = ^{
// In this case the lock is already gone we have to call the unlocked version therefore
[weakSelf setShouldAnimate:YES];
};
}
} else {
// Clean up after ourselves.
self.contents = nil;
[self setCoverImage:nil];
}

[self animatedImageSet:_animatedImage previousAnimatedImage:previousAnimatedImage];
Expand Down Expand Up @@ -107,8 +110,10 @@ - (BOOL)animatedImagePaused

- (void)setCoverImageCompleted:(UIImage *)coverImage
{
ASDN::MutexLocker l(__instanceLock__);
[self _locked_setCoverImageCompleted:coverImage];
if (ASInterfaceStateIncludesDisplay(self.interfaceState)) {
ASDN::MutexLocker l(__instanceLock__);
[self _locked_setCoverImageCompleted:coverImage];
}
}

- (void)_locked_setCoverImageCompleted:(UIImage *)coverImage
Expand All @@ -132,9 +137,12 @@ - (void)_locked_setCoverImage:(UIImage *)coverImage
{
//If we're a network image node, we want to set the default image so
//that it will correctly be restored if it exits the range.
#if ASAnimatedImageDebug
NSLog(@"setting cover image: %p", self);
#endif
if ([self isKindOfClass:[ASNetworkImageNode class]]) {
[(ASNetworkImageNode *)self _locked_setDefaultImage:coverImage];
} else {
} else if (_displayLink == nil || _displayLink.paused == YES) {
[self _locked_setImage:coverImage];
}
}
Expand Down Expand Up @@ -218,11 +226,14 @@ - (void)_locked_startAnimating
NSLog(@"starting animation: %p", self);
#endif

// Get frame interval before holding display link lock to avoid deadlock
NSUInteger frameInterval = self.animatedImage.frameInterval;
ASDN::MutexLocker l(_displayLinkLock);
if (_displayLink == nil) {
_playHead = 0;
_displayLink = [CADisplayLink displayLinkWithTarget:[ASWeakProxy weakProxyWithTarget:self] selector:@selector(displayLinkFired:)];
_displayLink.frameInterval = self.animatedImage.frameInterval;
_displayLink.frameInterval = frameInterval;
_lastSuccessfulFrameIndex = NSUIntegerMax;

[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode];
} else {
Expand Down Expand Up @@ -263,7 +274,9 @@ - (void)didEnterVisibleState
if (self.animatedImage.coverImageReady) {
[self setCoverImage:self.animatedImage.coverImage];
}
[self startAnimating];
if (self.animatedImage.playbackReady) {
[self startAnimating];
}
}

- (void)didExitVisibleState
Expand All @@ -274,6 +287,26 @@ - (void)didExitVisibleState
[self stopAnimating];
}

- (void)didExitDisplayState
{
ASDisplayNodeAssertMainThread();
#if ASAnimatedImageDebug
NSLog(@"exiting display state: %p", self);
#endif

// Check to see if we're an animated image before calling super in case someone
// decides they want to clear out the animatedImage itself on exiting the display
// state
BOOL isAnimatedImage = self.animatedImage != nil;
[super didExitDisplayState];

// Also clear out the contents we've set to be good citizens, we'll put it back in when we become visible.
if (isAnimatedImage) {
self.contents = nil;
[self setCoverImage:nil];
}
}

#pragma mark - Display Link Callbacks

- (void)displayLinkFired:(CADisplayLink *)displayLink
Expand All @@ -283,6 +316,8 @@ - (void)displayLinkFired:(CADisplayLink *)displayLink
CFTimeInterval timeBetweenLastFire;
if (self.lastDisplayLinkFire == 0) {
timeBetweenLastFire = 0;
} else if (AS_AT_LEAST_IOS10){
timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp;
} else {
timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire;
}
Expand All @@ -291,7 +326,8 @@ - (void)displayLinkFired:(CADisplayLink *)displayLink
_playHead += timeBetweenLastFire;

while (_playHead > self.animatedImage.totalDuration) {
_playHead -= self.animatedImage.totalDuration;
// Set playhead to zero to keep from showing different frames on different playthroughs
_playHead = 0;
_playedLoops++;
}

Expand All @@ -301,15 +337,18 @@ - (void)displayLinkFired:(CADisplayLink *)displayLink
}

NSUInteger frameIndex = [self frameIndexAtPlayHeadPosition:_playHead];
if (frameIndex == _lastSuccessfulFrameIndex) {
return;
}
CGImageRef frameImage = [self.animatedImage imageAtIndex:frameIndex];

if (frameImage == nil) {
_playHead -= timeBetweenLastFire;
//Pause the display link until we get a file ready notification
displayLink.paused = YES;
self.lastDisplayLinkFire = 0;
} else {
self.contents = (__bridge id)frameImage;
_lastSuccessfulFrameIndex = frameIndex;
[self displayDidFinish];
}
}
Expand Down
52 changes: 26 additions & 26 deletions Source/Details/ASPINRemoteImageDownloader.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>

#if __has_include (<PINRemoteImage/PINAnimatedImage.h>)
#if __has_include (<PINRemoteImage/PINGIFAnimatedImage.h>)
#define PIN_ANIMATED_AVAILABLE 1
#import <PINRemoteImage/PINAnimatedImage.h>
#import <PINRemoteImage/PINCachedAnimatedImage.h>
#import <PINRemoteImage/PINAlternateRepresentationProvider.h>
#else
#define PIN_ANIMATED_AVAILABLE 0
#endif

#if __has_include(<webp/decode.h>)
#define PIN_WEBP_AVAILABLE 1
#else
#define PIN_WEBP_AVAILABLE 0
#endif

#import <PINRemoteImage/PINRemoteImageManager.h>
#import <PINRemoteImage/NSData+ImageDetectors.h>
#import <PINRemoteImage/PINRemoteImageCaching.h>
Expand All @@ -42,35 +48,23 @@ @interface ASPINRemoteImageDownloader () <PINRemoteImageManagerAlternateRepresen

@end

@interface PINAnimatedImage (ASPINRemoteImageDownloader) <ASAnimatedImageProtocol>
@interface PINCachedAnimatedImage (ASPINRemoteImageDownloader) <ASAnimatedImageProtocol>

@end

@implementation PINAnimatedImage (ASPINRemoteImageDownloader)

- (void)setCoverImageReadyCallback:(void (^)(UIImage * _Nonnull))coverImageReadyCallback
{
self.infoCompletion = coverImageReadyCallback;
}

- (void (^)(UIImage * _Nonnull))coverImageReadyCallback
{
return self.infoCompletion;
}

- (void)setPlaybackReadyCallback:(dispatch_block_t)playbackReadyCallback
{
self.fileReady = playbackReadyCallback;
}

- (dispatch_block_t)playbackReadyCallback
{
return self.fileReady;
}
@implementation PINCachedAnimatedImage (ASPINRemoteImageDownloader)

- (BOOL)isDataSupported:(NSData *)data
{
return [data pin_isGIF];
if ([data pin_isGIF]) {
return YES;
}
#if PIN_WEBP_AVAILABLE
else if ([data pin_isAnimatedWebP]) {
return YES;
}
#endif
return NO;
}

@end
Expand Down Expand Up @@ -187,7 +181,7 @@ - (BOOL)sharedImageManagerSupportsMemoryRemoval
#if PIN_ANIMATED_AVAILABLE
- (nullable id <ASAnimatedImageProtocol>)animatedImageWithData:(NSData *)animatedImageData
{
return [[PINAnimatedImage alloc] initWithAnimatedImageData:animatedImageData];
return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:animatedImageData];
}
#endif

Expand Down Expand Up @@ -365,6 +359,12 @@ - (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageMana
if ([data pin_isGIF]) {
return data;
}
#if PIN_WEBP_AVAILABLE
else if ([data pin_isAnimatedWebP]) {
return data;
}
#endif

#endif
return nil;
}
Expand Down
1 change: 1 addition & 0 deletions Source/Private/ASImageNode+AnimatedImagePrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern NSString *const ASAnimatedImageDefaultRunLoopMode;
BOOL _animatedImagePaused;
NSString *_animatedImageRunLoopMode;
CADisplayLink *_displayLink;
NSUInteger _lastSuccessfulFrameIndex;

//accessed on main thread only
CFTimeInterval _playHead;
Expand Down
2 changes: 1 addition & 1 deletion Texture.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Pod::Spec.new do |spec|
end

spec.subspec 'PINRemoteImage' do |pin|
pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.12'
pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.13'
pin.dependency 'PINRemoteImage/PINCache'
pin.dependency 'Texture/Core'
end
Expand Down
22 changes: 11 additions & 11 deletions examples/AnimatedGIF/ASAnimatedImage/ViewController.m
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
//
// ViewController.m
// Sample
//
// Created by Garrett Moon on 3/22/16.
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// http://www.apache.org/licenses/LICENSE-2.0
//

#import "ViewController.h"
Expand All @@ -33,6 +31,8 @@ - (void)viewDidLoad {

ASNetworkImageNode *imageNode = [[ASNetworkImageNode alloc] init];
imageNode.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/originals/07/44/38/074438e7c75034df2dcf37ba1057803e.gif"];
// Uncomment to see animated webp support
// imageNode.URL = [NSURL URLWithString:@"https://storage.googleapis.com/downloads.webmproject.org/webp/images/dancing_banana2.lossless.webp"];
imageNode.frame = self.view.bounds;
imageNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
imageNode.contentMode = UIViewContentModeScaleAspectFit;
Expand Down
1 change: 1 addition & 0 deletions examples/AnimatedGIF/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
target 'Sample' do
pod 'Texture', :path => '../..'
pod 'PINRemoteImage/WebP'
end