Skip to content

Commit

Permalink
Merge pull request #1 from SDWebImage/feature_encoding_cocoapods
Browse files Browse the repository at this point in the history
Upgrade the libavif version, supports the AVIF encoding feature
  • Loading branch information
dreampiggy authored Apr 29, 2019
2 parents ab262cd + edf6929 commit cf0b5b2
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "SDWebImage/SDWebImage" ~> 5.0
github "SDWebImage/libavif-Xcode"
github "SDWebImage/libavif-Xcode" >= 0.1.3
6 changes: 3 additions & 3 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
github "SDWebImage/SDWebImage" "5.0.1"
github "SDWebImage/libaom-Xcode" "1.0.0"
github "SDWebImage/libavif-Xcode" "0.1.0"
github "SDWebImage/SDWebImage" "5.0.2"
github "SDWebImage/libaom-Xcode" "1.0.1"
github "SDWebImage/libavif-Xcode" "0.1.3"
22 changes: 11 additions & 11 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
PODS:
- libaom (1.0.0)
- libavif (0.1.0):
- libaom
- SDWebImage (5.0.1):
- SDWebImage/Core (= 5.0.1)
- SDWebImage/Core (5.0.1)
- libaom (1.0.1)
- libavif (0.1.3):
- libaom (>= 1.0.1)
- SDWebImage (5.0.2):
- SDWebImage/Core (= 5.0.2)
- SDWebImage/Core (5.0.2)
- SDWebImageAVIFCoder (0.1.0):
- libavif
- libavif (>= 0.1.3)
- SDWebImage (~> 5.0)

DEPENDENCIES:
Expand All @@ -23,10 +23,10 @@ EXTERNAL SOURCES:
:path: "../"

SPEC CHECKSUMS:
libaom: d84044f314a1eac538c20965ac7df6fe6cee6ac6
libavif: 1513b919d6d7beb8fd8cc2e4a71538609409895b
SDWebImage: 27dd2c9ea07a2252f94557c9fbb6105ee94b74c9
SDWebImageAVIFCoder: 66893c86fbaa82c54556186fe9f62a601b27dfd0
libaom: 1e48c68559b8d6191c1a9f266e0bee83b2dd21fd
libavif: b6de15e6a91a347806b2fcc1fccd471c821f6d6a
SDWebImage: 6764b5fa0f73c203728052955dbefa2bf1f33282
SDWebImageAVIFCoder: 1e80598038f37e20a83a7a790cb192e0b362a557

PODFILE CHECKSUM: cb60778bff8fb5ce4fbc8792f6079317b7a897be

Expand Down
8 changes: 7 additions & 1 deletion Example/SDWebImageAVIFCoder/SDViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ - (void)viewDidLoad

[imageView1 sd_setImageWithURL:AVIFURL placeholderImage:nil options:0 completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
if (image) {
NSLog(@"Single HEIC load success");
NSLog(@"Static AVIF load success");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *avifData = [SDImageAVIFCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatAVIF options:nil];
if (avifData) {
NSLog(@"Static AVIF encode success");
}
});
}
}];
[imageView2 sd_setImageWithURL:HDRAVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
Expand Down
6 changes: 6 additions & 0 deletions Example/SDWebImageAVIFCoder_Example macOS/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ - (void)viewDidLoad {
[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), ^{
NSData *avifData = [SDImageAVIFCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatAVIF options:nil];
if (avifData) {
NSLog(@"Static AVIF encode success");
}
});
}
}];
[imageView2 sd_setImageWithURL:HDRAVIFURL completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
Expand Down
20 changes: 3 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ This is a [SDWebImage](https://github.com/rs/SDWebImage) coder plugin to add [AV

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

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

Note: AVIF image spec is still in evolve. And the current AVIF codec is a simple implementation.
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.

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.

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

Note: Current `libaom` dependency via CocoaPods, use the pre-built static library for each architecutre.

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.

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.

If you're using `use_frameworks!` in Podfile, you can check it with static framework instead.

```
pod 'SDWebImageAVIFCoder', :modular_headers => true
```
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.

#### Carthage

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

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).

The C implementation make it possible to cross-platform in tvOS/watchOS as well. But if you're care about performance, try CocoaPods instead.

## Usage

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.
Expand Down
4 changes: 3 additions & 1 deletion SDWebImageAVIFCoder.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ Which is built based on the open-sourced libavif codec.
s.module_map = 'SDWebImageAVIFCoder/Module/SDWebImageAVIFCoder.modulemap'
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.10'
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '2.0'

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

s.dependency 'SDWebImage', '~> 5.0'
s.dependency 'libavif'
s.dependency 'libavif', '>= 0.1.3'
end
145 changes: 141 additions & 4 deletions SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ static void ConvertAvifImagePlanarToRGB(avifImage * avif, uint8_t * outPixels) {
}
}

static void FillRGBABufferWithAVIFImage(vImage_Buffer *red, vImage_Buffer *green, vImage_Buffer *blue, vImage_Buffer *alpha, avifImage *img) {
red->width = img->width;
red->height = img->height;
red->data = img->rgbPlanes[AVIF_CHAN_R];
red->rowBytes = img->rgbRowBytes[AVIF_CHAN_R];

green->width = img->width;
green->height = img->height;
green->data = img->rgbPlanes[AVIF_CHAN_G];
green->rowBytes = img->rgbRowBytes[AVIF_CHAN_G];

blue->width = img->width;
blue->height = img->height;
blue->data = img->rgbPlanes[AVIF_CHAN_B];
blue->rowBytes = img->rgbRowBytes[AVIF_CHAN_B];

if (img->alphaPlane != NULL) {
alpha->width = img->width;
alpha->height = img->height;
alpha->data = img->alphaPlane;
alpha->rowBytes = img->alphaRowBytes;
}
}

static void FreeImageData(void *info, const void *data, size_t size) {
free((void *)data);
}
Expand Down Expand Up @@ -140,13 +164,126 @@ - (nullable CGImageRef)sd_createAVIFImageWithData:(nonnull NSData *)data CF_RETU
return imageRef;
}

// The AVIF encoding seems too slow at the current time
// The AVIF encoding seems slow at the current time, but at least works
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
return NO;
return format == SDImageFormatAVIF;
}

- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
return nil;
- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
CGImageRef imageRef = image.CGImage;
if (!imageRef) {
return nil;
}

size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL byteOrderNormal = NO;
switch (byteOrderInfo) {
case kCGBitmapByteOrderDefault: {
byteOrderNormal = YES;
} break;
case kCGBitmapByteOrder32Little: {
} break;
case kCGBitmapByteOrder32Big: {
byteOrderNormal = YES;
} break;
default: break;
}

vImageConverterRef convertor = NULL;
vImage_Error v_error = kvImageNoError;

vImage_CGImageFormat srcFormat = {
.bitsPerComponent = (uint32_t)bitsPerComponent,
.bitsPerPixel = (uint32_t)bitsPerPixel,
.colorSpace = CGImageGetColorSpace(imageRef),
.bitmapInfo = bitmapInfo
};
vImage_CGImageFormat destFormat = {
.bitsPerComponent = 8,
.bitsPerPixel = hasAlpha ? 32 : 24,
.colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB],
.bitmapInfo = hasAlpha ? kCGImageAlphaFirst | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/ARGB8888 (Non-premultiplied to works for libbpg)
};

convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, &destFormat, NULL, kvImageNoFlags, &v_error);
if (v_error != kvImageNoError) {
return nil;
}

vImage_Buffer src;
v_error = vImageBuffer_InitWithCGImage(&src, &srcFormat, NULL, imageRef, kvImageNoFlags);
if (v_error != kvImageNoError) {
return nil;
}
vImage_Buffer dest;
vImageBuffer_Init(&dest, height, width, hasAlpha ? 32 : 24, kvImageNoFlags);
if (!dest.data) {
free(src.data);
return nil;
}

// Convert input color mode to RGB888/ARGB8888
v_error = vImageConvert_AnyToAny(convertor, &src, &dest, NULL, kvImageNoFlags);
free(src.data);
vImageConverter_Release(convertor);
if (v_error != kvImageNoError) {
free(dest.data);
return nil;
}

avifPixelFormat avifFormat = AVIF_PIXEL_FORMAT_YUV444;
enum avifPlanesFlags planesFlags = hasAlpha ? AVIF_PLANES_RGB | AVIF_PLANES_A : AVIF_PLANES_RGB;

avifImage *avif = avifImageCreate((int)width, (int)height, 8, avifFormat);
if (!avif) {
free(dest.data);
return nil;
}
avifImageAllocatePlanes(avif, planesFlags);

NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile([SDImageCoderHelper colorSpaceGetDeviceRGB]);

avifImageSetProfileICC(avif, (uint8_t *)iccProfile.bytes, iccProfile.length);

vImage_Buffer red, green, blue, alpha;
FillRGBABufferWithAVIFImage(&red, &green, &blue, &alpha, avif);

if (hasAlpha) {
v_error = vImageConvert_ARGB8888toPlanar8(&dest, &alpha, &red, &green, &blue, kvImageNoFlags);
} else {
v_error = vImageConvert_RGB888toPlanar8(&dest, &red, &green, &blue, kvImageNoFlags);
}
free(dest.data);
if (v_error != kvImageNoError) {
return nil;
}

double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
int rescaledQuality = 63 - (int)((compressionQuality) * 63.0f);

avifRawData raw = AVIF_RAW_DATA_EMPTY;
avifResult result = avifImageWrite(avif, &raw, 2, rescaledQuality);

if (result != AVIF_RESULT_OK) {
return nil;
}

NSData *imageData = [NSData dataWithBytes:raw.data length:raw.size];
free(raw.data);

return imageData;
}


Expand Down

0 comments on commit cf0b5b2

Please sign in to comment.