mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-01-03 19:54:31 +00:00
Animated WebP support (#605)
* Updating to support animated WebP * Fix a deadlock with display link * Fix playhead issue. * Fix up timing on iOS 10 and above * Don't redraw the same frame over and over * Clear out layer contents if we're an animated GIF on exit range * Clear out cover image on exit of visible range * Don't set cover image if we're no longer in display range. * Don't clear out image if we're not an animated image * Only set image if we're not already animating * Get rid of changes to podfile * Add CHANGELOG entry * Update license * Update PINRemoteImage * Remove commented out lines in example
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
4
Cartfile
4
Cartfile
@@ -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
Podfile
2
Podfile
@@ -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?
|
||||
|
||||
@@ -66,14 +66,17 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
};
|
||||
}
|
||||
|
||||
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];
|
||||
@@ -107,8 +110,10 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
|
||||
- (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
|
||||
@@ -132,9 +137,12 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
{
|
||||
//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];
|
||||
}
|
||||
}
|
||||
@@ -218,11 +226,14 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
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 {
|
||||
@@ -263,7 +274,9 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
if (self.animatedImage.coverImageReady) {
|
||||
[self setCoverImage:self.animatedImage.coverImage];
|
||||
}
|
||||
[self startAnimating];
|
||||
if (self.animatedImage.playbackReady) {
|
||||
[self startAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didExitVisibleState
|
||||
@@ -274,6 +287,26 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
[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
|
||||
@@ -283,6 +316,8 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
CFTimeInterval timeBetweenLastFire;
|
||||
if (self.lastDisplayLinkFire == 0) {
|
||||
timeBetweenLastFire = 0;
|
||||
} else if (AS_AT_LEAST_IOS10){
|
||||
timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp;
|
||||
} else {
|
||||
timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire;
|
||||
}
|
||||
@@ -291,7 +326,8 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
_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++;
|
||||
}
|
||||
|
||||
@@ -301,15 +337,18 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -42,35 +48,23 @@
|
||||
|
||||
@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
|
||||
@@ -187,7 +181,7 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
|
||||
#if PIN_ANIMATED_AVAILABLE
|
||||
- (nullable id <ASAnimatedImageProtocol>)animatedImageWithData:(NSData *)animatedImageData
|
||||
{
|
||||
return [[PINAnimatedImage alloc] initWithAnimatedImageData:animatedImageData];
|
||||
return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:animatedImageData];
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -365,6 +359,12 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
|
||||
if ([data pin_isGIF]) {
|
||||
return data;
|
||||
}
|
||||
#if PIN_WEBP_AVAILABLE
|
||||
else if ([data pin_isAnimatedWebP]) {
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ extern NSString *const ASAnimatedImageDefaultRunLoopMode;
|
||||
BOOL _animatedImagePaused;
|
||||
NSString *_animatedImageRunLoopMode;
|
||||
CADisplayLink *_displayLink;
|
||||
NSUInteger _lastSuccessfulFrameIndex;
|
||||
|
||||
//accessed on main thread only
|
||||
CFTimeInterval _playHead;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
// 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.
|
||||
// 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
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
@@ -33,6 +31,8 @@
|
||||
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user