From 634f4800aa8d956d920bb677ecc7474b6ca59bfd Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 16:36:12 +0800 Subject: [PATCH 1/5] Support libavif loop count This requires libavif 1.0.0+ --- Cartfile | 2 +- Package.resolved | 16 ++++++++-------- Package.swift | 2 +- SDWebImageAVIFCoder.podspec | 4 ++-- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 13 +++++++++++-- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Cartfile b/Cartfile index bd1a409..8cb2ebe 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "SDWebImage/SDWebImage" ~> 5.10 -github "SDWebImage/libavif-Xcode" >= 0.11.2-rc1 \ No newline at end of file +github "SDWebImage/libavif-Xcode" >= 1.0.0 \ No newline at end of file diff --git a/Package.resolved b/Package.resolved index f464b7f..afa585d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/SDWebImage/libaom-Xcode.git", "state": { "branch": null, - "revision": "482cafbebbc5f32378b82339b7580761fab4fd23", - "version": "2.0.2" + "revision": "b00c20d10f13608c7579aad1f849e0f815d4d3a8", + "version": "3.0.0" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/SDWebImage/libavif-Xcode.git", "state": { "branch": null, - "revision": "28be85d8693b8bc2ea3a4d323caf652e740b4683", - "version": "0.9.1" + "revision": "0dc978b08b9bf8b7d1b505f1cb15218e8f637a6c", + "version": "1.0.0" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/SDWebImage/libvmaf-Xcode.git", "state": { "branch": null, - "revision": "26544e92506764862358ce2198ddab9af7685ed5", - "version": "2.2.0" + "revision": "41db5dc11d05c02d1aca7de0b572a068f528c37c", + "version": "2.3.1" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", "state": { "branch": null, - "revision": "a6b6e44eadf0d39250c10a7cc0e3b91d0bdb0e94", - "version": "5.10.4" + "revision": "36e79ba485e9bb4d3cd4e3318908866dac5e7b51", + "version": "5.21.5" } } ] diff --git a/Package.swift b/Package.swift index 6198bb4..0bcaeac 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.10.0"), - .package(url: "https://github.com/SDWebImage/libavif-Xcode.git", from: "0.11.0") + .package(url: "https://github.com/SDWebImage/libavif-Xcode.git", from: "1.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index e90b0fc..ebca2d1 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.11.1' + s.version = '0.12.0' s.summary = 'A SDWebImage coder plugin to support AVIF(AV1 Image File Format) image' # This description is used to generate tags and improve search results. @@ -40,6 +40,6 @@ Which is built based on the open-sourced libavif codec. } s.dependency 'SDWebImage', '~> 5.10' - s.dependency 'libavif/core', '>= 0.11.0' + s.dependency 'libavif/core', '>= 1.0.0' s.libraries = 'c++' end diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index d413328..7f2c935 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -182,6 +182,11 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) // Animated image NSMutableArray *frames = [NSMutableArray array]; + // if repetitionCount is a non-negative integer `n`, then the image sequence should be played back `n + 1` times. + int loopCount = decoder->repetitionCount + 1; + if (loopCount < 0) { + loopCount = 0; + } while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) { @autoreleasepool { CGImageRef originImageRef = SDCreateCGImageFromAVIF(decoder->image); @@ -209,7 +214,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) avifDecoderDestroy(decoder); UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames]; - animatedImage.sd_imageLoopCount = 0; + animatedImage.sd_imageLoopCount = loopCount; animatedImage.sd_imageFormat = SDImageFormatAVIF; return animatedImage; @@ -361,7 +366,11 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOp } // TODO: Optimize the performance like WebPCoder (frame meta cache, etc) _frameCount = decoder->imageCount; - _loopCount = 0; + int loopCount = decoder->repetitionCount + 1; + if (loopCount < 0) { + loopCount = 0; + } + _loopCount = loopCount; _hasAnimation = decoder->imageCount > 1; CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; From 9273aefe9fc90f7e9eb629368f18f974b97ae7ac Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 17:09:50 +0800 Subject: [PATCH 2/5] Update the calculation for quality/qualityAlpha because of deprecation --- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 7f2c935..09a2290 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -318,15 +318,13 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm if (options[SDImageCoderEncodeCompressionQuality]) { compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue]; } - int rescaledQuality = AVIF_QUANTIZER_WORST_QUALITY - (int)((compressionQuality) * AVIF_QUANTIZER_WORST_QUALITY); + int quality = compressionQuality * (AVIF_QUALITY_BEST - AVIF_QUALITY_WORST); avifRWData raw = AVIF_DATA_EMPTY; avifEncoder *encoder = avifEncoderCreate(); encoder->codecChoice = codecChoice; - encoder->minQuantizer = rescaledQuality; - encoder->maxQuantizer = rescaledQuality; - encoder->minQuantizerAlpha = rescaledQuality; - encoder->maxQuantizerAlpha = rescaledQuality; + encoder->quality = quality; + encoder->qualityAlpha = quality; encoder->maxThreads = 2; avifResult result = avifEncoderWrite(encoder, avif, &raw); From bc5f74ddcc202460d846bc490fd734cca28a36f4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 17:10:17 +0800 Subject: [PATCH 3/5] Support thumbnail encoding --- .../Classes/SDImageAVIFCoder.m | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 09a2290..5ef2647 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -319,6 +319,15 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue]; } int quality = compressionQuality * (AVIF_QUALITY_BEST - AVIF_QUALITY_WORST); + CGSize maxPixelSize = CGSizeZero; + NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize]; + if (maxPixelSizeValue != nil) { +#if SD_MAC + maxPixelSize = maxPixelSizeValue.sizeValue; +#else + maxPixelSize = maxPixelSizeValue.CGSizeValue; +#endif + } avifRWData raw = AVIF_DATA_EMPTY; avifEncoder *encoder = avifEncoderCreate(); @@ -326,6 +335,19 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm encoder->quality = quality; encoder->qualityAlpha = quality; encoder->maxThreads = 2; + // Check if need to scale pixel size + CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(width, height) scaleSize:maxPixelSize preserveAspectRatio:YES shouldScaleUp:NO]; + if (!CGSizeEqualToSize(scaledSize, CGSizeMake(width, height))) { + // Thumbnail Encoding + assert(scaledSize.width <= width); + assert(scaledSize.height <= height); + avifScalingMode scale; + scale.horizontal.n = (int)scaledSize.width; + scale.horizontal.d = (int)width; + scale.vertical.n = (int)scaledSize.height; + scale.vertical.d = (int)height; + encoder->scalingMode = scale; + } avifResult result = avifEncoderWrite(encoder, avif, &raw); avifImageDestroy(avif); From 7c7d8fcfc2c9101d969b6e7f557ed6decbece994 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 17:13:41 +0800 Subject: [PATCH 4/5] Upgrade the encode ICCData logic --- .../Classes/SDImageAVIFCoder.m | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 5ef2647..6d3bd5f 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -310,9 +310,27 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm avifImageRGBToYUV(avif, &rgb); free(dest.data); - NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile([SDImageCoderHelper colorSpaceGetDeviceRGB]); - - avifImageSetProfileICC(avif, (uint8_t *)iccProfile.bytes, iccProfile.length); + // We must prefer the input CGImage's color space, which may contains ICC profile + CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef); + // We only supports RGB colorspace, filter the un-supported one (like Monochrome, CMYK, etc) + if (CGColorSpaceGetModel(colorSpace) != kCGColorSpaceModelRGB) { + // Ignore and convert, we don't know how to encode this colorspace directlly to WebP + // This may cause little visible difference because of colorpsace conversion + colorSpace = NULL; + } + if (!colorSpace) { + colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB]; + } + // Add ICC profile if present + CFDataRef iccData = NULL; + if (colorSpace) { + if (@available(iOS 10, tvOS 10, macOS 10.12, watchOS 3, *)) { + iccData = CGColorSpaceCopyICCData(colorSpace); + } + } + if (iccData && CFDataGetLength(iccData) > 0) { + avifImageSetProfileICC(avif, CFDataGetBytePtr(iccData), CFDataGetLength(iccData)); + } double compressionQuality = 1; if (options[SDImageCoderEncodeCompressionQuality]) { From 2b09dfc2b5cf4a57ba975cdf7dc7116e2e787d90 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 6 Feb 2026 17:15:27 +0800 Subject: [PATCH 5/5] Update readme --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 6dfd1b8..ae206e1 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,24 @@ let avifData = SDImageAVIFCoder.shared.encodedData(with: image, format: .avif, o let lossyAVIFData = SDImageAVIFCoder.shared.encodedData(with: image, format: .avif, options: [.encodeCompressionQuality: 0.1]) // [0, 1] compression quality ``` +### Thumbnail Encoding (0.12.0+) + ++ Objective-C + +```objective-c +// AVIF image thumbnail encoding +UIImage *image; +NSData *thumbnailAVIFData = [[SDImageAVIFCoder sharedCoder] encodedDataWithImage:image format:SDImageFormatAVIF options:@{SDImageCoderEncodeMaxPixelSize : @(CGSizeMake(200, 200))}]; // encoding max pixel size +``` + ++ Swift + +```swift +// AVIF image thumbnail encoding +let image: UIImage +let thumbnailAVIFData = SDImageAVIFCoder.shared.encodedData(with: image, format: .AVIF, options: [.encodeMaxPixelSize: CGSize(width: 200, height: 200)]) // encoding max pixel size +``` + ## Screenshot