首页 » 漏洞 » YYImage源码分析

YYImage源码分析

 
文章目录

YYImage 是一个强大的iOS图像框架,是YYKit 的组件之一。具体用法可以参考Demo。

特性:

  • 支持以下类型动画图像的播放/编码/解码:
    WebP, APNG, GIF。
  • 支持以下类型静态图像的显示/编码/解码:
    WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS。
  • 支持以下类型图片的渐进式/逐行扫描/隔行扫描解码:
    PNG, GIF, JPEG, BMP。
  • 支持多张图片构成的帧动画播放,支持单张图片的 sprite sheet 动画。
  • 高效的动态内存缓存管理,以保证高性能低内存的动画播放。
  • 完全兼容 UIImage 和 UIImageView,使用方便。
  • 保留可扩展的接口,以支持自定义动画。
  • 每个类和方法都有完善的文档注释。

文件结构

  • YYImage : UIImage的子类,遵守 YYAnimatedImage 协议,更高级的方式来显示 Image
  • YYFrameImage : 同 YYImage,能够显示帧动画,仅支持png,jpeg 格式,可以配合 YYAnimatedImageView 来播放动画
  • YYSpriteSheetImage : 同 YYFrameImage, 实现另一种动画形式,可以将一张图片自定义剪裁成多张图片来播放图片动画,同样可以配合YYAnimatedImageView 使用
  • YYImageCoder : 图像的编码和解码功能类, YYImageEncoder 负责编码, YYImageDecoder 负责解码, YYImageFrame 负责管理帧图像信息, _YYImageDecoderFrame 内部私有类是其子类
  • YYAnimatedImageView: UIImageView 子类,用于播放图像动画,定义了 YYAnimatedImage 协议

YYAnimatedImageView

运行Demo,进入Animated Image 。有五种播放的图片动画,前三个图像格式分别是GIF,WebP,APNG,后两个分别转化成 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; } 

初始化方法只是一些简单的属性设置,默认 _autoPlayAnimatedImage 为YES,自动开启动画, _runloopModeNSRunLoopCommonModes ,避免滑动时动画停止,因为以上的五种动画都是通过 CADisplayLink 来实现的动画, 滑动时 mode 会切换到 UITrackingRunLoopMode 导致 _link 失效,代码中实现如下 ,保证滑动时依然有效

if (_runloopMode) {       [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];  } 

通过设置 self.image = image; 传入 YYAnimatedImageTypeImage type

- (void)setImage:(id)image withType:(YYAnimatedImageType)type {    //停止动画     [self stopAnimating];   //重置动画     if (_link) [self resetAnimated];     _curFrame = nil;   //设置图片信息     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)]) {             //获取帧图像个数         newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount;         if (newImageFrameCount > 1) {             //是否只显示部分图像,YYImage中只有YYSpriteSheetImage 实现了该协议方法             hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)];         }     }     if (!hasContentsRect && _curImageHasContentsRect) {         if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {             //一般不会走这部分代码,当且仅当上次是显示部分图像,现在显示完整图像,并且图层不完全显示时             [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];    //根据是否添加到父视图开始或停止动画     [self didMoved]; } 

如果当前是 YYSpriteSheetImage 会进入到 [self setContentsRect:rect forImage:newVisibleImage]; 此时设置 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;         }     } } 

自定义动画

准备工作,调用 resetAnimated , 在 dispatch_once 中初始化 _requestQueue , 设置 maxConcurrentOperationCount 为1,串行执行请求图像任务,初始化 _link ,设置target为 _YYImageWeakProxy ,传入 self 赋值给 weak 属性 target,避免循环引用,添加到 mainRunLoop,mode 设置为 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];      //加锁 清除原来的图像数据     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];              });          }     );    //初始化属性设置     _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次的频率(屏幕刷新频率)调用 step:(CADisplayLink *)link 其中 _buffer 存储图像数据, _YYAnimatedImageViewFetchOperation 用于获取图像数据 实现代码在 main 方法中的 UIImage *img = [_curImage animatedImageFrameAtIndex:idx];

- (void)step:(CADisplayLink *)link {     //当前显示的图像, 必须遵守 YYAnimatedImage 协议     UIImage <YYAnimatedImage> *image = _curAnimatedImage;   // 获取当前的图像数据字典     NSMutableDictionary *buffer = _buffer;   //下张要显示的图像     UIImage *bufferedImage = nil;   //下一张显示图像的Index     NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;  //是否获取所有图像数据     BOOL bufferIsFull = NO;     //当前无图像直接返回     if (!image) return;    // 结束动画     if (_loopEnd) { // view will keep in last frame         [self stopAnimating];         return;     }          NSTimeInterval delay = 0;   //下张图像存在     if (!_bufferMiss) {         // 累加时间,保证当前图像的显示时间为delay         _time += link.duration;         delay = [image animatedImageDurationAtIndex:_curIndex];         if (_time < delay) return;         //减去当前图像时间,保证下张图像显示时间正确         _time -= delay;         if (nextIndex == 0) {            //循环次数加1             _curLoop++;             if (_curLoop >= _totalLoop && _totalLoop != 0) {                  //总循环次数不为0时,且当前循环次数大于总循环次数,关闭动画                 _loopEnd = YES;                 [self stopAnimating];                 [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep                 return; // stop at last frame             }         } // 如果当前累加时间还是大于下张显示时间,设置累加时间为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) {                  // 还未完全获取所有图像时,清除下一张图像数据,保证数据正确                  [buffer removeObjectForKey:@(nextIndex)];              }             //更新当前Index              [self willChangeValueForKey:@"currentAnimatedImageIndex"];              _curIndex = nextIndex;              [self didChangeValueForKey:@"currentAnimatedImageIndex"];             // 更新当前图像              _curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;              if (_curImageHasContentsRect) {             // YYSpriteSheetImage :获取当前Index下的部分图像 Rect,设置 对应的 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. 确保当前图像存在,并且循环未结束
  2. 如果还在当前图像显示时间内,返回
  3. 进入下张图像显示,如存在直接显示,并更新相应属性数据,如不存在,初始化一个operation,获取下一张图像数据

_YYAnimatedImageViewFetchOperation 实现获取下张图像

_YYAnimatedImageViewFetchOperationNSOperation 的子类, 用来执行获取图像操作。通过自定义main 方法实现,每添加一个 operation , _incrBufferCount ++ , 遍历 _buffer , 获取丢失的图像。具体获取图像通过协议方法 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;     //当前已尝试获取的图像的次数,不大于 最大图像数     NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount;     NSUInteger total = view->_totalFrameCount;     view = nil;          for (int i = 0; i < max; i++, idx++) {       //遍历当前所需的图像 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 都实现了 YYAnimatedImage 协议方法,后两个比较简单,主要来看 YYImage , 内部获取图像通过 _decoder 实现, 其中 preloadAllAnimatedImageFrames 属性可以用来预先加载所有图像数据

- (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

最终获取图像调用到 YYImageDecoder 内部,返回一个 YYImageFrame 实例,存储了图像信息。可以参考文章 移动端图片格式调研

- (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; } 

图像解码主要方法

根据传入的图像data 更新数据到 _frames 数组 ,里面存储的是 _YYImageDecoderFrame 。大致有三类图片数据,webP,APNG, 其他。

webP 图片通过Google的 WebP.framework 实现,APNG是 自定义实现的图像解码,其他的通过 ImageIO 框架实现的

- (void)_updateSource {     switch (_type) {         case YYImageTypeWebP: {                       [self _updateSourceWebP];         } break;                      case YYImageTypePNG: {             [self _updateSourceAPNG];         } break;                      default: {             [self _updateSourceImageIO];         } break;     } } 

主要看一下 _updateSourceImageIO

- (void)_updateSourceImageIO {    //图像宽,高,显示方向初始化, 循环次数, 0 代表无限     _width = 0;     _height = 0;     _orientation = UIImageOrientationUp;     _loopCount = 0;   //清除原来的数据     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;     //获取图像个数     _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;

原文链接:YYImage源码分析,转载请注明来源!

0