Skip to content

Added support to encode APNG instead of GIF, when no animated image format provided#3867

Merged
dreampiggy merged 3 commits into
SDWebImage:masterfrom
dreampiggy:feature/encode_format_unknown_animation
Feb 6, 2026
Merged

Added support to encode APNG instead of GIF, when no animated image format provided#3867
dreampiggy merged 3 commits into
SDWebImage:masterfrom
dreampiggy:feature/encode_format_unknown_animation

Conversation

@dreampiggy

@dreampiggy dreampiggy commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

New Pull Request Checklist

  • I have read and understood the CONTRIBUTING guide

  • I have read the Documentation

  • I have searched for a similar pull request in the project and found none

  • I have updated this branch with the latest master to avoid conflicts (via merge from master or rebase)

  • I have added the required tests to prove the fix/feature I am adding

  • I have updated the documentation (if necessary)

  • I have run the tests and they pass

  • I have run the lint and it passes (pod lib lint)

This merge request fixes / refers to the following issues: ...

Pull Request Description

This can get better animation encoding quality, APNG is far more better than GIF. And since it's already used by many other platform, I think it's time to convert our solution for some cases.

Summary by CodeRabbit

  • New Features

    • Animated images now use PNG on iOS 12+/tvOS 12+/macOS 10.14+/watchOS 5+, falling back to GIF on older OS versions for improved rendering.
  • Documentation

    • Docs updated to reflect platform-specific animated image format support and AppKit behavior across macOS versions.
  • Tests

    • Test expectations adjusted to account for platform-dependent animated image format selection.

@coderabbitai

coderabbitai Bot commented Feb 6, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

When caching animated images with undefined format, the code now prefers PNG on iOS 12+/tvOS 12+/macOS 10.14+/watchOS 5.0+ and falls back to GIF on older OS versions. Documentation and tests were updated; gain-map handling received ARC bridging and control-flow adjustments.

Changes

Cohort / File(s) Summary
Cache & Helper Logic
SDWebImage/Core/SDImageCache.m, SDWebImage/Core/SDImageCoderHelper.m
Add runtime OS-availability checks to prefer PNG for animated-image encoding on newer OS releases and GIF on older releases. SDImageCoderHelper.m also introduces a local format variable (presently not used elsewhere in the diff).
Documentation
SDWebImage/Core/SDImageCoderHelper.h
Expand docs to clarify NSImage animation behavior across macOS versions (GIF-only before 10.13; APNG/PNG usage on 10.14+).
Tests — Cache Format Expectation
Tests/Tests/SDImageCacheTests.m
Adjust test to expect PNG on supported OS versions and GIF on older OS versions when format is unspecified.
Tests — Gain-map / Memory Handling
Tests/Tests/SDImageCoderTests.m
Refactor gain-map extraction to return ISOGainMap early when present, use __bridge_transfer for CF -> NSDictionary, remove manual CFRelease calls, and add explicit nil return paths.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibble bytes beneath the moon,
Animated frames now favor PNG soon.
Older trails keep the GIF in view,
Tests tuned to each system true.
A tiny hop — commits anew!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: adding APNG encoding support as a replacement for GIF when no animated image format is provided, which aligns with the core objectives and file modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dreampiggy
dreampiggy force-pushed the feature/encode_format_unknown_animation branch from 2737a2b to 6bd6b6e Compare February 6, 2026 14:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
Tests/Tests/SDImageCacheTests.m (2)

506-522: ⚠️ Potential issue | 🟡 Minor

Stale test description and comment.

Line 441 (expectation5's description) still says "should use GIF", and Line 506's comment says "should use GIF not APNG". Both are now inaccurate since newer OS versions will expect PNG. Update them to reflect the version-dependent behavior.

📝 Suggested updates
-    XCTestExpectation *expectation5 = [self expectationWithDescription:@"StoreImage UIAnimatedImage without sd_imageFormat should use GIF"];
+    XCTestExpectation *expectation5 = [self expectationWithDescription:@"StoreImage UIAnimatedImage without sd_imageFormat should use APNG on iOS 12+/GIF otherwise"];
-    // Case 5: UIAnimatedImage without sd_imageFormat should use GIF not APNG
+    // Case 5: UIAnimatedImage without sd_imageFormat should use APNG on newer OS, GIF on older OS

514-517: ⚠️ Potential issue | 🟡 Minor

Stale inline comment on Line 516.

The comment // Should save to GIF is no longer accurate — on newer OS versions it saves to PNG/APNG.

SDWebImage/Core/SDImageCoderHelper.m (1)

178-197: ⚠️ Potential issue | 🔴 Critical

Bug: format variable is declared but never used — animated frames always encoded as GIF.

Lines 178–183 compute format based on OS version (PNG for iOS 12+ / macOS 10.14+, GIF otherwise), but line 184 ignores it and hardcodes SDImageFormatGIF:

CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF]; // always GIF

Additionally, line 196 uses GIF-specific property keys (kCGImagePropertyGIFDictionary / kCGImagePropertyGIFDelayTime) regardless of the format. For APNG, these should use the PNG-specific equivalents nested inside kCGImagePropertyPNGDictionary (e.g., kCGImagePropertyAPNGDelayTime).

As a result, animatedImageWithFrames: always produces GIF data regardless of OS version, defeating the purpose of this PR.

🐛 Proposed fix
     SDImageFormat format;
     if (`@available`(iOS 12.0, tvOS 12.0, macOS 10.14, watchOS 5.0, *)) {
         format = SDImageFormatPNG;
     } else {
         format = SDImageFormatGIF;
     }
-    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF];
-    // Create an image destination. GIF does not support EXIF image orientation
+    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
+    // Create an image destination. GIF/APNG does not support EXIF image orientation
     CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
     if (!imageDestination) {
         // Handle failure.
         return nil;
     }
     
     for (size_t i = 0; i < frameCount; i++) {
         SDImageFrame *frame = frames[i];
         NSTimeInterval frameDuration = frame.duration;
         CGImageRef frameImageRef = frame.image.CGImage;
-        NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
+        NSDictionary *frameProperties;
+        if (format == SDImageFormatPNG) {
+            frameProperties = @{(__bridge NSString *)kCGImagePropertyPNGDictionary : @{(__bridge NSString *)kCGImagePropertyAPNGDelayTime : @(frameDuration)}};
+        } else {
+            frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
+        }
         CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
     }
🤖 Fix all issues with AI agents
In `@SDWebImage/Core/SDImageCache.m`:
- Around line 276-283: Update the stale comment near the SDImageFormatUndefined
check to reflect the conditional choice of PNG/APNG on newer OSes: mention that
if image.sd_imageFrameCount > 1 the code uses PNG (APNG on supported OS
versions) when available and falls back to GIF on older OSes (SDImageFormatPNG
vs SDImageFormatGIF), so modify the comment to describe that behavior instead of
saying "use GIF (APNG may be better...)".

In `@SDWebImage/Core/SDImageCoderHelper.h`:
- Around line 71-72: Update the doc comment in SDImageCoderHelper.h to match the
implementation's availability check: change the "before macOS 10.13" phrasing to
"before macOS 10.14" (or rephrase to "macOS prior to 10.14") and indicate that
APNG is used on macOS 10.14 and later so macOS 10.13 is clearly included in the
GIF path; edit the comment block around the NSImage/animate description to use
"before macOS 10.14" and "macOS 10.14 and later" to align with the
`@available`(macOS 10.14, *) check in the implementation.

Comment on lines 276 to +283
if (format == SDImageFormatUndefined) {
// If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
if (image.sd_imageFrameCount > 1) {
format = SDImageFormatGIF;
if (@available(iOS 12.0, tvOS 12.0, macOS 10.14, watchOS 5.0, *)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatGIF;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stale comment on Line 277.

The comment still reads "use GIF (APNG may be better, but has bugs before macOS 10.14)" but the code now conditionally uses PNG/APNG on newer OS versions. Update the comment to reflect the new behavior.

📝 Suggested comment update
-                // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
+                // If image is animated, prefer APNG on newer OS versions for better quality; fall back to GIF on older OS versions (APNG has bugs before macOS 10.14)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (format == SDImageFormatUndefined) {
// If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
if (image.sd_imageFrameCount > 1) {
format = SDImageFormatGIF;
if (@available(iOS 12.0, tvOS 12.0, macOS 10.14, watchOS 5.0, *)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatGIF;
}
if (format == SDImageFormatUndefined) {
// If image is animated, prefer APNG on newer OS versions for better quality; fall back to GIF on older OS versions (APNG has bugs before macOS 10.14)
if (image.sd_imageFrameCount > 1) {
if (`@available`(iOS 12.0, tvOS 12.0, macOS 10.14, watchOS 5.0, *)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatGIF;
}
🤖 Prompt for AI Agents
In `@SDWebImage/Core/SDImageCache.m` around lines 276 - 283, Update the stale
comment near the SDImageFormatUndefined check to reflect the conditional choice
of PNG/APNG on newer OSes: mention that if image.sd_imageFrameCount > 1 the code
uses PNG (APNG on supported OS versions) when available and falls back to GIF on
older OSes (SDImageFormatPNG vs SDImageFormatGIF), so modify the comment to
describe that behavior instead of saying "use GIF (APNG may be better...)".

Comment on lines +71 to +72
For AppKit before macOS 10.13, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering. Attention the animated image may loss some detail if the input frames contain full alpha channel because GIF only supports 1 bit alpha channel. (For 1 pixel, either transparent or not)
For AppKit after macOS 10.14, use APNG format, which has a better render result.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Documentation gap for macOS 10.13.

The doc says "before macOS 10.13" uses GIF and "after macOS 10.14" uses APNG, but says nothing about macOS 10.13 itself. Since the @available check in the implementation uses macOS 10.14, macOS 10.13 falls into the GIF path. Consider rewording to "before macOS 10.14" for clarity and consistency with the implementation.

📝 Suggested doc fix
- For AppKit before macOS 10.13, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering. Attention the animated image may loss some detail if the input frames contain full alpha channel because GIF only supports 1 bit alpha channel. (For 1 pixel, either transparent or not)
- For AppKit after macOS 10.14, use APNG format, which has a better render result.
+ For AppKit before macOS 10.14, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering. Attention the animated image may loss some detail if the input frames contain full alpha channel because GIF only supports 1 bit alpha channel. (For 1 pixel, either transparent or not)
+ For AppKit on macOS 10.14+, use APNG format, which has a better render result.
🤖 Prompt for AI Agents
In `@SDWebImage/Core/SDImageCoderHelper.h` around lines 71 - 72, Update the doc
comment in SDImageCoderHelper.h to match the implementation's availability
check: change the "before macOS 10.13" phrasing to "before macOS 10.14" (or
rephrase to "macOS prior to 10.14") and indicate that APNG is used on macOS
10.14 and later so macOS 10.13 is clearly included in the GIF path; edit the
comment block around the NSImage/animate description to use "before macOS 10.14"
and "macOS 10.14 and later" to align with the `@available`(macOS 10.14, *) check
in the implementation.

@dreampiggy
dreampiggy force-pushed the feature/encode_format_unknown_animation branch from 6bd6b6e to 77f2d03 Compare February 6, 2026 14:15
@dreampiggy
dreampiggy merged commit 7537255 into SDWebImage:master Feb 6, 2026
2 of 7 checks passed
@dreampiggy dreampiggy added this to the 5.21.6 milestone Feb 6, 2026
@dreampiggy
dreampiggy deleted the feature/encode_format_unknown_animation branch February 6, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant