Skip to content

Commit cf0b5b2

Browse files
authored
Merge pull request #1 from SDWebImage/feature_encoding_cocoapods
Upgrade the libavif version, supports the AVIF encoding feature
2 parents ab262cd + edf6929 commit cf0b5b2

File tree

8 files changed

+175
-38
lines changed

8 files changed

+175
-38
lines changed

Cartfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
github "SDWebImage/SDWebImage" ~> 5.0
2-
github "SDWebImage/libavif-Xcode"
2+
github "SDWebImage/libavif-Xcode" >= 0.1.3

Cartfile.resolved

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
github "SDWebImage/SDWebImage" "5.0.1"
2-
github "SDWebImage/libaom-Xcode" "1.0.0"
3-
github "SDWebImage/libavif-Xcode" "0.1.0"
1+
github "SDWebImage/SDWebImage" "5.0.2"
2+
github "SDWebImage/libaom-Xcode" "1.0.1"
3+
github "SDWebImage/libavif-Xcode" "0.1.3"

Example/Podfile.lock

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
PODS:
2-
- libaom (1.0.0)
3-
- libavif (0.1.0):
4-
- libaom
5-
- SDWebImage (5.0.1):
6-
- SDWebImage/Core (= 5.0.1)
7-
- SDWebImage/Core (5.0.1)
2+
- libaom (1.0.1)
3+
- libavif (0.1.3):
4+
- libaom (>= 1.0.1)
5+
- SDWebImage (5.0.2):
6+
- SDWebImage/Core (= 5.0.2)
7+
- SDWebImage/Core (5.0.2)
88
- SDWebImageAVIFCoder (0.1.0):
9-
- libavif
9+
- libavif (>= 0.1.3)
1010
- SDWebImage (~> 5.0)
1111

1212
DEPENDENCIES:
@@ -23,10 +23,10 @@ EXTERNAL SOURCES:
2323
:path: "../"
2424

2525
SPEC CHECKSUMS:
26-
libaom: d84044f314a1eac538c20965ac7df6fe6cee6ac6
27-
libavif: 1513b919d6d7beb8fd8cc2e4a71538609409895b
28-
SDWebImage: 27dd2c9ea07a2252f94557c9fbb6105ee94b74c9
29-
SDWebImageAVIFCoder: 66893c86fbaa82c54556186fe9f62a601b27dfd0
26+
libaom: 1e48c68559b8d6191c1a9f266e0bee83b2dd21fd
27+
libavif: b6de15e6a91a347806b2fcc1fccd471c821f6d6a
28+
SDWebImage: 6764b5fa0f73c203728052955dbefa2bf1f33282
29+
SDWebImageAVIFCoder: 1e80598038f37e20a83a7a790cb192e0b362a557
3030

3131
PODFILE CHECKSUM: cb60778bff8fb5ce4fbc8792f6079317b7a897be
3232

Example/SDWebImageAVIFCoder/SDViewController.m

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ - (void)viewDidLoad
3535

3636
[imageView1 sd_setImageWithURL:AVIFURL placeholderImage:nil options:0 completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
3737
if (image) {
38-
NSLog(@"Single HEIC load success");
38+
NSLog(@"Static AVIF load success");
39+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
40+
NSData *avifData = [SDImageAVIFCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatAVIF options:nil];
41+
if (avifData) {
42+
NSLog(@"Static AVIF encode success");
43+
}
44+
});
3945
}
4046
}];
4147
[imageView2 sd_setImageWithURL:HDRAVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {

Example/SDWebImageAVIFCoder_Example macOS/ViewController.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ - (void)viewDidLoad {
3434
[imageView1 sd_setImageWithURL:AVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
3535
if (image) {
3636
NSLog(@"Static AVIF load success");
37+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
38+
NSData *avifData = [SDImageAVIFCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatAVIF options:nil];
39+
if (avifData) {
40+
NSLog(@"Static AVIF encode success");
41+
}
42+
});
3743
}
3844
}];
3945
[imageView2 sd_setImageWithURL:HDRAVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {

README.md

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ This is a [SDWebImage](https://github.com/rs/SDWebImage) coder plugin to add [AV
1212

1313
This AVIF coder plugin currently support AVIF still image **decoding**. Including alpha channel, as well as 10bit/12bit HDR images.
1414

15-
The AVIF encoding is not currently support, because the software-based encoding speed is really slow. Need to wait for better enc implementation.
15+
The AVIF encoding is also supported now. Which always encode as 8-bit depth images.
1616

17-
Note: AVIF image spec is still in evolve. And the current AVIF codec is a simple implementation.
17+
Note: AVIF image spec is still in evolve. And the current upstream AVIF codec is a simple implementation. The encoding time may be long for large images.
1818

1919
Since AVIF is AV1-based inside HEIF image container. In the future, this repo may moved to existing HEIF coder plugin [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder) instead.
2020

@@ -35,17 +35,7 @@ it, simply add the following line to your Podfile:
3535
pod 'SDWebImageAVIFCoder'
3636
```
3737

38-
Note: Current `libaom` dependency via CocoaPods, use the pre-built static library for each architecutre.
39-
40-
The reason of this it's that we want to use SIMD/SSE/AVX2 CPU instruction optimization for each platforms. However libaom does not using dynamic CPU detection for Apple's platforms. We need the upstream to support it.
41-
42-
At the same time, CocoaPods does not allow you to write a framework contains so much of architecture detection (for example, iPhone Simulator is x86_x64, however, iPhone is ARM, they should use different assembly files). So we use the pre-built one instead.
43-
44-
If you're using `use_frameworks!` in Podfile, you can check it with static framework instead.
45-
46-
```
47-
pod 'SDWebImageAVIFCoder', :modular_headers => true
48-
```
38+
Note: From version 0.2.0, the dependency libavif and libaom use the portable C implementation to works on Apple platforms. If you need the pre-built library with SIMD/AVX and assembly optimization, try the 0.1.0 version.
4939

5040
#### Carthage
5141

@@ -55,10 +45,6 @@ SDWebImageAVIFCoder is available through [Carthage](https://github.com/Carthage/
5545
github "SDWebImage/SDWebImageAVIFCoder"
5646
```
5747

58-
Note: Carthage dependency of `libaom` using the C implementation codec, instead of original SIMD/SSE/AVX accelerated and assembly implementation, because it need extra dependency (CMake && NASM build tool).
59-
60-
The C implementation make it possible to cross-platform in tvOS/watchOS as well. But if you're care about performance, try CocoaPods instead.
61-
6248
## Usage
6349

6450
To use AVIF coder, you should firstly add the `SDImageAVIFCoder.sharedCoder` to the coders manager. Then you can call the View Category method to start load AVIF images.

SDWebImageAVIFCoder.podspec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ Which is built based on the open-sourced libavif codec.
3030
s.module_map = 'SDWebImageAVIFCoder/Module/SDWebImageAVIFCoder.modulemap'
3131
s.ios.deployment_target = '8.0'
3232
s.osx.deployment_target = '10.10'
33+
s.tvos.deployment_target = '9.0'
34+
s.watchos.deployment_target = '2.0'
3335

3436
s.source_files = 'SDWebImageAVIFCoder/Classes/**/*', 'SDWebImageAVIFCoder/Module/SDWebImageAVIFCoder.h'
3537

3638
s.dependency 'SDWebImage', '~> 5.0'
37-
s.dependency 'libavif'
39+
s.dependency 'libavif', '>= 0.1.3'
3840
end

SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ static void ConvertAvifImagePlanarToRGB(avifImage * avif, uint8_t * outPixels) {
5050
}
5151
}
5252

53+
static void FillRGBABufferWithAVIFImage(vImage_Buffer *red, vImage_Buffer *green, vImage_Buffer *blue, vImage_Buffer *alpha, avifImage *img) {
54+
red->width = img->width;
55+
red->height = img->height;
56+
red->data = img->rgbPlanes[AVIF_CHAN_R];
57+
red->rowBytes = img->rgbRowBytes[AVIF_CHAN_R];
58+
59+
green->width = img->width;
60+
green->height = img->height;
61+
green->data = img->rgbPlanes[AVIF_CHAN_G];
62+
green->rowBytes = img->rgbRowBytes[AVIF_CHAN_G];
63+
64+
blue->width = img->width;
65+
blue->height = img->height;
66+
blue->data = img->rgbPlanes[AVIF_CHAN_B];
67+
blue->rowBytes = img->rgbRowBytes[AVIF_CHAN_B];
68+
69+
if (img->alphaPlane != NULL) {
70+
alpha->width = img->width;
71+
alpha->height = img->height;
72+
alpha->data = img->alphaPlane;
73+
alpha->rowBytes = img->alphaRowBytes;
74+
}
75+
}
76+
5377
static void FreeImageData(void *info, const void *data, size_t size) {
5478
free((void *)data);
5579
}
@@ -140,13 +164,126 @@ - (nullable CGImageRef)sd_createAVIFImageWithData:(nonnull NSData *)data CF_RETU
140164
return imageRef;
141165
}
142166

143-
// The AVIF encoding seems too slow at the current time
167+
// The AVIF encoding seems slow at the current time, but at least works
144168
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
145-
return NO;
169+
return format == SDImageFormatAVIF;
146170
}
147171

148-
- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
149-
return nil;
172+
- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
173+
CGImageRef imageRef = image.CGImage;
174+
if (!imageRef) {
175+
return nil;
176+
}
177+
178+
size_t width = CGImageGetWidth(imageRef);
179+
size_t height = CGImageGetHeight(imageRef);
180+
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
181+
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
182+
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
183+
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
184+
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
185+
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
186+
alphaInfo == kCGImageAlphaNoneSkipFirst ||
187+
alphaInfo == kCGImageAlphaNoneSkipLast);
188+
BOOL byteOrderNormal = NO;
189+
switch (byteOrderInfo) {
190+
case kCGBitmapByteOrderDefault: {
191+
byteOrderNormal = YES;
192+
} break;
193+
case kCGBitmapByteOrder32Little: {
194+
} break;
195+
case kCGBitmapByteOrder32Big: {
196+
byteOrderNormal = YES;
197+
} break;
198+
default: break;
199+
}
200+
201+
vImageConverterRef convertor = NULL;
202+
vImage_Error v_error = kvImageNoError;
203+
204+
vImage_CGImageFormat srcFormat = {
205+
.bitsPerComponent = (uint32_t)bitsPerComponent,
206+
.bitsPerPixel = (uint32_t)bitsPerPixel,
207+
.colorSpace = CGImageGetColorSpace(imageRef),
208+
.bitmapInfo = bitmapInfo
209+
};
210+
vImage_CGImageFormat destFormat = {
211+
.bitsPerComponent = 8,
212+
.bitsPerPixel = hasAlpha ? 32 : 24,
213+
.colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB],
214+
.bitmapInfo = hasAlpha ? kCGImageAlphaFirst | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/ARGB8888 (Non-premultiplied to works for libbpg)
215+
};
216+
217+
convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, &destFormat, NULL, kvImageNoFlags, &v_error);
218+
if (v_error != kvImageNoError) {
219+
return nil;
220+
}
221+
222+
vImage_Buffer src;
223+
v_error = vImageBuffer_InitWithCGImage(&src, &srcFormat, NULL, imageRef, kvImageNoFlags);
224+
if (v_error != kvImageNoError) {
225+
return nil;
226+
}
227+
vImage_Buffer dest;
228+
vImageBuffer_Init(&dest, height, width, hasAlpha ? 32 : 24, kvImageNoFlags);
229+
if (!dest.data) {
230+
free(src.data);
231+
return nil;
232+
}
233+
234+
// Convert input color mode to RGB888/ARGB8888
235+
v_error = vImageConvert_AnyToAny(convertor, &src, &dest, NULL, kvImageNoFlags);
236+
free(src.data);
237+
vImageConverter_Release(convertor);
238+
if (v_error != kvImageNoError) {
239+
free(dest.data);
240+
return nil;
241+
}
242+
243+
avifPixelFormat avifFormat = AVIF_PIXEL_FORMAT_YUV444;
244+
enum avifPlanesFlags planesFlags = hasAlpha ? AVIF_PLANES_RGB | AVIF_PLANES_A : AVIF_PLANES_RGB;
245+
246+
avifImage *avif = avifImageCreate((int)width, (int)height, 8, avifFormat);
247+
if (!avif) {
248+
free(dest.data);
249+
return nil;
250+
}
251+
avifImageAllocatePlanes(avif, planesFlags);
252+
253+
NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile([SDImageCoderHelper colorSpaceGetDeviceRGB]);
254+
255+
avifImageSetProfileICC(avif, (uint8_t *)iccProfile.bytes, iccProfile.length);
256+
257+
vImage_Buffer red, green, blue, alpha;
258+
FillRGBABufferWithAVIFImage(&red, &green, &blue, &alpha, avif);
259+
260+
if (hasAlpha) {
261+
v_error = vImageConvert_ARGB8888toPlanar8(&dest, &alpha, &red, &green, &blue, kvImageNoFlags);
262+
} else {
263+
v_error = vImageConvert_RGB888toPlanar8(&dest, &red, &green, &blue, kvImageNoFlags);
264+
}
265+
free(dest.data);
266+
if (v_error != kvImageNoError) {
267+
return nil;
268+
}
269+
270+
double compressionQuality = 1;
271+
if (options[SDImageCoderEncodeCompressionQuality]) {
272+
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
273+
}
274+
int rescaledQuality = 63 - (int)((compressionQuality) * 63.0f);
275+
276+
avifRawData raw = AVIF_RAW_DATA_EMPTY;
277+
avifResult result = avifImageWrite(avif, &raw, 2, rescaledQuality);
278+
279+
if (result != AVIF_RESULT_OK) {
280+
return nil;
281+
}
282+
283+
NSData *imageData = [NSData dataWithBytes:raw.data length:raw.size];
284+
free(raw.data);
285+
286+
return imageData;
150287
}
151288

152289

0 commit comments

Comments
 (0)