Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.10
github "SDWebImage/libavif-Xcode" >= 0.11.2-rc1
github "SDWebImage/libavif-Xcode" >= 1.0.0
16 changes: 8 additions & 8 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.10.0"),
.package(url: "https://github.com/SDWebImage/libavif-Xcode.git", from: "0.11.0")
.package(url: "https://github.com/SDWebImage/libavif-Xcode.git", from: "1.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,24 @@ let avifData = SDImageAVIFCoder.shared.encodedData(with: image, format: .avif, o
let lossyAVIFData = SDImageAVIFCoder.shared.encodedData(with: image, format: .avif, options: [.encodeCompressionQuality: 0.1]) // [0, 1] compression quality
```

### Thumbnail Encoding (0.12.0+)

+ Objective-C

```objective-c
// AVIF image thumbnail encoding
UIImage *image;
NSData *thumbnailAVIFData = [[SDImageAVIFCoder sharedCoder] encodedDataWithImage:image format:SDImageFormatAVIF options:@{SDImageCoderEncodeMaxPixelSize : @(CGSizeMake(200, 200))}]; // encoding max pixel size
```

+ Swift

```swift
// AVIF image thumbnail encoding
let image: UIImage
let thumbnailAVIFData = SDImageAVIFCoder.shared.encodedData(with: image, format: .AVIF, options: [.encodeMaxPixelSize: CGSize(width: 200, height: 200)]) // encoding max pixel size
```

## Screenshot

<img src="https://raw.githubusercontent.com/SDWebImage/SDWebImageAVIFCoder/master/Example/Screenshot/AVIFDemo-iOS.png" width="300" />
Expand Down
4 changes: 2 additions & 2 deletions SDWebImageAVIFCoder.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'SDWebImageAVIFCoder'
s.version = '0.11.1'
s.version = '0.12.0'
s.summary = 'A SDWebImage coder plugin to support AVIF(AV1 Image File Format) image'

# This description is used to generate tags and improve search results.
Expand Down Expand Up @@ -40,6 +40,6 @@ Which is built based on the open-sourced libavif codec.
}

s.dependency 'SDWebImage', '~> 5.10'
s.dependency 'libavif/core', '>= 0.11.0'
s.dependency 'libavif/core', '>= 1.0.0'
s.libraries = 'c++'
end
67 changes: 57 additions & 10 deletions SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)

// Animated image
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
// if repetitionCount is a non-negative integer `n`, then the image sequence should be played back `n + 1` times.
int loopCount = decoder->repetitionCount + 1;
if (loopCount < 0) {
loopCount = 0;
}
while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) {
@autoreleasepool {
CGImageRef originImageRef = SDCreateCGImageFromAVIF(decoder->image);
Expand Down Expand Up @@ -209,7 +214,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
avifDecoderDestroy(decoder);

UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = 0;
animatedImage.sd_imageLoopCount = loopCount;
animatedImage.sd_imageFormat = SDImageFormatAVIF;

return animatedImage;
Expand Down Expand Up @@ -305,24 +310,62 @@ - (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDIm
avifImageRGBToYUV(avif, &rgb);
free(dest.data);

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

avifImageSetProfileICC(avif, (uint8_t *)iccProfile.bytes, iccProfile.length);
// We must prefer the input CGImage's color space, which may contains ICC profile
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
// We only supports RGB colorspace, filter the un-supported one (like Monochrome, CMYK, etc)
if (CGColorSpaceGetModel(colorSpace) != kCGColorSpaceModelRGB) {
// Ignore and convert, we don't know how to encode this colorspace directlly to WebP
// This may cause little visible difference because of colorpsace conversion
colorSpace = NULL;
}
if (!colorSpace) {
colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB];
}
// Add ICC profile if present
CFDataRef iccData = NULL;
if (colorSpace) {
if (@available(iOS 10, tvOS 10, macOS 10.12, watchOS 3, *)) {
iccData = CGColorSpaceCopyICCData(colorSpace);
}
}
if (iccData && CFDataGetLength(iccData) > 0) {
avifImageSetProfileICC(avif, CFDataGetBytePtr(iccData), CFDataGetLength(iccData));
}

double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
int rescaledQuality = AVIF_QUANTIZER_WORST_QUALITY - (int)((compressionQuality) * AVIF_QUANTIZER_WORST_QUALITY);
int quality = compressionQuality * (AVIF_QUALITY_BEST - AVIF_QUALITY_WORST);
CGSize maxPixelSize = CGSizeZero;
NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
if (maxPixelSizeValue != nil) {
#if SD_MAC
maxPixelSize = maxPixelSizeValue.sizeValue;
#else
maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
}

avifRWData raw = AVIF_DATA_EMPTY;
avifEncoder *encoder = avifEncoderCreate();
encoder->codecChoice = codecChoice;
encoder->minQuantizer = rescaledQuality;
encoder->maxQuantizer = rescaledQuality;
encoder->minQuantizerAlpha = rescaledQuality;
encoder->maxQuantizerAlpha = rescaledQuality;
encoder->quality = quality;
encoder->qualityAlpha = quality;
encoder->maxThreads = 2;
// Check if need to scale pixel size
CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(width, height) scaleSize:maxPixelSize preserveAspectRatio:YES shouldScaleUp:NO];
if (!CGSizeEqualToSize(scaledSize, CGSizeMake(width, height))) {
// Thumbnail Encoding
assert(scaledSize.width <= width);
assert(scaledSize.height <= height);
avifScalingMode scale;
scale.horizontal.n = (int)scaledSize.width;
scale.horizontal.d = (int)width;
scale.vertical.n = (int)scaledSize.height;
scale.vertical.d = (int)height;
encoder->scalingMode = scale;
}
Comment on lines 356 to 368
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Shadowed variable scaledSize on line 342 duplicates the computation from line 339.

Inside the if block, CGSize scaledSize is re-declared with the exact same expression already evaluated three lines above. This shadows the outer scaledSize and is dead/redundant code. Remove the inner declaration so the block uses the already-computed value.

Additionally, the assert on lines 343–344 uses strict <. With floating-point rounding in aspect-ratio-preserving scaling, one dimension could conceivably equal the original, causing a debug-mode abort. Consider <= or removing the asserts in favor of a graceful early-return.

🔧 Proposed fix — remove duplicate declaration
     // Check if need to scale pixel size
     CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(width, height) scaleSize:maxPixelSize preserveAspectRatio:YES shouldScaleUp:NO];
     if (!CGSizeEqualToSize(scaledSize, CGSizeMake(width, height))) {
         // Thumbnail Encoding
-        CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(width, height) scaleSize:maxPixelSize preserveAspectRatio:YES shouldScaleUp:NO];
-        assert(scaledSize.width < width);
-        assert(scaledSize.height < height);
+        assert(scaledSize.width <= width);
+        assert(scaledSize.height <= height);
         avifScalingMode scale;
         scale.horizontal.n = (int)scaledSize.width;
         scale.horizontal.d = (int)width;
🤖 Prompt for AI Agents
In `@SDWebImageAVIFCoder/Classes/SDImageAVIFCoder.m` around lines 338 - 351, The
local variable scaledSize is being recomputed and shadowed inside the if block;
remove the inner "CGSize scaledSize" declaration so the block uses the
previously computed scaledSize from the call to [SDImageCoderHelper
scaledSizeWithImageSize:scaleSize:preserveAspectRatio:shouldScaleUp:] and then
set encoder->scalingMode accordingly (assign scale.horizontal/vertical using
scaledSize and original width/height). Also replace the strict asserts
(assert(scaledSize.width < width); assert(scaledSize.height < height);) with
non-fatal checks (use <= or remove the asserts) to avoid debug aborts due to
floating-point rounding—ensure the code handles equality gracefully before
assigning encoder->scalingMode.

avifResult result = avifEncoderWrite(encoder, avif, &raw);

avifImageDestroy(avif);
Expand Down Expand Up @@ -361,7 +404,11 @@ - (instancetype)initWithAnimatedImageData:(NSData *)data options:(SDImageCoderOp
}
// TODO: Optimize the performance like WebPCoder (frame meta cache, etc)
_frameCount = decoder->imageCount;
_loopCount = 0;
int loopCount = decoder->repetitionCount + 1;
if (loopCount < 0) {
loopCount = 0;
}
_loopCount = loopCount;
_hasAnimation = decoder->imageCount > 1;
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
Expand Down
Loading