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