前言
YYImage 是一個強大的iOS圖像框架,是YYKit 的組件之一捣郊。具體用法可以參考Demo驮配。
** 特性:**
- 支持以下類型動畫圖像的播放/編碼/解碼:
WebP, APNG, GIF涮阔。
- 支持以下類型靜態(tài)圖像的顯示/編碼/解碼:
WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS牺陶。 - 支持以下類型圖片的漸進式/逐行掃描/隔行掃描解碼:
PNG, GIF, JPEG, BMP狮鸭。 - 支持多張圖片構(gòu)成的幀動畫播放,支持單張圖片的 sprite sheet 動畫。
- 高效的動態(tài)內(nèi)存緩存管理春弥,以保證高性能低內(nèi)存的動畫播放。
- 完全兼容 UIImage 和 UIImageView抡笼,使用方便藏古。
- 保留可擴展的接口厂捞,以支持自定義動畫。
- 每個類和方法都有完善的文檔注釋。
** 文件結(jié)構(gòu) **
- YYImage : UIImage的子類,遵守 YYAnimatedImage 協(xié)議,更高級的方式來顯示 Image
- YYFrameImage : 同 YYImage,能夠顯示幀動畫,僅支持png,jpeg 格式,可以配合 YYAnimatedImageView 來播放動畫
- YYSpriteSheetImage : 同 YYFrameImage, 實現(xiàn)另一種動畫形式,可以將一張圖片自定義剪裁成多張圖片來播放圖片動畫渣叛,同樣可以配合YYAnimatedImageView 使用
- YYImageCoder : 圖像的編碼和解碼功能類,
YYImageEncoder
負(fù)責(zé)編碼靴跛,YYImageDecoder
負(fù)責(zé)解碼绝葡,YYImageFrame
負(fù)責(zé)管理幀圖像信息,_YYImageDecoderFrame
內(nèi)部私有類是其子類 - YYAnimatedImageView: UIImageView 子類榜旦,用于播放圖像動畫,定義了 YYAnimatedImage 協(xié)議
YYAnimatedImageView
運行Demo挪蹭,進入Animated Image 八秃。有五種播放的圖片動畫骤肛,前三個圖像格式分別是GIF淑玫,WebP歌径,APNG克锣,后兩個分別轉(zhuǎn)化成 YYFrameImage验残,YYSpriteSheetImage 您没,全部都是通過YYAnimatedImageView 播放動畫的氨鹏,我們進入 YYAnimatedImageView 來分析
- (instancetype)initWithImage:(UIImage *)image {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
self.frame = (CGRect) {CGPointZero, image.size };
self.image = image;
return self;
}
初始化方法只是一些簡單的屬性設(shè)置,默認(rèn)_autoPlayAnimatedImage
為YES舔糖,自動開啟動畫金吗,_runloopMode
為 NSRunLoopCommonModes
,避免滑動時動畫停止,因為以上的五種動畫都是通過 CADisplayLink
來實現(xiàn)的動畫, 滑動時 mode 會切換到 UITrackingRunLoopMode
導(dǎo)致 _link
失效,代碼中實現(xiàn)如下 通砍,保證滑動時依然有效
if (_runloopMode) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
通過設(shè)置 self.image = image;
傳入 YYAnimatedImageTypeImage
type
- (void)setImage:(id)image withType:(YYAnimatedImageType)type {
//停止動畫
[self stopAnimating];
//重置動畫
if (_link) [self resetAnimated];
_curFrame = nil;
//設(shè)置圖片信息
switch (type) {
case YYAnimatedImageTypeNone: break;
case YYAnimatedImageTypeImage: super.image = image; break;
case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break;
case YYAnimatedImageTypeImages: super.animationImages = image; break;
case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break;
}
//圖像信息改變處理
[self imageChanged];
}
- (void)imageChanged {
YYAnimatedImageType newType = [self currentImageType];
//顯示的圖像
id newVisibleImage = [self imageForType:newType];
NSUInteger newImageFrameCount = 0;
BOOL hasContentsRect = NO;
if ([newVisibleImage isKindOfClass:[UIImage class]] &&
[newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) {
//獲取幀圖像個數(shù)
newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount;
if (newImageFrameCount > 1) {
//是否只顯示部分圖像莉兰,YYImage中只有YYSpriteSheetImage 實現(xiàn)了該協(xié)議方法
hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)];
}
}
if (!hasContentsRect && _curImageHasContentsRect) {
if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
//一般不會走這部分代碼硼端,當(dāng)且僅當(dāng)上次是顯示部分圖像兄春,現(xiàn)在顯示完整圖像绞佩,并且圖層不完全顯示時
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
[CATransaction commit];
}
}
_curImageHasContentsRect = hasContentsRect;
if (hasContentsRect) {
//獲取首張尺寸大小,Demo中為 {{0, 0}, {32, 32}}
CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
[self setContentsRect:rect forImage:newVisibleImage];
}
if (newImageFrameCount > 1) {
//多張幀圖像
[self resetAnimated];
_curAnimatedImage = newVisibleImage;
_curFrame = newVisibleImage;
_totalLoop = _curAnimatedImage.animatedImageLoopCount;
_totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
[self calcMaxBufferCount];
}
[self setNeedsDisplay];
//根據(jù)是否添加到父視圖開始或停止動畫
[self didMoved];
}
如果當(dāng)前是 YYSpriteSheetImage
會進入到 [self setContentsRect:rect forImage:newVisibleImage];
此時設(shè)置 self.layer.contentsRect = CGRectMake(0, 0, 32 / image.size.width, 32 / image.size.height);
即裁剪的第一張圖像
** 停止動畫 **
- (void)stopAnimating {
[super stopAnimating];
[_requestQueue cancelAllOperations];
_link.paused = YES;
self.currentIsPlayingAnimation = NO;
}
** 開始動畫 **
- (void)startAnimating {
YYAnimatedImageType type = [self currentImageType];
if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) {
// UIImageView 原始動畫
NSArray *images = [self imageForType:type];
if (images.count > 0) {
[super startAnimating];
self.currentIsPlayingAnimation = YES;
}
} else {
// 自定義動畫
if (_curAnimatedImage && _link.paused) {
_curLoop = 0;
_loopEnd = NO;
_link.paused = NO;
self.currentIsPlayingAnimation = YES;
}
}
}
自定義動畫
準(zhǔn)備工作篡腌,調(diào)用 resetAnimated
, 在 dispatch_once
中初始化 _requestQueue
, 設(shè)置maxConcurrentOperationCount
為1抖苦,串行執(zhí)行請求圖像任務(wù)勘天,初始化_link
藐翎,設(shè)置target為_YYImageWeakProxy
,傳入 self
賦值給 weak 屬性 target,避免循環(huán)引用,添加到 mainRunLoop屿愚,mode 設(shè)置為 NSRunLoopCommonModes
// init the animated params.
- (void)resetAnimated {
dispatch_once(&_onceToken, ^{
_lock = dispatch_semaphore_create(1);
_buffer = [NSMutableDictionary new];
_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;
_link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)];
if (_runloopMode) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
_link.paused = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
});
//取消所有之前的操作
[_requestQueue cancelAllOperations];
//加鎖 清除原來的圖像數(shù)據(jù)
LOCK(
if (_buffer.count) {
NSMutableDictionary *holder = _buffer;
_buffer = [NSMutableDictionary new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Capture the dictionary to global queue,
// release these images in background to avoid blocking UI thread.
[holder class];
});
}
);
//初始化屬性設(shè)置
_link.paused = YES;
_time = 0;
if (_curIndex != 0) {
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = 0;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
}
_curAnimatedImage = nil;
_curFrame = nil;
_curLoop = 0;
_totalLoop = 0;
_totalFrameCount = 1;
_loopEnd = NO;
_bufferMiss = NO;
_incrBufferCount = 0;
}
開啟 _link
咽安,以大約每秒60次的頻率(屏幕刷新頻率)調(diào)用 step:(CADisplayLink *)link
其中 _buffer
存儲圖像數(shù)據(jù)红选, _YYAnimatedImageViewFetchOperation
用于獲取圖像數(shù)據(jù) 實現(xiàn)代碼在 main
方法中的 UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
- (void)step:(CADisplayLink *)link {
//當(dāng)前顯示的圖像, 必須遵守 YYAnimatedImage 協(xié)議
UIImage <YYAnimatedImage> *image = _curAnimatedImage;
// 獲取當(dāng)前的圖像數(shù)據(jù)字典
NSMutableDictionary *buffer = _buffer;
//下張要顯示的圖像
UIImage *bufferedImage = nil;
//下一張顯示圖像的Index
NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;
//是否獲取所有圖像數(shù)據(jù)
BOOL bufferIsFull = NO;
//當(dāng)前無圖像直接返回
if (!image) return;
// 結(jié)束動畫
if (_loopEnd) { // view will keep in last frame
[self stopAnimating];
return;
}
NSTimeInterval delay = 0;
//下張圖像存在
if (!_bufferMiss) {
// 累加時間,保證當(dāng)前圖像的顯示時間為delay
_time += link.duration;
delay = [image animatedImageDurationAtIndex:_curIndex];
if (_time < delay) return;
//減去當(dāng)前圖像時間主到,保證下張圖像顯示時間正確
_time -= delay;
if (nextIndex == 0) {
//循環(huán)次數(shù)加1
_curLoop++;
if (_curLoop >= _totalLoop && _totalLoop != 0) {
//總循環(huán)次數(shù)不為0時塔鳍,且當(dāng)前循環(huán)次數(shù)大于總循環(huán)次數(shù)糯彬,關(guān)閉動畫
_loopEnd = YES;
[self stopAnimating];
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
return; // stop at last frame
}
}
// 如果當(dāng)前累加時間還是大于下張顯示時間挽拔,設(shè)置累加時間為delay猾编,避免直接跳過下張圖像顯示
delay = [image animatedImageDurationAtIndex:nextIndex];
if (_time > delay) _time = delay; // do not jump over frame
}
// 加鎖獲取并顯示下張圖像
LOCK(
bufferedImage = buffer[@(nextIndex)];
if (bufferedImage) {
if ((int)_incrBufferCount < _totalFrameCount) {
// 還未完全獲取所有圖像時稿存,清除下一張圖像數(shù)據(jù)蕉世,保證數(shù)據(jù)正確
[buffer removeObjectForKey:@(nextIndex)];
}
//更新當(dāng)前Index
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = nextIndex;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
// 更新當(dāng)前圖像
_curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;
if (_curImageHasContentsRect) {
// YYSpriteSheetImage :獲取當(dāng)前Index下的部分圖像 Rect躏嚎,設(shè)置 對應(yīng)的 self.layer.contentsRect
_curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex];
[self setContentsRect:_curContentsRect forImage:_curFrame];
}
nextIndex = (_curIndex + 1) % _totalFrameCount;
_bufferMiss = NO;
if (buffer.count == _totalFrameCount) {
//已獲取所有圖像
bufferIsFull = YES;
}
} else {
_bufferMiss = YES;
}
)//LOCK
if (!_bufferMiss) {
//更新圖像 layer.contents = (__bridge id)_curFrame.CGImage;
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
}
if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity
//還未獲取所有圖像仇参,交給_YYAnimatedImageViewFetchOperation 獲取下一張圖像
_YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
operation.view = self;
operation.nextIndex = nextIndex;
operation.curImage = image;
[_requestQueue addOperation:operation];
}
}
** 主要流程: **
- 確保當(dāng)前圖像存在喂饥,并且循環(huán)未結(jié)束
- 如果還在當(dāng)前圖像顯示時間內(nèi)捞高,返回
- 進入下張圖像顯示辈讶,如存在直接顯示碍讯,并更新相應(yīng)屬性數(shù)據(jù),如不存在始藕,初始化一個operation祥国,獲取下一張圖像數(shù)據(jù)
_YYAnimatedImageViewFetchOperation 實現(xiàn)獲取下張圖像
_YYAnimatedImageViewFetchOperation
是 NSOperation
的子類, 用來執(zhí)行獲取圖像操作。通過自定義main 方法實現(xiàn),每添加一個 operation
, _incrBufferCount ++
, 遍歷 _buffer
, 獲取丟失的圖像何恶。具體獲取圖像通過協(xié)議方法 animatedImageFrameAtIndex:
獲取
- (void)main {
__strong YYAnimatedImageView *view = _view;
if (!view) return;
if ([self isCancelled]) return;
view->_incrBufferCount++;
if (view->_incrBufferCount == 0) [view calcMaxBufferCount];
if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) {
view->_incrBufferCount = view->_maxBufferCount;
}
NSUInteger idx = _nextIndex;
//當(dāng)前已嘗試獲取的圖像的次數(shù),不大于 最大圖像數(shù)
NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount;
NSUInteger total = view->_totalFrameCount;
view = nil;
for (int i = 0; i < max; i++, idx++) {
//遍歷當(dāng)前所需的圖像 Index晚树,按下一張顯示圖像Index 開始查找
@autoreleasepool {
if (idx >= total) idx = 0;
if ([self isCancelled]) break;
__strong YYAnimatedImageView *view = _view;
if (!view) break;
//圖像是否丟失
LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil));
if (miss) {
//重新獲取丟失圖像
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
view = nil;
}
}
}
}
** 獲取圖像 **
YYImage
YYFrameImage
YYSpriteSheetImage
都實現(xiàn)了 YYAnimatedImage
協(xié)議方法席函,后兩個比較簡單蒂阱,主要來看 YYImage
, 內(nèi)部獲取圖像通過 _decoder
實現(xiàn)歪泳, 其中 preloadAllAnimatedImageFrames
屬性可以用來預(yù)先加載所有圖像數(shù)據(jù)
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
if (index >= _decoder.frameCount) return nil;
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
UIImage *image = _preloadedFrames[index];
dispatch_semaphore_signal(_preloadedLock);
if (image) return image == (id)[NSNull null] ? nil : image;
return [_decoder frameAtIndex:index decodeForDisplay:YES].image;
}
YYImageCoder
最終獲取圖像調(diào)用到 YYImageDecoder
內(nèi)部,返回一個 YYImageFrame
實例,存儲了圖像信息蚂夕。可以參考文章移動端圖片格式調(diào)研
- (YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
YYImageFrame *result = nil;
pthread_mutex_lock(&_lock);
result = [self _frameAtIndex:index decodeForDisplay:decodeForDisplay];
pthread_mutex_unlock(&_lock);
return result;
}
- (YYImageFrame *)_frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
if (index >= _frames.count) return 0;
_YYImageDecoderFrame *frame = [(_YYImageDecoderFrame *)_frames[index] copy];
BOOL decoded = NO;
BOOL extendToCanvas = NO;
if (_type != YYImageTypeICO && decodeForDisplay) { // ICO contains multi-size frame and should not extend to canvas.
extendToCanvas = YES;
}
//不需要混合繪制
if (!_needBlend) {
//獲取像素位圖
//包含三種數(shù)據(jù)源 APNG WEBP 其他
CGImageRef imageRef = [self _newUnblendedImageAtIndex:index extendToCanvas:extendToCanvas decoded:&decoded];
if (!imageRef) return nil;
if (decodeForDisplay && !decoded) {
//自定義的像素位圖復(fù)制
CGImageRef imageRefDecoded = YYCGImageCreateDecodedCopy(imageRef, YES);
if (imageRefDecoded) {
CFRelease(imageRef);
imageRef = imageRefDecoded;
decoded = YES;
}
}
//圖片屬性設(shè)置
UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation];
CFRelease(imageRef);
if (!image) return nil;
image.yy_isDecodedForDisplay = decoded;
frame.image = image;
return frame;
}
// blend
if (![self _createBlendContextIfNeeded]) return nil;
CGImageRef imageRef = NULL;
//混合繪制圖像 Demo 中的APNG 就是混合繪制的
if (_blendFrameIndex + 1 == frame.index) {
//當(dāng)前幀是預(yù)測編碼幀(P幀)前一幀是關(guān)鍵幀(即 I 幀鳄逾,幀內(nèi)編碼幀)
imageRef = [self _newBlendedImageWithFrame:frame];
_blendFrameIndex = index;
} else { // should draw canvas from previous frame
_blendFrameIndex = NSNotFound;
//清除畫布
CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
if (frame.blendFromIndex == frame.index) {
//繪制關(guān)鍵幀
CGImageRef unblendedImage = [self _newUnblendedImageAtIndex:index extendToCanvas:NO decoded:NULL];
if (unblendedImage) {
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendedImage);
CFRelease(unblendedImage);
}
imageRef = CGBitmapContextCreateImage(_blendCanvas);
//繪制當(dāng)前幀之前逆屡,先把畫布清空為默認(rèn)背景色
if (frame.dispose == YYImageDisposeBackground) {
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
}
_blendFrameIndex = index;
} else { // canvas is not ready
//組合繪制畫布
for (uint32_t i = (uint32_t)frame.blendFromIndex; i <= (uint32_t)frame.index; i++) {
if (i == frame.index) {
if (!imageRef) imageRef = [self _newBlendedImageWithFrame:frame];
} else {
[self _blendImageWithFrame:_frames[i]];
}
}
_blendFrameIndex = index;
}
}
//圖片屬性設(shè)置
if (!imageRef) return nil;
UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation];
CFRelease(imageRef);
if (!image) return nil;
image.yy_isDecodedForDisplay = YES;
frame.image = image;
if (extendToCanvas) {
frame.width = _width;
frame.height = _height;
frame.offsetX = 0;
frame.offsetY = 0;
frame.dispose = YYImageDisposeNone;
frame.blend = YYImageBlendNone;
}
return frame;
}
** 圖像解碼主要方法 **
根據(jù)傳入的圖像data 更新數(shù)據(jù)到 _frames
數(shù)組 床佳,里面存儲的是 _YYImageDecoderFrame
砌们。大致有三類圖片數(shù)據(jù),webP茧妒,APNG, 其他。
webP 圖片通過Google的 WebP.framework 實現(xiàn)商蕴,APNG是 自定義實現(xiàn)的圖像解碼,其他的通過 ImageIO 框架實現(xiàn)的
- (void)_updateSource {
switch (_type) {
case YYImageTypeWebP: {
[self _updateSourceWebP];
} break;
case YYImageTypePNG: {
[self _updateSourceAPNG];
} break;
default: {
[self _updateSourceImageIO];
} break;
}
}
主要看一下 _updateSourceImageIO
- (void)_updateSourceImageIO {
//圖像寬芝发,高绪商,顯示方向初始化, 循環(huán)次數(shù), 0 代表無限
_width = 0;
_height = 0;
_orientation = UIImageOrientationUp;
_loopCount = 0;
//清除原來的數(shù)據(jù)
dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
_frames = nil;
dispatch_semaphore_signal(_framesLock);
// 處理圖像源
if (!_source) {
if (_finalized) {
_source = CGImageSourceCreateWithData((__bridge CFDataRef)_data, NULL);
} else {
_source = CGImageSourceCreateIncremental(NULL);
if (_source) CGImageSourceUpdateData(_source, (__bridge CFDataRef)_data, false);
}
} else {
CGImageSourceUpdateData(_source, (__bridge CFDataRef)_data, _finalized);
}
if (!_source) return;
//獲取圖像個數(shù)
_frameCount = CGImageSourceGetCount(_source);
if (_frameCount == 0) return;
if (!_finalized) { // ignore multi-frame before finalized
_frameCount = 1;
} else {
if (_type == YYImageTypePNG) { // use custom apng decoder and ignore multi-frame
_frameCount = 1;
}
if (_type == YYImageTypeGIF) { // get gif loop count
CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL);
if (properties) {
CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
if (gif) {
CFTypeRef loop = CFDictionaryGetValue(gif, kCGImagePropertyGIFLoopCount);
if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount);
}
CFRelease(properties);
}
}
}
/*
ICO, GIF, APNG may contains multi-frame.
*/
NSMutableArray *frames = [NSMutableArray new];
for (NSUInteger i = 0; i < _frameCount; i++) {
_YYImageDecoderFrame *frame = [_YYImageDecoderFrame new];
frame.index = i;
frame.blendFromIndex = i;
frame.hasAlpha = YES;
frame.isFullSize = YES;
[frames addObject:frame];
// 獲取圖像源屬性信息
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, i, NULL);
if (properties) {
NSTimeInterval duration = 0;
NSInteger orientationValue = 0, width = 0, height = 0;
CFTypeRef value = NULL;
//圖像寬
value = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &width);
//圖像高
value = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &height);
if (_type == YYImageTypeGIF) {
CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
if (gif) {
// Use the unclamped frame delay if it exists.
value = CFDictionaryGetValue(gif, kCGImagePropertyGIFUnclampedDelayTime);
if (!value) {
// Fall back to the clamped frame delay if the unclamped frame delay does not exist.
value = CFDictionaryGetValue(gif, kCGImagePropertyGIFDelayTime);
}
// gif 圖像時間
if (value) CFNumberGetValue(value, kCFNumberDoubleType, &duration);
}
}
//賦值
frame.width = width;
frame.height = height;
frame.duration = duration;
if (i == 0 && _width + _height == 0) { // init first frame
_width = width;
_height = height;
value = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (value) {
CFNumberGetValue(value, kCFNumberNSIntegerType, &orientationValue);
_orientation = YYUIImageOrientationFromEXIFValue(orientationValue);
}
}
CFRelease(properties);
}
}
dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
_frames = frames;
dispatch_semaphore_signal(_framesLock);
}
通過最初的圖像解碼得到指定Index的frame辅鲸,
_YYImageDecoderFrame *frame = [(_YYImageDecoderFrame *)_frames[index] copy];
采用畫布進行渲染, 最終獲得圖像 frame.image = image;