YYImage

作者: Code_人生 | 来源:发表于2019-09-30 14:01 被阅读0次

    YYImage

    一、源码分析

    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");
            });
        });
    

    相关文章

      网友评论

          本文标题:YYImage

          本文链接:https://www.haomeiwen.com/subject/twqbyctx.html