From f84678a44d506d8518bf5e5c35185581ee453f97 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 20 Apr 2020 12:22:09 +0800 Subject: [PATCH 1/3] Add the support for embed thumbnail for JPEG/HEIF/AVIF --- SDWebImage/Core/SDImageCoder.h | 8 ++++++++ SDWebImage/Core/SDImageCoder.m | 1 + SDWebImage/Core/SDImageIOAnimatedCoder.m | 5 +++++ SDWebImage/Core/SDImageIOCoder.m | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/SDWebImage/Core/SDImageCoder.h b/SDWebImage/Core/SDImageCoder.h index fe5ca0cc..fe4c5f27 100644 --- a/SDWebImage/Core/SDImageCoder.h +++ b/SDWebImage/Core/SDImageCoder.h @@ -79,6 +79,14 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxPixelSi */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxFileSize; +/** + A Boolean value indicating the encoding format should contains a thumbnail image into the output data. Only some of image format (like JPEG/HEIF/AVIF) support this behavior. The embed thumbnail will be used during next time thumbnail decoding (provided `.thumbnailPixelSize`), which is faster than full image thumbnail decoding. (NSNumber) + Defaults to NO, which does not embed any thumbnail. + @note The thumbnail image's pixel size is not defined, the encoder can choose the proper pixel size which is suitable for encoding quality. + @note works for `SDImageCoder` + */ +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumbnail; + /** A SDWebImageContext object which hold the original context options from top-level API. (SDWebImageContext) This option is ignored for all built-in coders and take no effect. diff --git a/SDWebImage/Core/SDImageCoder.m b/SDWebImage/Core/SDImageCoder.m index 37dfdc41..0fda1983 100644 --- a/SDWebImage/Core/SDImageCoder.m +++ b/SDWebImage/Core/SDImageCoder.m @@ -18,5 +18,6 @@ SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompress SDImageCoderOption const SDImageCoderEncodeBackgroundColor = @"encodeBackgroundColor"; SDImageCoderOption const SDImageCoderEncodeMaxPixelSize = @"encodeMaxPixelSize"; SDImageCoderOption const SDImageCoderEncodeMaxFileSize = @"encodeMaxFileSize"; +SDImageCoderOption const SDImageCoderEncodeEmbedThumbnail = @"encodeEmbedThumbnail"; SDImageCoderOption const SDImageCoderWebImageContext = @"webImageContext"; diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index 5fb28a9c..df6415c3 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -484,6 +484,11 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination // Remove the quality if we have file size limit properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil; } + BOOL embedThumbnail = NO; + if (options[SDImageCoderEncodeEmbedThumbnail]) { + embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue]; + } + properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail); BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue]; if (encodeFirstFrame || frames.count == 0) { diff --git a/SDWebImage/Core/SDImageIOCoder.m b/SDWebImage/Core/SDImageIOCoder.m index df93e14e..11565e12 100644 --- a/SDWebImage/Core/SDImageIOCoder.m +++ b/SDWebImage/Core/SDImageIOCoder.m @@ -294,6 +294,11 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination // Remove the quality if we have file size limit properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil; } + BOOL embedThumbnail = NO; + if (options[SDImageCoderEncodeEmbedThumbnail]) { + embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue]; + } + properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail); // Add your image to the destination. CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties); From d76fb76e0df56854ac06ea2796820fc05c494144 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 20 Apr 2020 17:43:04 +0800 Subject: [PATCH 2/3] Added the test cases`test19ThatEmbedThumbnailHEICWorks` to ensure the embed thumbnail works --- Tests/Tests/SDImageCoderTests.m | 58 ++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m index a4574f59..ca0e1c16 100644 --- a/Tests/Tests/SDImageCoderTests.m +++ b/Tests/Tests/SDImageCoderTests.m @@ -231,6 +231,48 @@ isVectorImage:YES]; } +- (void)test18ThatImageIOAnimatedCoderAbstractClass { + SDImageIOAnimatedCoder *coder = [[SDImageIOAnimatedCoder alloc] init]; + @try { + [coder canEncodeToFormat:SDImageFormatPNG]; + XCTFail("Should throw exception"); + } @catch (NSException *exception) { + expect(exception); + } +} + +- (void)test19ThatEmbedThumbnailHEICWorks { + if (@available(iOS 11, macOS 10.13, *)) { + // The input HEIC does not contains any embed thumbnail + NSURL *heicURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"heic"]; + CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)heicURL, nil); + expect(source).notTo.beNil(); + NSArray *thumbnailImages = [self thumbnailImagesFromImageSource:source]; + expect(thumbnailImages.count).equal(0); + + CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil); + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; + CGImageRelease(imageRef); + // Encode with embed thumbnail + NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatHEIC options:@{SDImageCoderEncodeEmbedThumbnail : @(YES)}]; + + // The new HEIC contains one embed thumbnail + CGImageSourceRef source2 = CGImageSourceCreateWithData((__bridge CFDataRef)encodedData, nil); + expect(source2).notTo.beNil(); + NSArray *thumbnailImages2 = [self thumbnailImagesFromImageSource:source2]; + expect(thumbnailImages2.count).equal(1); + + // Currently ImageIO has no control to custom embed thumbnail pixel size, just check the behavior :) + NSDictionary *thumbnailImageInfo = thumbnailImages2.firstObject; + NSUInteger thumbnailWidth = [thumbnailImageInfo[(__bridge NSString *)kCGImagePropertyWidth] unsignedIntegerValue]; + NSUInteger thumbnailHeight = [thumbnailImageInfo[(__bridge NSString *)kCGImagePropertyHeight] unsignedIntegerValue]; + expect(thumbnailWidth).equal(320); + expect(thumbnailHeight).equal(212); + } +} + +#pragma mark - Utils + - (void)verifyCoder:(id)coder withLocalImageURL:(NSURL *)imageUrl supportsEncoding:(BOOL)supportsEncoding @@ -353,14 +395,14 @@ withLocalImageURL:(NSURL *)imageUrl } } -- (void)test16ThatImageIOAnimatedCoderAbstractClass { - SDImageIOAnimatedCoder *coder = [[SDImageIOAnimatedCoder alloc] init]; - @try { - [coder canEncodeToFormat:SDImageFormatPNG]; - XCTFail("Should throw exception"); - } @catch (NSException *exception) { - expect(exception); - } +- (NSArray *)thumbnailImagesFromImageSource:(CGImageSourceRef)source API_AVAILABLE(ios(11.0), tvos(11.0), macos(13.0)) { + NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil); + NSDictionary *fileProperties = properties[(__bridge NSString *)kCGImagePropertyFileContentsDictionary]; + NSArray *imagesProperties = fileProperties[(__bridge NSString *)kCGImagePropertyImages]; + NSDictionary *imageProperties = imagesProperties.firstObject; + NSArray *thumbnailImages = imageProperties[(__bridge NSString *)kCGImagePropertyThumbnailImages]; + + return thumbnailImages; } @end From 9a7544163476723c218d7f58143ee892b0ac679d Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 21 Apr 2020 17:49:40 +0800 Subject: [PATCH 3/3] Fix test case compile issue on macOS --- Tests/Tests/SDImageCoderTests.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m index ca0e1c16..eaf58ec9 100644 --- a/Tests/Tests/SDImageCoderTests.m +++ b/Tests/Tests/SDImageCoderTests.m @@ -251,7 +251,11 @@ expect(thumbnailImages.count).equal(0); CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil); - UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; +#if SD_UIKIT + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:1 orientation: UIImageOrientationUp]; +#else + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:1 orientation:kCGImagePropertyOrientationUp]; +#endif CGImageRelease(imageRef); // Encode with embed thumbnail NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatHEIC options:@{SDImageCoderEncodeEmbedThumbnail : @(YES)}];