TwitterImagePipelineTests/TIPImageTest.m (1,517 lines of code) (raw):

// // TIPImageTest.m // TwitterImagePipeline // // Created on 2/19/15. // Copyright (c) 2015 Twitter, Inc. All rights reserved. // #import <objc/runtime.h> #import "NSData+TIPAdditions.h" #import "TIP_Project.h" #import "TIPError.h" #import "TIPImageCodecCatalogue.h" #import "TIPImageContainer.h" #import "TIPImageUtils.h" #import "TIPTests.h" #import "TIPXMP4Codec.h" #if !TARGET_OS_TV #import "TIPXWebPCodec.h" #endif #import "UIImage+TIPAdditions.h" @import Foundation; @import MobileCoreServices; @import UIKit; @import XCTest; @interface TestParamSet : NSObject @property (nonatomic) BOOL useFloat; @property (nonatomic) size_t bytesPerComponent; @property (nonatomic) CGImageAlphaInfo alphaInfo; @property (nonatomic) uint32_t byteOrder; + (instancetype)floatParamSetWithAlphaInfo:(CGImageAlphaInfo)alphaInfo byteOrder:(uint32_t)byteOrder; + (instancetype)integerParamSetWithAlphaInfo:(CGImageAlphaInfo)alphaInfo byteOrder:(uint32_t)byteOrder bytesPerComponent:(size_t)bytesPerComponent; @end #define PARAM_SET_FLOAT(ai, bo) [TestParamSet floatParamSetWithAlphaInfo:(ai) byteOrder:(bo)] #define PARAM_SET_INT(ai, bo) [TestParamSet integerParamSetWithAlphaInfo:(ai) byteOrder:(bo) bytesPerComponent:1] #if !TARGET_OS_TV #define PLUG_IN_WEBP() \ TIPXWebPCodec *webpCodec = [[TIPXWebPCodec alloc] initWithPreferredCodec:nil]; \ [[TIPImageCodecCatalogue sharedInstance] setCodec:webpCodec forImageType:TIPImageTypeWEBP]; \ tip_defer(^{ \ [[TIPImageCodecCatalogue sharedInstance] removeCodecForImageType:TIPImageTypeWEBP]; \ }); #endif #define PLUG_IN_MP4() \ TIPXMP4Codec *mp4Codec = [[TIPXMP4Codec alloc] init]; \ [[TIPImageCodecCatalogue sharedInstance] setCodec:mp4Codec forImageType:TIPXImageTypeMP4]; \ tip_defer(^{ \ [[TIPImageCodecCatalogue sharedInstance] removeCodecForImageType:TIPXImageTypeMP4]; \ }); #define SLOW_IMAGE_CHECK 0 #if SLOW_IMAGE_CHECK NS_INLINE NSData * __nullable UIImagePNGRepresentationUndeprecated(UIImage * __nonnull image) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return UIImagePNGRepresentation(image); #pragma clang diagnostic pop } #endif @implementation TestParamSet + (instancetype)floatParamSetWithAlphaInfo:(CGImageAlphaInfo)alphaInfo byteOrder:(uint32_t)byteOrder { TestParamSet *set = [[self alloc] init]; set.alphaInfo = alphaInfo; set.byteOrder = byteOrder; set.useFloat = YES; set.bytesPerComponent = sizeof(float); return set; } + (instancetype)integerParamSetWithAlphaInfo:(CGImageAlphaInfo)alphaInfo byteOrder:(uint32_t)byteOrder bytesPerComponent:(size_t)bytesPerComponent { TestParamSet *set = [[self alloc] init]; set.alphaInfo = alphaInfo; set.byteOrder = byteOrder; set.useFloat = NO; set.bytesPerComponent = bytesPerComponent; return set; } @end @interface TestColorSpace : NSObject @property (nonatomic, readonly) CGColorSpaceRef colorSpace; @property (nonatomic, readonly) NSArray *validParamSets; + (instancetype)colorSpaceWithOwnedRef:(CGColorSpaceRef)colorSpace validParamSets:(NSArray *)validParamSets; @end @implementation TestColorSpace + (instancetype)colorSpaceWithOwnedRef:(CGColorSpaceRef)colorSpace validParamSets:(NSArray *)validParamSets { TestColorSpace *tcs = [[self alloc] init]; tcs->_colorSpace = colorSpace; tcs->_validParamSets = [validParamSets copy]; return tcs; } - (void)dealloc { if (_colorSpace) { CFRelease(_colorSpace); } } @end @interface NSData (Description) - (NSString *)tst_shortDescription; @end @implementation NSData (Description) - (NSString*)tst_shortDescription { return [NSString stringWithFormat:@"<%@:%p length=%tu>", NSStringFromClass([self class]), self, self.length]; } @end @interface TIPImageTest : XCTestCase @end #define JPEG_QUALITY_PERFECT (1.0f) #define JPEG_QUALITY_GOOD (kTIPAppleQualityValueRepresentingJFIFQuality85) #define JPEG_QUALITY_OK (0.15f) #define HEIF_QUALITY_PERFECT (1.0f) #define HEIF_QUALITY_GOOD (0.650f /*.575f is less quality than JPEG .575f*/) #define HEIF_QUALITY_OK (0.415f /*.500f is really the minimum match to JPEG at .150f, but that's basically the same size*/) #define JPEG2000_QUALITY_PERFECT (1.0f) #define JPEG2000_QUALITY_GOOD (kTIPAppleQualityValueRepresentingJFIFQuality85) #define JPEG2000_QUALITY_OK (0.15f) #define WEBP_QUALITY_PERFECT (.99f) /* use .99 because 1. is lossless and slower than molasses in Edmonton in January */ #define WEBP_QUALITY_GOOD (0.6f) #define WEBP_QUALITY_OK (0.3f) #define TEST_IMAGE_WIDTH ((CGFloat)1880.0) #define TEST_IMAGE_HEIGHT ((CGFloat)1253.0) #define TEST_ANIMATION_WIDTH ((CGFloat)480.0) #define TEST_ANIMATION_HEIGHT ((CGFloat)320.0) NS_INLINE BOOL CODEC_FULLY_SUPPORTED(NSString *imageType) { id<TIPImageCodec> codec = [[TIPImageCodecCatalogue sharedInstance] codecForImageType:imageType]; return codec.tip_encoder != nil && codec.tip_decoder != nil; } static TIPImageContainer *sImageContainer; static TIPImageContainer *sAnimatedImageContainer; static NSMutableArray<NSString *> *sSavedImages; static NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, NSNumber *> *> *sPerformanceInfo; @implementation TIPImageTest + (NSArray <NSInvocation *> *)testInvocations { NSArray<NSInvocation *> *invocations = [super testInvocations]; invocations = [invocations sortedArrayUsingComparator:^NSComparisonResult(NSInvocation *inv1, NSInvocation *inv2) { return [NSStringFromSelector(inv1.selector) compare:NSStringFromSelector(inv2.selector)]; }]; return invocations; } + (void)setUp { NSBundle *thisBundle = TIPTestsResourceBundle(); NSString *imagePath = [thisBundle pathForResource:@"carnival" ofType:@"png"]; UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; [image tip_decode]; sImageContainer = [[TIPImageContainer alloc] initWithImage:image]; imagePath = [thisBundle pathForResource:@"fireworks" ofType:@"gif"]; NSUInteger loopCount; NSArray<NSNumber *> *durations; image = [UIImage tip_imageWithAnimatedImageFile:imagePath durations:&durations loopCount:&loopCount]; [image tip_decode]; // yes, this is a no-op, but we'll leave this here in case we can optimize the decode for animated images later sAnimatedImageContainer = [[TIPImageContainer alloc] initWithAnimatedImage:image loopCount:loopCount frameDurations:durations]; sSavedImages = [NSMutableArray array]; sPerformanceInfo = [NSMutableDictionary dictionary]; // make the NSData description less verbose Method originalMethod = class_getInstanceMethod([NSData class], @selector(description)); Method swizzledMethod = class_getInstanceMethod([NSData class], @selector(tst_shortDescription)); method_exchangeImplementations(originalMethod, swizzledMethod); } + (void)tearDown { // return NSData description Method originalMethod = class_getInstanceMethod([NSData class], @selector(description)); Method swizzledMethod = class_getInstanceMethod([NSData class], @selector(tst_shortDescription)); method_exchangeImplementations(originalMethod, swizzledMethod); sImageContainer = nil; sAnimatedImageContainer = nil; for (NSString *file in sSavedImages) { NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:file error:NULL]; NSLog(@"%@ size: %@", file.lastPathComponent, [NSByteCountFormatter stringFromByteCount:(long long)attributes.fileSize countStyle:NSByteCountFormatterCountStyleBinary]); [[NSFileManager defaultManager] removeItemAtPath:file error:NULL]; } [self logPerf]; sPerformanceInfo = nil; } + (void)logPerf { NSMutableString *perfString = [NSMutableString stringWithFormat:@"%8s | %8s | %8s | %8s", "format", "save", "load", "speed"]; [perfString appendString:@"\n----------------------------------------"]; NSMutableArray<NSString *> *keys = [sPerformanceInfo.allKeys mutableCopy]; [keys sortUsingComparator:^NSComparisonResult(NSString *key1, NSString *key2) { float val1 = sPerformanceInfo[key1][@"speed"].floatValue; float val2 = sPerformanceInfo[key2][@"speed"].floatValue; if (val1 < val2) { return NSOrderedAscending; } else if (val1 > val2) { return NSOrderedDescending; } return NSOrderedSame; }]; for (NSString *imageType in keys) { NSDictionary<NSString *, NSNumber *> *metrics = sPerformanceInfo[imageType]; [perfString appendFormat:@"\n%8s | %7.3fs | %7.3fs | %7.3fs", [imageType UTF8String], metrics[@"save"].floatValue, metrics[@"load"].floatValue, metrics[@"speed"].floatValue]; } NSLog(@"\n%@", perfString); } - (void)tearDown { // reset the codecs NSDictionary<NSString *, id<TIPImageCodec>> *codecs = [TIPImageCodecCatalogue defaultCodecs]; [codecs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull type, id<TIPImageCodec> _Nonnull codec, BOOL * _Nonnull stop) { [[TIPImageCodecCatalogue sharedInstance] setCodec:codec forImageType:type]; }]; (void)[TIPImageCodecCatalogue sharedInstance].allCodecs; // flush [super tearDown]; } #pragma mark Test SetUp - (void)testLoadedImage { XCTAssert(sImageContainer.dimensions.width == TEST_IMAGE_WIDTH); XCTAssert(sImageContainer.dimensions.height == TEST_IMAGE_HEIGHT); XCTAssert(sAnimatedImageContainer.dimensions.width == TEST_ANIMATION_WIDTH); XCTAssert(sAnimatedImageContainer.dimensions.height == TEST_ANIMATION_HEIGHT); XCTAssert(sAnimatedImageContainer.image.images.count > 2); } #pragma mark Test Read/Write of different formats - (void)runSaveTest:(NSString *)type options:(TIPImageEncodingOptions)options extension:(NSString *)extension quality:(float)quality image:(TIPImageContainer *)imageContainer { NSString *file = [[[NSTemporaryDirectory() stringByAppendingPathComponent:@"test"] stringByAppendingPathExtension:[@((NSUInteger)(quality * 100.0f)) stringValue]] stringByAppendingPathExtension:extension]; NSError *error = nil; const BOOL writeToFileSuccess = [imageContainer saveToFilePath:file type:type codecCatalogue:nil options:options quality:quality atomic:YES error:&error]; XCTAssertTrue(writeToFileSuccess, @"file=`%@`, error=%@", file, error); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:NULL], @"%@", file); [sSavedImages addObject:file]; } - (void)runLoadTest:(NSString *)expectedType options:(TIPImageEncodingOptions)options extension:(NSString *)extension quality:(float)quality isAnimated:(BOOL)animated { NSString *file = [[[NSTemporaryDirectory() stringByAppendingPathComponent:@"test"] stringByAppendingPathExtension:[@((NSUInteger)(quality * 100.0f)) stringValue]] stringByAppendingPathExtension:extension]; NSData *data = [NSData dataWithContentsOfFile:file]; XCTAssertGreaterThan(data.length, (NSUInteger)0, @"%@", file); NSUInteger detectedAnimatedFrameCount = 0; TIPImageEncodingOptions detectedOptions = 0; NSString *detectedType = TIPDetectImageType(data, &detectedOptions, &detectedAnimatedFrameCount, NO); BOOL couldBeCustomType = detectedType == nil; if (!couldBeCustomType) { XCTAssertEqualObjects(detectedType, expectedType, @"%@", file); XCTAssertEqual(options, detectedOptions); XCTAssertEqual(!!animated, (detectedAnimatedFrameCount > 1)); } TIPImageContainer *container = [TIPImageContainer imageContainerWithData:data decoderConfigMap:nil codecCatalogue:nil]; UIImage *image = container.image; XCTAssertNotNil(image, @"extension = '%@'", extension); NSTimeInterval decompressTime = [self decompressImage:image]; NSLog(@"%@ decompress time: %fs", file.lastPathComponent, decompressTime); XCTAssertEqual(container.isAnimated, animated); if (animated) { XCTAssertGreaterThan(image.images.count, (NSUInteger)1); XCTAssertEqual(image.images.count, container.frameCount); XCTAssertEqual(image.images.count, container.frameDurations.count); if (!couldBeCustomType) { XCTAssertEqual(container.frameDurations.count, detectedAnimatedFrameCount); } XCTAssertEqual(sAnimatedImageContainer.frameCount, container.frameDurations.count); if (sAnimatedImageContainer.frameCount == container.frameDurations.count) { for (NSUInteger i = 0; i < container.frameDurations.count; i++) { XCTAssertEqualWithAccuracy([sAnimatedImageContainer.frameDurations[i] floatValue], [container.frameDurations[i] floatValue], 0.005f); } } } else { XCTAssertLessThanOrEqual(image.images.count, (NSUInteger)1); } } - (NSTimeInterval)decompressImage:(UIImage *)image { CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); [image tip_decode]; return CFAbsoluteTimeGetCurrent() - startTime; } - (void)runSpeedTest:(NSString *)type options:(TIPImageEncodingOptions)options { TIPImageCodecCatalogue *catalogue = [TIPImageCodecCatalogue sharedInstance]; TIPImageContainer *imageContainer = sImageContainer; for (NSUInteger i = 0; i < 5; i++) { @autoreleasepool { float quality = 1.0f - ((i % 10) / 10.0f); #if !TARGET_OS_TV if (type == TIPImageTypeWEBP && quality > .99f) { // Lossless WebP is super slow, // drop down to 99% in order to give WebP a fighting chance quality = .99f; } #endif NSError *error = nil; NSData *data = [catalogue encodeImage:imageContainer withImageType:type quality:quality options:options error:&error]; XCTAssertGreaterThan(data.length, (NSUInteger)0, @"Write image (q=%f) to data failed: %@", quality, error); } } } - (void)runMeasurement:(NSString *)measurement format:(NSString *)format block:(dispatch_block_t)block { CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); block(); CFAbsoluteTime end = CFAbsoluteTimeGetCurrent(); NSMutableDictionary<NSString *, NSNumber *> *measurements = sPerformanceInfo[format]; if (!measurements) { measurements = [NSMutableDictionary dictionary]; sPerformanceInfo[format] = measurements; } measurements[measurement] = @(end - start); } #pragma mark Image Formats R+W tests - (void)testSaveJPEG { [self runSaveTest:TIPImageTypeJPEG options:0 extension:@"jpg" quality:JPEG_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeJPEG options:0 extension:@"jpg" quality:JPEG_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"jpg" block:^{ [self runSaveTest:TIPImageTypeJPEG options:0 extension:@"jpg" quality:JPEG_QUALITY_OK image:sImageContainer]; }]; } - (void)testXLoadJPEG { [self runLoadTest:TIPImageTypeJPEG options:0 extension:@"jpg" quality:JPEG_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeJPEG options:0 extension:@"jpg" quality:JPEG_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"jpg" block:^{ [self runLoadTest:TIPImageTypeJPEG options:0 extension:@"jpg" quality:JPEG_QUALITY_OK isAnimated:NO]; }]; } - (void)testSpeedJPEG { [self runMeasurement:@"speed" format:@"jpg" block:^{ [self runSpeedTest:TIPImageTypeJPEG options:0]; }]; } - (void)testSavePJPEG { [self runSaveTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive extension:@"pjpg" quality:JPEG_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive extension:@"pjpg" quality:JPEG_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"pjpg" block:^{ [self runSaveTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive extension:@"pjpg" quality:JPEG_QUALITY_OK image:sImageContainer]; }]; } - (void)testXLoadPJPEG { [self runLoadTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive extension:@"pjpg" quality:JPEG_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive extension:@"pjpg" quality:JPEG_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"pjpg" block:^{ [self runLoadTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive extension:@"pjpg" quality:JPEG_QUALITY_OK isAnimated:NO]; }]; } - (void)testSpeedPJPEG { [self runMeasurement:@"speed" format:@"pjpg" block:^{ [self runSpeedTest:TIPImageTypeJPEG options:TIPImageEncodingProgressive]; }]; } - (void)testSaveJPEG2000 { [self runSaveTest:TIPImageTypeJPEG2000 options:0 extension:@"j2k" quality:JPEG2000_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeJPEG2000 options:0 extension:@"j2k" quality:JPEG2000_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"j2k" block:^{ [self runSaveTest:TIPImageTypeJPEG2000 options:0 extension:@"j2k" quality:JPEG2000_QUALITY_OK image:sImageContainer]; }]; } - (void)testXLoadJPEG2000 { [self runLoadTest:TIPImageTypeJPEG2000 options:0 extension:@"j2k" quality:JPEG2000_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeJPEG2000 options:0 extension:@"j2k" quality:JPEG2000_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"j2k" block:^{ [self runLoadTest:TIPImageTypeJPEG2000 options:0 extension:@"j2k" quality:JPEG2000_QUALITY_OK isAnimated:NO]; }]; } - (void)testSpeedJPEG2000 { [self runMeasurement:@"speed" format:@"j2k" block:^{ [self runSpeedTest:TIPImageTypeJPEG2000 options:0]; }]; } - (void)testSavePNG { [self runMeasurement:@"save" format:@"png" block:^{ [self runSaveTest:TIPImageTypePNG options:0 extension:@"png" quality:1.0f image:sImageContainer]; }]; } - (void)testXLoadPNG { [self runMeasurement:@"load" format:@"png" block:^{ [self runLoadTest:TIPImageTypePNG options:0 extension:@"png" quality:1.0f isAnimated:NO]; }]; } - (void)testSpeedPNG { [self runMeasurement:@"speed" format:@"png" block:^{ [self runSpeedTest:TIPImageTypePNG options:0]; }]; } - (void)testSaveIPNG { [self runMeasurement:@"save" format:@"ipng" block:^{ [self runSaveTest:TIPImageTypePNG options:TIPImageEncodingProgressive extension:@"i.png" quality:1.0f image:sImageContainer]; }]; } - (void)testXLoadIPNG { [self runMeasurement:@"load" format:@"ipng" block:^{ [self runLoadTest:TIPImageTypePNG options:TIPImageEncodingProgressive extension:@"i.png" quality:1.0f isAnimated:NO]; }]; } - (void)testSpeedIPNG { [self runMeasurement:@"speed" format:@"ipng" block:^{ [self runSpeedTest:TIPImageTypePNG options:TIPImageEncodingProgressive]; }]; } - (void)testSaveTIFF { [self runMeasurement:@"save" format:@"tiff" block:^{ [self runSaveTest:TIPImageTypeTIFF options:0 extension:@"tiff" quality:1.0f image:sImageContainer]; }]; } - (void)testXLoadTIFF { [self runMeasurement:@"load" format:@"tiff" block:^{ [self runLoadTest:TIPImageTypeTIFF options:0 extension:@"tiff" quality:1.0f isAnimated:NO]; }]; } - (void)testSpeedTIFF { [self runMeasurement:@"speed" format:@"tiff" block:^{ [self runSpeedTest:TIPImageTypeTIFF options:0]; }]; } - (void)testSaveBMP { [self runMeasurement:@"save" format:@"bmp" block:^{ [self runSaveTest:TIPImageTypeBMP options:0 extension:@"bmp" quality:1.0f image:sImageContainer]; }]; } - (void)testXLoadBMP { [self runMeasurement:@"load" format:@"bmp" block:^{ [self runLoadTest:TIPImageTypeBMP options:0 extension:@"bmp" quality:1.0f isAnimated:NO]; }]; } - (void)testSpeedBMP { [self runMeasurement:@"speed" format:@"bmp" block:^{ [self runSpeedTest:TIPImageTypeBMP options:0]; }]; } - (void)testSaveTGA { [self runMeasurement:@"save" format:@"tga" block:^{ [self runSaveTest:TIPImageTypeTARGA options:0 extension:@"tga" quality:1.0f image:sImageContainer]; }]; } - (void)testXLoadTGA { [self runMeasurement:@"load" format:@"tga" block:^{ [self runLoadTest:TIPImageTypeTARGA options:0 extension:@"tga" quality:1.0f isAnimated:NO]; }]; } - (void)testSpeedTGA { [self runMeasurement:@"speed" format:@"tga" block:^{ [self runSpeedTest:TIPImageTypeTARGA options:0]; }]; } - (void)testSaveHEIC { if (!CODEC_FULLY_SUPPORTED(TIPImageTypeHEIC)) { return; } [self runSaveTest:TIPImageTypeHEIC options:0 extension:@"heic" quality:HEIF_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeHEIC options:0 extension:@"heic" quality:HEIF_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"heic" block:^{ [self runSaveTest:TIPImageTypeHEIC options:0 extension:@"heic" quality:HEIF_QUALITY_OK image:sImageContainer]; }]; } - (void)testXLoadHEIC { if (!CODEC_FULLY_SUPPORTED(TIPImageTypeHEIC)) { return; } [self runLoadTest:TIPImageTypeHEIC options:0 extension:@"heic" quality:HEIF_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeHEIC options:0 extension:@"heic" quality:HEIF_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"heic" block:^{ [self runLoadTest:TIPImageTypeHEIC options:0 extension:@"heic" quality:HEIF_QUALITY_OK isAnimated:NO]; }]; } - (void)testSpeedHEIC { if (!CODEC_FULLY_SUPPORTED(TIPImageTypeHEIC)) { return; } [self runMeasurement:@"speed" format:@"heic" block:^{ [self runSpeedTest:TIPImageTypeHEIC options:0]; }]; } - (void)testSaveAVCI { if (!CODEC_FULLY_SUPPORTED(TIPImageTypeAVCI)) { return; } [self runSaveTest:TIPImageTypeAVCI options:0 extension:@"avci" quality:HEIF_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeAVCI options:0 extension:@"avci" quality:HEIF_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"avci" block:^{ [self runSaveTest:TIPImageTypeAVCI options:0 extension:@"avci" quality:HEIF_QUALITY_OK image:sImageContainer]; }]; } - (void)testXLoadAVCI { if (!CODEC_FULLY_SUPPORTED(TIPImageTypeAVCI)) { return; } [self runLoadTest:TIPImageTypeAVCI options:0 extension:@"avci" quality:HEIF_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeAVCI options:0 extension:@"avci" quality:HEIF_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"avci" block:^{ [self runLoadTest:TIPImageTypeAVCI options:0 extension:@"avci" quality:HEIF_QUALITY_OK isAnimated:NO]; }]; } - (void)testSpeedAVCI { if (!CODEC_FULLY_SUPPORTED(TIPImageTypeAVCI)) { return; } [self runMeasurement:@"speed" format:@"avci" block:^{ [self runSpeedTest:TIPImageTypeAVCI options:0]; }]; } #pragma mark Less Supported Image Formats tests - (void)runLoadTestForReadOnlyFormat:(NSString *)format imageType:(NSString *)imageType { NSBundle *thisBundle = TIPTestsResourceBundle(); NSString *imagePath = [thisBundle pathForResource:@"sample" ofType:format]; NSData *data = [NSData dataWithContentsOfFile:imagePath]; XCTAssertGreaterThan(data.length, (NSUInteger)0, @"%@", imagePath); NSUInteger detectedAnimatedFrameCount = 0; TIPImageEncodingOptions detectedOptions = 0; NSString *detectedType = TIPDetectImageType(data, &detectedOptions, &detectedAnimatedFrameCount, YES); XCTAssertEqualObjects(detectedType, imageType, @"%@", imagePath); XCTAssertEqual(0, detectedOptions); XCTAssertEqual(NO, (detectedAnimatedFrameCount > 1), @"%@", imagePath); TIPImageContainer *container = [TIPImageContainer imageContainerWithData:data decoderConfigMap:nil codecCatalogue:nil]; NSTimeInterval decompressTime = [self decompressImage:container.image]; XCTAssertNotNil(container, @"%@", imagePath); if (detectedAnimatedFrameCount > 1) { XCTAssertTrue(container.isAnimated, @"Format: %@, imageType: %@", format, detectedType); } else { XCTAssertFalse(container.isAnimated, @"Format: %@, imageType: %@", format, detectedType); } NSLog(@"%@ decompress time: %fs", imagePath.lastPathComponent, decompressTime); } - (void)runLoadTestForUnreadableFormat:(NSString *)format { NSBundle *thisBundle = TIPTestsResourceBundle(); NSString *imagePath = [thisBundle pathForResource:@"sample" ofType:format]; NSData *data = [NSData dataWithContentsOfFile:imagePath]; XCTAssertGreaterThan(data.length, (NSUInteger)0, @"format='%@', file='%@'", format, imagePath); NSUInteger detectedAnimatedFrameCount = 0; TIPImageEncodingOptions detectedOptions = 0; NSString *detectedType = TIPDetectImageType(data, &detectedOptions, &detectedAnimatedFrameCount, YES); XCTAssertNil(detectedType, @"%@", imagePath); UIImage *image = [UIImage imageWithData:data]; XCTAssertNil(image, @"%@", imagePath); } - (void)runAttemptToSaveImageAsReadOnlyImageType:(NSString *)imageType { NSError *error = nil; NSData *imageData = nil; imageData = [sImageContainer.image tip_writeToDataWithType:imageType encodingOptions:0 quality:1.f animationLoopCount:0 animationFrameDurations:nil error:&error]; XCTAssertNil(imageData); XCTAssertNotNil(error); XCTAssertEqualObjects(error.domain, TIPErrorDomain); XCTAssertEqual(error.code, TIPErrorCodeEncodingUnsupported); XCTAssertEqualObjects(error.userInfo[@"imageType"], imageType); error = nil; imageData = [[TIPImageCodecCatalogue sharedInstance] encodeImage:sImageContainer withImageType:imageType quality:1.f options:0 error:&error]; XCTAssertNil(imageData); XCTAssertNotNil(error); XCTAssertEqualObjects(error.domain, TIPErrorDomain); XCTAssertEqual(error.code, TIPErrorCodeEncodingUnsupported); XCTAssertEqualObjects(error.userInfo[@"imageType"], imageType); } - (void)testSavePICT { [self runAttemptToSaveImageAsReadOnlyImageType:TIPImageTypePICT]; } - (void)testXLoadPICT { #if TARGET_OS_MACCATALYST [self runLoadTestForReadOnlyFormat:@"pict" imageType:TIPImageTypePICT]; #else [self runLoadTestForUnreadableFormat:@"pict"]; #endif } - (void)testSpeedPICT { // unsupported with read only format } - (void)testSaveQTIF { [self runAttemptToSaveImageAsReadOnlyImageType:TIPImageTypeQTIF]; } - (void)testXLoadQTIF { // Cannot even figure out how to create a qif file to test this :P // [self runLoadTestForUnreadableFormat:@"qif"]; } - (void)testSpeedQTIF { // unsupported with read only format } - (void)testSaveICO { [self runAttemptToSaveImageAsReadOnlyImageType:TIPImageTypeICO]; } - (void)testXLoadICO { if (TIPImageTypeCanReadWithImageIO(TIPImageTypeICO)) { [self runMeasurement:@"load" format:@"*ico" block:^{ [self runLoadTestForReadOnlyFormat:@"ico" imageType:TIPImageTypeICO]; }]; } else { [self runLoadTestForUnreadableFormat:@"ico"]; } } - (void)testSpeedICO { // unsupported with read only format } - (void)testSaveICNS { [self runAttemptToSaveImageAsReadOnlyImageType:TIPImageTypeICNS]; } - (void)testXLoadICNS { if (TIPImageTypeCanReadWithImageIO(TIPImageTypeICNS)) { [self runMeasurement:@"load" format:@"*icns" block:^{ [self runLoadTestForReadOnlyFormat:@"icns" imageType:TIPImageTypeICNS]; }]; } else { [self runLoadTestForUnreadableFormat:@"icns"]; } } - (void)testSpeedICNS { // unsupported with read only format } - (void)testSaveRAW { [self runAttemptToSaveImageAsReadOnlyImageType:TIPImageTypeRAW]; } - (void)testXLoadRAW { #if TARGET_OS_IOS [self runMeasurement:@"load" format:@"*cr2" block:^{ [self runLoadTestForReadOnlyFormat:@"cr2" imageType:TIPImageTypeRAW]; }]; #else [self runLoadTestForUnreadableFormat:@"cr2"]; #endif } - (void)testSpeedRAW { // unsupported with read only format } #if !TARGET_OS_TV - (void)testSaveWebP { XCTAssertNil([[TIPImageCodecCatalogue sharedInstance] codecForImageType:TIPImageTypeWEBP].tip_encoder); /* If Apple adds the WebP encoder if ([[TIPImageCodecCatalogue sharedInstance] codecForImageType:TIPImageTypeWEBP].tip_encoder) { [self runSaveTest:TIPImageTypeWEBP options:0 extension:@"apl.webp" quality:WEBP_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeWEBP options:0 extension:@"apl.webp" quality:WEBP_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"apl.webp" block:^{ [self runSaveTest:TIPImageTypeWEBP options:0 extension:@"apl.webp" quality:WEBP_QUALITY_OK image:sImageContainer]; }]; } */ PLUG_IN_WEBP(); [self runSaveTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_PERFECT image:sImageContainer]; [self runSaveTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_GOOD image:sImageContainer]; [self runMeasurement:@"save" format:@"webp" block:^{ [self runSaveTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_OK image:sImageContainer]; }]; } #endif #if !TARGET_OS_TV - (void)testXLoadWebP { if ([[TIPImageCodecCatalogue sharedInstance] codecForImageType:TIPImageTypeWEBP].tip_decoder) { [self runLoadTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"apl.webp" block:^{ [self runLoadTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_OK isAnimated:NO]; }]; } else { [self runLoadTestForUnreadableFormat:@"webp"]; } PLUG_IN_WEBP(); [self runLoadTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_PERFECT isAnimated:NO]; [self runLoadTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_GOOD isAnimated:NO]; [self runMeasurement:@"load" format:@"webp" block:^{ [self runLoadTest:TIPImageTypeWEBP options:0 extension:@"webp" quality:WEBP_QUALITY_OK isAnimated:NO]; }]; } #endif #if !TARGET_OS_TV - (void)testSpeedWebP { XCTAssertNil([[TIPImageCodecCatalogue sharedInstance] codecForImageType:@"webp"].tip_encoder); /* If Apple adds the WebP encoder if ([[TIPImageCodecCatalogue sharedInstance] codecForImageType:TIPImageTypeWEBP].tip_encoder) { [self runMeasurement:@"speed" format:@"apl.webp" block:^{ [self runSpeedTest:TIPImageTypeWEBP options:0]; }]; } */ PLUG_IN_WEBP(); [self runMeasurement:@"speed" format:@"webp" block:^{ [self runSpeedTest:TIPImageTypeWEBP options:0]; }]; } #endif #if !TARGET_OS_TV - (void)testRobustnessWebP { // Go through decoding the webp image 1 byte added at a time // to ensure there is no bug in our decoder on any metadata boundaries TIPXWebPCodec *webpCodec = [[TIPXWebPCodec alloc] initWithPreferredCodec:nil]; id<TIPImageDecoder> webpDecoder = webpCodec.tip_decoder; NSData *imageData = [NSData dataWithContentsOfFile:[TIPTestsResourceBundle() pathForResource:@"tenor_test2" ofType:@"webp"] options:0 error:nil]; XCTAssertNotNil(imageData); id<TIPImageDecoderContext> webpContext = [webpDecoder tip_initiateDecoding:nil expectedDataLength:imageData.length buffer:nil]; TIPImageContainer *progressImage = nil; for (NSRange range = NSMakeRange(0, 1); range.location < imageData.length; range.location++) { @autoreleasepool { NSData *nextByte = [imageData tip_safeSubdataNoCopyWithRange:range error:NULL]; XCTAssert(nextByte); const TIPImageDecoderAppendResult result = [webpDecoder tip_append:webpContext data:nextByte]; const NSUInteger priorFrameCount = progressImage.frameCount; progressImage = [webpDecoder tip_renderImage:webpContext renderMode:TIPImageDecoderRenderModeAnyProgress targetDimensions:CGSizeMake(120, 120) targetContentMode:UIViewContentModeScaleAspectFit]; XCTAssertGreaterThanOrEqual(progressImage.frameCount, priorFrameCount); if (!progressImage) { XCTAssertLessThan(result, TIPImageDecoderAppendResultDidLoadFrame); } else if (priorFrameCount == 0) { XCTAssertGreaterThan(progressImage.frameCount, priorFrameCount); } } } const TIPImageDecoderAppendResult finalizeResult = [webpDecoder tip_finalizeDecoding:webpContext]; XCTAssertEqual(finalizeResult, TIPImageDecoderAppendResultDidCompleteLoading); TIPImageContainer *finalImage = [webpDecoder tip_renderImage:webpContext renderMode:TIPImageDecoderRenderModeAnyProgress targetDimensions:CGSizeMake(120, 120) targetContentMode:UIViewContentModeScaleAspectFit]; XCTAssertEqual(finalImage.frameCount, 20); } #endif #pragma mark Animated Formats R+W tests - (void)testSaveAnimatedGIF { [self runMeasurement:@"save" format:@"gif" block:^{ [self runSaveTest:TIPImageTypeGIF options:0 extension:@"gif" quality:1.0f image:sAnimatedImageContainer]; }]; } - (void)testXLoadAnimatedGIF { [self runMeasurement:@"load" format:@"gif" block:^{ [self runLoadTest:TIPImageTypeGIF options:0 extension:@"gif" quality:1.0f isAnimated:YES]; }]; } - (void)testSpeedAnimatedGIF { [self runMeasurement:@"speed" format:@"gif" block:^{ [self runSpeedTest:TIPImageTypeGIF options:0]; }]; } - (void)testSaveAnimatedPNG { [self runMeasurement:@"save" format:@"apng" block:^{ [self runSaveTest:TIPImageTypePNG options:0 extension:@"apng" quality:1.0f image:sAnimatedImageContainer]; }]; } - (void)testXLoadAnimatedPNG { [self runMeasurement:@"load" format:@"apng" block:^{ [self runLoadTest:TIPImageTypePNG options:0 extension:@"apng" quality:1.0f isAnimated:YES]; }]; } - (void)testSpeedAnimatedPNG { [self runMeasurement:@"speed" format:@"apng" block:^{ [self runSpeedTest:TIPImageTypePNG options:0]; }]; } - (void)testSaveAnimatedWEBP { // noop } - (void)testXLoadAnimatedWEBP { PLUG_IN_WEBP(); // need to "seed" the test file since we don't have the encoder NSString *srcFile = [TIPTestsResourceBundle() pathForResource:@"fireworks_original" ofType:@"webp"]; NSString *tmpDir = NSTemporaryDirectory(); NSString *tmpFile = [tmpDir stringByAppendingPathComponent:@"test.100.awebp"]; NSFileManager *fm = [NSFileManager defaultManager]; [fm createDirectoryAtPath:tmpDir withIntermediateDirectories:YES attributes:nil error:NULL]; [fm copyItemAtPath:srcFile toPath:tmpFile error:NULL]; tip_defer(^{ [fm removeItemAtPath:tmpFile error:NULL]; }); // run the actual test TIPImageContainer *container = [TIPImageContainer imageContainerWithFilePath:tmpFile decoderConfigMap:nil codecCatalogue:nil memoryMap:YES]; if ([TIPXWebPCodec hasAnimationDecoding]) { XCTAssertNotNil(container.image); XCTAssertEqual((NSUInteger)10, container.frameCount); } else { XCTAssertNil(container); } } - (void)testSpeedAnimatedWEBP { // noop } - (void)testSaveAnimatedMP4 { // noop } - (void)testXLoadAnimatedMP4 { PLUG_IN_MP4(); // need to "seed" the test file since we don't have the encoder NSString *srcFile = [TIPTestsResourceBundle() pathForResource:@"200w" ofType:@"mp4"]; NSString *tmpDir = NSTemporaryDirectory(); NSString *tmpFile = [tmpDir stringByAppendingPathComponent:@"test.100.mp4"]; NSFileManager *fm = [NSFileManager defaultManager]; [fm createDirectoryAtPath:tmpDir withIntermediateDirectories:YES attributes:nil error:NULL]; [fm copyItemAtPath:srcFile toPath:tmpFile error:NULL]; tip_defer(^{ [fm removeItemAtPath:tmpFile error:NULL]; }); // run the actual test TIPImageContainer *container = [TIPImageContainer imageContainerWithFilePath:tmpFile decoderConfigMap:nil codecCatalogue:nil memoryMap:YES]; XCTAssertNotNil(container.image); XCTAssertEqual((NSUInteger)35, container.frameCount); } - (void)testSpeedAnimatedMP4 { // noop } #pragma mark Robustness Tests - (void)testDataDribbleJPEG { // test an image by appending 1 byte at a time TIPImageContainer *scaledImage = [sImageContainer scaleToTargetDimensions:CGSizeMake(48, 48) contentMode:UIViewContentModeScaleAspectFit]; id<TIPImageCodec> jpegCodec = [[TIPImageCodecCatalogue sharedInstance] codecForImageType:TIPImageTypeJPEG]; id<TIPImageDecoder> jpegDecoder = jpegCodec.tip_decoder; NSData *data = [[TIPImageCodecCatalogue sharedInstance] encodeImage:scaledImage withImageType:TIPImageTypeJPEG quality:kTIPAppleQualityValueRepresentingJFIFQuality85 options:0 error:NULL]; XCTAssertGreaterThan(data.length, (NSUInteger)0); id<TIPImageDecoderContext> decoderContext = [jpegDecoder tip_initiateDecoding:nil expectedDataLength:data.length buffer:nil]; TIPImageDecoderAppendResult result = TIPImageDecoderAppendResultDidProgress; NSUInteger counts[4] = { 0 }; const Byte * dataBytePtr = data.bytes; const Byte * dataBytePtrEnd = dataBytePtr + data.length; for (; dataBytePtr < dataBytePtrEnd; dataBytePtr++) { result = [jpegDecoder tip_append:decoderContext data:[NSData dataWithBytesNoCopy:(void *)dataBytePtr length:1 freeWhenDone:NO]]; counts[result]++; } result = [jpegDecoder tip_finalizeDecoding:decoderContext]; counts[result]++; TIPImageContainer *decodedImage = [jpegDecoder tip_renderImage:decoderContext renderMode:TIPImageDecoderRenderModeCompleteImage targetDimensions:CGSizeZero targetContentMode:UIViewContentModeCenter]; XCTAssertNotNil(decodedImage); XCTAssertTrue(CGSizeEqualToSize(decodedImage.dimensions, scaledImage.dimensions)); } #pragma mark Test Functions - (void)testImageWriteToFile { TIPSetDebugSTOPOnAssertEnabled(NO); NSString *tmpPath = [[[NSTemporaryDirectory() stringByAppendingPathComponent:@"test"] stringByAppendingPathExtension:[NSUUID UUID].UUIDString] stringByAppendingPathExtension:@"jpg"]; UIImage *image = nil; NSString *path = nil; BOOL success = NO; #define TEST_WRITE(shouldSucceed) \ @try { \ success = [image tip_writeToFile:path type:TIPImageTypeJPEG encodingOptions:0 quality:1.f animationLoopCount:0 animationFrameDurations:nil atomically:YES error:NULL]; \ } \ @catch (NSException *exception) { \ success = NO; \ } \ if (shouldSucceed) { \ XCTAssertTrue(success); \ } else { \ XCTAssertFalse(success); \ } path = tmpPath; TEST_WRITE(NO); image = sImageContainer.image; path = nil; TEST_WRITE(NO); path = tmpPath; TEST_WRITE(YES); [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:NULL]; } - (void)testTypeSupportsProgressiveLoading { XCTAssertEqual([[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsProgressiveLoading:TIPImageTypeJPEG2000], NO); XCTAssertEqual([[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsProgressiveLoading:TIPImageTypeJPEG], YES); XCTAssertEqual([[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsProgressiveLoading:TIPImageTypePNG], NO); // for now XCTAssertEqual([[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsProgressiveLoading:nil], NO); } - (BOOL)_typeHasProgressiveVariant:(NSString *)type { return [[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsProgressiveLoading:type]; } - (void)testTypeHasProgressiveVariant { XCTAssertEqual([self _typeHasProgressiveVariant:TIPImageTypeJPEG], YES); XCTAssertEqual([self _typeHasProgressiveVariant:TIPImageTypeJPEG2000], NO); XCTAssertEqual([self _typeHasProgressiveVariant:TIPImageTypePNG], NO); XCTAssertEqual([self _typeHasProgressiveVariant:nil], NO); } - (void)testGetMemorySize { XCTAssertEqual((CGFloat)1.0, sImageContainer.image.scale); NSUInteger pixelSize = (NSUInteger)TEST_IMAGE_WIDTH * (NSUInteger)TEST_IMAGE_HEIGHT; XCTAssertEqual([sImageContainer.image tip_estimatedSizeInBytes], pixelSize * 4); CGSize size = sImageContainer.image.size; XCTAssertEqual(TIPEstimateMemorySizeOfImageWithSettings(size, 1.0, 3, 1), pixelSize * 3); XCTAssertEqual(TIPEstimateMemorySizeOfImageWithSettings(size, 2.0, 3, 1), pixelSize * 3 * 4); XCTAssertEqual(TIPEstimateMemorySizeOfImageWithSettings(size, 1.0, 4, 1), pixelSize * 4); XCTAssertEqual(TIPEstimateMemorySizeOfImageWithSettings(size, 2.0, 4, 1), pixelSize * 4 * 4); XCTAssertEqual(TIPEstimateMemorySizeOfImageWithSettings(CGSizeZero, 1.0, 4, 1), (NSUInteger)0); } - (void)testImageType { XCTAssertNil(TIPImageTypeFromUTType((__bridge NSString *)kUTTypeImage)); XCTAssertEqualObjects(TIPImageTypeJPEG, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeJPEG)); XCTAssertEqualObjects(TIPImageTypeJPEG2000, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeJPEG2000)); XCTAssertEqualObjects(TIPImageTypeTIFF, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeTIFF)); XCTAssertEqualObjects(TIPImageTypeGIF, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeGIF)); XCTAssertEqualObjects(TIPImageTypePNG, TIPImageTypeFromUTType((__bridge NSString *)kUTTypePNG)); XCTAssertEqualObjects(TIPImageTypeBMP, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeBMP)); XCTAssertEqualObjects(TIPImageTypeTARGA, TIPImageTypeFromUTType(@"com.truevision.tga-image")); XCTAssertTrue(UTTypeConformsTo(CFSTR("com.truevision.tga-image"), kUTTypeImage)); XCTAssertEqualObjects(TIPImageTypePICT, TIPImageTypeFromUTType((__bridge NSString *)kUTTypePICT)); XCTAssertEqualObjects(TIPImageTypeQTIF, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeQuickTimeImage)); XCTAssertEqualObjects(TIPImageTypeICNS, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeAppleICNS)); XCTAssertEqualObjects(TIPImageTypeICO, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeICO)); XCTAssertEqualObjects(TIPImageTypeRAW, TIPImageTypeFromUTType((__bridge NSString *)kUTTypeRawImage)); XCTAssertEqualObjects(TIPImageTypeRAW, TIPImageTypeFromUTType(@"com.canon.cr2-raw-image")); // read XCTAssertFalse(TIPImageTypeCanReadWithImageIO((__bridge NSString *)kUTTypeImage)); XCTAssertFalse(TIPImageTypeCanReadWithImageIO((__bridge NSString *)kUTTypeMPEG2Video)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeJPEG)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeJPEG2000)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeTIFF)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeGIF)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypePNG)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeBMP)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeTARGA)); XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeICO)); #if TARGET_OS_IOS XCTAssertTrue(TIPImageTypeCanReadWithImageIO(@"com.canon.cr2-raw-image")); #else XCTAssertFalse(TIPImageTypeCanReadWithImageIO(@"com.canon.cr2-raw-image")); #endif #if TARGET_OS_MACCATALYST XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypePICT)); #else XCTAssertFalse(TIPImageTypeCanReadWithImageIO(TIPImageTypePICT)); #endif XCTAssertFalse(TIPImageTypeCanReadWithImageIO(TIPImageTypeQTIF)); XCTAssertFalse(TIPImageTypeCanReadWithImageIO(TIPImageTypeRAW)); #if TARGET_OS_IOS if (tip_available_ios_11) { XCTAssertTrue(TIPImageTypeCanReadWithImageIO(TIPImageTypeICNS)); } else { XCTAssertFalse(TIPImageTypeCanReadWithImageIO(TIPImageTypeICNS)); } #else XCTAssertFalse(TIPImageTypeCanReadWithImageIO(TIPImageTypeICNS)); #endif // write XCTAssertFalse(TIPImageTypeCanWriteWithImageIO((__bridge NSString *)kUTTypeImage)); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO((__bridge NSString *)kUTTypeMPEG2Video)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypeJPEG)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypeJPEG2000)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypeTIFF)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypeGIF)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypePNG)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypeBMP)); XCTAssertTrue(TIPImageTypeCanWriteWithImageIO(TIPImageTypeTARGA)); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO(@"com.canon.cr2-raw-image")); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO(TIPImageTypeICO)); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO(TIPImageTypePICT)); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO(TIPImageTypeQTIF)); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO(TIPImageTypeRAW)); XCTAssertFalse(TIPImageTypeCanWriteWithImageIO(TIPImageTypeICNS)); // matching with catalogue #define ASSERT_CATALOGUE_MATCHES_IO(type) \ do { \ XCTAssertEqual(TIPImageTypeCanReadWithImageIO((type)), [[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsDecoding:(type)]); \ XCTAssertEqual(TIPImageTypeCanWriteWithImageIO((type)), [[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsEncoding:(type)]); \ } while (0) ASSERT_CATALOGUE_MATCHES_IO((__bridge NSString *)kUTTypeImage); ASSERT_CATALOGUE_MATCHES_IO((__bridge NSString *)kUTTypeMPEG2Video); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeJPEG); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeJPEG2000); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeTIFF); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeGIF); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypePNG); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeBMP); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeTARGA); ASSERT_CATALOGUE_MATCHES_IO(TIPImageTypeICO); #if TARGET_OS_IOS XCTAssertNotEqual(TIPImageTypeCanReadWithImageIO(@"com.canon.cr2-raw-image"), [[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsDecoding:@"com.canon.cr2-raw-image"]); XCTAssertEqual(TIPImageTypeCanWriteWithImageIO(@"com.canon.cr2-raw-image"), [[TIPImageCodecCatalogue sharedInstance] codecWithImageTypeSupportsEncoding:@"com.canon.cr2-raw-image"]); #endif } - (void)testMatchesTargetDimensionsAndContentMode { CGSize targetDimensions; // Equal target and source targetDimensions = sImageContainer.dimensions; XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); // Smaller target targetDimensions = sImageContainer.dimensions; targetDimensions.width /= 2.f; targetDimensions.height /= 2.f; XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); // Larger target targetDimensions = sImageContainer.dimensions; targetDimensions.width *= 2.f; targetDimensions.height *= 2.f; XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); // Larger height targetDimensions = sImageContainer.dimensions; targetDimensions.height *= 2.f; XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); // Smaller height targetDimensions = sImageContainer.dimensions; targetDimensions.height /= 2.f; XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); // Larger width targetDimensions = sImageContainer.dimensions; targetDimensions.width *= 2.f; XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); // Smaller width targetDimensions = sImageContainer.dimensions; targetDimensions.width /= 2.f; XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeTopLeft]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeRedraw]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]); XCTAssertFalse([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]); XCTAssertTrue([sImageContainer.image tip_matchesTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]); } - (void)testScaled { UIImage *scaledImage; CGSize targetDimensions, scaledDimensions, imageDimensions; imageDimensions = sImageContainer.dimensions; @autoreleasepool { // Zero size targetDimensions = CGSizeZero; scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Matching size targetDimensions = imageDimensions; scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Smaller size targetDimensions = imageDimensions; targetDimensions.width = (CGFloat)ceil(targetDimensions.width / 2.0); targetDimensions.height = (CGFloat)ceil(targetDimensions.height / 2.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)1.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)1.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Larger size targetDimensions = imageDimensions; targetDimensions.width *= 2.f; targetDimensions.height *= 2.f; scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)2.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)2.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Smaller width targetDimensions = imageDimensions; targetDimensions.width = (CGFloat)ceil(targetDimensions.width / 2.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(imageDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(imageDimensions.height / 2.0, scaledDimensions.height, (CGFloat)1.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Larger width targetDimensions = imageDimensions; targetDimensions.width *= 2.f; scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(imageDimensions.height * 2.0, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(imageDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Smaller height targetDimensions = imageDimensions; targetDimensions.height = (CGFloat)ceil(targetDimensions.height / 2.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(imageDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(imageDimensions.width / 2.0, scaledDimensions.width, (CGFloat)1.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } @autoreleasepool { // Larger height targetDimensions = imageDimensions; targetDimensions.height *= 2.f; scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleToFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFill]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(targetDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(imageDimensions.width * 2.0, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeScaleAspectFit]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualWithAccuracy(imageDimensions.height, scaledDimensions.height, (CGFloat)0.0); XCTAssertEqualWithAccuracy(targetDimensions.width, scaledDimensions.width, (CGFloat)0.0); scaledImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:targetDimensions contentMode:UIViewContentModeCenter]; scaledDimensions = [scaledImage tip_dimensions]; XCTAssertEqualObjects(sImageContainer.image, scaledImage); } } - (void)testFixOrientation { UIImage *sourceImage = [sImageContainer.image tip_scaledImageWithTargetDimensions:CGSizeMake(1024, 768) contentMode:UIViewContentModeScaleToFill]; #if SLOW_IMAGE_CHECK NSData *originalImagePNGData = UIImagePNGRepresentationUndeprecated(sourceImage); #endif UIImage *modifiedImage = nil; UIImage *fixedImage = nil; XCTAssertEqual(UIImageOrientationUp, sourceImage.imageOrientation); // Up @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationUp]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertEqualObjects(modifiedImage, fixedImage); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // Down @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationDown]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // Left @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationLeft]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // Right @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationRight]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // UpMirrored @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationUpMirrored]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // DownMirrored @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationDownMirrored]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // LeftMirrored @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationLeftMirrored]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } // RightMirrored @autoreleasepool { modifiedImage = [self imageByRotatingImage:sourceImage andSettingOrientation:UIImageOrientationRightMirrored]; fixedImage = [modifiedImage tip_orientationAdjustedImage]; XCTAssertNotEqual(modifiedImage.imageOrientation, sourceImage.imageOrientation); XCTAssertEqual(fixedImage.imageOrientation, sourceImage.imageOrientation); #if SLOW_IMAGE_CHECK XCTAssertNotEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(modifiedImage)); XCTAssertEqualObjects(originalImagePNGData, UIImagePNGRepresentationUndeprecated(fixedImage)); #endif modifiedImage = nil; fixedImage = nil; } } - (UIImage *)imageByRotatingImage:(UIImage *)image andSettingOrientation:(UIImageOrientation)orientation { UIImageOrientation antiOrientation = orientation; switch (orientation) { case UIImageOrientationDown: case UIImageOrientationUpMirrored: case UIImageOrientationDownMirrored: case UIImageOrientationLeftMirrored: case UIImageOrientationRightMirrored: antiOrientation = orientation; break; case UIImageOrientationLeft: antiOrientation = UIImageOrientationRight; break; case UIImageOrientationRight: antiOrientation = UIImageOrientationLeft; break; case UIImageOrientationUp: default: return image; } UIImage *anti = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:antiOrientation]; anti = [anti tip_orientationAdjustedImage]; anti = [UIImage imageWithCGImage:anti.CGImage scale:anti.scale orientation:orientation]; return anti; } #pragma mark Image Alpha - (void)_runTestImageHasAlpha:(UIImage *)image hasAlphaPixel:(BOOL)hasAlphaPixel { NSArray * const colorSpaces = @[ [TestColorSpace colorSpaceWithOwnedRef:CGColorSpaceCreateDeviceGray() validParamSets:@[ PARAM_SET_INT(kCGImageAlphaNone, kCGBitmapByteOrderDefault), PARAM_SET_INT(kCGImageAlphaOnly, kCGBitmapByteOrderDefault), #if !TARGET_OS_IPHONE // == !(IOS + WATCHOS + TVOS) [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNone byteOrder:kCGBitmapByteOrderDefault bytesPerComponent:2], PARAM_SET_FLOAT(kCGImageAlphaNone, kCGBitmapByteOrderDefault), PARAM_SET_FLOAT(kCGImageAlphaNone, kCGBitmapByteOrder32Little), PARAM_SET_FLOAT(kCGImageAlphaNone, kCGBitmapByteOrder32Big), #endif // !TARGET_OS_IPHONE ]], [TestColorSpace colorSpaceWithOwnedRef:CGColorSpaceCreateDeviceRGB() validParamSets:@[ PARAM_SET_INT(kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrderDefault), PARAM_SET_INT(kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little), PARAM_SET_INT(kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Big), PARAM_SET_INT(kCGImageAlphaNoneSkipLast, kCGBitmapByteOrderDefault), PARAM_SET_INT(kCGImageAlphaNoneSkipLast, kCGBitmapByteOrder32Little), PARAM_SET_INT(kCGImageAlphaNoneSkipLast, kCGBitmapByteOrder32Big), PARAM_SET_INT(kCGImageAlphaPremultipliedFirst, kCGBitmapByteOrderDefault), PARAM_SET_INT(kCGImageAlphaPremultipliedFirst, kCGBitmapByteOrder32Little), PARAM_SET_INT(kCGImageAlphaPremultipliedFirst, kCGBitmapByteOrder32Big), PARAM_SET_INT(kCGImageAlphaPremultipliedLast, kCGBitmapByteOrderDefault), PARAM_SET_INT(kCGImageAlphaPremultipliedLast, kCGBitmapByteOrder32Little), PARAM_SET_INT(kCGImageAlphaPremultipliedLast, kCGBitmapByteOrder32Big), #if !TARGET_OS_IPHONE // == !(IOS + WATCHOS + TVOS) /* Skipping: 16 bits per pixel, 5 bits per component, kCGImageAlphaNoneSkipFirst */ [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaPremultipliedLast byteOrder:kCGBitmapByteOrderDefault bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaPremultipliedLast byteOrder:kCGBitmapByteOrder32Little bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaPremultipliedLast byteOrder:kCGBitmapByteOrder32Big bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaPremultipliedLast byteOrder:kCGBitmapByteOrder16Little bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaPremultipliedLast byteOrder:kCGBitmapByteOrder16Big bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNoneSkipLast byteOrder:kCGBitmapByteOrderDefault bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNoneSkipLast byteOrder:kCGBitmapByteOrder32Little bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNoneSkipLast byteOrder:kCGBitmapByteOrder32Big bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNoneSkipLast byteOrder:kCGBitmapByteOrder16Little bytesPerComponent:2], [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNoneSkipLast byteOrder:kCGBitmapByteOrder16Big bytesPerComponent:2], PARAM_SET_FLOAT(kCGImageAlphaNoneSkipLast, kCGBitmapByteOrderDefault), PARAM_SET_FLOAT(kCGImageAlphaPremultipliedLast, kCGBitmapByteOrderDefault), #endif // !TARGET_OS_IPHONE ]], [TestColorSpace colorSpaceWithOwnedRef:CGColorSpaceCreateDeviceCMYK() validParamSets:@[ #if !TARGET_OS_IPHONE // == !(IOS + WATCHOS + TVOS) PARAM_SET_INT(kCGImageAlphaNone, kCGBitmapByteOrderDefault), [TestParamSet integerParamSetWithAlphaInfo:kCGImageAlphaNone byteOrder:kCGBitmapByteOrderDefault bytesPerComponent:2], PARAM_SET_FLOAT(kCGImageAlphaNone, kCGBitmapByteOrderDefault), #endif // !TARGET_OS_IPHONE ]], ]; for (TestColorSpace *colorSpace in colorSpaces) { for (TestParamSet *paramSet in colorSpace.validParamSets) { for (uint32_t useExactBufferSize = 0; useExactBufferSize <= 1; useExactBufferSize++) { UIImage *testImage = [self _testImageHasAlpha:image hasAlphaPixel:hasAlphaPixel colorSpace:colorSpace.colorSpace paramSet:paramSet useExactBufferSize:(BOOL)useExactBufferSize]; if (!testImage) { continue; } BOOL hasAlphaComponent = NO; BOOL alphaOnly = NO; switch (paramSet.alphaInfo) { case kCGImageAlphaOnly: alphaOnly = YES; case kCGImageAlphaPremultipliedLast: case kCGImageAlphaPremultipliedFirst: case kCGImageAlphaLast: case kCGImageAlphaFirst: hasAlphaComponent = YES; break; case kCGImageAlphaNoneSkipFirst: case kCGImageAlphaNoneSkipLast: case kCGImageAlphaNone: default: break; } if (!hasAlphaComponent) { XCTAssertFalse([testImage tip_hasAlpha:NO]); XCTAssertFalse([testImage tip_hasAlpha:YES]); } else { XCTAssertTrue([testImage tip_hasAlpha:NO]); if (hasAlphaPixel) { XCTAssertTrue([testImage tip_hasAlpha:YES]); } else { XCTAssertFalse([testImage tip_hasAlpha:YES]); } } } } } } - (UIImage *)_testImageHasAlpha:(UIImage *)image hasAlphaPixel:(BOOL)hasAlphaPixel colorSpace:(CGColorSpaceRef)colorSpace paramSet:(TestParamSet *)paramSet useExactBufferSize:(BOOL)useExactBufferSize { CGBitmapInfo bitmapInfo = 0; if (paramSet.useFloat) { bitmapInfo |= kCGBitmapFloatComponents; } bitmapInfo |= paramSet.byteOrder & kCGBitmapByteOrderMask; bitmapInfo |= paramSet.alphaInfo; BOOL hasAlphaComponent = NO; BOOL alphaOnly = NO; switch (paramSet.alphaInfo) { case kCGImageAlphaOnly: alphaOnly = YES; case kCGImageAlphaPremultipliedLast: case kCGImageAlphaPremultipliedFirst: case kCGImageAlphaLast: case kCGImageAlphaFirst: hasAlphaComponent = YES; break; case kCGImageAlphaNoneSkipFirst: case kCGImageAlphaNoneSkipLast: hasAlphaComponent = YES; // component, but empty break; case kCGImageAlphaNone: default: break; } CGSize size = [image tip_dimensions]; size_t bytesPerRow = 0; if (useExactBufferSize) { bytesPerRow = paramSet.bytesPerComponent; if (!alphaOnly) { bytesPerRow *= (hasAlphaComponent ? 1 : 0) + CGColorSpaceGetNumberOfComponents(colorSpace); } bytesPerRow *= (size_t)size.width; } CGContextRef cgContext = CGBitmapContextCreate(NULL, // auto buffer (size_t)size.width, (size_t)size.height, paramSet.bytesPerComponent * 8, bytesPerRow, colorSpace, bitmapInfo); TIPDeferRelease(cgContext); if (!cgContext) { return nil; } CGContextDrawImage(cgContext, CGRectMake(0, 0, size.width, size.height), image.CGImage); CGImageRef cgImage = CGBitmapContextCreateImage(cgContext); TIPDeferRelease(cgImage); UIImage *testImage = [UIImage imageWithCGImage:cgImage]; return testImage; } - (void)testImageHasAlpha { setenv("CGBITMAP_CONTEXT_LOG_ERRORS", "1", 0); NSBundle *bundle = TIPTestsResourceBundle(); NSData *dataNoAlpha = [NSData dataWithContentsOfFile:[bundle pathForResource:@"noAlpha" ofType:@"png"]]; NSData *dataSomeAlpha = [NSData dataWithContentsOfFile:[bundle pathForResource:@"someAlpha" ofType:@"png"]]; NSData *dataAllAlpha = [NSData dataWithContentsOfFile:[bundle pathForResource:@"allAlpha" ofType:@"png"]]; UIImage *imageNoAlpha = [UIImage imageWithData:dataNoAlpha]; UIImage *imageSomeAlpha = [UIImage imageWithData:dataSomeAlpha]; UIImage *imageAllAlpha = [UIImage imageWithData:dataAllAlpha]; [self _runTestImageHasAlpha:imageNoAlpha hasAlphaPixel:NO]; [self _runTestImageHasAlpha:imageSomeAlpha hasAlphaPixel:YES]; [self _runTestImageHasAlpha:imageAllAlpha hasAlphaPixel:YES]; } - (void)testTIPImageTypeMatchesUTType { XCTAssertEqualObjects((NSString *)kUTTypeJPEG, TIPImageTypeJPEG); XCTAssertEqualObjects((NSString *)kUTTypeJPEG2000, TIPImageTypeJPEG2000); XCTAssertEqualObjects((NSString *)kUTTypeTIFF, TIPImageTypeTIFF); XCTAssertEqualObjects((NSString *)kUTTypePICT, TIPImageTypePICT); XCTAssertEqualObjects((NSString *)kUTTypeGIF, TIPImageTypeGIF); XCTAssertEqualObjects((NSString *)kUTTypePNG, TIPImageTypePNG); XCTAssertEqualObjects((NSString *)kUTTypeQuickTimeImage, TIPImageTypeQTIF); XCTAssertEqualObjects((NSString *)kUTTypeAppleICNS, TIPImageTypeICNS); XCTAssertEqualObjects((NSString *)kUTTypeBMP, TIPImageTypeBMP); XCTAssertEqualObjects(@"com.truevision.tga-image", TIPImageTypeTARGA); XCTAssertEqualObjects((NSString *)kUTTypeICO, TIPImageTypeICO); XCTAssertEqualObjects((NSString *)kUTTypeRawImage, TIPImageTypeRAW); } @end