一、源码分析
1、来到YYImage.m
的+ (nullable YYImage *)imageNamed:(NSString *)name;
+ (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];
}
2、点击initWithData: scale:
- 2.1、
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
创建了 ImageSource!!! ,_YYImageDecoderFrame - 2.2、
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
解码的操作
- (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 {
// 元数据信息
// 创建了 ImageSource!!! ,_YYImageDecoderFrame
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
// 解码的操作 -> imageBuffer
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;
}
2.1、点击decoderWithData
+ (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;
}
- (BOOL)updateData:(NSData *)data final:(BOOL)final {
BOOL result = NO;
//递归锁!!!
//渐进式解码(渐进式加载)
//被同一个线程反复进入 (lockCount 同一个线程获取锁的次数)
pthread_mutex_lock(&_lock);
result = [self _updateData:data final:final];
pthread_mutex_unlock(&_lock);
return result;
}
- (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;
}
- (void)_updateSource {
switch (_type) {
case YYImageTypeWebP: {
[self _updateSourceWebP];
} break;
case YYImageTypePNG: {
[self _updateSourceAPNG];
} break;
default: {
[self _updateSourceImageIO];
} break;
}
}
- (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);
}
2.2、点击frameAtIndex: decodeForDisplay:
- (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) {
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;
}
- (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;
}
二、使用
//利用 CPU 操作!!!
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//线程安全的!!!
YYImage *image = [YYImage imageNamed:@"mew_baseline"];//不用放在主线程,YYImage线程安全的
dispatch_async(dispatch_get_main_queue(), ^{
//imageView 的显示当中会不会出现隐士事务的提交
_imageView.image = image;
NSLog(@"imageview");
});
});

网友评论