YYImage源碼分析

前言

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舔糖,自動開啟動畫金吗,_runloopModeNSRunLoopCommonModes,避免滑動時動畫停止,因為以上的五種動畫都是通過 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];
    }
}

** 主要流程: **

  1. 確保當(dāng)前圖像存在喂饥,并且循環(huán)未結(jié)束
  1. 如果還在當(dāng)前圖像顯示時間內(nèi)捞高,返回
  2. 進入下張圖像顯示辈讶,如存在直接顯示碍讯,并更新相應(yīng)屬性數(shù)據(jù),如不存在始藕,初始化一個operation祥国,獲取下一張圖像數(shù)據(jù)

_YYAnimatedImageViewFetchOperation 實現(xiàn)獲取下張圖像

_YYAnimatedImageViewFetchOperationNSOperation的子類, 用來執(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;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末格郁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓢湃,更是在濱河造成了極大的恐慌理张,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绵患,死亡現(xiàn)場離奇詭異雾叭,居然都是意外死亡,警方通過查閱死者的電腦和手機落蝙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門织狐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暂幼,“玉大人,你說我怎么就攤上這事移迫⊥遥” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵厨埋,是天一觀的道長邪媳。 經(jīng)常有香客問我,道長荡陷,這世上最難降的妖魔是什么雨效? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮废赞,結(jié)果婚禮上徽龟,老公的妹妹穿的比我還像新娘。我一直安慰自己唉地,他們只是感情好据悔,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耘沼,像睡著了一般极颓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耕拷,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天讼昆,我揣著相機與錄音,去河邊找鬼骚烧。 笑死浸赫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赃绊。 我是一名探鬼主播既峡,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碧查!你這毒婦竟也來了运敢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤忠售,失蹤者是張志新(化名)和其女友劉穎传惠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稻扬,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡卦方,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泰佳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盼砍。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡尘吗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浇坐,到底是詐尸還是另有隱情睬捶,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布近刘,位于F島的核電站擒贸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跌宛。R本人自食惡果不足惜酗宋,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一积仗、第九天 我趴在偏房一處隱蔽的房頂上張望疆拘。 院中可真熱鬧,春花似錦寂曹、人聲如沸哎迄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漱挚。三九已至,卻和暖如春渺氧,著一層夾襖步出監(jiān)牢的瞬間旨涝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工侣背, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留白华,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓贩耐,卻偏偏與公主長得像弧腥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子潮太,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫管搪、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,030評論 4 62
  • Swift版本點擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,291評論 7 249
  • 打開音樂播放器澡为,點一首舒緩的曲子,放松身體蛇券,放空大腦缀壤,想象自己正置身于我所喜歡的某個地方樊拓。 一個小院子,綠意盎然塘慕,...
    悠然遇見南山閱讀 272評論 2 3
  • 近年來筋夏,協(xié)同辦公系統(tǒng)越來越成為互聯(lián)網(wǎng)企業(yè)的標(biāo)配,國內(nèi)多款協(xié)同辦公系統(tǒng)紛紛進入人們的視野图呢,其中条篷,移動互聯(lián)網(wǎng)企業(yè)規(guī)范化...
    狂奔的蝸牛666閱讀 797評論 0 0
  • 10月1號到2號,我參加了黃金紀(jì)元色彩能量塔羅課蛤织。由臺灣的老師帶領(lǐng)赴叹。 3號生命之樹,用色彩能量瓶和獨特的地圖指蚜,展示...
    陳紫玉閱讀 474評論 0 0