美文网首页SDWebImage
SDWebImageDecoder

SDWebImageDecoder

作者: 认不出我来 | 来源:发表于2018-06-08 12:15 被阅读66次

    这个类其实是UIImage的一个分类,主要用来解码UIImage,那么为什么要对UIImage进行解码呢?难道不能直接使用吗?

    其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程立即进行图片的解码工作。这一过程就是把image解码成可供控件直接使用的位图。

    当在主线程调用了大量的imageNamed:方法后,就会产生卡顿了。为了解决这个问题我们有两种比较简单的处理方法:

    我们不使用imageNamed:加载图片,使用其他的方法,比如imageWithContentsOfFile:
    从网络上下载回来的图片也不能直接在UI控件上显示,所以sdwebimage选择自己解码图片,而且sd将解码操作基本都放在了子线程来执行。

    static const size_t kBytesPerPixel = 4; //每个像素占用四个字节
    static const size_t kBitsPerComponent = 8;

    这个方法主要是单纯的解码图片,方便GPU直接渲染。

    + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
        if (![UIImage shouldDecodeImage:image]) {
            return image;
        }
        
        // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
        // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
        @autoreleasepool{
            
            CGImageRef imageRef = image.CGImage;
            CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
            
            size_t width = CGImageGetWidth(imageRef);
            size_t height = CGImageGetHeight(imageRef);
            size_t bytesPerRow = kBytesPerPixel * width;
    
            // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
            // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
            // to create bitmap graphics contexts without alpha info.
            CGContextRef context = CGBitmapContextCreate(NULL,
                                                         width,
                                                         height,
                                                         kBitsPerComponent,
                                                         bytesPerRow,
                                                         colorspaceRef,
                                                         kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
            if (context == NULL) {
                return image;
            }
            
            // Draw the image into the context and retrieve the new bitmap image without alpha
            CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
            CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
            UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                             scale:image.scale
                                                       orientation:image.imageOrientation];
            
            CGContextRelease(context);
            CGImageRelease(imageRefWithoutAlpha);
            
            return imageWithoutAlpha;
        }
    }
    
    static const CGFloat kDestImageSizeMB = 60.0f;   //大图片scaleDown后制定的最大开销
    
    static const CGFloat kSourceImageTileSizeMB = 20.0f; // 图片分块后每个块大小
    
    static const CGFloat kBytesPerMB = 1024.0f * 1024.0f; // 1MB包含多少个字节
    static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel; // 1MB大小能包含多少像素
    static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB; // 图片scaleDown后的最大像素数
    static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB; // 图片scaleDown过程中每个块的像素数量
    

    将图片解码并进行scaleDown的方法:
    主要处理流程:
    1.先算出imageScale,也就是scaleDown的比例;
    2.开辟内存空间,这个内存空间一定会小于60MB;
    3.将原图片分成若干块,然后分别渲染到目标画布的对应块上。

    + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
        if (![UIImage shouldDecodeImage:image]) {
            return image;
        }
        
        if (![UIImage shouldScaleDownImage:image]) {
            return [UIImage decodedImageWithImage:image];
        }
        
        CGContextRef destContext;
        
        // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
        // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
        @autoreleasepool {
            CGImageRef sourceImageRef = image.CGImage;
            
            CGSize sourceResolution = CGSizeZero;
            sourceResolution.width = CGImageGetWidth(sourceImageRef);
            sourceResolution.height = CGImageGetHeight(sourceImageRef);
            float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
            // Determine the scale ratio to apply to the input image
            // that results in an output image of the defined size.
            // see kDestImageSizeMB, and how it relates to destTotalPixels.
            float imageScale = kDestTotalPixels / sourceTotalPixels;
            CGSize destResolution = CGSizeZero;
            destResolution.width = (int)(sourceResolution.width*imageScale);
            destResolution.height = (int)(sourceResolution.height*imageScale);
            
            // current color space
            CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
            
            size_t bytesPerRow = kBytesPerPixel * destResolution.width;
            
            // Allocate enough pixel data to hold the output image.
            void* destBitmapData = malloc( bytesPerRow * destResolution.height );
            if (destBitmapData == NULL) {
                return image;
            }
            
            // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
            // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
            // to create bitmap graphics contexts without alpha info.
            destContext = CGBitmapContextCreate(destBitmapData,
                                                destResolution.width,
                                                destResolution.height,
                                                kBitsPerComponent,
                                                bytesPerRow,
                                                colorspaceRef,
                                                kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
            
            if (destContext == NULL) {
                free(destBitmapData);
                return image;
            }
            CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
            
            // Now define the size of the rectangle to be used for the
            // incremental blits from the input image to the output image.
            // we use a source tile width equal to the width of the source
            // image due to the way that iOS retrieves image data from disk.
            // iOS must decode an image from disk in full width 'bands', even
            // if current graphics context is clipped to a subrect within that
            // band. Therefore we fully utilize all of the pixel data that results
            // from a decoding opertion by achnoring our tile size to the full
            // width of the input image.
            CGRect sourceTile = CGRectZero;
            sourceTile.size.width = sourceResolution.width;
            // The source tile height is dynamic. Since we specified the size
            // of the source tile in MB, see how many rows of pixels high it
            // can be given the input image width.
            sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
            sourceTile.origin.x = 0.0f;
            // The output tile is the same proportions as the input tile, but
            // scaled to image scale.
            CGRect destTile;
            destTile.size.width = destResolution.width;
            destTile.size.height = sourceTile.size.height * imageScale;
            destTile.origin.x = 0.0f;
            // The source seem overlap is proportionate to the destination seem overlap.
            // this is the amount of pixels to overlap each tile as we assemble the ouput image.
            float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
            CGImageRef sourceTileImageRef;
            // calculate the number of read/write operations required to assemble the
            // output image.
            int iterations = (int)( sourceResolution.height / sourceTile.size.height );
            // If tile height doesn't divide the image height evenly, add another iteration
            // to account for the remaining pixels.
            int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
            if(remainder) {
                iterations++;
            }
            // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
            float sourceTileHeightMinusOverlap = sourceTile.size.height;
            sourceTile.size.height += sourceSeemOverlap;
            destTile.size.height += kDestSeemOverlap;
            for( int y = 0; y < iterations; ++y ) {
                @autoreleasepool {
                    sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
                    destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
                    sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
                    if( y == iterations - 1 && remainder ) {
                        float dify = destTile.size.height;
                        destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
                        dify -= destTile.size.height;
                        destTile.origin.y += dify;
                    }
                    CGContextDrawImage( destContext, destTile, sourceTileImageRef );
                    CGImageRelease( sourceTileImageRef );
                }
            }
            
            CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
            CGContextRelease(destContext);
            if (destImageRef == NULL) {
                return image;
            }
            UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
            CGImageRelease(destImageRef);
            if (destImage == nil) {
                return image;
            }
            return destImage;
        }
    }
    

    是否需要将图片解码:
    1.不解码动图
    2.不解码带有alpha的图片

    + (BOOL)shouldDecodeImage:(nullable UIImage *)image {
        // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
        if (image == nil) {
            return NO;
        }
    
        // do not decode animated images
        if (image.images != nil) {
            return NO;
        }
        
        CGImageRef imageRef = image.CGImage;
        
        CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
        BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                         alpha == kCGImageAlphaLast ||
                         alpha == kCGImageAlphaPremultipliedFirst ||
                         alpha == kCGImageAlphaPremultipliedLast);
        // do not decode images with alpha
        if (anyAlpha) {
            return NO;
        }
        
        return YES;
    }
    

    是否需要将图片进行scaleDown,如果原图片总的像素数的大小 > kDestTotalPixels就需要scaleDown

    + (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
        BOOL shouldScaleDown = YES;
            
        CGImageRef sourceImageRef = image.CGImage;
        CGSize sourceResolution = CGSizeZero;
        sourceResolution.width = CGImageGetWidth(sourceImageRef);
        sourceResolution.height = CGImageGetHeight(sourceImageRef);
        float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
        float imageScale = kDestTotalPixels / sourceTotalPixels;
        if (imageScale < 1) {
            shouldScaleDown = YES;
        } else {
            shouldScaleDown = NO;
        }
        
        return shouldScaleDown;
    }
    

    获取图片的颜色空间,什么是图片的颜色空间?请自行百度,

    + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
        // current
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
        
        BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                      imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                      imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                      imageColorSpaceModel == kCGColorSpaceModelIndexed);
        if (unsupportedColorSpace) {
            colorspaceRef = CGColorSpaceCreateDeviceRGB();
            CFAutorelease(colorspaceRef);
        }
        return colorspaceRef;
    }
    

    相关文章

      网友评论

        本文标题:SDWebImageDecoder

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