YYKit源碼分析(9)-YYImage(上)

現(xiàn)在分析到Y(jié)YImage


首先看文件

YYImage

YYFrameImage

YYSpriteSheetImage

YYAnimatedImageView

YYImageCoder

YYImageCache

YYWebImageOperation

YYWebImageManager



YYImageCache

結(jié)構(gòu)


結(jié)構(gòu)簡單,五個屬性

初始化

兩個初始化

- (instancetype)initWithPath:(NSString *)path {

? ? YYMemoryCache *memoryCache = [YYMemoryCache new];

? ? memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES;

? ? memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES;

? ? memoryCache.countLimit = NSUIntegerMax;

? ? memoryCache.costLimit = NSUIntegerMax;

? ? memoryCache.ageLimit = 12 * 60 * 60;


? ? YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];

? ? diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; };

? ? diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; };

? ? if (!memoryCache || !diskCache) return nil;


? ? self = [super init];

? ? _memoryCache = memoryCache;

? ? _diskCache = diskCache;

? ? _allowAnimatedImage = YES;

? ? _decodeForDisplay = YES;

? ? return self;

}

1.初始化YYMemoryCache朝群。給相關(guān)屬性賦值。(標記當記憶內(nèi)存警告岭佳,刪除內(nèi)存中的數(shù)據(jù),標記當進入后臺,刪除內(nèi)存數(shù)據(jù)花沉,并且不設(shè)置條數(shù)和大小限制,保存最長時間設(shè)置12個小時)

2.初始化YYDiskCache谨湘。

3.給self的變量賦值(_memoryCache,_diskCache)這里還有兩個屬性,_allowAnimatedImage 芥丧,_decodeForDisplay 紧阔。字面意思是開啟動畫,和解碼续担。

public method

既然是cache擅耽,那么就應(yīng)該有增刪改查

- (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type {

? ? if (!key || (image == nil && imageData.length == 0)) return;


? ? __weak typeof(self) _self = self;

? ? if (type & YYImageCacheTypeMemory) { // add to memory cache

? ? ? ? if (image) {

? ? ? ? ? ? if (image.isDecodedForDisplay) {

? ? ? ? ? ? ? ? [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]];

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? dispatch_async(YYImageCacheDecodeQueue(), ^{

? ? ? ? ? ? ? ? ? ? __strong typeof(_self) self = _self;

? ? ? ? ? ? ? ? ? ? if (!self) return;

? ? ? ? ? ? ? ? ? ? [self.memoryCache setObject:[image imageByDecoded] forKey:key withCost:[self imageCost:image]];

? ? ? ? ? ? ? ? });

? ? ? ? ? ? }

? ? ? ? } else if (imageData) {

? ? ? ? ? ? dispatch_async(YYImageCacheDecodeQueue(), ^{

? ? ? ? ? ? ? ? __strong typeof(_self) self = _self;

? ? ? ? ? ? ? ? if (!self) return;

? ? ? ? ? ? ? ? UIImage *newImage = [self imageFromData:imageData];

? ? ? ? ? ? ? ? [self.memoryCache setObject:newImage forKey:key withCost:[self imageCost:newImage]];

? ? ? ? ? ? });

? ? ? ? }

? ? }

? ? if (type & YYImageCacheTypeDisk) { // add to disk cache

? ? ? ? if (imageData) {

? ? ? ? ? ? if (image) {

? ? ? ? ? ? ? ? [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData];

? ? ? ? ? ? }

? ? ? ? ? ? [_diskCache setObject:imageData forKey:key];

? ? ? ? } else if (image) {

? ? ? ? ? ? dispatch_async(YYImageCacheIOQueue(), ^{

? ? ? ? ? ? ? ? __strong typeof(_self) self = _self;

? ? ? ? ? ? ? ? if (!self) return;

? ? ? ? ? ? ? ? NSData *data = [image imageDataRepresentation];

? ? ? ? ? ? ? ? [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data];

? ? ? ? ? ? ? ? [self.diskCache setObject:data forKey:key];

? ? ? ? ? ? });

? ? ? ? }

? ? }

}

1.檢查加入緩存數(shù)據(jù)參數(shù)是否正確

2.檢查type 類型

3要是type 類型包含YYImageCacheTypeMemory,那么就將數(shù)據(jù)保存在內(nèi)存中

《1》要是參數(shù)image不是nil?

《2》要是image!=nil,那么檢查image是否decode物遇,decode就直接保存在內(nèi)存中乖仇,沒有,則先將image decode 询兴,再保存到內(nèi)存中

《3》要是image ==nil乃沙,但是imageData不是nil,那么,將imageData 轉(zhuǎn)換成image诗舰,將數(shù)據(jù)保存到內(nèi)存中警儒。

4.要是type類型包含YYImageCacheTypeDisk,那么就將數(shù)據(jù)保存在磁盤上

《1》要是imageData不是nil,并且image 也是不是nil 眶根,那么我們就將image的scale 一并保存到數(shù)據(jù)庫中蜀铲。這里是同步執(zhí)行的

《2》要是imageData是nil,image不是nil属百,那么將image轉(zhuǎn)換成data记劝,將數(shù)據(jù)保存到磁盤上

[YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data];

這句話給data添加關(guān)聯(lián),寫入的sqlite表的?extended_data中族扰。

- (void)setImage:(UIImage *)image forKey:(NSString *)key {

? ? [self setImage:image imageData:nil forKey:key withType:YYImageCacheTypeAll];

}

這個函數(shù)就是調(diào)用上面函數(shù)厌丑,type類型是YYImageCacheTypeAll 钳恕,既保持到內(nèi)存,也保存到磁盤上

- (void)removeImageForKey:(NSString *)key {

? ? [self removeImageForKey:key withType:YYImageCacheTypeAll];

}

- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type {

? ? if (type & YYImageCacheTypeMemory) [_memoryCache removeObjectForKey:key];

? ? if (type & YYImageCacheTypeDisk) [_diskCache removeObjectForKey:key];

}

刪除很簡單蹄衷,就是依次刪除內(nèi)存和磁盤上的數(shù)據(jù)忧额,這里不過是同步刪除。

- (BOOL)containsImageForKey:(NSString *)key {

? ? return [self containsImageForKey:key withType:YYImageCacheTypeAll];

}

- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type {

? ? if (type & YYImageCacheTypeMemory) {

? ? ? ? if ([_memoryCache containsObjectForKey:key]) return YES;

? ? }

? ? if (type & YYImageCacheTypeDisk) {

? ? ? ? if ([_diskCache containsObjectForKey:key]) return YES;

? ? }

? ? return NO;

}

內(nèi)存或者磁盤上分別檢查是否包含key愧口,當前線程檢查

- (UIImage *)getImageForKey:(NSString *)key {

? ? return [self getImageForKey:key withType:YYImageCacheTypeAll];

}

- (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type {

? ? if (!key) return nil;

? ? if (type & YYImageCacheTypeMemory) {

? ? ? ? UIImage *image = [_memoryCache objectForKey:key];

? ? ? ? if (image) return image;

? ? }

? ? if (type & YYImageCacheTypeDisk) {

? ? ? ? NSData *data = (id)[_diskCache objectForKey:key];

? ? ? ? UIImage *image = [self imageFromData:data];

? ? ? ? if (image && (type & YYImageCacheTypeMemory)) {

? ? ? ? ? ? [_memoryCache setObject:image forKey:key withCost:[self imageCost:image]];

? ? ? ? }

? ? ? ? return image;

? ? }

? ? return nil;

}

獲取key對應(yīng)的數(shù)據(jù)睦番,因為磁盤上存入的是NSData數(shù)據(jù),從disk上獲取到的數(shù)據(jù)要轉(zhuǎn)行成NSdata

- (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void (^)(UIImage *image, YYImageCacheType type))block {

? ? if (!block) return;

? ? dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

? ? ? ? UIImage *image = nil;


? ? ? ? if (type & YYImageCacheTypeMemory) {

? ? ? ? ? ? image = [_memoryCache objectForKey:key];

? ? ? ? ? ? if (image) {

? ? ? ? ? ? ? ? dispatch_async(dispatch_get_main_queue(), ^{

? ? ? ? ? ? ? ? ? ? block(image, YYImageCacheTypeMemory);

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? if (type & YYImageCacheTypeDisk) {

? ? ? ? ? ? NSData *data = (id)[_diskCache objectForKey:key];

? ? ? ? ? ? image = [self imageFromData:data];

? ? ? ? ? ? if (image) {

? ? ? ? ? ? ? ? [_memoryCache setObject:image forKey:key];

? ? ? ? ? ? ? ? dispatch_async(dispatch_get_main_queue(), ^{

? ? ? ? ? ? ? ? ? ? block(image, YYImageCacheTypeDisk);

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? dispatch_async(dispatch_get_main_queue(), ^{

? ? ? ? ? ? block(nil, YYImageCacheTypeNone);

? ? ? ? });

? ? });

}

這個是異步獲取image耍属,但是獲取到的image是回調(diào)到主線程執(zhí)行block托嚣。

- (NSData *)getImageDataForKey:(NSString *)key {

? ? return (id)[_diskCache objectForKey:key];

}

獲取key對應(yīng)在磁盤上的數(shù)據(jù)

- (void)getImageDataForKey:(NSString *)key withBlock:(void (^)(NSData *imageData))block {

? ? if (!block) return;

? ? dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

? ? ? ? NSData *data = (id)[_diskCache objectForKey:key];

? ? ? ? dispatch_async(dispatch_get_main_queue(), ^{

? ? ? ? ? ? block(data);

? ? ? ? });

? ? });

}

異步獲取,這里代碼很清晰厚骗。不做介紹

不影響邏輯的其他方法

- (NSUInteger)imageCost:(UIImage *)image {

? ? CGImageRef cgImage = image.CGImage;

? ? if (!cgImage) return 1;

? ? CGFloat height = CGImageGetHeight(cgImage);

? ? size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);

? ? NSUInteger cost = bytesPerRow * height;

? ? if (cost == 0) cost = 1;

? ? return cost;

}

這個是返回數(shù)據(jù)的cost示启,我們知道在內(nèi)存或者磁盤上都有一個屬性cost 屬性。在YYImageCache中领舰,內(nèi)存或者磁盤上的cost 記錄的是image的像素大小夫嗓。

- (UIImage *)imageFromData:(NSData *)data {

? ? NSData *scaleData = [YYDiskCache getExtendedDataFromObject:data];

? ? CGFloat scale = 0;

? ? if (scaleData) {

? ? ? ? scale = ((NSNumber *)[NSKeyedUnarchiver unarchiveObjectWithData:scaleData]).doubleValue;

? ? }

? ? if (scale <= 0) scale = [UIScreen mainScreen].scale;

? ? UIImage *image;

? ? if (_allowAnimatedImage) {

? ? ? ? image = [[YYImage alloc] initWithData:data scale:scale];

? ? ? ? if (_decodeForDisplay) image = [image imageByDecoded];

? ? } else {

? ? ? ? YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];

? ? ? ? image = [decoder frameAtIndex:0 decodeForDisplay:_decodeForDisplay].image;

? ? }

? ? return image;

}

這個函數(shù)將data 轉(zhuǎn)換成image。不過這里生成的image是YYImage冲秽。



YYImage

繼承UIImage

結(jié)構(gòu)


初始化

初始化public方法只有四個舍咖。

+ (nullable YYImage *)imageNamed:(NSString *)name; // no cache!

+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;

+ (nullable YYImage *)imageWithData:(NSData *)data;

+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;

+ (YYImage *)imageNamed:(NSString *)name {

? ? if (name.length == 0) return nil;

? ? if ([name hasSuffix:@"/"]) return nil;


? ? NSString *res = name.stringByDeletingPathExtension;

? ? NSString *ext = name.pathExtension;

? ? NSString *path = nil;

? ? CGFloat scale = 1;


? ? // If no extension, guess by system supported (same as UIImage).

? ? NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];

? ? NSArray *scales = [NSBundle preferredScales];

? ? for (int s = 0; s < scales.count; s++) {

? ? ? ? scale = ((NSNumber *)scales[s]).floatValue;

? ? ? ? NSString *scaledName = [res stringByAppendingNameScale:scale];

? ? ? ? for (NSString *e in exts) {

? ? ? ? ? ? path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];

? ? ? ? ? ? if (path) break;

? ? ? ? }

? ? ? ? if (path) break;

? ? }

? ? if (path.length == 0) return nil;


? ? NSData *data = [NSData dataWithContentsOfFile:path];

? ? if (data.length == 0) return nil;


? ? return [[self alloc] initWithData:data scale:scale];

}

我們知道,UIImage 調(diào)用該方法是在內(nèi)存中保存一份的锉桑。但是在YYImage 是沒有緩存在內(nèi)存中的

1.首先檢查name是否合法

2.從本地獲取image路徑排霉。

《1》獲取去除后綴的字符串

《2》獲取后綴名字

《3》檢查后綴名字是不是nil,不是nil民轴,將其包裝成數(shù)組攻柠,是nil,那么將支持的后綴包裝 成在一個數(shù)組中

《4》重新拼裝path后裸,拼接成 eg name1@2x.png 瑰钮。檢查路徑是否存在,存在就結(jié)束

3獲取path 的數(shù)據(jù)

4.生成image

+ (YYImage *)imageWithContentsOfFile:(NSString *)path {

? ? return [[self alloc] initWithContentsOfFile:path];

}

+ (YYImage *)imageWithData:(NSData *)data {

? ? return [[self alloc] initWithData:data];

}

+ (YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale {

? ? return [[self alloc] initWithData:data scale:scale];

}

- (instancetype)initWithContentsOfFile:(NSString *)path {

? ? NSData *data = [NSData dataWithContentsOfFile:path];

? ? return [self initWithData:data scale:path.pathScale];

}

- (instancetype)initWithData:(NSData *)data {

? ? return [self initWithData:data scale:1];

}

上面的所有函數(shù)最后都調(diào)用下面這個函數(shù)

- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {

? ? if (data.length == 0) return nil;

? ? if (scale <= 0) scale = [UIScreen mainScreen].scale;

? ? _preloadedLock = dispatch_semaphore_create(1);

? ? @autoreleasepool {

? ? ? ? YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];

? ? ? ? YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];

? ? ? ? UIImage *image = frame.image;

? ? ? ? if (!image) return nil;

? ? ? ? self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];

? ? ? ? if (!self) return nil;

? ? ? ? _animatedImageType = decoder.type;

? ? ? ? if (decoder.frameCount > 1) {

? ? ? ? ? ? _decoder = decoder;

? ? ? ? ? ? _bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);

? ? ? ? ? ? _animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;

? ? ? ? }

? ? ? ? self.isDecodedForDisplay = YES;

? ? }

? ? return self;

}

核心函數(shù)了轻抱》赏浚看看具體干嘛了

1.數(shù)據(jù)nil 就返回

2.獲取scale

3.初始化信號量?

4因為涉及到圖片,圖片對象比較大祈搜,希望放入最上面的自動釋放池里面较店。所以自動釋放池里面操作image

5.對圖片進行decode

6獲取decode 的中的第一張imageFrame

7獲取image

8.將image轉(zhuǎn)換成bitmap格式的image

9 根據(jù)decode解碼圖片是否是多張,是多張就保存當前decode

10.標記image 已經(jīng)解碼

這個函數(shù)里面出現(xiàn)了YYImageDecoder 和?YYImageFrame?

YYImageDecoder 代表圖片解碼集合容燕,類似圖片數(shù)組

YYImageFrame 代表 一張圖片的解碼梁呈。可以解碼出一張圖片蘸秘。

其他方法

- (NSData *)animatedImageData {

? ? return _decoder.data;

}

獲取image的原始數(shù)據(jù)

- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames {

? ? if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) {

? ? ? ? if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) {

? ? ? ? ? ? NSMutableArray *frames = [NSMutableArray new];

? ? ? ? ? ? for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) {

? ? ? ? ? ? ? ? UIImage *img = [self animatedImageFrameAtIndex:i];

? ? ? ? ? ? ? ? if (img) {

? ? ? ? ? ? ? ? ? ? [frames addObject:img];

? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? [frames addObject:[NSNull null]];

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);

? ? ? ? ? ? _preloadedFrames = frames;

? ? ? ? ? ? dispatch_semaphore_signal(_preloadedLock);

? ? ? ? } else {

? ? ? ? ? ? dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);

? ? ? ? ? ? _preloadedFrames = nil;

? ? ? ? ? ? dispatch_semaphore_signal(_preloadedLock);

? ? ? ? }

? ? }

}

這個是重新的屬性方法官卡。搜索了這個文件蝗茁,綜合分析,這個屬性要是設(shè)置為Yes寻咒,那么提前將decode解碼成圖片數(shù)組哮翘。提前保存起來,直接用就行了毛秘。這就是典型的空間換時間

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

? ? NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"];

? ? NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"];

? ? if (data.length) {

? ? ? ? self = [self initWithData:data scale:scale.doubleValue];

? ? } else {

? ? ? ? self = [super initWithCoder:aDecoder];

? ? }

? ? return self;

}

- (void)encodeWithCoder:(NSCoder *)aCoder {

? ? if (_decoder.data.length) {

? ? ? ? [aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"];

? ? ? ? [aCoder encodeObject:_decoder.data forKey:@"YYImageData"];

? ? } else {

? ? ? ? [super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage.

? ? }

}

+ (BOOL)supportsSecureCoding {

? ? return? YES;

}

實現(xiàn)UIimage的NSSecureCoding必須返回YES

下面實現(xiàn)了一個動畫協(xié)議

- (NSUInteger)animatedImageFrameCount {

? ? return _decoder.frameCount;

}

獲取有多少張圖片

- (NSUInteger)animatedImageLoopCount {

? ? return _decoder.loopCount;

}

循環(huán)次數(shù)

- (NSUInteger)animatedImageBytesPerFrame {

? ? return _bytesPerFrame;

}

沒幀圖片的大小

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

}

獲取index處的圖片饭寺。

UIImage *image = _preloadedFrames[index]; ?

這里我們知道_preloadedFrames 賦值是通過preloadAllAnimatedImageFrames 設(shè)置為Yes的時候,因此_preloadedFrames 可能是nil叫挟,我們通過nil獲取index 艰匙,返回值肯定是image。我在想這里是不是會產(chǎn)生崩潰呢抹恳?測試不會员凝。

測試代碼

NSArray * testCrash=nil;

? ?UIImage * image = testCrash[4];


- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { NSTimeInterval duration = [_decoder frameDurationAtIndex:index]; /* http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp Many annoying ads specify a 0 duration to make an image flash as quickly as possible. We follow Safari and Firefox's behavior and use a duration of 100 ms for any frames that specify a duration of <= 10 ms. Seeand for more information.


? ? See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser.

? ? */

? ? if (duration < 0.011f) return 0.100f;

? ? return duration;

}

圖片之間播放間隔。


YYImageCoder

最難搞的一個類了奋献。

結(jié)構(gòu)


初始化

類方法

+ (instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale {

? ? if (!data) return nil;

? ? YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:scale];

? ? [decoder updateData:data final:YES];

? ? if (decoder.frameCount == 0) return nil;

? ? return decoder;

}


- (instancetype)init {

? ? return [self initWithScale:[UIScreen mainScreen].scale];

}

- (instancetype)initWithScale:(CGFloat)scale {

? ? self = [super init];

? ? if (scale <= 0) scale = 1;

? ? _scale = scale;

? ? _framesLock = dispatch_semaphore_create(1);

? ? pthread_mutex_init_recursive(&_lock, true);

? ? return self;

}

這里很簡單健霹,初始化基本參數(shù)。


不過這里有個c函數(shù)秽荞,遞歸鎖

static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {

#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \

__unused volatile int res = (x_); \

assert(res == 0); \

} while (0)

? ? assert(mutex != NULL);

? ? if (!recursive) {

? ? ? ? YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));

? ? } else {

? ? ? ? pthread_mutexattr_t attr;

? ? ? ? YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));

? ? ? ? YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));

? ? ? ? YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init (mutex, &attr));

? ? ? ? YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));

? ? }

#undef YYMUTEX_ASSERT_ON_ERROR

}

遞歸鎖骤公。調(diào)用每個函數(shù),都檢測assert是否正確扬跋。

傳入數(shù)據(jù)函數(shù)

- (BOOL)updateData:(NSData *)data final:(BOOL)final {

? ? BOOL result = NO;

? ? pthread_mutex_lock(&_lock);

? ? result = [self _updateData:data final:final];

? ? pthread_mutex_unlock(&_lock);

? ? return result;

}

這個沒有啥看掉用的函數(shù)

- (BOOL)_updateData:(NSData *)data final:(BOOL)final {

? ? if (_finalized) return NO;

? ? if (data.length < _data.length) return NO;

? ? _finalized = final;

? ? _data = data;


? ? YYImageType type = YYImageDetectType((__bridge CFDataRef)data);

? ? if (_sourceTypeDetected) {

? ? ? ? if (_type != type) {

? ? ? ? ? ? return NO;

? ? ? ? } else {

? ? ? ? ? ? [self _updateSource];

? ? ? ? }

? ? } else {

? ? ? ? if (_data.length > 16) {

? ? ? ? ? ? _type = type;

? ? ? ? ? ? _sourceTypeDetected = YES;

? ? ? ? ? ? [self _updateSource];

? ? ? ? }

? ? }

? ? return YES;

}

1.給相關(guān)變量賦值。

2.檢測數(shù)據(jù)圖片類型凌节。

3.更新源文件

這里檢查圖片的類型使用的函數(shù)是

YYImageType YYImageDetectType(CFDataRef data) {

? ? if (!data) return YYImageTypeUnknown;

? ? uint64_t length = CFDataGetLength(data);

? ? if (length < 16) return YYImageTypeUnknown;


? ? const char *bytes = (char *)CFDataGetBytePtr(data);


? ? uint32_t magic4 = *((uint32_t *)bytes);

? ? switch (magic4) {

? ? ? ? case YY_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF

? ? ? ? ? ? return YYImageTypeTIFF;

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF

? ? ? ? ? ? return YYImageTypeTIFF;

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO

? ? ? ? ? ? return YYImageTypeICO;

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR

? ? ? ? ? ? return YYImageTypeICO;

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC('i', 'c', 'n', 's'): { // ICNS

? ? ? ? ? ? return YYImageTypeICNS;

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC('G', 'I', 'F', '8'): { // GIF

? ? ? ? ? ? return YYImageTypeGIF;

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC(0x89, 'P', 'N', 'G'): {? // PNG

? ? ? ? ? ? uint32_t tmp = *((uint32_t *)(bytes + 4));

? ? ? ? ? ? if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n')) {

? ? ? ? ? ? ? ? return YYImageTypePNG;

? ? ? ? ? ? }

? ? ? ? } break;


? ? ? ? case YY_FOUR_CC('R', 'I', 'F', 'F'): { // WebP

? ? ? ? ? ? uint32_t tmp = *((uint32_t *)(bytes + 8));

? ? ? ? ? ? if (tmp == YY_FOUR_CC('W', 'E', 'B', 'P')) {

? ? ? ? ? ? ? ? return YYImageTypeWebP;

? ? ? ? ? ? }

? ? ? ? } break;

? ? ? ? /*

? ? ? ? case YY_FOUR_CC('B', 'P', 'G', 0xFB): { // BPG

? ? ? ? ? ? return YYImageTypeBPG;

? ? ? ? } break;

? ? ? ? */

? ? }


? ? uint16_t magic2 = *((uint16_t *)bytes);

? ? switch (magic2) {

? ? ? ? case YY_TWO_CC('B', 'A'):

? ? ? ? case YY_TWO_CC('B', 'M'):

? ? ? ? case YY_TWO_CC('I', 'C'):

? ? ? ? case YY_TWO_CC('P', 'I'):

? ? ? ? case YY_TWO_CC('C', 'I'):

? ? ? ? case YY_TWO_CC('C', 'P'): { // BMP

? ? ? ? ? ? return YYImageTypeBMP;

? ? ? ? }

? ? ? ? case YY_TWO_CC(0xFF, 0x4F): { // JPEG2000

? ? ? ? ? ? return YYImageTypeJPEG2000;

? ? ? ? }

? ? }


? ? // JPG? ? ? ? ? ? FF D8 FF

? ? if (memcmp(bytes,"\377\330\377",3) == 0) return YYImageTypeJPEG;


? ? // JP2

? ? if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return YYImageTypeJPEG2000;


? ? return YYImageTypeUnknown;

}

是檢測文件頭來判斷數(shù)據(jù)的格式的钦听。

#define YY_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))

#define YY_TWO_CC(c1,c2) ((uint16_t)(((c2) << 8) | (c1)))

因為圖片的排列是假如gif8 的內(nèi)存排列是 gif8xxxxx ,當轉(zhuǎn)換成int 類類型的時候,

做個圖


好多圖片格式倍奢,這里不做介紹

- (void)_updateSource {

? ? switch (_type) {

? ? ? ? case YYImageTypeWebP: {

? ? ? ? ? ? [self _updateSourceWebP];

? ? ? ? } break;


? ? ? ? case YYImageTypePNG: {

? ? ? ? ? ? [self _updateSourceAPNG];

? ? ? ? } break;


? ? ? ? default: {

? ? ? ? ? ? [self _updateSourceImageIO];

? ? ? ? } break;

? ? }

}

看這個函數(shù)朴上,我們知道了,圖片中單獨解析了webP 和 png 卒煞,其他類型作為默認類型了

先看不是webP和png的圖片解析- (void)_updateSourceImageIO

- (void)_updateSourceImageIO {

? ? _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);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? 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);

}

我們分布分析這個decode函數(shù)

1.默認_width =_height = 0 ,圖片方向默認是UIImageOrientationUp痪宰,_loopCount 默認0,_frames = nil畔裕。這里給_frames 之間加入了鎖衣撬,說明這個變量可能被多處訪問。要求訪問這個變量的時候扮饶,只能一處一處進行具练。

2檢查變量_source是否是nil

3?_source 是nil,那么將_data 轉(zhuǎn)換成_source

CGImageSourceCreateIncremental這個函數(shù)干嘛的甜无,(shift+cmd+0)

The function?CGImageSourceCreateIncremental?creates an empty image source container to which you can add data later by calling the functions?CGImageSourceUpdateDataProvider?or?CGImageSourceUpdateData. You don’t provide data when you call this function.?

An incremental image is an image that is created in chunks, similar to the way large images viewed over the web are loaded piece by piece.

只是生成一個empty的source扛点,可以將數(shù)據(jù)加入其中哥遮。

4_source!=nil ,那么將_data 更新到_source

5 獲取_source 有多少幀保存到_frameCount 中

6.要是_frameCount 是0 ,就返回

7.這里有個_finalized 陵究,猜測是不是decode結(jié)束標志

8.這里對gif 數(shù)據(jù)單獨處理了眠饮,要是gif圖。需要獲取gif圖循環(huán)次數(shù)

9.針對有多幀的image铜邮,我們需要將每張圖片解析成_YYImageDecoderFrame仪召,將其保存在_frame中,比如圖片的寬牲距,高返咱。

10要是圖片格式是gif,就要讀取兩幀間隔時間

- (void)_updateSourceAPNG {

? ? /*

? ? APNG extends PNG format to support animation, it was supported by ImageIO

? ? since iOS 8.


? ? We use a custom APNG decoder to make APNG available in old system, so we

? ? ignore the ImageIO's APNG frame info. Typically the custom decoder is a bit

? ? faster than ImageIO.

? ? */


? ? yy_png_info_release(_apngSource);

? ? _apngSource = nil;


? ? [self _updateSourceImageIO]; // decode first frame

? ? if (_frameCount == 0) return; // png decode failed

? ? if (!_finalized) return; // ignore multi-frame before finalized


? ? yy_png_info *apng = yy_png_info_create(_data.bytes, (uint32_t)_data.length);

? ? if (!apng) return; // apng decode failed

? ? if (apng->apng_frame_num == 0 ||

? ? ? ? (apng->apng_frame_num == 1 && apng->apng_first_frame_is_cover)) {

? ? ? ? yy_png_info_release(apng);

? ? ? ? return; // no animation

? ? }

? ? if (_source) { // apng decode succeed, no longer need image souce

? ? ? ? CFRelease(_source);

? ? ? ? _source = NULL;

? ? }


? ? uint32_t canvasWidth = apng->header.width;

? ? uint32_t canvasHeight = apng->header.height;

? ? NSMutableArray *frames = [NSMutableArray new];

? ? BOOL needBlend = NO;

? ? uint32_t lastBlendIndex = 0;

? ? for (uint32_t i = 0; i < apng->apng_frame_num; i++) {

? ? ? ? _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new];

? ? ? ? [frames addObject:frame];


? ? ? ? yy_png_frame_info *fi = apng->apng_frames + i;

? ? ? ? frame.index = i;

? ? ? ? frame.duration = yy_png_delay_to_seconds(fi->frame_control.delay_num, fi->frame_control.delay_den);

? ? ? ? frame.hasAlpha = YES;

? ? ? ? frame.width = fi->frame_control.width;

? ? ? ? frame.height = fi->frame_control.height;

? ? ? ? frame.offsetX = fi->frame_control.x_offset;

? ? ? ? frame.offsetY = canvasHeight - fi->frame_control.y_offset - fi->frame_control.height;


? ? ? ? BOOL sizeEqualsToCanvas = (frame.width == canvasWidth && frame.height == canvasHeight);

? ? ? ? BOOL offsetIsZero = (fi->frame_control.x_offset == 0 && fi->frame_control.y_offset == 0);

? ? ? ? frame.isFullSize = (sizeEqualsToCanvas && offsetIsZero);


? ? ? ? switch (fi->frame_control.dispose_op) {

? ? ? ? ? ? case YY_PNG_DISPOSE_OP_BACKGROUND: {

? ? ? ? ? ? ? ? frame.dispose = YYImageDisposeBackground;

? ? ? ? ? ? } break;

? ? ? ? ? ? case YY_PNG_DISPOSE_OP_PREVIOUS: {

? ? ? ? ? ? ? ? frame.dispose = YYImageDisposePrevious;

? ? ? ? ? ? } break;

? ? ? ? ? ? default: {

? ? ? ? ? ? ? ? frame.dispose = YYImageDisposeNone;

? ? ? ? ? ? } break;

? ? ? ? }

? ? ? ? switch (fi->frame_control.blend_op) {

? ? ? ? ? ? case YY_PNG_BLEND_OP_OVER: {

? ? ? ? ? ? ? ? frame.blend = YYImageBlendOver;

? ? ? ? ? ? } break;


? ? ? ? ? ? default: {

? ? ? ? ? ? ? ? frame.blend = YYImageBlendNone;

? ? ? ? ? ? } break;

? ? ? ? }


? ? ? ? if (frame.blend == YYImageBlendNone && frame.isFullSize) {

? ? ? ? ? ? frame.blendFromIndex? = i;

? ? ? ? ? ? if (frame.dispose != YYImageDisposePrevious) lastBlendIndex = i;

? ? ? ? } else {

? ? ? ? ? ? if (frame.dispose == YYImageDisposeBackground && frame.isFullSize) {

? ? ? ? ? ? ? ? frame.blendFromIndex = lastBlendIndex;

? ? ? ? ? ? ? ? lastBlendIndex = i + 1;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? frame.blendFromIndex = lastBlendIndex;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? if (frame.index != frame.blendFromIndex) needBlend = YES;

? ? }


? ? _width = canvasWidth;

? ? _height = canvasHeight;

? ? _frameCount = frames.count;

? ? _loopCount = apng->apng_loop_num;

? ? _needBlend = needBlend;

? ? _apngSource = apng;

? ? dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);

? ? _frames = frames;

? ? dispatch_semaphore_signal(_framesLock);

}

這個類是專門針對apng 圖片做的處理,把apng的多張圖片解析出來

這里就是對apng 圖片解析了牍鞠,再將每一幀圖片封裝成_YYImageDecoderFrame?

不想對apng圖片解析做具體介紹咖摹。可自行查詢难述,是自定義了apng圖片的chunk

- (void)_updateSourceWebP

這個函數(shù)是對webp的解析萤晴,不做介紹了。這個是使用的webP.framework api

從上面解析我們知道了胁后,這個類將圖片解析成統(tǒng)一格式

_YYImageDecoderFrame 代表圖片的一幀店读,而_frame中裝有所有的圖片幀。

上面對圖片都解析完成了攀芯。下面我們看看其他的獲取api

- (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屯断,其實這個類是暴露給外界的類,而_YYImageDecoderFrame 是內(nèi)部解析類侣诺,這兩個類是對應(yīng)的殖演。

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

? ? ? ? CGImageRef imageRef = [self _newUnblendedImageAtIndex:index extendToCanvas:extendToCanvas decoded:&decoded];

? ? ? ? if (!imageRef) return nil;

? ? ? ? if (decodeForDisplay && !decoded) {

? ? ? ? ? ? CGImageRef imageRefDecoded = YYCGImageCreateDecodedCopy(imageRef, YES);

? ? ? ? ? ? if (imageRefDecoded) {

? ? ? ? ? ? ? ? CFRelease(imageRef);

? ? ? ? ? ? ? ? imageRef = imageRefDecoded;

? ? ? ? ? ? ? ? decoded = YES;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation];

? ? ? ? CFRelease(imageRef);

? ? ? ? if (!image) return nil;

? ? ? ? image.isDecodedForDisplay = decoded;

? ? ? ? frame.image = image;

? ? ? ? return frame;

? ? }


? ? // blend

? ? if (![self _createBlendContextIfNeeded]) return nil;

? ? CGImageRef imageRef = NULL;


? ? if (_blendFrameIndex + 1 == frame.index) {

? ? ? ? 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) {

? ? ? ? ? ? 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);

? ? ? ? ? ? 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;

? ? ? ? }

? ? }


? ? if (!imageRef) return nil;

? ? UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation];

? ? CFRelease(imageRef);

? ? if (!image) return nil;


? ? image.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;

}

這個函數(shù)獲取動畫的第幾張圖片

1.首先獲取_YYImageDecoderFrame

2_needBlend=NO, 那么獲取圖片image 返回frame年鸳。

3.2_needBlend=YES,_blendCanvas 初始化成bitmap(這里_blendFrameIndex=-1)

4.要是_blendFrameIndex+1==frame.index ,說明frame還沒blend過趴久,那么調(diào)用- (CGImageRef)_newBlendedImageWithFrame:(_YYImageDecoderFrame *)frame?

5._blendFrameIndex+1 不等于frame.index,清空_blendCanvas

6.要是frame的?blendFromIndex 等于index搔确。直接獲取image

7要是從blendFromIndex 到index彼棍,那么將幾張圖片融合到一起。返回index膳算。

8最后返回frame

據(jù)我估計大家看到這里不明白座硕。第七步為啥要將幾步融合到一起呢?最后想通了畦幢。

做動畫的時候坎吻,為了節(jié)省內(nèi)存,我們把把第二幀動畫與前一幀動畫想同的部分不繪制宇葱,只繪制與前一幀不同的圖片瘦真,這樣節(jié)省內(nèi)存刊头,在做動畫的時候,我們需要將這樣的幾張圖片進行重新blend下诸尽,就是相互疊加一起渲染原杂。

這里面有幾個新函數(shù),

- (CGImageRef)_newBlendedImageWithFrame:(_YYImageDecoderFrame *)frame CF_RETURNS_RETAINED{

? ? CGImageRef imageRef = NULL;

? ? if (frame.dispose == YYImageDisposePrevious) {

? ? ? ? if (frame.blend == YYImageBlendOver) {

? ? ? ? ? ? CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? ? ? CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];

? ? ? ? ? ? if (unblendImage) {

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);

? ? ? ? ? ? ? ? CFRelease(unblendImage);

? ? ? ? ? ? }

? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));

? ? ? ? ? ? if (previousImage) {

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage);

? ? ? ? ? ? ? ? CFRelease(previousImage);

? ? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? ? CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? ? ? CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];

? ? ? ? ? ? if (unblendImage) {

? ? ? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);

? ? ? ? ? ? ? ? CFRelease(unblendImage);

? ? ? ? ? ? }

? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));

? ? ? ? ? ? if (previousImage) {

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage);

? ? ? ? ? ? ? ? CFRelease(previousImage);

? ? ? ? ? ? }

? ? ? ? }

? ? } else if (frame.dispose == YYImageDisposeBackground) {

? ? ? ? if (frame.blend == YYImageBlendOver) {

? ? ? ? ? ? CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];

? ? ? ? ? ? if (unblendImage) {

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);

? ? ? ? ? ? ? ? CFRelease(unblendImage);

? ? ? ? ? ? }

? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));

? ? ? ? } else {

? ? ? ? ? ? CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];

? ? ? ? ? ? if (unblendImage) {

? ? ? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);

? ? ? ? ? ? ? ? CFRelease(unblendImage);

? ? ? ? ? ? }

? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));

? ? ? ? }

? ? } else { // no dispose

? ? ? ? if (frame.blend == YYImageBlendOver) {

? ? ? ? ? ? CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];

? ? ? ? ? ? if (unblendImage) {

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);

? ? ? ? ? ? ? ? CFRelease(unblendImage);

? ? ? ? ? ? }

? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? } else {

? ? ? ? ? ? CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];

? ? ? ? ? ? if (unblendImage) {

? ? ? ? ? ? ? ? CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));

? ? ? ? ? ? ? ? CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);

? ? ? ? ? ? ? ? CFRelease(unblendImage);

? ? ? ? ? ? }

? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(_blendCanvas);

? ? ? ? }

? ? }

? ? return imageRef;

}

1當frame.dispose ==?YYImageDisposePrevious 您机,

《1》當frame.blend ==?YYImageBlendOver穿肄,獲取image,(這里把現(xiàn)在的image融合到以前的_blendCanvas中)而_blendCanvas 不過要渲染成以前的樣式previousImage际看,

《2》當frame.blend 是其他情況咸产,獲取image,(這里把現(xiàn)在的image不要融合到以前的_blendCanvas中)而_blendCanvas 要渲染成以前的樣式previousImage仲闽。

2.當frame.dispose==?YYImageDisposeBackground

《1》當frame.blend ==?YYImageBlendOver脑溢,獲取image,不過這了需要清空_blendCanvas

《2》當frame.blend 是其他情況赖欣,這里需要將_blendCanvas清空屑彻,之后再繪制。

3.當frame.dispose== 其他情況

《1》當frame.blend ==?YYImageBlendOver顶吮,就是獲取image

《2》當frame.blend 是其他情況社牲,_blendCanvas清空,之后再繪制悴了。

這里有兩個枚舉

typedef NS_ENUM(NSUInteger, YYImageBlendOperation) {

? ? /**

? ? All color components of the frame, including alpha, overwrite the current

? ? contents of the frame's canvas region.

? ? */

? ? YYImageBlendNone = 0,


? ? /**

? ? The frame should be composited onto the output buffer based on its alpha.

? ? */

? ? YYImageBlendOver,

};

YYImageBlendOver 搏恤,這個就是讓_blendCanvas 作為畫布,在繪制image之前湃交,不要清除以前的_blendCanvas挑社。默認是繪制image之前,要清除_blendCanvas

typedef NS_ENUM(NSUInteger, YYImageDisposeMethod) {

? ? /**

? ? No disposal is done on this frame before rendering the next; the contents

? ? of the canvas are left as is.

? ? */

? ? YYImageDisposeNone = 0,


? ? /**

? ? The frame's region of the canvas is to be cleared to fully transparent black

? ? before rendering the next frame.

? ? */

? ? YYImageDisposeBackground,


? ? /**

? ? The frame's region of the canvas is to be reverted to the previous contents

? ? before rendering the next frame.

? ? */

? ? YYImageDisposePrevious,

};

這個枚舉是繪制完image之后巡揍,_blendCanvas 的狀態(tài)

YYImageDisposeBackground ,請_blendCanvas 清空

YYImageDisposePrevious 將_blendCanvas 渲染成以前的狀態(tài)

YYImageDisposeNone 菌瘪,不對_blendCanvas 做任何處理

- (CGImageRef)_newUnblendedImageAtIndex:(NSUInteger)index

? ? ? ? ? ? ? ? ? ? ? ? extendToCanvas:(BOOL)extendToCanvas

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? decoded:(BOOL *)decoded CF_RETURNS_RETAINED {


? ? if (!_finalized && index > 0) return NULL;

? ? if (_frames.count <= index) return NULL;

? ? _YYImageDecoderFrame *frame = _frames[index];


? ? if (_source) {

? ? ? ? CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});

? ? ? ? if (imageRef && extendToCanvas) {

? ? ? ? ? ? size_t width = CGImageGetWidth(imageRef);

? ? ? ? ? ? size_t height = CGImageGetHeight(imageRef);

? ? ? ? ? ? if (width == _width && height == _height) {

? ? ? ? ? ? ? ? CGImageRef imageRefExtended = YYCGImageCreateDecodedCopy(imageRef, YES);

? ? ? ? ? ? ? ? if (imageRefExtended) {

? ? ? ? ? ? ? ? ? ? CFRelease(imageRef);

? ? ? ? ? ? ? ? ? ? imageRef = imageRefExtended;

? ? ? ? ? ? ? ? ? ? if (decoded) *decoded = YES;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);

? ? ? ? ? ? ? ? if (context) {

? ? ? ? ? ? ? ? ? ? CGContextDrawImage(context, CGRectMake(0, _height - height, width, height), imageRef);

? ? ? ? ? ? ? ? ? ? CGImageRef imageRefExtended = CGBitmapContextCreateImage(context);

? ? ? ? ? ? ? ? ? ? CFRelease(context);

? ? ? ? ? ? ? ? ? ? if (imageRefExtended) {

? ? ? ? ? ? ? ? ? ? ? ? CFRelease(imageRef);

? ? ? ? ? ? ? ? ? ? ? ? imageRef = imageRefExtended;

? ? ? ? ? ? ? ? ? ? ? ? if (decoded) *decoded = YES;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return imageRef;

? ? }


? ? if (_apngSource) {

? ? ? ? uint32_t size = 0;

? ? ? ? uint8_t *bytes = yy_png_copy_frame_data_at_index(_data.bytes, _apngSource, (uint32_t)index, &size);

? ? ? ? if (!bytes) return NULL;

? ? ? ? CGDataProviderRef provider = CGDataProviderCreateWithData(bytes, bytes, size, YYCGDataProviderReleaseDataCallback);

? ? ? ? if (!provider) {

? ? ? ? ? ? free(bytes);

? ? ? ? ? ? return NULL;

? ? ? ? }

? ? ? ? bytes = NULL; // hold by provider


? ? ? ? CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);

? ? ? ? if (!source) {

? ? ? ? ? ? CFRelease(provider);

? ? ? ? ? ? return NULL;

? ? ? ? }

? ? ? ? CFRelease(provider);


? ? ? ? if(CGImageSourceGetCount(source) < 1) {

? ? ? ? ? ? CFRelease(source);

? ? ? ? ? ? return NULL;

? ? ? ? }


? ? ? ? CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});

? ? ? ? CFRelease(source);

? ? ? ? if (!imageRef) return NULL;

? ? ? ? if (extendToCanvas) {

? ? ? ? ? ? CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); //bgrA

? ? ? ? ? ? if (context) {

? ? ? ? ? ? ? ? CGContextDrawImage(context, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), imageRef);

? ? ? ? ? ? ? ? CFRelease(imageRef);

? ? ? ? ? ? ? ? imageRef = CGBitmapContextCreateImage(context);

? ? ? ? ? ? ? ? CFRelease(context);

? ? ? ? ? ? ? ? if (decoded) *decoded = YES;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return imageRef;

? ? }


#if YYIMAGE_WEBP_ENABLED

? ? if (_webpSource) {

? ? ? ? WebPIterator iter;

? ? ? ? if (!WebPDemuxGetFrame(_webpSource, (int)(index + 1), &iter)) return NULL; // demux webp frame data

? ? ? ? // frame numbers are one-based in webp -----------^


? ? ? ? int frameWidth = iter.width;

? ? ? ? int frameHeight = iter.height;

? ? ? ? if (frameWidth < 1 || frameHeight < 1) return NULL;


? ? ? ? int width = extendToCanvas ? (int)_width : frameWidth;

? ? ? ? int height = extendToCanvas ? (int)_height : frameHeight;

? ? ? ? if (width > _width || height > _height) return NULL;


? ? ? ? const uint8_t *payload = iter.fragment.bytes;

? ? ? ? size_t payloadSize = iter.fragment.size;


? ? ? ? WebPDecoderConfig config;

? ? ? ? if (!WebPInitDecoderConfig(&config)) {

? ? ? ? ? ? WebPDemuxReleaseIterator(&iter);

? ? ? ? ? ? return NULL;

? ? ? ? }

? ? ? ? if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) {

? ? ? ? ? ? WebPDemuxReleaseIterator(&iter);

? ? ? ? ? ? return NULL;

? ? ? ? }


? ? ? ? size_t bitsPerComponent = 8;

? ? ? ? size_t bitsPerPixel = 32;

? ? ? ? size_t bytesPerRow = YYImageByteAlign(bitsPerPixel / 8 * width, 32);

? ? ? ? size_t length = bytesPerRow * height;

? ? ? ? CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; //bgrA


? ? ? ? void *pixels = calloc(1, length);

? ? ? ? if (!pixels) {

? ? ? ? ? ? WebPDemuxReleaseIterator(&iter);

? ? ? ? ? ? return NULL;

? ? ? ? }


? ? ? ? config.output.colorspace = MODE_bgrA;

? ? ? ? config.output.is_external_memory = 1;

? ? ? ? config.output.u.RGBA.rgba = pixels;

? ? ? ? config.output.u.RGBA.stride = (int)bytesPerRow;

? ? ? ? config.output.u.RGBA.size = length;

? ? ? ? VP8StatusCode result = WebPDecode(payload, payloadSize, &config); // decode

? ? ? ? if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) {

? ? ? ? ? ? WebPDemuxReleaseIterator(&iter);

? ? ? ? ? ? free(pixels);

? ? ? ? ? ? return NULL;

? ? ? ? }

? ? ? ? WebPDemuxReleaseIterator(&iter);


? ? ? ? if (extendToCanvas && (iter.x_offset != 0 || iter.y_offset != 0)) {

? ? ? ? ? ? void *tmp = calloc(1, length);

? ? ? ? ? ? if (tmp) {

? ? ? ? ? ? ? ? vImage_Buffer src = {pixels, height, width, bytesPerRow};

? ? ? ? ? ? ? ? vImage_Buffer dest = {tmp, height, width, bytesPerRow};

? ? ? ? ? ? ? ? vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset};

? ? ? ? ? ? ? ? uint8_t backColor[4] = {0};

? ? ? ? ? ? ? ? vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill);

? ? ? ? ? ? ? ? if (error == kvImageNoError) {

? ? ? ? ? ? ? ? ? ? memcpy(pixels, tmp, length);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? free(tmp);

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? CGDataProviderRef provider = CGDataProviderCreateWithData(pixels, pixels, length, YYCGDataProviderReleaseDataCallback);

? ? ? ? if (!provider) {

? ? ? ? ? ? free(pixels);

? ? ? ? ? ? return NULL;

? ? ? ? }

? ? ? ? pixels = NULL; // hold by provider


? ? ? ? CGImageRef image = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, YYCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault);

? ? ? ? CFRelease(provider);

? ? ? ? if (decoded) *decoded = YES;

? ? ? ? return image;

? ? }

#endif


? ? return NULL;

}

從這個函數(shù)的名字看出來腮敌,就是獲取一張沒有渲染的image

1獲取_YYImageDecoderFrame

2.當_source 不是nil,獲取image,這種圖片是緩存的俏扩。

《1》extendToCanvas 是yes糜工,將圖片解析成bitmap。

3當_apngSource 不是nil 录淡,獲取image捌木。這是真的apng來說的

4.當_webpSource 不是nil,獲取image嫉戚,這是真對webp來說的

這里主要的介紹完畢了刨裆。圖片的具體解析不做介紹澈圈。不懂可以留言,共同研究帆啃,這里還有個YYImageEncoder 就是逆運算瞬女,暫時不做介紹。

做個圖吧努潘》掏担看看組織結(jié)構(gòu)


類似這個結(jié)構(gòu)



YYFrameImage?

這個類記錄動畫執(zhí)行的相關(guān)操作實現(xiàn)了協(xié)議YYAnimatedImage

結(jié)構(gòu)


初始化

四個初始化方法

- (nullable instancetype)initWithImagePaths:(NSArray*)paths oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount;

- (nullable instancetype)initWithImagePaths:(NSArray*)paths frameDurations:(NSArray*)frameDurations loopCount:(NSUInteger)loopCount;

- (nullable instancetype)initWithImageDataArray:(NSArray*)dataArray oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount;

- (nullable instancetype)initWithImageDataArray:(NSArray *)dataArray ?frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount;

這里只看最后一個初始化方法就行了,其他都一樣疯坤。

- (instancetype)initWithImageDataArray:(NSArray *)dataArray frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount {

? ? if (dataArray.count == 0) return nil;

? ? if (dataArray.count != frameDurations.count) return nil;


? ? NSData *firstData = dataArray[0];

? ? CGFloat scale = [UIScreen mainScreen].scale;

? ? UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] imageByDecoded];

? ? self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp];

? ? if (!self) return nil;

? ? long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage);

? ? _oneFrameBytes = (NSUInteger)frameByte;

? ? _imageDatas = dataArray.copy;

? ? _frameDurations = frameDurations.copy;

? ? _loopCount = loopCount;


? ? return self;

}

就是給相關(guān)參數(shù)做檢驗报慕,賦值。

接下來看看協(xié)議YYAnimatedImage 協(xié)議的幾個實現(xiàn)压怠。

- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {

? ? if (_imagePaths) {

? ? ? ? if (index >= _imagePaths.count) return nil;

? ? ? ? NSString *path = _imagePaths[index];

? ? ? ? CGFloat scale = [path pathScale];

? ? ? ? NSData *data = [NSData dataWithContentsOfFile:path];

? ? ? ? return [[UIImage imageWithData:data scale:scale] imageByDecoded];

? ? } else if (_imageDatas) {

? ? ? ? if (index >= _imageDatas.count) return nil;

? ? ? ? NSData *data = _imageDatas[index];

? ? ? ? return [[UIImage imageWithData:data scale:[UIScreen mainScreen].scale] imageByDecoded];

? ? } else {

? ? ? ? return index == 0 ? self : nil;

? ? }

}

這里獲取的圖片是臨時生成的眠冈,我認為這里要是講究效率,空間換時間刑峡,將原始數(shù)據(jù)轉(zhuǎn)換到image數(shù)組中洋闽,直接獲取更快。(但是創(chuàng)建的時候轉(zhuǎn)換會慢寫)我猜測這里獲取圖片應(yīng)該是在異步線程里突梦。這個類是關(guān)聯(lián)YYAnimatedImageView 類的诫舅。我們看看YYAnimatedImageView 類咋實現(xiàn)的


YYAnimatedImageView

這個專門做動畫的類


初始化

- (instancetype)init {

? ? self = [super init];

? ? _runloopMode = NSRunLoopCommonModes;

? ? _autoPlayAnimatedImage = YES;

? ? return self;

}

- (instancetype)initWithFrame:(CGRect)frame {

? ? self = [super initWithFrame:frame];

? ? _runloopMode = NSRunLoopCommonModes;

? ? _autoPlayAnimatedImage = YES;

? ? return self;

}

簡單初始化,

- (instancetype)initWithImage:(UIImage *)image {

? ? self = [super init];

? ? _runloopMode = NSRunLoopCommonModes;

? ? _autoPlayAnimatedImage = YES;

? ? self.frame = (CGRect) {CGPointZero, image.size };

? ? self.image = image;

? ? return self;

}

- (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage {

? ? self = [super init];

? ? _runloopMode = NSRunLoopCommonModes;

? ? _autoPlayAnimatedImage = YES;

? ? CGSize size = image ? image.size : highlightedImage.size;

? ? self.frame = (CGRect) {CGPointZero, size };

? ? self.image = image;

? ? self.highlightedImage = highlightedImage;

? ? return self;

}

這兩個是帶image的初始化

這里要注意宫患,該類重寫了好多方法

self.image=image會調(diào)用setImage: 方法

重寫的四個image方法

- (void)setImage:(UIImage *)image {

? ? if (self.image == image) return;

? ? [self setImage:image withType:YYAnimatedImageTypeImage];

}

- (void)setHighlightedImage:(UIImage *)highlightedImage {

? ? if (self.highlightedImage == highlightedImage) return;

? ? [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];

}


- (void)setAnimationImages:(NSArray *)animationImages {

? ? if (self.animationImages == animationImages) return;

? ? [self setImage:animationImages withType:YYAnimatedImageTypeImages];

}

- (void)setHighlightedAnimationImages:(NSArray *)highlightedAnimationImages {

? ? if (self.highlightedAnimationImages == highlightedAnimationImages) return;

? ? [self setImage:highlightedAnimationImages withType:YYAnimatedImageTypeHighlightedImages];

}

設(shè)置圖片的image才是動畫的開始

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

}

根據(jù)type類型設(shè)置super的image

這里根據(jù)_link 調(diào)用-resetAnimated方法刊懈。先不管_link 看下面的這個函數(shù)

- (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*) newVisibleImage).animatedImageFrameCount; if (newImageFrameCount > 1) { hasContentsRect = [((UIImage*) 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) { CGRect rect = [((UIImage *) 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];

}

1.獲取image動畫類型

2.獲取image

3.要是image是UIImage類型不是數(shù)組,并且實現(xiàn)了協(xié)議YYAnimatedImage 就獲取下執(zhí)行動畫的幀數(shù)娃闲,要是執(zhí)行幀數(shù)大于1虚汛,檢測是否響應(yīng)@selector(animatedImageContentsRectAtIndex:方法

4要是檢測當前沒有實現(xiàn)@selector(animatedImageContentsRectAtIndex:方法,而以前的image實現(xiàn)了@selector(animatedImageContentsRectAtIndex:方法皇帮,那么將layer的contentRect設(shè)置為nil卷哩。其實這個操作就是刪除以前的layer圖片。

5 調(diào)用@selector(animatedImageContentsRectAtIndex: 方法獲取rect

6.要是動畫的幀數(shù)大于1 你属拾、将谊,那么沖設(shè)動畫

7.設(shè)置layer重新繪制

8 .開啟動畫

這里有個 [self resetAnimated];方法

看看實現(xiàn)

- (void)resetAnimated {

? ? if (!_link) {

? ? ? ? _lock = dispatch_semaphore_create(1);

? ? ? ? _buffer = [NSMutableDictionary new];

? ? ? ? _requestQueue = [[NSOperationQueue alloc] init];

? ? ? ? _requestQueue.maxConcurrentOperationCount = 1;

? ? ? ? _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy 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;

}

這里初始化了好多參數(shù)。關(guān)鍵參數(shù)有_requestQueue 渐白,_link. 這里的_link 是類型CADisplayLink(?為啥用這個計時器而不用timer)

這里_buffer 進行重置尊浓,重置_curIndex

- (void)didMoved {

? ? if (self.autoPlayAnimatedImage) {

? ? ? ? if(self.superview && self.window) {

? ? ? ? ? ? [self startAnimating];

? ? ? ? } else {

? ? ? ? ? ? [self stopAnimating];

? ? ? ? }

? ? }

}

檢測是否要進行動畫

我們看看執(zhí)行動畫

- (void)startAnimating {

? ? YYAnimatedImageType type = [self currentImageType];

? ? if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) {

? ? ? ? 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;

? ? ? ? }

? ? }

}

1當動畫是數(shù)組的時候,運用UiimageView 的自帶動畫系統(tǒng)進行動畫

2.當動畫不是image的時候纯衍,檢測要是計時器是暫停的栋齿,那么就開啟計時器,標記動畫執(zhí)行執(zhí)行。

我們看看計時器執(zhí)行的方法-step:

- (void)step:(CADisplayLink *)link { UIImage *image = _curAnimatedImage;

? ? NSMutableDictionary *buffer = _buffer;

? ? UIImage *bufferedImage = nil;

? ? 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) {

? ? ? ? _time += link.duration;

? ? ? ? delay = [image animatedImageDurationAtIndex:_curIndex];

? ? ? ? if (_time < delay) return;

? ? ? ? _time -= delay;

? ? ? ? if (nextIndex == 0) {

? ? ? ? ? ? _curLoop++;

? ? ? ? ? ? if (_curLoop >= _totalLoop && _totalLoop != 0) {

? ? ? ? ? ? ? ? _loopEnd = YES;

? ? ? ? ? ? ? ? [self stopAnimating];

? ? ? ? ? ? ? ? [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep

? ? ? ? ? ? ? ? return; // stop at last frame

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? 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)];

? ? ? ? ? ? }

? ? ? ? ? ? [self willChangeValueForKey:@"currentAnimatedImageIndex"];

? ? ? ? ? ? _curIndex = nextIndex;

? ? ? ? ? ? [self didChangeValueForKey:@"currentAnimatedImageIndex"];

? ? ? ? ? ? _curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;

? ? ? ? ? ? if (_curImageHasContentsRect) {

? ? ? ? ? ? ? ? _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) {

? ? ? ? [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 *operation = [_YYAnimatedImageViewFetchOperation new];

? ? ? ? operation.view = self;

? ? ? ? operation.nextIndex = nextIndex;

? ? ? ? operation.curImage = image;

? ? ? ? [_requestQueue addOperation:operation];

? ? }

}

1獲取image

2.獲取buffer

3獲取下一幀index

4.要是循環(huán)結(jié)束就停止動畫

5 _timer 累加到超過delay瓦堵。

6.當下一幀是0說明循環(huán)一圈基协,檢查是否結(jié)束循環(huán)。結(jié)束就停止動畫谷丸,并且重新繪制layer的contents

7.檢查buffer中是否有下一幀堡掏。沒有標記_bufferMiss =YES,有刨疼,進行相關(guān)操作

8.在operation 執(zhí)行操作

我們看看再這個_YYAnimatedImageViewFetchOperation 線程中執(zhí)行啥操作

- (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++) {

? ? ? ? @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.imageByDecoded;

? ? ? ? ? ? ? ? if ([self isCancelled]) break;

? ? ? ? ? ? ? ? LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);

? ? ? ? ? ? ? ? view = nil;

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

這個是main函數(shù)泉唁。同步執(zhí)行。

1獲取YYAnimatedImageView

2.增加buffer數(shù)量

3.檢查bufferCount

4.將buffer 數(shù)據(jù)填入

- (void)calcMaxBufferCount {

? ? int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame;

? ? if (bytes == 0) bytes = 1024;


? ? int64_t total = [UIDevice currentDevice].memoryTotal;

? ? int64_t free = [UIDevice currentDevice].memoryFree;

? ? int64_t max = MIN(total * 0.2, free * 0.6);

? ? max = MAX(max, BUFFER_SIZE);

? ? if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;

? ? double maxBufferCount = (double)max / (double)bytes;

? ? maxBufferCount = YY_CLAMP(maxBufferCount, 1, 512);

? ? _maxBufferCount = maxBufferCount;

}

這個函數(shù)計算最大buffer 可以保存圖片的數(shù)量揩慕。不過這里這樣寫不知道有啥好處亭畜。

邏輯比較混亂。做個圖吧


這個設(shè)計的很巧妙迎卤,獲取image是從buffer中獲取拴鸵,所有image的具體編解碼都是異步線程中加載到buffer中。



YYSpriteSheetImage

這個函數(shù)與YYFrameImage 都是UIimage 的擴展蜗搔,配合YYAnimatedImageView

- (instancetype)initWithSpriteSheetImage:(UIImage *)image

? ? ? ? ? ? ? ? ? ? ? ? ? ? contentRects:(NSArray *)contentRects

? ? ? ? ? ? ? ? ? ? ? ? ? frameDurations:(NSArray *)frameDurations

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? loopCount:(NSUInteger)loopCount {

? ? if (!image.CGImage) return nil;

? ? if (contentRects.count < 1 || frameDurations.count < 1) return nil;

? ? if (contentRects.count != frameDurations.count) return nil;

? ? self = [super initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];

? ? if (!self) return nil;

? ? _contentRects = contentRects.copy;

? ? _frameDurations = frameDurations.copy;

? ? _loopCount = loopCount;

? ? return self;

}

在這個函數(shù)有個變量_contentRects ?記錄的是一張圖片上的圖片的位置信息劲藐。

這里實現(xiàn)了這個方法

- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index {

? ? if (index >= _contentRects.count) return CGRectZero;

? ? return ((NSValue *)_contentRects[index]).CGRectValue;

}

理解如圖

外界傳入的image 是 藍色圖

_contentRects 記錄的是每個紅色圖起始點和大小。

_frameDurations 記錄的是每一幀動畫執(zhí)行的間隔時間樟凄。

動畫執(zhí)行其實就是紅色圖片的一幀一幀的顯示而已

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末聘芜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缝龄,更是在濱河造成了極大的恐慌汰现,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叔壤,死亡現(xiàn)場離奇詭異瞎饲,居然都是意外死亡,警方通過查閱死者的電腦和手機炼绘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門嗅战,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人俺亮,你說我怎么就攤上這事仗哨。” “怎么了铅辞?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萨醒。 經(jīng)常有香客問我斟珊,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任囤踩,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弯汰。我一直安慰自己莱找,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布勤庐。 她就那樣靜靜地躺著示惊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愉镰。 梳的紋絲不亂的頭發(fā)上米罚,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音丈探,去河邊找鬼录择。 笑死,一個胖子當著我的面吹牛碗降,可吹牛的內(nèi)容都是我干的隘竭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼讼渊,長吁一口氣:“原來是場噩夢啊……” “哼动看!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起精偿,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤弧圆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笔咽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔预,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年叶组,在試婚紗的時候發(fā)現(xiàn)自己被綠了拯田。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡甩十,死狀恐怖船庇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侣监,我是刑警寧澤鸭轮,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站橄霉,受9級特大地震影響窃爷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一按厘、第九天 我趴在偏房一處隱蔽的房頂上張望医吊。 院中可真熱鬧,春花似錦逮京、人聲如沸卿堂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽草描。三九已至,卻和暖如春漓藕,著一層夾襖步出監(jiān)牢的瞬間陶珠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工享钞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揍诽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓栗竖,卻偏偏與公主長得像暑脆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子狐肢,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

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