1 概述 SDWebImage
使用了很多工具类来对图片的处理。比如获取图片类型、图片放大缩小、GIF图片处理、图片解压缩处理等。接下来我就要分析下面这几个工具类的实现。
2 NSData+ImageContentType分析 这个类提供了一个类方法sd_imageFormatForImageData
。通过这个方法传入图片的NSData数据,然后返回图片类型。图片类型通过SDImageFormat
来定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 typedef NS_ENUM (NSInteger , SDImageFormat) { SDImageFormatUndefined = -1 , SDImageFormatJPEG = 0 , SDImageFormatPNG, SDImageFormatGIF, SDImageFormatTIFF, SDImageFormatWebP }; + (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data { if (!data) { return SDImageFormatUndefined; } uint8_t c; [data getBytes:&c length:1 ]; switch (c) { case 0xFF : return SDImageFormatJPEG; case 0x89 : return SDImageFormatPNG; case 0x47 : return SDImageFormatGIF; case 0x49 : case 0x4D : return SDImageFormatTIFF; case 0x52 : 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; }
3 SDWebImageCompat分析 SDWebImageCompat
就提供一个全局方法SDScaledImageForKey
。这个方法根据原始图片绘制一张放大或者缩小的图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) { if (!image) { return nil ; } #if SD_MAC return image; #elif SD_UIKIT || SD_WATCH 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 ; 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的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { if (!data) { return nil ; } UIImage *image; SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data]; if (imageFormat == SDImageFormatGIF) { 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 +(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]; } CFRelease ((CFTypeRef ) properties); } else { } CFRelease (imageSource); } return result; } + (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]; } - (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); BOOL usePNG = hasAlpha; if (imageFormat != SDImageFormatUndefined) { usePNG = (imageFormat == SDImageFormatPNG); } 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; }
5 UIImage+GIF分类分析 UIImage+GIF
实现了对GIF图片的NSData的处理。并且处理方法就是取出GIF图片的第一张UIImage来显示。如果真的要显示动态图片的话,我们需要使用FLAnimatedImageView
来显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 + (UIImage *)sd_animatedGIFWithData:(NSData *)data { if (!data) { return nil ; } CGImageSourceRef source = CGImageSourceCreateWithData ((__bridge CFDataRef )data, NULL ); size_t count = CGImageSourceGetCount (source); UIImage *staticImage; 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 CGImageRef CGImage = CGImageSourceCreateImageAtIndex (source, 0 , NULL ); #if SD_UIKIT || SD_WATCH UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp ]; staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0 f]; #elif SD_MAC staticImage = [[UIImage alloc] initWithCGImage:CGImage size:NSZeroSize ]; #endif CGImageRelease (CGImage ); } CFRelease (source); return staticImage; } - (BOOL )isGIF { return (self .images != nil ); }
6 SDWebImageDecoder分析 通过这个类实现图片的解压缩操作。对于太大的图片,先按照一定比例缩小图片然后再解压缩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 #if SD_UIKIT || SD_WATCH static const size_t kBytesPerPixel = 4 ;static const size_t kBitsPerComponent = 8 ;+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image { if (![UIImage shouldDecodeImage:image]) { return image; } @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; CGContextRef context = CGBitmapContextCreate (NULL , width, height, kBitsPerComponent, bytesPerRow, colorspaceRef, kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast); if (context == NULL ) { return image; } 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.0 f;static const CGFloat kSourceImageTileSizeMB = 20.0 f;static const CGFloat kBytesPerMB = 1024.0 f * 1024.0 f;static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;static const CGFloat kDestSeemOverlap = 2.0 f; + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image { if (![UIImage shouldDecodeImage:image]) { return image; } if (![UIImage shouldScaleDownImage:image]) { return [UIImage decodedImageWithImage:image]; } CGContextRef destContext; @autoreleasepool { 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; CGSize destResolution = CGSizeZero ; destResolution.width = (int )(sourceResolution.width*imageScale); destResolution.height = (int )(sourceResolution.height*imageScale); CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef]; size_t bytesPerRow = kBytesPerPixel * destResolution.width; 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.0 f; CGRect destTile; destTile.size.width = destResolution.width; destTile.size.height = sourceTile.size.height * imageScale; destTile.origin.x = 0.0 f; 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++; } 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; } } + (BOOL )shouldDecodeImage:(nullable UIImage *)image { if (image == nil ) { return NO ; } if (image.images != nil ) { return NO ; } CGImageRef imageRef = image.CGImage; CGImageAlphaInfo alpha = CGImageGetAlphaInfo (imageRef); BOOL anyAlpha = (alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast); if (anyAlpha) { return NO ; } return YES ; } + (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 { 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; }
7 总结 下面是几个分类工具的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 - (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 ]; 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 ]; } - (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 ]; }
最后原文地址 .html),demo地址 。