// // YYImageProfileExample.m // YYKitExample // // Created by ibireme on 15/8/10. // Copyright (c) 2015 ibireme. All rights reserved. // #import "YYImageBenchmark.h" #import "YYImage.h" #import "YYBPGCoder.h" #import "UIView+YYAdd.h" #import #import #import /** Profile time cost. @param ^block code to benchmark @param ^complete code time cost (millisecond) Usage: YYBenchmark(^{ // code }, ^(double ms) { NSLog("time cost: %.2f ms",ms); }); */ static inline void YYBenchmark(void (^block)(void), void (^complete)(double ms)) { double begin, end, ms; begin = CACurrentMediaTime(); block(); end = CACurrentMediaTime(); ms = (end - begin) * 1000.0; complete(ms); } /* Enable this value and run in simulator, the image will write to desktop. Then you can view this image with preview. */ #define ENABLE_OUTPUT 0 #define IMAGE_OUTPUT_DIR @"/Users/ibireme/Desktop/image_out/" @interface NSData(YYAdd) @end @implementation NSData(YYAdd) + (NSData *)dataNamed:(NSString *)name { NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@""]; if (!path) return nil; NSData *data = [NSData dataWithContentsOfFile:path]; return data; } @end @implementation YYImageBenchmark { UIActivityIndicatorView *_indicator; UIView *_hud; NSMutableArray *_titles; NSMutableArray *_blocks; } - (void)viewDidLoad { [super viewDidLoad]; [self initHUD]; _titles = [NSMutableArray new]; _blocks = [NSMutableArray new]; self.title = @"Benchmark (See Logs in Xcode)"; [self addCell:@"ImageIO Image Decode" selector:@selector(runImageDecodeBenchmark)]; [self addCell:@"ImageIO Image Encode" selector:@selector(runImageEncodeBenchmark)]; [self addCell:@"WebP Encode and Decode (Slow)" selector:@selector(runWebPBenchmark)]; [self addCell:@"BPG Decode" selector:@selector(runBPGBenchmark)]; [self addCell:@"Animated Image Decode" selector:@selector(runAnimatedImageBenchmark)]; [self.tableView reloadData]; } - (void)addCell:(NSString *)title selector:(SEL)sel { __weak typeof(self) _self = self; void (^block)(void) = ^() { if (![_self respondsToSelector:sel]) return; [_self startHUD]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [_self performSelector:sel]; #pragma clang diagnostic pop dispatch_async(dispatch_get_main_queue(), ^{ [_self stopHUD]; }); }); }; [_titles addObject:title]; [_blocks addObject:block]; } - (void)dealloc { [_hud removeFromSuperview]; } - (void)initHUD { _hud = [UIView new]; _hud.size = CGSizeMake(130, 80); _hud.backgroundColor = [UIColor colorWithWhite:0.000 alpha:0.7]; _hud.clipsToBounds = YES; _hud.layer.cornerRadius = 5; _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; _indicator.size = CGSizeMake(50, 50); _indicator.centerX = _hud.width / 2; _indicator.centerY = _hud.height / 2 - 9; [_hud addSubview:_indicator]; UILabel *label = [UILabel new]; label.textAlignment = NSTextAlignmentCenter; label.size = CGSizeMake(_hud.width, 20); label.text = @"See logs in Xcode"; label.font = [UIFont systemFontOfSize:12]; label.textColor = [UIColor whiteColor]; label.centerX = _hud.width / 2; label.bottom = _hud.height - 8; [_hud addSubview:label]; } - (void)startHUD { UIWindow *window = [[UIApplication sharedApplication].windows firstObject]; _hud.center = CGPointMake(window.width / 2, window.height / 2); [_indicator startAnimating]; [window addSubview:_hud]; self.navigationController.view.userInteractionEnabled = NO; } - (void)stopHUD { [_indicator stopAnimating]; [_hud removeFromSuperview]; self.navigationController.view.userInteractionEnabled = YES; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:YES]; ((void (^)(void))_blocks[indexPath.row])(); } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return _titles.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"YY"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"YY"]; } cell.textLabel.text = _titles[indexPath.row]; return cell; } #pragma mark - Benchmark - (NSArray *)imageNames { return @[ @"dribbble", @"lena" ]; } - (NSArray *)imageSizes { return @[ @64, @128, @256, @512 ]; } - (NSArray *)imageSources { return @[ @"imageio", @"photoshop", @"imageoptim", @"pngcrush", @"tinypng", @"twitter", @"weibo", @"facebook" ]; } - (NSArray *)imageTypes { return @[ (id)kUTTypeJPEG, (id)kUTTypeJPEG2000, (id)kUTTypeTIFF, (id)kUTTypeGIF, (id)kUTTypePNG, (id)kUTTypeBMP ]; } - (NSString *)imageTypeGetExt:(id)type { static NSDictionary *map; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ map = @{(id)kUTTypeJPEG : @"jpg", (id)kUTTypeJPEG2000 : @"jp2", (id)kUTTypeTIFF : @"tif", (id)kUTTypeGIF : @"gif", (id)kUTTypePNG : @"png", (id)kUTTypeBMP : @"bmp"}; }); return type ? map[type] : nil; } - (NSArray *)imageTypeGetQuality:(NSString *)type { BOOL hasQuality = [type isEqualToString:(id)kUTTypeJPEG] || [type isEqualToString:(id)kUTTypeJPEG2000] || [type isEqualToString:@"webp"]; return hasQuality ? @[@1.0, @0.95, @0.9, @0.85, @0.8, @0.75, @0.7, @0.6, @0.5, @0.4, @0.3, @0.2, @0.1, @0] : @[@1.0]; } - (void)runImageDecodeBenchmark { printf("==========================================\n"); printf("ImageIO Decode Benchmark\n"); printf("name size type quality length decode_time\n"); for (NSString *imageName in self.imageNames) { for (NSNumber *imageSize in self.imageSizes) { for (NSString *imageSource in self.imageSources) { for (NSString *imageType in @[@"png", @"jpg"]) { @autoreleasepool { NSString *fileName = [NSString stringWithFormat:@"%@%@_%@",imageName, imageSize, imageSource]; NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:imageType]; NSData *data = filePath ? [NSData dataWithContentsOfFile:filePath] : nil; if (!data) continue; int count = 100; YYBenchmark(^{ for (int i = 0; i < count; i++) { CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL); CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); CFRelease(decoded); CFRelease(image); CFRelease(source); } }, ^(double ms) { printf("%8s %3d %3s %10s %6d %2.3f\n", imageName.UTF8String, imageSize.intValue, imageType.UTF8String, imageSource.UTF8String, (int)data.length, ms / count); }); #if ENABLE_OUTPUT if ([UIDevice currentDevice].isSimulator) { NSString *outFilePath = [NSString stringWithFormat:@"%@%@.%@", IMAGE_OUTPUT_DIR, fileName, imageType]; [data writeToFile:outFilePath atomically:YES]; } #endif } } } } } printf("------------------------------------------\n\n"); } - (void)runImageEncodeBenchmark { printf("==========================================\n"); printf("ImageIO Encode Benchmark\n"); printf("name size type quality length encode decode\n"); for (NSString *imageName in self.imageNames) { for (NSNumber *imageSize in self.imageSizes) { NSString *fileName = [NSString stringWithFormat:@"%@%@_imageio",imageName, imageSize]; NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"png"]; NSData *data = filePath ? [NSData dataWithContentsOfFile:filePath] : nil; CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL); CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); for (NSString *uti in [self imageTypes]) { for (NSNumber *quality in [self imageTypeGetQuality:uti]) { __block int encodeCount = 0; __block double encodeTime = 0; __block long length = 0; __block CFMutableDataRef outData = NULL; __block int decodeCount = 0; __block double decodeTime = 0; while (encodeTime < 200) { //200ms YYBenchmark(^{ if (outData) CFRelease(outData); outData = CFDataCreateMutable(CFAllocatorGetDefault(), 0); CGImageDestinationRef dest = CGImageDestinationCreateWithData(outData, (CFStringRef)uti, 1, NULL); NSDictionary *options = @{(id)kCGImageDestinationLossyCompressionQuality : quality }; CGImageDestinationAddImage(dest, decoded, (CFDictionaryRef)options); CGImageDestinationFinalize(dest); length = CFDataGetLength(outData); CFRelease(dest); }, ^(double ms) { encodeTime += ms; encodeCount += 1; }); } #if ENABLE_OUTPUT if ([UIDevice currentDevice].isSimulator) { NSString *outFilePath = [NSString stringWithFormat:@"%@%@%@_%.2f.%@", IMAGE_OUTPUT_DIR, imageName, imageSize, quality.floatValue, [self imageTypeGetExt:uti]]; [((__bridge NSData *)outData) writeToFile:outFilePath atomically:YES]; } #endif decodeCount = 100; YYBenchmark(^{ for (int i = 0; i < decodeCount; i++) { CGImageSourceRef source = CGImageSourceCreateWithData(outData, NULL); CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, NO); CFRelease(decoded); CFRelease(image); CFRelease(source); } }, ^(double ms) { decodeTime = ms; }); CFRelease(outData); printf("%8s %3d %3s %.2f %7d %7.3f %7.3f\n",imageName.UTF8String, imageSize.intValue, [self imageTypeGetExt:uti].UTF8String, quality.floatValue, (int)length, encodeTime / encodeCount, decodeTime / decodeCount); } } CFRelease(decoded); CFRelease(image); CFRelease(source); } } printf("------------------------------------------\n\n"); } - (void)runWebPBenchmark { printf("==========================================\n"); printf("WebP Benchmark\n"); printf("name size type quality method length encode decode\n"); for (NSString *imageName in self.imageNames) { for (NSNumber *imageSize in self.imageSizes) { NSString *fileName = [NSString stringWithFormat:@"%@%@_imageio", imageName, imageSize]; NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"png"]; NSData *data = filePath ? [NSData dataWithContentsOfFile:filePath] : nil; CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL); CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache : @(NO) }); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); for (NSNumber *lossless in @[ @YES, @NO ]) { for (NSNumber *q in [self imageTypeGetQuality:@"webp"]) { for (NSNumber *m in @[ @0, @1, @2, @3, @4, @5, @6 ]) { @autoreleasepool { __block int encodeCount = 0; __block double encodeTime = 0; __block long length = 0; __block CFDataRef webpData = NULL; int decodeCount = 100; double decodeTime[8] = {0}; // useThreads,bypassFiltering,noFancyUpsampling 0,0,0; 0,0,1; 0,1,0; 0,1,1; 1,0,0; 1,0,1; 1,1,0; 1,1,1 while (encodeTime < 200) { // 200ms YYBenchmark( ^{ if (webpData) CFRelease(webpData); webpData = YYCGImageCreateEncodedWebPData(decoded, lossless.boolValue, q.floatValue, m.intValue, YYImagePresetDefault); length = CFDataGetLength(webpData); }, ^(double ms) { encodeTime += ms; encodeCount += 1; }); } #if ENABLE_OUTPUT if ([UIDevice currentDevice].isSimulator) { NSString *outFilePath = [NSString stringWithFormat:@"%@%@%@_%@_q%.2f_m%d.webp", IMAGE_OUTPUT_DIR, imageName, imageSize, lossless.boolValue ? @"lossless" : @"lossy", q.floatValue, m.intValue]; [((__bridge NSData *)webpData)writeToFile:outFilePath atomically:YES]; CGImageRef image = YYCGImageCreateWithWebPData(webpData, NO, NO, NO, NO); NSData *pngData = UIImagePNGRepresentation([UIImage imageWithCGImage:image]); NSString *pngOutFilePath = [NSString stringWithFormat:@"%@%@%@_%@_q%.2f_m%d.webp.png", IMAGE_OUTPUT_DIR, imageName, imageSize, lossless.boolValue ? @"lossless" : @"lossy", q.floatValue, m.intValue]; [pngData writeToFile:pngOutFilePath atomically:YES]; CFRelease(image); } #endif for (NSNumber *useThreads in @[ @NO, @YES ]) { for (NSNumber *bypassFiltering in @[ @NO, @YES ]) { for (NSNumber *noFancyUpsampling in @[ @NO, @YES ]) { __block double time = 0; YYBenchmark(^{ for (int i = 0; i < decodeCount; i++) { CGImageRef image = YYCGImageCreateWithWebPData(webpData, YES, useThreads.boolValue, bypassFiltering.boolValue,noFancyUpsampling.boolValue); CFRelease(image); } }, ^(double ms) { time = ms; }); decodeTime[useThreads.intValue << 2 | bypassFiltering.intValue << 1 | noFancyUpsampling.intValue] = time; } } } if (webpData) CFRelease(webpData); printf("%8s %3d %.8s %.2f %1d %7d %9.3f %7.3f %7.3f %7.3f %7.3f %7.3f %7.3f %7.3f %7.3f\n", imageName.UTF8String, imageSize.intValue, lossless.boolValue ? "lossless" : "lossy", q.floatValue, m.intValue, (int)length, encodeTime / encodeCount, decodeTime[0] / decodeCount, decodeTime[1] / decodeCount, decodeTime[2] / decodeCount, decodeTime[3] / decodeCount, decodeTime[4] / decodeCount, decodeTime[5] / decodeCount, decodeTime[6] / decodeCount, decodeTime[7] / decodeCount); } } } } CFRelease(decoded); CFRelease(image); CFRelease(source); } } printf("------------------------------------------\n\n"); } - (void)runBPGBenchmark { printf("==========================================\n"); printf("BPG Decode Benchmark\n"); printf("name size quality length decode_time\n"); for (NSString *imageName in self.imageNames) { for (NSNumber *imageSize in self.imageSizes) { for (NSString *quality in @[ @"lossless",@"q0",@"q5",@"q10",@"q15",@"q20",@"q25",@"q30",@"q35",@"q40",@"q45",@"q50"]) { @autoreleasepool { NSString *fileName = [NSString stringWithFormat:@"%@%@_%@",imageName, imageSize, quality]; NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"bpg"]; NSData *data = filePath ? [NSData dataWithContentsOfFile:filePath] : nil; if (!data) continue; int count = 100; YYBenchmark(^{ for (int i = 0; i < count; i++) { CGImageRef image = YYCGImageCreateWithBPGData((__bridge CFDataRef)data, YES); CFRelease(image); } }, ^(double ms) { printf("%8s %3d %8s %6d %2.3f\n", imageName.UTF8String, imageSize.intValue, quality.UTF8String, (int)data.length, ms / count); }); #if ENABLE_OUTPUT if ([UIDevice currentDevice].isSimulator) { NSString *outFilePath = [NSString stringWithFormat:@"%@%@.bpg", IMAGE_OUTPUT_DIR,fileName]; [data writeToFile:outFilePath atomically:YES]; CGImageRef image = YYCGImageCreateWithBPGData((__bridge CFDataRef)data, YES); NSData *pngData = UIImagePNGRepresentation([UIImage imageWithCGImage:image]); CFRelease(image); NSString *pngOutFilePath = [NSString stringWithFormat:@"%@%@.bpg.png", IMAGE_OUTPUT_DIR,fileName]; [pngData writeToFile:pngOutFilePath atomically:YES]; } #endif } } } } printf("------------------------------------------\n\n"); } - (void)runAnimatedImageBenchmark { printf("==========================================\n"); printf("Animated Image Decode Benckmark\n"); if ([UIDevice currentDevice].systemVersion.floatValue < 8) { printf("ImageIO's APNG require iOS8 or later\n"); return; } NSData *gif = [NSData dataNamed:@"ermilio.gif"]; NSData *apng = [NSData dataNamed:@"ermilio.png"]; NSData *webp_q85 = [NSData dataNamed:@"ermilio_q85.webp"]; NSData *webp_q90 = [NSData dataNamed:@"ermilio_q90.webp"]; NSData *webp_lossless = [NSData dataNamed:@"ermilio_lossless.webp"]; NSData *bpg_q15 = [NSData dataNamed:@"ermilio_q15.bpg"]; NSData *bpg_q20 = [NSData dataNamed:@"ermilio_q20.bpg"]; NSData *bpg_lossless = [NSData dataNamed:@"ermilio_lossless.bpg"]; NSArray *datas = @[gif, apng, webp_q85, webp_q90, webp_lossless, bpg_q20, bpg_q15, bpg_lossless]; NSArray *names = @[@"gif", @"apng", @"webp_85", @"webp_90", @"webp_ll", @"bpg_20", @"bpg_15", @"bpg_ll"]; #if ENABLE_OUTPUT if ([UIDevice currentDevice].isSimulator) { @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio.gif.png",IMAGE_OUTPUT_DIR]; NSData *outData = UIImagePNGRepresentation([UIImage imageWithData:gif]); [outData writeToFile:outPath atomically:YES]; [gif writeToFile:[NSString stringWithFormat:@"%@ermilio.gif",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio.apng.png",IMAGE_OUTPUT_DIR]; NSData *outData = UIImagePNGRepresentation([UIImage imageWithData:apng]); [outData writeToFile:outPath atomically:YES]; [apng writeToFile:[NSString stringWithFormat:@"%@ermilio.png",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio_q85.webp.png",IMAGE_OUTPUT_DIR]; NSData *outData = UIImagePNGRepresentation([YYImageDecoder decodeImage:webp_q85 scale:1]); [outData writeToFile:outPath atomically:YES]; [webp_q85 writeToFile:[NSString stringWithFormat:@"%@ermilio_q85.webp",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio_q90.webp.png",IMAGE_OUTPUT_DIR]; NSData *outData = UIImagePNGRepresentation([YYImageDecoder decodeImage:webp_q90 scale:1]); [outData writeToFile:outPath atomically:YES]; [webp_q90 writeToFile:[NSString stringWithFormat:@"%@ermilio_q90.webp",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio_lossless.webp.png",IMAGE_OUTPUT_DIR]; NSData *outData = UIImagePNGRepresentation([YYImageDecoder decodeImage:webp_lossless scale:1]); [outData writeToFile:outPath atomically:YES]; [webp_lossless writeToFile:[NSString stringWithFormat:@"%@ermilio_lossless.webp",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio_q15.bpg.png",IMAGE_OUTPUT_DIR]; CGImageRef imageRef = YYCGImageCreateWithBPGData((__bridge CFDataRef)bpg_q15, NO); NSData *outData = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]); [outData writeToFile:outPath atomically:YES]; CFRelease(imageRef); [bpg_q15 writeToFile:[NSString stringWithFormat:@"%@ermilio_q15.bpg",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio_q20.bpg.png",IMAGE_OUTPUT_DIR]; CGImageRef imageRef = YYCGImageCreateWithBPGData((__bridge CFDataRef)bpg_q20, NO); NSData *outData = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]); [outData writeToFile:outPath atomically:YES]; CFRelease(imageRef); [bpg_q20 writeToFile:[NSString stringWithFormat:@"%@ermilio_q20.bpg",IMAGE_OUTPUT_DIR] atomically:YES]; } @autoreleasepool { NSString *outPath = [NSString stringWithFormat:@"%@ermilio_lossless.bpg.png",IMAGE_OUTPUT_DIR]; CGImageRef imageRef = YYCGImageCreateWithBPGData((__bridge CFDataRef)bpg_lossless, NO); NSData *outData = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]); [outData writeToFile:outPath atomically:YES]; CFRelease(imageRef); [bpg_lossless writeToFile:[NSString stringWithFormat:@"%@ermilio_lossless.bpg",IMAGE_OUTPUT_DIR] atomically:YES]; } } #endif printf("------------------------------------------\n"); printf("image length\n"); for (int i = 0; i < names.count; i++) { NSString *name = names[i]; NSData *data = datas[i]; printf("%7s %6d\n",name.UTF8String, (int)data.length); } printf("\n\n"); int count = 20; int frame_num = 28; typedef void (^CoverDecodeBlock)(id src); typedef void (^SingleFrameDecodeBlock)(id src, NSUInteger index); typedef void (^AllFrameDecodeBlock)(id src, BOOL reverseOrder); /// Cover: gif/apng CoverDecodeBlock imageioCoverDecoder = ^(NSData *data){ CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL); CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); CFRelease(decoded); CFRelease(image); CFRelease(source); }; /// Cover: gif/apng/webp CoverDecodeBlock yyCoverDecoder = ^(NSData *data) { @autoreleasepool { YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:1]; [decoder frameAtIndex:0 decodeForDisplay:YES];; } }; /// Cover: webp CoverDecodeBlock webpCoverDecoder = ^(NSData *data) { CGImageRef image = YYCGImageCreateWithWebPData((__bridge CFDataRef)data, YES, NO, NO, NO); CFRelease(image); }; /// Cover: bpg CoverDecodeBlock bpgCoverDecoder = ^(NSData *data) { CGImageRef image = YYCGImageCreateWithBPGData((__bridge CFDataRef)data, YES); CFRelease(image); }; NSArray *coverSrcs = @[@"gif imageio", gif, imageioCoverDecoder, @"gif yydecoder", gif, yyCoverDecoder, @"apng imageio", apng, imageioCoverDecoder, @"apng yydecoder", apng, yyCoverDecoder, @"webp_85 yyimage", webp_q85, webpCoverDecoder, @"webp_85 yydecocer", webp_q85, yyCoverDecoder, @"webp_90 yyimage", webp_q90, webpCoverDecoder, @"webp_90 yydecocer", webp_q90, yyCoverDecoder, @"webp_ll yyimage", webp_lossless, webpCoverDecoder, @"webp_ll yydecoder", webp_lossless, yyCoverDecoder, @"bpg_20 yyimage", bpg_q20, bpgCoverDecoder, @"bpg_15 yyimage", bpg_q20, bpgCoverDecoder, @"bpg_ll yyimage", bpg_lossless, bpgCoverDecoder, ]; printf("------------------------------------------\n"); printf("First frame (cover) decode\n"); count = 20; for (int i = 0; i < coverSrcs.count / 3; i++) { NSString *name = coverSrcs[i * 3]; id src = coverSrcs[i * 3 + 1]; CoverDecodeBlock block = coverSrcs[i * 3 + 2]; YYBenchmark(^{ for (int r = 0; r < count; r++) { block(src); } }, ^(double ms) { printf("%s %8.3f\n",name.UTF8String, ms / count); }); } printf("\n\n"); /// Single: gif/apng SingleFrameDecodeBlock imagioSingleFrameDecoder = ^(id src, NSUInteger index) { CGImageSourceRef source = (__bridge CGImageSourceRef)src; CGImageRef image = CGImageSourceCreateImageAtIndex(source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); CFRelease(decoded); CFRelease(image); }; /// Single: gif/apng/webp SingleFrameDecodeBlock yySingleFrameDecoder = ^(YYImageDecoder *decoder, NSUInteger index) { @autoreleasepool { UIImage *img = [decoder frameAtIndex:index decodeForDisplay:YES].image; [img scale]; } }; NSArray *singleSrcs = @[@"gif imageio", @"gif src", imagioSingleFrameDecoder, @"gif yydecoder", @"gif", yySingleFrameDecoder, @"apng imageio", @"apng src", imagioSingleFrameDecoder, @"apng yydecoder", @"apng", yySingleFrameDecoder, @"webp_85 yydecocer", @"webp85", yySingleFrameDecoder, @"webp_90 yydecocer", @"webp90", yySingleFrameDecoder, @"webp_ll yydecoder", @"webpll", yySingleFrameDecoder, ]; printf("------------------------------------------\n"); printf("Single frame decode\n"); count = 5; for (int i = 0; i < singleSrcs.count / 3; i++) { NSString *name = singleSrcs[i * 3]; NSString *srcStr = singleSrcs[i * 3 + 1]; SingleFrameDecodeBlock block = singleSrcs[i * 3 + 2]; printf("%s ",name.UTF8String); for (int f = 0; f < frame_num; f++) { YYBenchmark(^{ for (int r = 0; r < count; r++) { id src = NULL; if ([srcStr isEqual:@"gif src"]) { src = CFBridgingRelease(CGImageSourceCreateWithData((__bridge CFDataRef)gif, NULL)); } else if ([srcStr isEqual:@"gif"]) { src = [YYImageDecoder decoderWithData:gif scale:1]; } else if ([srcStr isEqual:@"apng src"]) { src = CFBridgingRelease(CGImageSourceCreateWithData((__bridge CFDataRef)apng, NULL)); } else if ([srcStr isEqual:@"apng"]) { src = [YYImageDecoder decoderWithData:apng scale:1]; } else if ([srcStr isEqual:@"webp85"]) { src = [YYImageDecoder decoderWithData:webp_q85 scale:1]; } else if ([srcStr isEqual:@"webp90"]) { src = [YYImageDecoder decoderWithData:webp_q90 scale:1]; } else if ([srcStr isEqual:@"webpll"]) { src = [YYImageDecoder decoderWithData:webp_lossless scale:1]; } block(src, f); } }, ^(double ms) { printf("%8.3f ",ms / count); }); } printf("\n"); } printf("\n\n"); /// All: gif/apng AllFrameDecodeBlock imageioAllFrameDecoder = ^(NSData *data, BOOL reverseOrder){ CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)gif, NULL); if (reverseOrder) { for (int i = frame_num - 1; i >= 0; i--) { CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); CFRelease(decoded); CFRelease(image); } } else { for (int i = 0; i < frame_num; i++) { CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)}); CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES); CFRelease(decoded); CFRelease(image); } } CFRelease(source); }; /// All: gif/apng/webp AllFrameDecodeBlock yyAllFrameDecoder = ^(NSData *data, BOOL reverseOrder){ @autoreleasepool { YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:1]; if (reverseOrder) { for (int i = frame_num - 1; i > 0; i--) { [decoder frameAtIndex:i decodeForDisplay:YES]; } } else { for (int i = 1; i < frame_num; i++) { [decoder frameAtIndex:i decodeForDisplay:YES]; } } } }; /// All: bpg AllFrameDecodeBlock bpgAllFrameDecoder = ^(NSData *data, BOOL reverseOrder){ @autoreleasepool { YYCGImageDecodeAllFrameInBPGData((__bridge CFDataRef)data, YES); } }; NSArray *allSrcs = @[@"gif imageio", gif, imageioAllFrameDecoder, @"gif yydecoder", gif, yyAllFrameDecoder, @"apng imageio", apng, imageioAllFrameDecoder, @"apng yydecoder", apng, yyAllFrameDecoder, @"webp_85 yydecocer", webp_q85, yyAllFrameDecoder, @"webp_90 yydecocer", webp_q90, yyAllFrameDecoder, @"webp_ll yydecoder", webp_lossless, yyAllFrameDecoder, @"bpg_20 yyimage", bpg_q20, bpgAllFrameDecoder, @"bpg_15 yyimage", bpg_q20, bpgAllFrameDecoder, @"bpg_ll yyimage", bpg_lossless, bpgAllFrameDecoder, ]; printf("------------------------------------------\n"); printf("All frame decode\n"); printf("type decoder asc desc\n"); count = 5; for (int i = 0; i < allSrcs.count / 3; i++) { NSString *name = allSrcs[i * 3]; id src = allSrcs[i * 3 + 1]; AllFrameDecodeBlock block = allSrcs[i * 3 + 2]; printf("%s ",name.UTF8String); for (NSNumber *rev in @[@NO, @YES]) { if ([name hasPrefix:@"bpg"] && rev.boolValue) continue; YYBenchmark(^{ for (int r = 0; r < count; r++) { block(src, rev.boolValue); } }, ^(double ms) { printf("%8.3f ",ms / count); }); } printf("\n"); } printf("\n\n"); } @end