From f9909c48a8cb4d83c155d5da98bb1d89a8ad6104 Mon Sep 17 00:00:00 2001 From: acefsm Date: Thu, 2 Dec 2021 13:53:36 +0300 Subject: [PATCH 01/37] fixed avifDecoder memory leak --- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 4ef1d67..0a81e83 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -131,6 +131,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; #endif CGImageRelease(imageRef); + avifDecoderDestroy(decoder); return image; } From 45a3d16da0992a82c04ff4513f9d8393eb19a73f Mon Sep 17 00:00:00 2001 From: acefsm Date: Thu, 2 Dec 2021 18:18:25 +0300 Subject: [PATCH 02/37] fixed avifDecoder memory leak --- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 0a81e83..15ed1e5 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -123,6 +123,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) } CGImageRef imageRef = SDCreateCGImageFromAVIF(decoder->image); if (!imageRef) { + avifDecoderDestroy(decoder); return nil; } #if SD_MAC From a8f4294099ddc640eeb81a699531781275d4b1ff Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 17 Dec 2021 18:10:16 +0800 Subject: [PATCH 03/37] Bumped version to 0.9.1 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index e3db3a0..54f502c 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.9.0' + s.version = '0.9.1' 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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index a8f353a..5491a06 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.0 + 0.9.1 CFBundleVersion - 0.9.0 + 0.9.1 From 661caadf5d117e0a024f4c223174da8315e54ab7 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 28 Mar 2022 16:29:36 +0800 Subject: [PATCH 04/37] Fix the build of SwiftPM which use the wrong umbrella header --- Package.resolved | 17 +++++++++++++---- SDWebImageAVIFCoder/Classes/ColorSpace.m | 2 +- SDWebImageAVIFCoder/Classes/Conversion.m | 2 +- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 2 +- _Pods.xcodeproj | 1 - 5 files changed, 16 insertions(+), 8 deletions(-) delete mode 120000 _Pods.xcodeproj diff --git a/Package.resolved b/Package.resolved index 0f610f2..f464b7f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/SDWebImage/libaom-Xcode.git", "state": { "branch": null, - "revision": "81e75e1663855b8b53f6e26ea1ae087ea8f6ccbc", - "version": "1.0.2" + "revision": "482cafbebbc5f32378b82339b7580761fab4fd23", + "version": "2.0.2" } }, { @@ -15,8 +15,17 @@ "repositoryURL": "https://github.com/SDWebImage/libavif-Xcode.git", "state": { "branch": null, - "revision": "d02dedabc1bf50f415a11aabbe5ad876a01251e9", - "version": "0.8.1" + "revision": "28be85d8693b8bc2ea3a4d323caf652e740b4683", + "version": "0.9.1" + } + }, + { + "package": "libvmaf", + "repositoryURL": "https://github.com/SDWebImage/libvmaf-Xcode.git", + "state": { + "branch": null, + "revision": "26544e92506764862358ce2198ddab9af7685ed5", + "version": "2.2.0" } }, { diff --git a/SDWebImageAVIFCoder/Classes/ColorSpace.m b/SDWebImageAVIFCoder/Classes/ColorSpace.m index dca0a8b..6a61f34 100644 --- a/SDWebImageAVIFCoder/Classes/ColorSpace.m +++ b/SDWebImageAVIFCoder/Classes/ColorSpace.m @@ -11,7 +11,7 @@ #import #import #else -#import "avif/avifs.h" +#import "avif/avif.h" #import "avif/internal.h" #endif diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index 3b0aa86..980a008 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -11,7 +11,7 @@ #import #import #else -#import "avif/avifs.h" +#import "avif/avif.h" #import "avif/internal.h" #endif #import "Private/ColorSpace.h" diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 15ed1e5..c5adcbc 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -13,7 +13,7 @@ #import #import #else -#import "avif/avifs.h" +#import "avif/avif.h" #import "avif/internal.h" #endif diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj deleted file mode 120000 index 3c5a8e7..0000000 --- a/_Pods.xcodeproj +++ /dev/null @@ -1 +0,0 @@ -Example/Pods/Pods.xcodeproj \ No newline at end of file From e385595a52d29137deb2027135cdc62ba796663c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 28 Mar 2022 16:41:44 +0800 Subject: [PATCH 05/37] Bumped version to 0.9.2 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 54f502c..f6e1987 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.9.1' + s.version = '0.9.2' 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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index 5491a06..aed7225 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.1 + 0.9.2 CFBundleVersion - 0.9.1 + 0.9.2 From 786c9afeb3c82b4b3f6d6042d90101e70fa8b781 Mon Sep 17 00:00:00 2001 From: "xinjiu.zw" Date: Thu, 26 May 2022 14:19:24 +0800 Subject: [PATCH 06/37] fixed typo,Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ed2864..6390430 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Note: From version 0.4.0, if you want to use rav1e or dav1e instead aom for fast ```ruby pod 'SDWebImageAVIFCoder' -pod 'libavif', :subpsecs => [ +pod 'libavif', :subspecs => [ 'libdav1d', 'librav1e' ] @@ -102,7 +102,7 @@ or, for libgav1 && SVT-AV1, use: ```ruby pod 'SDWebImageAVIFCoder' -pod 'libavif', :subpsecs => [ +pod 'libavif', :subspecs => [ 'libgva1', 'SVT-AV1' ] From 99e6c22fc5cbee648b41d79b91bb102f5f757034 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 11 Aug 2022 11:59:22 +0800 Subject: [PATCH 07/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6390430..33bd8fa 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ The framework through SwiftPM only supports libaom for AVIF decoding && encoding ```swift let package = Package( dependencies: [ - .package(url: "https://github.com/SDWebImage/SDWebImageAVIF.git", from: "0.5") + .package(url: "https://github.com/SDWebImage/SDWebImageAVIFCoder.git", from: "0.5") ] ) ``` From 2f74cf97b82193022daf446aa6646c2d47847596 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 11 Aug 2022 17:33:27 +0800 Subject: [PATCH 08/37] Fix the case that CGColorSync will cache the profile and so data buffer with `CFDataCreateWithBytesNoCopy` may fail on some cases Always copy the data buffer via `CFDataCreate` --- SDWebImageAVIFCoder/Classes/ColorSpace.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/ColorSpace.m b/SDWebImageAVIFCoder/Classes/ColorSpace.m index 6a61f34..f04163f 100644 --- a/SDWebImageAVIFCoder/Classes/ColorSpace.m +++ b/SDWebImageAVIFCoder/Classes/ColorSpace.m @@ -215,7 +215,7 @@ void SDAVIFCalcColorSpaceMono(avifImage * avif, CGColorSpaceRef* ref, BOOL* shou } if(avif->icc.data && avif->icc.size) { if(@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { - CFDataRef iccData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, avif->icc.data, avif->icc.size,kCFAllocatorNull); + CFDataRef iccData = CFDataCreate(kCFAllocatorDefault, avif->icc.data, avif->icc.size); *ref = CGColorSpaceCreateWithICCData(iccData); CFRelease(iccData); *shouldRelease = TRUE; @@ -313,7 +313,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul } if(avif->icc.data && avif->icc.size) { if(@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { - CFDataRef iccData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, avif->icc.data, avif->icc.size,kCFAllocatorNull); + CFDataRef iccData = CFDataCreate(kCFAllocatorDefault, avif->icc.data, avif->icc.size); *ref = CGColorSpaceCreateWithICCData(iccData); CFRelease(iccData); *shouldRelease = TRUE; From 632750736ad99f9bdab5793a876daa7979265f5c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 11 Aug 2022 17:37:22 +0800 Subject: [PATCH 09/37] Fix the crash because of free null pointer --- SDWebImageAVIFCoder/Classes/Conversion.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index 980a008..43e63e7 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -704,11 +704,11 @@ static CGImageRef CreateCGImage8(avifImage * avif) { } end_all: - free(resultBufferData); - free(argbBufferData); - free(dummyCbData); - free(dummyCrData); - free(scaledAlphaBufferData); + if (resultBufferData) free(resultBufferData); + if (argbBufferData) free(argbBufferData); + if (dummyCbData) free(dummyCbData); + if (dummyCrData) free(dummyCrData); + if (scaledAlphaBufferData) free(scaledAlphaBufferData); return result; } From d5b9708b51db81bd5cf5d2c333dfbf6442b7c573 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 11 Aug 2022 17:46:49 +0800 Subject: [PATCH 10/37] Fix the memory leak during AVIF encoding This because of some error handle does not release the buffer --- .../Classes/SDImageAVIFCoder.m | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index c5adcbc..d881b7e 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -210,12 +210,14 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm vImage_Buffer src; v_error = vImageBuffer_InitWithCGImage(&src, &srcFormat, NULL, imageRef, kvImageNoFlags); if (v_error != kvImageNoError) { + vImageConverter_Release(convertor); return nil; } vImage_Buffer dest; - vImageBuffer_Init(&dest, height, width, hasAlpha ? 32 : 24, kvImageNoFlags); - if (!dest.data) { - free(src.data); + v_error = vImageBuffer_Init(&dest, height, width, hasAlpha ? 32 : 24, kvImageNoFlags); + if (v_error != kvImageNoError) { + if (src.data) free(src.data); + vImageConverter_Release(convertor); return nil; } @@ -224,7 +226,7 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm free(src.data); vImageConverter_Release(convertor); if (v_error != kvImageNoError) { - free(dest.data); + if(dest.data) free(dest.data); return nil; } @@ -232,7 +234,7 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm avifImage *avif = avifImageCreate((int)width, (int)height, 8, avifFormat); if (!avif) { - free(dest.data); + if (dest.data) free(dest.data); return nil; } avifRGBImage rgb = { @@ -245,7 +247,6 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm }; avifImageRGBToYUV(avif, &rgb); free(dest.data); - dest.data = NULL; NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile([SDImageCoderHelper colorSpaceGetDeviceRGB]); @@ -264,14 +265,15 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm encoder->maxThreads = 2; avifResult result = avifEncoderWrite(encoder, avif, &raw); + avifImageDestroy(avif); + avifEncoderDestroy(encoder); if (result != AVIF_RESULT_OK) { - avifEncoderDestroy(encoder); + if (raw.data) avifRWDataFree(&raw); return nil; } NSData *imageData = [NSData dataWithBytes:raw.data length:raw.size]; - free(raw.data); - avifEncoderDestroy(encoder); + avifRWDataFree(&raw); return imageData; } From acf410092645d486aa5dd44b7608ae45af8eee5f Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 12 Aug 2022 12:20:47 +0800 Subject: [PATCH 11/37] Bumped version to 0.9.3 --- SDWebImageAVIFCoder.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index f6e1987..0e9e658 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.9.2' + s.version = '0.9.3' 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. From 9beb241999ecf054859b0f9e8ad1e43ae2f80273 Mon Sep 17 00:00:00 2001 From: doihei Date: Wed, 12 Oct 2022 14:19:45 +0900 Subject: [PATCH 12/37] [bug]fix include internal --- SDWebImageAVIFCoder/Classes/ColorSpace.m | 2 +- SDWebImageAVIFCoder/Classes/Conversion.m | 2 +- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/ColorSpace.m b/SDWebImageAVIFCoder/Classes/ColorSpace.m index f04163f..fb3ead8 100644 --- a/SDWebImageAVIFCoder/Classes/ColorSpace.m +++ b/SDWebImageAVIFCoder/Classes/ColorSpace.m @@ -7,7 +7,7 @@ #import "SDImageAVIFCoder.h" #import -#if __has_include() +#if __has_include() && __has_include() #import #import #else diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index 43e63e7..539efd8 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -7,7 +7,7 @@ #import "SDImageAVIFCoder.h" #import -#if __has_include() +#if __has_include() && __has_include() #import #import #else diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index d881b7e..40ac3d6 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -9,7 +9,7 @@ #import #import #import -#if __has_include() +#if __has_include() && __has_include() #import #import #else From eddb4530948dc75c47a7136b853c0942f47f8418 Mon Sep 17 00:00:00 2001 From: Ryo Hirafuji Date: Mon, 17 Oct 2022 14:44:49 +0900 Subject: [PATCH 13/37] Update ledyba-z's profile --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33bd8fa..82fb62b 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ DreamPiggy, lizhuoli1126@126.com ## Contributor -ledyba-z, ryo.hirafuji@link-u.co.jp +[ledyba-z](https://github.com/ledyba-z), ryo.hirafuji@gmail.com ## License From 334f61bb955b6ee0e3490a78ccef04e846affd32 Mon Sep 17 00:00:00 2001 From: bowen <1214569257@qq.com> Date: Thu, 15 Dec 2022 12:30:10 +0800 Subject: [PATCH 14/37] fix decode animated image leak --- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 40ac3d6..6036938 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -149,6 +149,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) #else UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; #endif + CGImageRelease(imageRef); NSTimeInterval duration = decoder->imageTiming.duration; // Should use `decoder->imageTiming`, not the `decoder->duration`, see libavif source code SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; [frames addObject:frame]; From 0f0003bb667050097492907e7909145545dbeff8 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 16 Dec 2022 16:53:37 +0800 Subject: [PATCH 15/37] Bumped version to 0.9.4 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 0e9e658..8304a4d 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.9.3' + s.version = '0.9.4' 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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index aed7225..f76045c 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.2 + 0.9.4 CFBundleVersion - 0.9.2 + 0.9.4 From 6559b550fbef86c0af4df1d3e0678aa57a167d9d Mon Sep 17 00:00:00 2001 From: bowen <1214569257@qq.com> Date: Mon, 30 Jan 2023 16:58:42 +0800 Subject: [PATCH 16/37] fix animated avif decode failed, os_unfair_lock crash --- SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 6036938..a0111e4 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -348,6 +348,7 @@ - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { SD_LOCK(_lock); avifResult decodeResult = avifDecoderNthImage(_decoder, (uint32_t)index); if (decodeResult != AVIF_RESULT_OK) { + SD_UNLOCK(_lock); return nil; } CGImageRef imageRef = SDCreateCGImageFromAVIF(_decoder->image); From dc7c0ba2640754a93e68fc1da18534a280eafc3b Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 30 Jan 2023 17:25:21 +0800 Subject: [PATCH 17/37] Bumped version to 0.9.5 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 8304a4d..628eb2f 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.9.4' + s.version = '0.9.5' 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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index f76045c..8432126 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.4 + 0.9.5 CFBundleVersion - 0.9.4 + 0.9.5 From 33ad65f995693898d48b4610888079a8a5f4924a Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 30 Jan 2023 17:33:06 +0800 Subject: [PATCH 18/37] Using Clang Analyzer and fix some memory issues --- SDWebImageAVIFCoder/Classes/Conversion.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index 539efd8..47a34ab 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -1129,6 +1129,7 @@ static CGImageRef CreateCGImage16U(avifImage * avif) { err = vImageConvert_Planar8toARGB8888(&alphaBuffer1, &alphaBuffer2, &monoBuffer1, &monoBuffer2, &resultBuffer, kvImageNoFlags); if(err != kvImageNoError) { free(resultBufferData); + resultBufferData = NULL; NSLog(@"Failed to convert Planar Alpha + Mono to MonoA: %ld", err); goto end_alpha_mono; } From ad88f8150339c96cbf5ccd595fb913133d1aee5c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 16 May 2023 15:23:52 +0800 Subject: [PATCH 19/37] Fix the compile issue with libavif 0.11.0+ by removing the limit alpha range check Update the min deps to 0.11.0+ as well --- Cartfile | 2 +- Package.swift | 2 +- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Classes/Conversion.m | 73 ++++-------------------- 4 files changed, 15 insertions(+), 64 deletions(-) diff --git a/Cartfile b/Cartfile index 025a051..11c87eb 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "SDWebImage/SDWebImage" ~> 5.10 -github "SDWebImage/libavif-Xcode" >= 0.9.1 \ No newline at end of file +github "SDWebImage/libavif-Xcode" >= 0.11.0 \ No newline at end of file diff --git a/Package.swift b/Package.swift index 67794d4..6198bb4 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.9.1") + .package(url: "https://github.com/SDWebImage/libavif-Xcode.git", from: "0.11.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 628eb2f..669e126 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -40,6 +40,6 @@ Which is built based on the open-sourced libavif codec. } s.dependency 'SDWebImage', '~> 5.10' - s.dependency 'libavif', '>= 0.9.1' + s.dependency 'libavif', '>= 0.11.0' s.libraries = 'c++' end diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index 47a34ab..b15e33a 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -155,10 +155,8 @@ static avifBool avifPrepareReformatState(const avifImage * image, const avifRGBI state->rgbMaxChannelF = (float)state->rgbMaxChannel; state->biasY = (state->yuvRange == AVIF_RANGE_LIMITED) ? (float)(16 << (state->yuvDepth - 8)) : 0.0f; state->biasUV = (float)(1 << (state->yuvDepth - 1)); - state->biasA = (image->alphaRange == AVIF_RANGE_LIMITED) ? (float)(16 << (state->yuvDepth - 8)) : 0.0f; state->rangeY = (float)((state->yuvRange == AVIF_RANGE_LIMITED) ? (219 << (state->yuvDepth - 8)) : state->yuvMaxChannel); state->rangeUV = (float)((state->yuvRange == AVIF_RANGE_LIMITED) ? (224 << (state->yuvDepth - 8)) : state->yuvMaxChannel); - state->rangeA = (float)((image->alphaRange == AVIF_RANGE_LIMITED) ? (219 << (state->yuvDepth - 8)) : state->yuvMaxChannel); uint32_t cpCount = 1 << image->depth; if (state->mode == AVIF_REFORMAT_MODE_IDENTITY) { @@ -386,6 +384,7 @@ static CGImageRef CreateCGImage8(avifImage * avif) { uint8_t const permuteMap[4] = {0, 1, 2, 3}; switch(avif->yuvFormat) { case AVIF_PIXEL_FORMAT_NONE: + case AVIF_PIXEL_FORMAT_COUNT: NSLog(@"Invalid pixel format."); goto end_all; case AVIF_PIXEL_FORMAT_YUV420: @@ -567,51 +566,11 @@ static CGImageRef CreateCGImage8(avifImage * avif) { if(hasAlpha) { // alpha vImage_Buffer alphaBuffer = {0}; - if(avif->alphaRange == AVIF_RANGE_LIMITED) { - float* floatAlphaBufferData = NULL; - floatAlphaBufferData = calloc(avif->width * avif->height, sizeof(float)); - scaledAlphaBufferData = calloc(avif->width * avif->height, sizeof(uint8_t)); - if(floatAlphaBufferData == NULL || scaledAlphaBufferData == NULL) { - err = kvImageMemoryAllocationError; - goto end_prepare_alpha; - } - vImage_Buffer origAlphaBuffer = { - .data = avif->alphaPlane, - .width = avif->width, - .height = avif->height, - .rowBytes = avif->alphaRowBytes, - }; - vImage_Buffer floatAlphaBuffer = { - .data = floatAlphaBufferData, - .width = avif->width, - .height = avif->height, - .rowBytes = avif->width * sizeof(float), - }; - alphaBuffer.width = avif->width; - alphaBuffer.height = avif->height; - alphaBuffer.data = scaledAlphaBufferData; - alphaBuffer.rowBytes = avif->width * sizeof(uint8_t); - err = vImageConvert_Planar8toPlanarF(&origAlphaBuffer, &floatAlphaBuffer, 255.0f, 0.0f, kvImageNoFlags); - if(err != kvImageNoError) { - NSLog(@"Failed to convert alpha planes from uint8 to float: %ld", err); - goto end_prepare_alpha; - } - err = vImageConvert_PlanarFtoPlanar8(&floatAlphaBuffer, &alphaBuffer, 235.0f, 16.0f, kvImageNoFlags); - if(err != kvImageNoError) { - NSLog(@"Failed to convert alpha planes from float to uint8: %ld", err); - goto end_prepare_alpha; - } - end_prepare_alpha: - free(floatAlphaBufferData); - if(err != kvImageNoError) { - goto end_alpha; - } - } else { - alphaBuffer.width = avif->width; - alphaBuffer.height = avif->height; - alphaBuffer.data = avif->alphaPlane; - alphaBuffer.rowBytes = avif->alphaRowBytes; - } + // libavif 0.11.0: alphaRange field was removed from the avifImage struct. It it presumed that alpha plane is always full range. + alphaBuffer.width = avif->width; + alphaBuffer.height = avif->height; + alphaBuffer.data = avif->alphaPlane; + alphaBuffer.rowBytes = avif->alphaRowBytes; if(monochrome) { // alpha_mono uint8_t* tmpBufferData = NULL; uint8_t* monoBufferData = NULL; @@ -861,22 +820,13 @@ static CGImageRef CreateCGImage16U(avifImage * avif) { }; float offset = 0.0f; float rangeMax = 0.0f; + // libavif 0.11.0: alphaRange field was removed from the avifImage struct. It it presumed that alpha plane is always full range. if(avif->depth == 10) { - if(avif->alphaRange == AVIF_RANGE_LIMITED) { - offset = 64.0f; - rangeMax = 940.0f; - } else { - offset = 0.0f; - rangeMax = 1023.0f; - } + offset = 0.0f; + rangeMax = 1023.0f; } else if(avif->depth == 12) { - if(avif->alphaRange == AVIF_RANGE_LIMITED) { - offset = 256.0f; - rangeMax = 3760.0f; - } else { - offset = 0.0f; - rangeMax = 4095.0f; - } + offset = 0.0f; + rangeMax = 4095.0f; } float const scale = (float)(rangeMax - offset) / 65535.0f; err = vImageConvert_16UToF(&origAlpha, &floatAlphaBuffer, 0.0f, 1.0f, kvImageNoFlags); @@ -919,6 +869,7 @@ static CGImageRef CreateCGImage16U(avifImage * avif) { uint8_t const permuteMap[4] = {0, 1, 2, 3}; switch(avif->yuvFormat) { case AVIF_PIXEL_FORMAT_NONE: + case AVIF_PIXEL_FORMAT_COUNT: NSLog(@"Invalid pixel format."); goto end_all; case AVIF_PIXEL_FORMAT_YUV420: From 7e2e3dbc382f01674eb7896c7f039b1736fbc6e8 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 16 May 2023 16:55:25 +0800 Subject: [PATCH 20/37] Added support for SDWebImage thumbnail decoding Use the coder helper's re-scale on CGImage currently May optimize using vImageScale in the future --- .../SDWebImageAVIFCoder/SDViewController.m | 7 +- .../Classes/SDImageAVIFCoder.m | 79 +++++++++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/Example/SDWebImageAVIFCoder/SDViewController.m b/Example/SDWebImageAVIFCoder/SDViewController.m index afad1d0..4c342bf 100644 --- a/Example/SDWebImageAVIFCoder/SDViewController.m +++ b/Example/SDWebImageAVIFCoder/SDViewController.m @@ -33,7 +33,7 @@ - (void)viewDidLoad [self.view addSubview:imageView1]; [self.view addSubview:imageView2]; - [imageView1 sd_setImageWithURL:AVIFURL placeholderImage:nil options:0 completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { + [imageView1 sd_setImageWithURL:AVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (image) { NSLog(@"Static AVIF load success"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -44,7 +44,10 @@ - (void)viewDidLoad }); } }]; - [imageView2 sd_setImageWithURL:animatedAVIFSURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { + CGSize animatedThumbnailSize = CGSizeMake(100, 100); + [imageView2 sd_setImageWithURL:animatedAVIFSURL placeholderImage:nil options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(animatedThumbnailSize)} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { + NSCAssert(image.size.width == 100, @"Thumbnail width should be 100"); + NSCAssert(image.size.height == 100, @"Thumbnail height should be 100"); if (image) { NSLog(@"Animated AVIFS load success"); } diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index a0111e4..3b8d1f1 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -67,7 +67,10 @@ @implementation SDImageAVIFCoder { CGFloat _scale; NSUInteger _loopCount; NSUInteger _frameCount; + BOOL _hasAnimation; SD_LOCK_DECLARE(_lock); + BOOL _preserveAspectRatio; + CGSize _thumbnailSize; } - (void)dealloc { @@ -93,14 +96,32 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) if (!data) { return nil; } + BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue]; CGFloat scale = 1; - if ([options valueForKey:SDImageCoderDecodeScaleFactor]) { - scale = [[options valueForKey:SDImageCoderDecodeScaleFactor] doubleValue]; + NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; + if (scaleFactor != nil) { + scale = [scaleFactor doubleValue]; if (scale < 1) { scale = 1; } } + CGSize thumbnailSize = CGSizeZero; + NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; + if (thumbnailSizeValue != nil) { +#if SD_MAC + thumbnailSize = thumbnailSizeValue.sizeValue; +#else + thumbnailSize = thumbnailSizeValue.CGSizeValue; +#endif + } + + BOOL preserveAspectRatio = YES; + NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; + if (preserveAspectRatioValue != nil) { + preserveAspectRatio = preserveAspectRatioValue.boolValue; + } + // Decode it avifDecoder * decoder = avifDecoderCreate(); avifDecoderSetIOMemory(decoder, data.bytes, data.length); @@ -113,15 +134,27 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) return nil; } + BOOL hasAnimation = decoder->imageCount > 1; + uint32_t width = decoder->image->width; + uint32_t height = decoder->image->height; + CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(width, height) scaleSize:thumbnailSize preserveAspectRatio:preserveAspectRatio shouldScaleUp:NO]; + // Static image - if (decoder->imageCount <= 1) { + if (!hasAnimation || decodeFirstFrame) { avifResult nextImageResult = avifDecoderNextImage(decoder); if (nextImageResult != AVIF_RESULT_OK) { NSLog(@"Failed to decode image: %s", avifResultToString(nextImageResult)); avifDecoderDestroy(decoder); return nil; } - CGImageRef imageRef = SDCreateCGImageFromAVIF(decoder->image); + CGImageRef originImageRef = SDCreateCGImageFromAVIF(decoder->image); + if (!originImageRef) { + avifDecoderDestroy(decoder); + return nil; + } + // TODO: optimization using vImageScale directly during transform + CGImageRef imageRef = [SDImageCoderHelper CGImageCreateScaled:originImageRef size:scaledSize]; + CGImageRelease(originImageRef); if (!imageRef) { avifDecoderDestroy(decoder); return nil; @@ -140,7 +173,13 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) NSMutableArray *frames = [NSMutableArray array]; while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) { @autoreleasepool { - CGImageRef imageRef = SDCreateCGImageFromAVIF(decoder->image); + CGImageRef originImageRef = SDCreateCGImageFromAVIF(decoder->image); + if (!originImageRef) { + continue; + } + // TODO: optimization using vImageScale directly during transform + CGImageRef imageRef = [SDImageCoderHelper CGImageCreateScaled:originImageRef size:scaledSize]; + CGImageRelease(originImageRef); if (!imageRef) { continue; } @@ -296,6 +335,7 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOp // TODO: Optimize the performance like WebPCoder (frame meta cache, etc) _frameCount = decoder->imageCount; _loopCount = 0; + _hasAnimation = decoder->imageCount > 1; CGFloat scale = 1; NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; if (scaleFactor != nil) { @@ -305,6 +345,22 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOp } } _scale = scale; + CGSize thumbnailSize = CGSizeZero; + NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; + if (thumbnailSizeValue != nil) { + #if SD_MAC + thumbnailSize = thumbnailSizeValue.sizeValue; + #else + thumbnailSize = thumbnailSizeValue.CGSizeValue; + #endif + } + _thumbnailSize = thumbnailSize; + BOOL preserveAspectRatio = YES; + NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; + if (preserveAspectRatioValue != nil) { + preserveAspectRatio = preserveAspectRatioValue.boolValue; + } + _preserveAspectRatio = preserveAspectRatio; _decoder = decoder; _imageData = data; SD_LOCK_INIT(_lock); @@ -345,14 +401,25 @@ - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { if (index >= _frameCount) { return nil; } + uint32_t width = 0; + uint32_t height = 0; SD_LOCK(_lock); avifResult decodeResult = avifDecoderNthImage(_decoder, (uint32_t)index); if (decodeResult != AVIF_RESULT_OK) { SD_UNLOCK(_lock); return nil; } - CGImageRef imageRef = SDCreateCGImageFromAVIF(_decoder->image); + width = _decoder->image->width; + height = _decoder->image->height; + CGImageRef originImageRef = SDCreateCGImageFromAVIF(_decoder->image); SD_UNLOCK(_lock); + if (!originImageRef) { + return nil; + } + CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(width, height) scaleSize:_thumbnailSize preserveAspectRatio:_preserveAspectRatio shouldScaleUp:NO]; + // TODO: optimization using vImageScale directly during transform + CGImageRef imageRef = [SDImageCoderHelper CGImageCreateScaled:originImageRef size:scaledSize]; + CGImageRelease(originImageRef); if (!imageRef) { return nil; } From 716093c79998a643d0984d49700b9807b19bfc9a Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 16 May 2023 17:08:36 +0800 Subject: [PATCH 21/37] Update README, example and test case --- .../project.pbxproj | 14 +++- .../Samples/fox.profile0.8bpc.yuv420.avif | Bin 0 -> 80743 bytes Example/Tests/Tests.m | 15 +++++ README.md | 60 ++++++++++++++++++ .../Classes/SDImageAVIFCoder.m | 2 + 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 Example/Tests/Samples/fox.profile0.8bpc.yuv420.avif diff --git a/Example/SDWebImageAVIFCoder.xcodeproj/project.pbxproj b/Example/SDWebImageAVIFCoder.xcodeproj/project.pbxproj index cfdfa4c..e4f706b 100644 --- a/Example/SDWebImageAVIFCoder.xcodeproj/project.pbxproj +++ b/Example/SDWebImageAVIFCoder.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 32B000392A137E8700FB2873 /* fox.profile0.8bpc.yuv420.avif in Resources */ = {isa = PBXBuildFile; fileRef = 32B000372A137DAA00FB2873 /* fox.profile0.8bpc.yuv420.avif */; }; 32D3263C226344EC001B208C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D3263B226344EC001B208C /* AppDelegate.m */; }; 32D3263F226344EC001B208C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D3263E226344EC001B208C /* ViewController.m */; }; 32D32641226344EC001B208C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32D32640226344EC001B208C /* Assets.xcassets */; }; @@ -1866,6 +1867,7 @@ 207C633217DCC3D0312C335C /* Pods-SDWebImageAVIFCoder_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDWebImageAVIFCoder_Example.release.xcconfig"; path = "Target Support Files/Pods-SDWebImageAVIFCoder_Example/Pods-SDWebImageAVIFCoder_Example.release.xcconfig"; sourceTree = ""; }; 2783F1D7B48DB3ED9ADFF864 /* libPods-SDWebImageAVIFCoder_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SDWebImageAVIFCoder_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3260C60722634CF90046E4C8 /* SDWebImageAVIFCoder_Example macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SDWebImageAVIFCoder_Example macOS.entitlements"; sourceTree = ""; }; + 32B000372A137DAA00FB2873 /* fox.profile0.8bpc.yuv420.avif */ = {isa = PBXFileReference; lastKnownFileType = file; path = fox.profile0.8bpc.yuv420.avif; sourceTree = ""; }; 32D32638226344EC001B208C /* SDWebImageAVIFCoder_Example macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SDWebImageAVIFCoder_Example macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32D3263A226344EC001B208C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 32D3263B226344EC001B208C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -3757,6 +3759,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 32B000362A137DAA00FB2873 /* Samples */ = { + isa = PBXGroup; + children = ( + 32B000372A137DAA00FB2873 /* fox.profile0.8bpc.yuv420.avif */, + ); + path = Samples; + sourceTree = ""; + }; 32D32639226344EC001B208C /* SDWebImageAVIFCoder_Example macOS */ = { isa = PBXGroup; children = ( @@ -3844,6 +3854,7 @@ 6003F5B5195388D20070C39A /* Tests */ = { isa = PBXGroup; children = ( + 32B000362A137DAA00FB2873 /* Samples */, 6D074D3D240E94FC002197C8 /* Images */, 6003F5BB195388D20070C39A /* Tests.m */, 6003F5B6195388D20070C39A /* Supporting Files */, @@ -7565,6 +7576,7 @@ 6D07558A240E9507002197C8 /* green.255x255.8bpc.yuv420.color.limited.without-alpha.avif in Resources */, 6D075676240E9508002197C8 /* blue.255x255.10bpc.yuv444.color.limited.with-alpha.avif in Resources */, 6D0756EC240E9508002197C8 /* e47a8c.256x255.12bpc.yuv422.color.limited.without-alpha.avif in Resources */, + 32B000392A137E8700FB2873 /* fox.profile0.8bpc.yuv420.avif in Resources */, 6D0755EE240E9508002197C8 /* gray.256x255.12bpc.yuv422.color.full.without-alpha.avif in Resources */, 6D07563D240E9508002197C8 /* e47a8c.256x256.8bpc.yuv444.color.limited.without-alpha.avif in Resources */, 6D0758C6240E9508002197C8 /* blue.255x255.10bpc.yuv420.mono.limited.with-alpha.avif in Resources */, @@ -8049,10 +8061,10 @@ "DEBUG=1", "$(inherited)", ); + HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private"; INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; - HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SDWebImageAVIFCoder_Example.app/SDWebImageAVIFCoder_Example"; WRAPPER_EXTENSION = xctest; }; diff --git a/Example/Tests/Samples/fox.profile0.8bpc.yuv420.avif b/Example/Tests/Samples/fox.profile0.8bpc.yuv420.avif new file mode 100644 index 0000000000000000000000000000000000000000..2bae4c713103e7dcc6698faef8e868eb7fa2bb14 GIT binary patch literal 80743 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 z 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 z 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" img:image.CGImage expectedColor: kRed8]; } +-(void)testThumbnailDecoding +{ + NSString *filename = @"fox.profile0.8bpc.yuv420.avif"; + NSString* imgBundle = [[NSBundle mainBundle] pathForResource:filename ofType:@""]; + NSData* imgData = [[NSData alloc] initWithContentsOfFile: imgBundle]; + UIImage *originImage = [self->coder decodedImageWithData:imgData options:nil]; + XCTAssertEqual(originImage.size.width, 1204); + XCTAssertEqual(originImage.size.height, 800); + + CGSize thumbnailSize = CGSizeMake(100, 100); + UIImage *thumbnailImage = [self->coder decodedImageWithData:imgData options:@{SDImageCoderDecodeThumbnailPixelSize: @(thumbnailSize)}]; + XCTAssertEqual(thumbnailImage.size.width, 100); + XCTAssertEqual(thumbnailImage.size.height, 67); +} + -(void)assertColor8: (NSString*)filename img:(CGImageRef)img expectedColor:(UInt8*)expectedColor { CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(img)); diff --git a/README.md b/README.md index 82fb62b..bc0b34b 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,66 @@ let imageView: UIImageView imageView.sd_setImage(with: url) ``` +### Decoding + ++ Objective-C + +```objective-c +// AVIF image decoding +NSData *avifData; +UIImage *image = [[SDImageAVIFCoder sharedCoder] decodedImageWithData:avifData options:nil]; +``` + ++ Swift + +```swift +// AVIF image decoding +let avifData: Data +let image = SDImageAVIFCoder.shared.decodedImage(with: data, options: nil) +``` + +### Thumbnail Decoding (0.10.0+) + ++ Objective-C + +```objective-c +// AVIF thumbnail image decoding +NSData *avifData; +CGSize thumbnailSize = CGSizeMake(300, 300); +UIImage *thumbnailImage = [[SDImageAVIFCoder sharedCoder] decodedImageWithData:avifData options:@{SDImageCoderDecodeThumbnailPixelSize : @(thumbnailSize)}]; +``` + ++ Swift + +```swift +// AVIF thumbnail image decoding +let avifData: Data +let thumbnailSize = CGSize(width: 300, height: 300) +let image = SDImageAVIFCoder.shared.decodedImage(with: data, options: [.decodeThumbnailPixelSize: thumbnailSize]) +``` + +### Encoding (No animtion support) + ++ Objective-c + +```objective-c +// AVIF image encoding +UIImage *image; +NSData *avifData = [[SDImageAVIFCoder sharedCoder] encodedDataWithImage:image format:SDImageFormatAVIF options:nil]; +// Encode Quality +NSData *lossyAVIFData = [[SDImageAVIFCoder sharedCoder] encodedDataWithImage:image format:SDImageFormatAVIF options:@{SDImageCoderEncodeCompressionQuality : @(0.1)}]; // [0, 1] compression quality +``` + ++ Swift + +```swift +// AVIF image encoding +let image: UIImage +let avifData = SDImageAVIFCoder.shared.encodedData(with: image, format: .avif, options: nil) +// Encode Quality +let lossyAVIFData = SDImageAVIFCoder.shared.encodedData(with: image, format: .avif, options: [.encodeCompressionQuality: 0.1]) // [0, 1] compression quality +``` + ## Screenshot diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index 3b8d1f1..d5c30af 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -302,6 +302,8 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm avifEncoder *encoder = avifEncoderCreate(); encoder->minQuantizer = rescaledQuality; encoder->maxQuantizer = rescaledQuality; + encoder->minQuantizerAlpha = rescaledQuality; + encoder->maxQuantizerAlpha = rescaledQuality; encoder->maxThreads = 2; avifResult result = avifEncoderWrite(encoder, avif, &raw); From 357b283ae65e2227cb83b1589706d8162cc00bc8 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 16 May 2023 17:16:34 +0800 Subject: [PATCH 22/37] Bumped version to 0.10.0 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 669e126..6d686ab 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.9.5' + s.version = '0.10.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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index 8432126..344075b 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.5 + 0.10.0 CFBundleVersion - 0.9.5 + 0.10.0 From 2cad7679c200ea1e648e2331d6bff25f920ce745 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 16 May 2023 17:39:42 +0800 Subject: [PATCH 23/37] Update the badge --- .github/workflows/check-image-decoding.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-image-decoding.yml b/.github/workflows/check-image-decoding.yml index 12d9219..83ea600 100644 --- a/.github/workflows/check-image-decoding.yml +++ b/.github/workflows/check-image-decoding.yml @@ -1,4 +1,4 @@ -name: Check the decoded images. +name: Image Decoding Test on: [push, pull_request] diff --git a/README.md b/README.md index bc0b34b..3c152be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SDWebImageAVIFCoder -[![CI Status](https://img.shields.io/travis/SDWebImage/SDWebImageAVIFCoder.svg?style=flat)](https://travis-ci.org/SDWebImage/SDWebImageAVIFCoder) +[![Build Status](https://github.com/SDWebImage/SDWebImageAVIFCoder/actions/workflows/check-image-decoding.yml/badge.svg)](https://github.com/SDWebImage/SDWebImageAVIFCoder/actions/workflows/check-image-decoding.yml) [![Version](https://img.shields.io/cocoapods/v/SDWebImageAVIFCoder.svg?style=flat)](https://cocoapods.org/pods/SDWebImageAVIFCoder) [![License](https://img.shields.io/cocoapods/l/SDWebImageAVIFCoder.svg?style=flat)](https://cocoapods.org/pods/SDWebImageAVIFCoder) [![Platform](https://img.shields.io/cocoapods/p/SDWebImageAVIFCoder.svg?style=flat)](https://cocoapods.org/pods/SDWebImageAVIFCoder) From 1b5a58c4dcaba392bf39431807da467cf323dd13 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 22 Jul 2023 23:06:00 +0800 Subject: [PATCH 24/37] Quick hack to support 10 bit HDR AVIF using PQ transfer function... https://en.wikipedia.org/wiki/Perceptual_quantizer --- SDWebImageAVIFCoder/Classes/ColorSpace.m | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/SDWebImageAVIFCoder/Classes/ColorSpace.m b/SDWebImageAVIFCoder/Classes/ColorSpace.m index fb3ead8..fb0873b 100644 --- a/SDWebImageAVIFCoder/Classes/ColorSpace.m +++ b/SDWebImageAVIFCoder/Classes/ColorSpace.m @@ -395,6 +395,29 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul *shouldRelease = FALSE; return; } + if(colorPrimaries == AVIF_COLOR_PRIMARIES_BT2020 && + transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084) { + static CGColorSpaceRef bt2020pq = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + CFStringRef colorSpaceName = NULL; + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)) { + colorSpaceName = kCGColorSpaceITUR_2100_PQ; + } else if (@available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *)) { + colorSpaceName = kCGColorSpaceITUR_2020_PQ; + } else if (@available(macOS 10.14.6, iOS 12.6, tvOS 12.0, watchOS 5.0, *)) { + colorSpaceName = kCGColorSpaceITUR_2020_PQ_EOTF; + } + if (colorSpaceName) { + bt2020pq = CGColorSpaceCreateWithName(colorSpaceName); + } else { + bt2020pq = defaultColorSpace; + } + }); + *ref = bt2020pq; + *shouldRelease = FALSE; + return; + } if(colorPrimaries == AVIF_COLOR_PRIMARIES_BT2020 && transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_LINEAR) { static CGColorSpaceRef bt2020linear = NULL; From acf892335e0cbdf109c4e0cd139e4b329fd0b447 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 22 Jul 2023 23:25:15 +0800 Subject: [PATCH 25/37] Hack the bt2020 hlg and p3 pq as well --- SDWebImageAVIFCoder/Classes/ColorSpace.m | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/SDWebImageAVIFCoder/Classes/ColorSpace.m b/SDWebImageAVIFCoder/Classes/ColorSpace.m index fb0873b..103938b 100644 --- a/SDWebImageAVIFCoder/Classes/ColorSpace.m +++ b/SDWebImageAVIFCoder/Classes/ColorSpace.m @@ -418,6 +418,28 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul *shouldRelease = FALSE; return; } + if(colorPrimaries == AVIF_COLOR_PRIMARIES_BT2020 && + transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_HLG) { + static CGColorSpaceRef bt2020hlg = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + CFStringRef colorSpaceName = NULL; + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)) { + colorSpaceName = kCGColorSpaceITUR_2100_HLG; + } else if (@available(macOS 10.15.6, iOS 12.6, tvOS 12.0, watchOS 5.0, *)) { + colorSpaceName = kCGColorSpaceITUR_2020_HLG; + } + if (colorSpaceName) { + bt2020hlg = CGColorSpaceCreateWithName(colorSpaceName); + } else { + bt2020hlg = defaultColorSpace; + } + }); + + *ref = bt2020hlg; + *shouldRelease = FALSE; + return; + } if(colorPrimaries == AVIF_COLOR_PRIMARIES_BT2020 && transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_LINEAR) { static CGColorSpaceRef bt2020linear = NULL; @@ -448,6 +470,27 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul *shouldRelease = FALSE; return; } + if(colorPrimaries == AVIF_COLOR_PRIMARIES_SMPTE432 /* Display P3 */ && + transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084) { + static CGColorSpaceRef p3pq = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + CFStringRef colorSpaceName = NULL; + if (@available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *)) { + colorSpaceName = kCGColorSpaceDisplayP3_PQ; + } else if (@available(macOS 10.14.6, iOS 12.6, tvOS 12.0, watchOS 5.0, *)) { + colorSpaceName = kCGColorSpaceDisplayP3_PQ_EOTF; + } + if (colorSpaceName) { + p3pq = CGColorSpaceCreateWithName(colorSpaceName); + } else { + p3pq = defaultColorSpace; + } + }); + *ref = p3pq; + *shouldRelease = FALSE; + return; + } if(colorPrimaries == AVIF_COLOR_PRIMARIES_SMPTE432 /* Display P3 */ && transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_HLG) { static CGColorSpaceRef p3hlg = NULL; From 30135e8f4ff4a50482a0c5c0f3590252457f3a57 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 23 Jul 2023 00:21:30 +0800 Subject: [PATCH 26/37] Update the watchOS available support --- Cartfile | 2 +- Cartfile.resolved | 7 ++- SDWebImageAVIFCoder/Classes/ColorSpace.m | 72 +++++++++++------------- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/Cartfile b/Cartfile index 11c87eb..bd1a409 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "SDWebImage/SDWebImage" ~> 5.10 -github "SDWebImage/libavif-Xcode" >= 0.11.0 \ No newline at end of file +github "SDWebImage/libavif-Xcode" >= 0.11.2-rc1 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 174319a..ca7e367 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,4 @@ -github "SDWebImage/SDWebImage" "5.10.2" -github "SDWebImage/libaom-Xcode" "1.0.2" -github "SDWebImage/libavif-Xcode" "0.8.1" +github "SDWebImage/SDWebImage" "5.17.0" +github "SDWebImage/libaom-Xcode" "3.0.0" +github "SDWebImage/libavif-Xcode" "0.11.2-rc1" +github "SDWebImage/libvmaf-Xcode" "2.3.1" diff --git a/SDWebImageAVIFCoder/Classes/ColorSpace.m b/SDWebImageAVIFCoder/Classes/ColorSpace.m index 103938b..e90f0f2 100644 --- a/SDWebImageAVIFCoder/Classes/ColorSpace.m +++ b/SDWebImageAVIFCoder/Classes/ColorSpace.m @@ -164,45 +164,37 @@ static void CalcTransferFunction(uint16_t const transferCharacteristics, vImageT } } CGColorSpaceRef SDAVIFCreateColorSpaceMono(avifColorPrimaries const colorPrimaries, avifTransferCharacteristics const transferCharacteristics) { - if (@available(macOS 10.10, iOS 8.0, tvOS 8.0, *)) { - vImage_Error err; - vImageWhitePoint white; - vImageTransferFunction transfer; - CalcWhitePoint(colorPrimaries, &white); - CalcTransferFunction(transferCharacteristics, &transfer); - CGColorSpaceRef colorSpace = vImageCreateMonochromeColorSpaceWithWhitePointAndTransferFunction(&white, &transfer, kCGRenderingIntentDefault, kvImagePrintDiagnosticsToConsole, &err); - if(err != kvImageNoError) { - NSLog(@"[BUG] Failed to create monochrome color space: %ld", err); - if(colorSpace != NULL) { - CGColorSpaceRelease(colorSpace); - } - return NULL; + vImage_Error err; + vImageWhitePoint white; + vImageTransferFunction transfer; + CalcWhitePoint(colorPrimaries, &white); + CalcTransferFunction(transferCharacteristics, &transfer); + CGColorSpaceRef colorSpace = vImageCreateMonochromeColorSpaceWithWhitePointAndTransferFunction(&white, &transfer, kCGRenderingIntentDefault, kvImagePrintDiagnosticsToConsole, &err); + if(err != kvImageNoError) { + NSLog(@"[BUG] Failed to create monochrome color space: %ld", err); + if(colorSpace != NULL) { + CGColorSpaceRelease(colorSpace); } - return colorSpace; - }else{ return NULL; } + return colorSpace; } CGColorSpaceRef SDAVIFCreateColorSpaceRGB(avifColorPrimaries const colorPrimaries, avifTransferCharacteristics const transferCharacteristics) { - if (@available(macOS 10.10, iOS 8.0, tvOS 8.0, *)) { - vImage_Error err; - vImageRGBPrimaries primaries; - vImageTransferFunction transfer; - CalcRGBPrimaries(colorPrimaries, &primaries); - CalcTransferFunction(transferCharacteristics, &transfer); - CGColorSpaceRef colorSpace = vImageCreateRGBColorSpaceWithPrimariesAndTransferFunction(&primaries, &transfer, kCGRenderingIntentDefault, kvImagePrintDiagnosticsToConsole, &err); - if(err != kvImageNoError) { - NSLog(@"[BUG] Failed to create monochrome color space: %ld", err); - if(colorSpace != NULL) { - CGColorSpaceRelease(colorSpace); - } - return NULL; + vImage_Error err; + vImageRGBPrimaries primaries; + vImageTransferFunction transfer; + CalcRGBPrimaries(colorPrimaries, &primaries); + CalcTransferFunction(transferCharacteristics, &transfer); + CGColorSpaceRef colorSpace = vImageCreateRGBColorSpaceWithPrimariesAndTransferFunction(&primaries, &transfer, kCGRenderingIntentDefault, kvImagePrintDiagnosticsToConsole, &err); + if(err != kvImageNoError) { + NSLog(@"[BUG] Failed to create monochrome color space: %ld", err); + if(colorSpace != NULL) { + CGColorSpaceRelease(colorSpace); } - return colorSpace; - }else{ return NULL; } + return colorSpace; } void SDAVIFCalcColorSpaceMono(avifImage * avif, CGColorSpaceRef* ref, BOOL* shouldRelease) { @@ -214,7 +206,7 @@ void SDAVIFCalcColorSpaceMono(avifImage * avif, CGColorSpaceRef* ref, BOOL* shou }); } if(avif->icc.data && avif->icc.size) { - if(@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { + if(@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) { CFDataRef iccData = CFDataCreate(kCFAllocatorDefault, avif->icc.data, avif->icc.size); *ref = CGColorSpaceCreateWithICCData(iccData); CFRelease(iccData); @@ -312,7 +304,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul }); } if(avif->icc.data && avif->icc.size) { - if(@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { + if(@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) { CFDataRef iccData = CFDataCreate(kCFAllocatorDefault, avif->icc.data, avif->icc.size); *ref = CGColorSpaceCreateWithICCData(iccData); CFRelease(iccData); @@ -339,7 +331,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef bt709 = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) { + if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { bt709 = CGColorSpaceCreateWithName(kCGColorSpaceITUR_709); } else { bt709 = defaultColorSpace; @@ -354,7 +346,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef sRGB = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.5, iOS 9.0, tvOS 9.0, *)) { + if (@available(macOS 10.5, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { sRGB = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); } else { sRGB = defaultColorSpace; @@ -369,7 +361,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef sRGBlinear = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { + if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) { sRGBlinear = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB); } else { sRGBlinear = defaultColorSpace; @@ -385,7 +377,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef bt2020 = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) { + if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { bt2020 = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020); } else { bt2020 = defaultColorSpace; @@ -445,7 +437,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef bt2020linear = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.14.3, iOS 12.3, tvOS 12.3, *)) { + if (@available(macOS 10.14.3, iOS 12.3, tvOS 12.3, watchOS 5.0, *)) { bt2020linear = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearITUR_2020); } else { bt2020linear = defaultColorSpace; @@ -460,7 +452,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef p3 = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.11.2, iOS 9.3, tvOS 9.3, *)) { + if (@available(macOS 10.11.2, iOS 9.3, tvOS 9.3, watchOS 2.2, *)) { p3 = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3); } else { p3 = defaultColorSpace; @@ -496,7 +488,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef p3hlg = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.14.6, iOS 13.0, tvOS 13.0, *)) { + if (@available(macOS 10.14.6, iOS 13.0, tvOS 13.0, watchOS 5.0, *)) { p3hlg = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3_HLG); } else { p3hlg = defaultColorSpace; @@ -512,7 +504,7 @@ void SDAVIFCalcColorSpaceRGB(avifImage * avif, CGColorSpaceRef* ref, BOOL* shoul static CGColorSpaceRef p3linear = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - if (@available(macOS 10.14.3, iOS 12.3, tvOS 12.3, *)) { + if (@available(macOS 10.14.3, iOS 12.3, tvOS 12.3, watchOS 5.0, *)) { p3linear = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3); } else { p3linear = defaultColorSpace; From e7033342bfc26991affeaa299b83a2b2e579cc6d Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 23 Jul 2023 00:30:21 +0800 Subject: [PATCH 27/37] Remove the unused code to setup matrix --- SDWebImageAVIFCoder/Classes/Conversion.m | 135 +---------------------- 1 file changed, 3 insertions(+), 132 deletions(-) diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index b15e33a..7b470c3 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -55,36 +55,7 @@ static CGImageRef CreateImageFromBuffer(avifImage * avif, vImage_Buffer* result) return imageRef; } -static avifBool avifPrepareReformatState(const avifImage * image, const avifRGBImage * rgb, avifReformatState * state) -{ - if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) { - return AVIF_FALSE; - } - if ((rgb->depth != 8) && (rgb->depth != 10) && (rgb->depth != 12) && (rgb->depth != 16)) { - return AVIF_FALSE; - } - - // These matrix coefficients values are currently unsupported. Revise this list as more support is added. - // - // YCgCo performs limited-full range adjustment on R,G,B but the current implementation performs range adjustment - // on Y,U,V. So YCgCo with limited range is unsupported. - if ((image->matrixCoefficients == 3 /* CICP reserved */) || - ((image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_YCGCO) && (image->yuvRange == AVIF_RANGE_LIMITED)) || - (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT2020_CL) || - (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_SMPTE2085) || - (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL) || - (image->matrixCoefficients >= AVIF_MATRIX_COEFFICIENTS_ICTCP)) { // Note the >= catching "future" CICP values here too - return AVIF_FALSE; - } - - if ((image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) && (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444)) { - return AVIF_FALSE; - } - - if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) { - return AVIF_FALSE; - } - +static void PrepareReformatState(const avifImage * image, avifReformatState * state) { avifGetPixelFormatInfo(image->yuvFormat, &state->formatInfo); avifCalcYUVCoefficients(image, &state->kr, &state->kg, &state->kb); state->mode = AVIF_REFORMAT_MODE_YUV_COEFFICIENTS; @@ -100,113 +71,13 @@ static avifBool avifPrepareReformatState(const avifImage * image, const avifRGBI state->kg = 0.0f; state->kb = 0.0f; } - - state->yuvChannelBytes = (image->depth > 8) ? 2 : 1; - state->rgbChannelBytes = (rgb->depth > 8) ? 2 : 1; - state->rgbChannelCount = avifRGBFormatChannelCount(rgb->format); - state->rgbPixelBytes = state->rgbChannelBytes * state->rgbChannelCount; - - switch (rgb->format) { - case AVIF_RGB_FORMAT_RGB: - state->rgbOffsetBytesR = state->rgbChannelBytes * 0; - state->rgbOffsetBytesG = state->rgbChannelBytes * 1; - state->rgbOffsetBytesB = state->rgbChannelBytes * 2; - state->rgbOffsetBytesA = 0; - break; - case AVIF_RGB_FORMAT_RGBA: - state->rgbOffsetBytesR = state->rgbChannelBytes * 0; - state->rgbOffsetBytesG = state->rgbChannelBytes * 1; - state->rgbOffsetBytesB = state->rgbChannelBytes * 2; - state->rgbOffsetBytesA = state->rgbChannelBytes * 3; - break; - case AVIF_RGB_FORMAT_ARGB: - state->rgbOffsetBytesA = state->rgbChannelBytes * 0; - state->rgbOffsetBytesR = state->rgbChannelBytes * 1; - state->rgbOffsetBytesG = state->rgbChannelBytes * 2; - state->rgbOffsetBytesB = state->rgbChannelBytes * 3; - break; - case AVIF_RGB_FORMAT_BGR: - state->rgbOffsetBytesB = state->rgbChannelBytes * 0; - state->rgbOffsetBytesG = state->rgbChannelBytes * 1; - state->rgbOffsetBytesR = state->rgbChannelBytes * 2; - state->rgbOffsetBytesA = 0; - break; - case AVIF_RGB_FORMAT_BGRA: - state->rgbOffsetBytesB = state->rgbChannelBytes * 0; - state->rgbOffsetBytesG = state->rgbChannelBytes * 1; - state->rgbOffsetBytesR = state->rgbChannelBytes * 2; - state->rgbOffsetBytesA = state->rgbChannelBytes * 3; - break; - case AVIF_RGB_FORMAT_ABGR: - state->rgbOffsetBytesA = state->rgbChannelBytes * 0; - state->rgbOffsetBytesB = state->rgbChannelBytes * 1; - state->rgbOffsetBytesG = state->rgbChannelBytes * 2; - state->rgbOffsetBytesR = state->rgbChannelBytes * 3; - break; - - default: - return AVIF_FALSE; - } - - state->yuvDepth = image->depth; - state->yuvRange = image->yuvRange; - state->yuvMaxChannel = (1 << image->depth) - 1; - state->rgbMaxChannel = (1 << rgb->depth) - 1; - state->rgbMaxChannelF = (float)state->rgbMaxChannel; - state->biasY = (state->yuvRange == AVIF_RANGE_LIMITED) ? (float)(16 << (state->yuvDepth - 8)) : 0.0f; - state->biasUV = (float)(1 << (state->yuvDepth - 1)); - state->rangeY = (float)((state->yuvRange == AVIF_RANGE_LIMITED) ? (219 << (state->yuvDepth - 8)) : state->yuvMaxChannel); - state->rangeUV = (float)((state->yuvRange == AVIF_RANGE_LIMITED) ? (224 << (state->yuvDepth - 8)) : state->yuvMaxChannel); - - uint32_t cpCount = 1 << image->depth; - if (state->mode == AVIF_REFORMAT_MODE_IDENTITY) { - for (uint32_t cp = 0; cp < cpCount; ++cp) { - state->unormFloatTableY[cp] = ((float)cp - state->biasY) / state->rangeY; - state->unormFloatTableUV[cp] = ((float)cp - state->biasY) / state->rangeY; - } - } else { - for (uint32_t cp = 0; cp < cpCount; ++cp) { - // Review this when implementing YCgCo limited range support. - state->unormFloatTableY[cp] = ((float)cp - state->biasY) / state->rangeY; - state->unormFloatTableUV[cp] = ((float)cp - state->biasUV) / state->rangeUV; - } - } - - state->toRGBAlphaMode = AVIF_ALPHA_MULTIPLY_MODE_NO_OP; - if (image->alphaPlane) { - if (!avifRGBFormatHasAlpha(rgb->format) || rgb->ignoreAlpha) { - // if we are converting some image with alpha into a format without alpha, we should do 'premultiply alpha' before - // discarding alpha plane. This has the same effect of rendering this image on a black background, which makes sense. - if (!image->alphaPremultiplied) { - state->toRGBAlphaMode = AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY; - } - } else { - if (!image->alphaPremultiplied && rgb->alphaPremultiplied) { - state->toRGBAlphaMode = AVIF_ALPHA_MULTIPLY_MODE_MULTIPLY; - } else if (image->alphaPremultiplied && !rgb->alphaPremultiplied) { - state->toRGBAlphaMode = AVIF_ALPHA_MULTIPLY_MODE_UNMULTIPLY; - } - } - } - - return AVIF_TRUE; } - -static void SetupConversionInfo(avifImage * avif, +static void SetupConversionInfo(const avifImage * avif, avifReformatState* state, vImage_YpCbCrToARGBMatrix* matrix, vImage_YpCbCrPixelRange* pixelRange) { - avifRGBImage emptyRGBImage = { - .width = avif->width, - .height = avif->height, - .depth = avif->depth, - .format = AVIF_RGB_FORMAT_ARGB, - - .pixels = NULL, - .rowBytes = 0, - }; - avifPrepareReformatState(avif, &emptyRGBImage, state); + PrepareReformatState(avif, state); // Setup Matrix matrix->Yp = 1.0f; From 989e4b6ad18200644956ba2a908cb7d939878690 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 23 Jul 2023 00:38:51 +0800 Subject: [PATCH 28/37] Bumped version to 0.10.1 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 6d686ab..9c94a49 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.10.0' + s.version = '0.10.1' 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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index 344075b..479b075 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.10.0 + 0.10.1 CFBundleVersion - 0.10.0 + 0.10.1 From 791bde0f51ff92d15c8c1bfa5419351c1718382f Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 28 Sep 2023 15:41:37 +0800 Subject: [PATCH 29/37] The dependency should dependency libavif/core instead of default subspecs --- Example/Podfile | 6 +++--- SDWebImageAVIFCoder.podspec | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Example/Podfile b/Example/Podfile index d1a9718..3dae6df 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -3,7 +3,7 @@ install! 'cocoapods', :generate_multiple_pod_projects => true target 'SDWebImageAVIFCoder_Example' do platform :ios, '9.0' pod 'SDWebImageAVIFCoder', :path => '../' - pod 'libavif', :subspecs => ['libaom', 'libdav1d'] + pod 'libavif', :subspecs => ['core', 'librav1e', 'libdav1d'] target 'SDWebImageAVIFCoder_Tests' do inherit! :search_paths @@ -13,11 +13,11 @@ end target 'SDWebImageAVIFCoder_Example macOS' do platform :osx, '10.11' pod 'SDWebImageAVIFCoder', :path => '../' - pod 'libavif', :subspecs => ['libaom', 'libdav1d'] + pod 'libavif', :subspecs => ['core', 'librav1e', 'libdav1d'] end target 'SDWebImageAVIFCoder_Example CLI' do platform :osx, '10.11' pod 'SDWebImageAVIFCoder', :path => '../' - pod 'libavif', :subspecs => ['libaom', 'libdav1d'] + pod 'libavif', :subspecs => ['core', 'librav1e', 'libdav1d'] end diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 9c94a49..0a1b1e2 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -40,6 +40,6 @@ Which is built based on the open-sourced libavif codec. } s.dependency 'SDWebImage', '~> 5.10' - s.dependency 'libavif', '>= 0.11.0' + s.dependency 'libavif/core', '>= 0.11.0' s.libraries = 'c++' end From 584ce523a3c1abb10cd2d5ccadbd6edb8c02352c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 28 Sep 2023 15:55:50 +0800 Subject: [PATCH 30/37] Added the option for avifCodecChoice --- .../Classes/Public/SDImageAVIFCoder.h | 6 +++++ .../Classes/SDImageAVIFCoder.m | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/SDWebImageAVIFCoder/Classes/Public/SDImageAVIFCoder.h b/SDWebImageAVIFCoder/Classes/Public/SDImageAVIFCoder.h index b146fd1..79a84df 100644 --- a/SDWebImageAVIFCoder/Classes/Public/SDImageAVIFCoder.h +++ b/SDWebImageAVIFCoder/Classes/Public/SDImageAVIFCoder.h @@ -13,6 +13,12 @@ static const SDImageFormat SDImageFormatAVIF = 15; // AV1-codec based HEIF +/// A `avifCodecChoice` enum which specify the custom codec for AVIF decoding, defaults to 0 (`AVIF_CODEC_CHOICE_AUTO`) +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderAVIFDecodeCodecChoice; + +/// A `avifCodecChoice` enum which specify the custom codec for AVIF encoding, defaults to 0 (`AVIF_CODEC_CHOICE_AUTO`) +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderAVIFEncodeCodecChoice; + /// Supports AVIF static image and AVIFS animated image @interface SDImageAVIFCoder : NSObject diff --git a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m index d5c30af..d413328 100644 --- a/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m +++ b/SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m @@ -61,6 +61,9 @@ #endif #endif +SDImageCoderOption _Nonnull const SDImageCoderAVIFDecodeCodecChoice = @"avifDecodeCodecChoice"; +SDImageCoderOption _Nonnull const SDImageCoderAVIFEncodeCodecChoice = @"avifEncodeCodecChoice"; + @implementation SDImageAVIFCoder { avifDecoder *_decoder; NSData *_imageData; @@ -122,9 +125,17 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *) preserveAspectRatio = preserveAspectRatioValue.boolValue; } + avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO; + NSNumber *codecChoiceValue = options[SDImageCoderAVIFDecodeCodecChoice]; + if (codecChoiceValue != nil) { + codecChoice = [codecChoiceValue intValue]; + } + // Decode it avifDecoder * decoder = avifDecoderCreate(); avifDecoderSetIOMemory(decoder, data.bytes, data.length); + decoder->maxThreads = 2; + decoder->codecChoice = codecChoice; // Disable strict mode to keep some AVIF image compatible decoder->strictFlags = AVIF_STRICT_DISABLED; avifResult decodeResult = avifDecoderParse(decoder); @@ -270,6 +281,12 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm return nil; } + avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO; + NSNumber *codecChoiceValue = options[SDImageCoderAVIFEncodeCodecChoice]; + if (codecChoiceValue != nil) { + codecChoice = [codecChoiceValue intValue]; + } + avifPixelFormat avifFormat = AVIF_PIXEL_FORMAT_YUV444; avifImage *avif = avifImageCreate((int)width, (int)height, 8, avifFormat); @@ -300,6 +317,7 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm avifRWData raw = AVIF_DATA_EMPTY; avifEncoder *encoder = avifEncoderCreate(); + encoder->codecChoice = codecChoice; encoder->minQuantizer = rescaledQuality; encoder->maxQuantizer = rescaledQuality; encoder->minQuantizerAlpha = rescaledQuality; @@ -324,8 +342,15 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm - (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOptions *)options { self = [super init]; if (self) { + avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO; + NSNumber *codecChoiceValue = options[SDImageCoderAVIFDecodeCodecChoice]; + if (codecChoiceValue != nil) { + codecChoice = [codecChoiceValue intValue]; + } avifDecoder *decoder = avifDecoderCreate(); avifDecoderSetIOMemory(decoder, data.bytes, data.length); + decoder->maxThreads = 2; + decoder->codecChoice = codecChoice; // Disable strict mode to keep some AVIF image compatible decoder->strictFlags = AVIF_STRICT_DISABLED; avifResult decodeResult = avifDecoderParse(decoder); From 0606c3e199e52d81df3681fd1c004cb7ca130894 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 28 Sep 2023 16:05:04 +0800 Subject: [PATCH 31/37] Update the readme about codecChoice --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 3c152be..ac0e0dd 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,31 @@ git lfs install From v0.8.3, libavif can built with STV-AV1. For For CocoaPods user, you can simply use the subspec for this. +## Choose codec at runtime + +Although libavif has a automatic detection of installed AVIF codec, but for example, `aom` supports both decoding and encoding, but slower than `rav1e` on encoding. + +You can force to use the `aom` for decoding and `rav1e` for encoding like this: + ++ Objective-C + +```objective-c +NSDictionary *decodeOptions = [SDImageCoderAVIFDecodeCodecChoice: @(AVIF_CODEC_CHOICE_AOM)]; +NSDictionary *encodeOptions = [SDImageCoderAVIFEncodeCodecChoice: @(AVIF_CODEC_CHOICE_RAV1E)]; + +// Pass from UI level options +[imageView sd_setImageWithURL:url placeholderImage:nil options:0 context:@{SDWebImageContextImageDecodeOptions: decodeOptions, SDWebImageContextImageEncodeOptions: encodeOptions} progress:nil completed:nil]; +``` + ++ Swift + +```swift +let decodeOptions: [SDImageCoderOption: Any] = [.avifDecodeCodecChoice: AVIF_CODEC_CHOICE_AOM.rawValue] +let encodeOptions = [SDImageCoderOption: Any] = [.avifEncodeCodecChoice: AVIF_CODEC_CHOICE_RAV1E.rawValue] + +// Pass from UI level options +imageView.sd_setImage(with: url, placeholderImage: nil, options: [], context: [.imageDecodeOptions: decodeOptions, .imageEncodeOptions: encodeOptions], progress: nil, completed: nil) +``` ## Requirements From 715df4ace986e1fb332c8c28b45b73dba6a40e5a Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 28 Sep 2023 16:05:53 +0800 Subject: [PATCH 32/37] Bumped version to 0.11.0 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index 0a1b1e2..f814128 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.10.1' + s.version = '0.11.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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index 479b075..d2a6275 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.10.1 + 0.11.0 CFBundleVersion - 0.10.1 + 0.11.0 From 10b939f7e576bdaeed781f449734c64ae744a689 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 8 Oct 2023 16:11:48 +0800 Subject: [PATCH 33/37] Update the README about aom on CocoaPods --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac0e0dd..5cd4d14 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,25 @@ You can choose the codec and use `libavif` CocoaPods subspec to choose the one y ### aom -By default, libavif is built with [aom](https://aomedia.googlesource.com/aom/) codec support. aom is the first AV1 codec during the standard draft implementation. The aom support both decodinng and encoding ++ For Carthage/SwifftPM + +libavif is built with [aom](https://aomedia.googlesource.com/aom/) codec support. aom is the first AV1 codec during the standard draft implementation. The aom support both decodinng and encoding + ++ For CocoaPods + +Because of strange design of **subspec dependencies resolution algorithm**, we can not make aom by default (or all the subspecs will always link aom, which means not optional). So libavif is built with nothing codec (`libavif/core`). You need to choose the actual AV1 codec using one or more of below. + +If you want to use aom, use: + +```ruby +pod 'libavif/liaom' +``` + +or other AV1 codec, like dav1d: + +```ruby +pod 'libavif/libdav1d' +``` ### dav1d (Decoding) From 9f49b0f7839cabac4734d9072a9f2419ce261ebd Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 27 Oct 2023 01:49:29 +0800 Subject: [PATCH 34/37] Update README.md Update the latest changes about rav1e codec --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 5cd4d14..665c678 100644 --- a/README.md +++ b/README.md @@ -73,14 +73,7 @@ See more about [performance](https://github.com/xiph/rav1e/issues/1248) From v0.4.3, libavif can built with rav1e. For CocoaPods user, you can simply use the subspec for this. Carthage for optional rav1c codec is not supported currently. -Note rav1e currently only support iOS && macOS. watchOS and tvOS supports need Rust community upstream support. - -Note that for CocoaPods user, rav1e is prebuilt binary (to avoid developer to install rust toolchain) and hosted on GitHub Git LFS. Make sure you have git-lfs installed. - -```bash -brew install git-lfs -git lfs install -``` +Note before librav1e v0.6.1, it only support iOS && macOS. watchOS and tvOS is avaiable from v0.6.2 with the latest Rust toolchain. And for visionOS supports need Rust community upstream support. For latest integration, check or fire issues in https://github.com/SDWebImage/librav1e-Xcode. ### SVT-AV1 (Encoding) From f784fcf7be9e02ec2fb16028d44f5889bbb4cfe9 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 14 Dec 2023 16:05:15 +0800 Subject: [PATCH 35/37] Update README.md Add description about librav1e static linkage --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 665c678..6dfd1b8 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,24 @@ From v0.8.3, libavif can built with libgav1. For For CocoaPods user, you can sim See more about [performance](https://github.com/xiph/rav1e/issues/1248) -From v0.4.3, libavif can built with rav1e. For CocoaPods user, you can simply use the subspec for this. Carthage for optional rav1c codec is not supported currently. +From v0.4.3, libavif can built with rav1e. For CocoaPods user, you can simply use the subspec for this. Carthage/SPM for optional rav1c codec is not supported currently. -Note before librav1e v0.6.1, it only support iOS && macOS. watchOS and tvOS is avaiable from v0.6.2 with the latest Rust toolchain. And for visionOS supports need Rust community upstream support. For latest integration, check or fire issues in https://github.com/SDWebImage/librav1e-Xcode. +Note: The librav1e on CocoaPods use pre-built static-linking binary. Which means, it can not be used for CocoaPods's dynamic-linking. + +To use for framework package format (CocoaPods defaults use ar archive format), you should write something like this: + +```ruby +use_frameworks! :linkage => :static +``` + +but not this: + +```ruby +use_frameworks! +# use_frameworks! :linkage => :dynamic +``` + +Note: before librav1e v0.6.1, it only support iOS && macOS. watchOS and tvOS is avaiable from v0.6.2 with the latest Rust toolchain. And for visionOS supports need Rust community upstream support. For latest integration, check or fire issues in https://github.com/SDWebImage/librav1e-Xcode. ### SVT-AV1 (Encoding) From da93d8fcf515115fd045228eb9544a006b182d8c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 15 Sep 2025 11:24:07 +0800 Subject: [PATCH 36/37] Fix the wrong calculation of buffer of AVIF image which cause memory peak --- SDWebImageAVIFCoder/Classes/Conversion.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImageAVIFCoder/Classes/Conversion.m b/SDWebImageAVIFCoder/Classes/Conversion.m index 7b470c3..9cc0f7a 100644 --- a/SDWebImageAVIFCoder/Classes/Conversion.m +++ b/SDWebImageAVIFCoder/Classes/Conversion.m @@ -186,7 +186,7 @@ static CGImageRef CreateCGImage8(avifImage * avif) { vImage_YpCbCrToARGB convInfo = {0}; - resultBufferData = calloc(components * rowBytes * avif->height, sizeof(uint8_t)); + resultBufferData = calloc(rowBytes * avif->height, sizeof(uint8_t)); if(resultBufferData == NULL) { goto end_all; } From 1c25e7aaca8b948d823401c34b2ba2595c96280c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 15 Sep 2025 11:29:07 +0800 Subject: [PATCH 37/37] Bumped version to 0.11.1 --- SDWebImageAVIFCoder.podspec | 2 +- SDWebImageAVIFCoder/Module/Info.plist | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SDWebImageAVIFCoder.podspec b/SDWebImageAVIFCoder.podspec index f814128..e90b0fc 100644 --- a/SDWebImageAVIFCoder.podspec +++ b/SDWebImageAVIFCoder.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'SDWebImageAVIFCoder' - s.version = '0.11.0' + s.version = '0.11.1' 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. diff --git a/SDWebImageAVIFCoder/Module/Info.plist b/SDWebImageAVIFCoder/Module/Info.plist index d2a6275..e10219e 100644 --- a/SDWebImageAVIFCoder/Module/Info.plist +++ b/SDWebImageAVIFCoder/Module/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.0 + 0.11.1 CFBundleVersion - 0.11.0 + 0.11.1