SDWebImage
使用了不少工具類來對圖片的處理。好比獲取圖片類型、圖片放大縮小、GIF圖片處理、圖片解壓縮處理等。接下來我就要分析下面這幾個工具類的實現。html
這個類提供了一個類方法sd_imageFormatForImageData
。經過這個方法傳入圖片的NSData數據,而後返回圖片類型。圖片類型經過SDImageFormat
來定義。git
/** 不一樣圖片類型的枚舉 - SDImageFormatUndefined: 未知 - SDImageFormatJPEG: JPG - SDImageFormatPNG: PNG - SDImageFormatGIF: GIF - SDImageFormatTIFF: TIFF - SDImageFormatWebP: WEBP */ typedef NS_ENUM(NSInteger, SDImageFormat) { SDImageFormatUndefined = -1, SDImageFormatJPEG = 0, SDImageFormatPNG, SDImageFormatGIF, SDImageFormatTIFF, SDImageFormatWebP }; /** 根據圖片NSData獲取圖片的類型 @param data NSData數據 @return 圖片數據類型 */ + (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data { if (!data) { return SDImageFormatUndefined; } uint8_t c; //獲取圖片數據的第一個字節數據 [data getBytes:&c length:1]; //根據字母的ASC碼比較 switch (c) { case 0xFF: return SDImageFormatJPEG; case 0x89: return SDImageFormatPNG; case 0x47: return SDImageFormatGIF; case 0x49: case 0x4D: return SDImageFormatTIFF; case 0x52: // R as RIFF for WEBP if (data.length < 12) { return SDImageFormatUndefined; } NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return SDImageFormatWebP; } } return SDImageFormatUndefined; }
SDWebImageCompat
就提供一個全局方法SDScaledImageForKey
。這個方法根據原始圖片繪製一張放大或者縮小的圖片。github
/** 給定一張圖片,經過scale屬性返回一個放大的圖片。 @param key 圖片名稱 @param image 資源圖片 @return 處理之後的圖片 */ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) { //異常處理 if (!image) { return nil; } #if SD_MAC return image; #elif SD_UIKIT || SD_WATCH //若是是動態圖片,好比GIF圖片,則迭代處理 if ((image.images).count > 0) { NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array]; //迭代處理每一張圖片 for (UIImage *tempImage in image.images) { [scaledImages addObject:SDScaledImageForKey(key, tempImage)]; } //把處理結束的圖片再合成一張動態圖片 return [UIImage animatedImageWithImages:scaledImages duration:image.duration]; } else {//非動態圖片 #if SD_WATCH if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)]) { #elif SD_UIKIT if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { #endif CGFloat scale = 1; // 「@2x.png」的長度爲7,因此此處添加了這個判斷,很巧妙 if (key.length >= 8) { NSRange range = [key rangeOfString:@"@2x."]; if (range.location != NSNotFound) { scale = 2.0; } range = [key rangeOfString:@"@3x."]; if (range.location != NSNotFound) { scale = 3.0; } } //返回對應分辨率下面的圖片 UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; image = scaledImage; } return image; } #endif }
UIImage+MultiFormat
分類實現了NSData與UIImage對象之間的相互轉換。而且是根據圖片類型作轉換。好比GIF的UIImage轉換爲GIF格式的NSData。
而且還有UIImage的Orientation和alpha的處理。chrome
/** 根據image的data數據。生成對應的image對象 @param data 圖片的數據 @return image對象 */ + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { if (!data) { return nil; } UIImage *image; //獲取data的圖片類型,png,gif,jpg SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data]; if (imageFormat == SDImageFormatGIF) { //gif處理:返回一張只包含數據第一張image 的gif圖片 image = [UIImage sd_animatedGIFWithData:data]; } #ifdef SD_WEBP else if (imageFormat == SDImageFormatWebP) { image = [UIImage sd_imageWithWebPData:data]; } #endif else { image = [[UIImage alloc] initWithData:data]; #if SD_UIKIT || SD_WATCH //獲取方向 UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data]; //若是不是向上的,還須要再次生成圖片 if (orientation != UIImageOrientationUp) { image = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:orientation]; } #endif } return image; } #if SD_UIKIT || SD_WATCH /** 根據圖片數據獲取圖片的方向 @param imageData 圖片數據 @return 方向 */ +(UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData { //默認是向上的 UIImageOrientation result = UIImageOrientationUp; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); if (imageSource) { //獲取圖片的屬性列表 CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); if (properties) { CFTypeRef val; int exifOrientation; //獲取圖片方向 val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) { CFNumberGetValue(val, kCFNumberIntType, &exifOrientation); result = [self sd_exifOrientationToiOSOrientation:exifOrientation]; } // else - if it's not set it remains at up CFRelease((CFTypeRef) properties); } else { //NSLog(@"NO PROPERTIES, FAIL"); } CFRelease(imageSource); } return result; } /** 根據不一樣的值返回不一樣的圖片方向 @param exifOrientation 輸入值 @return 圖片的方向 */ + (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation { UIImageOrientation orientation = UIImageOrientationUp; switch (exifOrientation) { case 1: orientation = UIImageOrientationUp; break; case 3: orientation = UIImageOrientationDown; break; case 8: orientation = UIImageOrientationLeft; break; case 6: orientation = UIImageOrientationRight; break; case 2: orientation = UIImageOrientationUpMirrored; break; case 4: orientation = UIImageOrientationDownMirrored; break; case 5: orientation = UIImageOrientationLeftMirrored; break; case 7: orientation = UIImageOrientationRightMirrored; break; default: break; } return orientation; } #endif - (nullable NSData *)sd_imageData { return [self sd_imageDataAsFormat:SDImageFormatUndefined]; } /** 根據指定的圖片類型,把image對象轉換爲對應格式的data @param imageFormat 指定的image格式 @return 返回data對象 */ - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat { NSData *imageData = nil; if (self) { #if SD_UIKIT || SD_WATCH int alphaInfo = CGImageGetAlphaInfo(self.CGImage); //是否有透明度 BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); //只有png圖片有alpha屬性 BOOL usePNG = hasAlpha; // the imageFormat param has priority here. But if the format is undefined, we relly on the alpha channel //是不是PNG類型的圖片 if (imageFormat != SDImageFormatUndefined) { usePNG = (imageFormat == SDImageFormatPNG); } //根據不一樣的圖片類型獲取到對應的圖片data if (usePNG) { imageData = UIImagePNGRepresentation(self); } else { imageData = UIImageJPEGRepresentation(self, (CGFloat)1.0); } #else NSBitmapImageFileType imageFileType = NSJPEGFileType; if (imageFormat == SDImageFormatGIF) { imageFileType = NSGIFFileType; } else if (imageFormat == SDImageFormatPNG) { imageFileType = NSPNGFileType; } imageData = [NSBitmapImageRep representationOfImageRepsInArray:self.representations usingType:imageFileType properties:@{}]; #endif } return imageData; }
UIImage+GIF
實現了對GIF圖片的NSData的處理。而且處理方法就是取出GIF圖片的第一張UIImage來顯示。若是真的要顯示動態圖片的話,咱們須要使用FLAnimatedImageView
來顯示。app
/** 根據gif圖片的data生成對應的gif的UIImage對象。並且只會取GIF圖片的第一張UIImage。 @param data gif圖片的data對象 @return 生成的image對象。這裏只獲取gif圖片的第一張圖像,若是要實現gif完整圖像,使用FLAnimatedImageView */ + (UIImage *)sd_animatedGIFWithData:(NSData *)data { if (!data) { return nil; } CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); //獲取GIF圖片包含的UIImage數量 size_t count = CGImageSourceGetCount(source); UIImage *staticImage; //若是隻有一張UIImage if (count <= 1) { staticImage = [[UIImage alloc] initWithData:data]; } else { #if SD_WATCH CGFloat scale = 1; scale = [WKInterfaceDevice currentDevice].screenScale; #elif SD_UIKIT CGFloat scale = 1; scale = [UIScreen mainScreen].scale; #endif //獲取第一張UIImage對象 CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL); #if SD_UIKIT || SD_WATCH //獲取gif圖片的第一張圖片 UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp]; //用第一張圖片生成一個新的gif圖片 staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f]; #elif SD_MAC staticImage = [[UIImage alloc] initWithCGImage:CGImage size:NSZeroSize]; #endif CGImageRelease(CGImage); } CFRelease(source); return staticImage; } /** 判斷一張圖片是否是GIF圖片 @return bool值 */ - (BOOL)isGIF { return (self.images != nil); }
經過這個類實現圖片的解壓縮操做。對於太大的圖片,先按照必定比例縮小圖片而後再解壓縮。工具
#if SD_UIKIT || SD_WATCH //每一個像素佔用的字節數 static const size_t kBytesPerPixel = 4; //色彩空間佔用的字節數 static const size_t kBitsPerComponent = 8; /** 解壓縮圖片 @param image UIImage對象 @return 返回解壓縮之後的圖片 */ + (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); //建立一個麼有alpha通道的圖片 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; static const CGFloat kSourceImageTileSizeMB = 20.0f; static const CGFloat kBytesPerMB = 1024.0f * 1024.0f; //1MB能夠存儲多少像素 static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel; //若是像素小於這個值,則不解壓縮 static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB; static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB; static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet. /** 若是原始圖片佔用的空間太大。則按照必定的比例解壓縮。從而不讓解壓縮之後的圖片佔用的空間太大。 @param image UIImage對象 @return 返回處理結束的UIImage對象 */ + (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 //獲取原始圖片的像素空間。默認是RGB 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; } //根據各類設置建立一個上下文環境 destContext = CGBitmapContextCreate(destBitmapData, destResolution.width, destResolution.height, kBitsPerComponent, bytesPerRow, colorspaceRef, kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast); if (destContext == NULL) { free(destBitmapData); return image; } //設置目標圖片的質量 CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh); CGRect sourceTile = CGRectZero; sourceTile.size.width = sourceResolution.width; sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width ); sourceTile.origin.x = 0.0f; CGRect destTile; destTile.size.width = destResolution.width; destTile.size.height = sourceTile.size.height * imageScale; destTile.origin.x = 0.0f; float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height); CGImageRef sourceTileImageRef; int iterations = (int)( sourceResolution.height / sourceTile.size.height ); 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; } } /** imge是否可以加壓縮 @param image 圖片 @return 可否解壓縮 */ + (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; //獲取image的alpha通道。經過通道獲取圖片數據 CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef); BOOL anyAlpha = (alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast); // do not decode images with alpha //若是有alpha通道值,則不處理 if (anyAlpha) { return NO; } return YES; } /** 是否須要減小原始圖片的大小 @param image UIImage對象 @return 是否支持scale */ + (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; } /** 獲取圖片的色彩空間 @param imageRef 圖片 @return 色彩空間 */ + (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; } #elif SD_MAC + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image { return image; } + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image { return image; }
下面是幾個分類工具的使用。ui
/** 根據圖片數據獲取圖片類型 */ - (IBAction)getImageType:(id)sender { NSData *imageData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"rock.gif" ofType:nil]]; SDImageFormat formate = [NSData sd_imageFormatForImageData:imageData]; NSString *message = [NSString stringWithFormat:@"%d",formate]; showMessage(message,self); } /** 獲取一張圖片對應的兩倍或者三倍屏幕對應的圖片 */ - (IBAction)getScaleImage:(id)sender { UIImage *sourceImage = [UIImage imageNamed:@"2.png"]; UIImage *dis2ScaleImage = SDScaledImageForKey(@"dist@2x.png", sourceImage); UIImage *dis3ScaleImage = SDScaledImageForKey(@"dist@3x.png", sourceImage); NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; //NSLog(@"document:%@",documentPath); NSString *path1 = [documentPath stringByAppendingPathComponent:@"dist.png"]; [UIImagePNGRepresentation(sourceImage) writeToFile:path1 atomically:YES]; NSString *path2 = [documentPath stringByAppendingPathComponent:@"dist@2x.png"]; [UIImagePNGRepresentation(dis2ScaleImage) writeToFile:path2 atomically:YES]; NSString *path3 = [documentPath stringByAppendingPathComponent:@"dist@3x.png"]; [UIImagePNGRepresentation(dis3ScaleImage) writeToFile:path3 atomically:YES]; } /** 解壓縮圖片 @param sender 解壓縮圖片 */ - (IBAction)unZipImage:(id)sender { UIImage *sourceImage = [UIImage imageNamed:@"2.png"]; UIImage *distImage = [UIImage decodedAndScaledDownImageWithImage:sourceImage]; NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *path1 = [documentPath stringByAppendingPathComponent:@"distImage.png"]; [UIImagePNGRepresentation(distImage) writeToFile:path1 atomically:YES]; NSString *path2 = [documentPath stringByAppendingPathComponent:@"sourceImage.png"]; [UIImagePNGRepresentation(sourceImage) writeToFile:path2 atomically:YES]; }