Skip to content

Refactor: extract shared WebPEncoder, unify error handling, improve API#1

Merged
alexey1312 merged 1 commit intomainfrom
claude/explore-library-improvements-iiasQ
Feb 12, 2026
Merged

Refactor: extract shared WebPEncoder, unify error handling, improve API#1
alexey1312 merged 1 commit intomainfrom
claude/explore-library-improvements-iiasQ

Conversation

@alexey1312
Copy link
Copy Markdown
Owner

  • Extract shared WebP encoding logic into WebPEncoder.swift, eliminating
    ~200 lines of duplicated code between UIImage+WebP and NSImage+WebP
  • Add WebPEncodingStatistics API for tracking compression metrics
    (original size, encoded size, compression ratio, encoding duration)
  • Unify NSImage diffing to return descriptive String? error messages
    matching UIImage behavior (was returning uninformative Bool)
  • Replace fatalError in NSView/NSViewController with graceful error
    images when view size is zero, preventing test runner crashes
  • Add value clamping (0.0...1.0) for CompressionQuality.custom() in
    both init(rawValue:) and rawValue getter
  • Document recommended precision thresholds per compression quality
    level with CIE Lab Delta E explanation

https://claude.ai/code/session_01C2v2vuCbmKoHnXZyAAPtRg

- Extract shared WebP encoding logic into WebPEncoder.swift, eliminating
  ~200 lines of duplicated code between UIImage+WebP and NSImage+WebP
- Add WebPEncodingStatistics API for tracking compression metrics
  (original size, encoded size, compression ratio, encoding duration)
- Unify NSImage diffing to return descriptive String? error messages
  matching UIImage behavior (was returning uninformative Bool)
- Replace fatalError in NSView/NSViewController with graceful error
  images when view size is zero, preventing test runner crashes
- Add value clamping (0.0...1.0) for CompressionQuality.custom() in
  both init(rawValue:) and rawValue getter
- Document recommended precision thresholds per compression quality
  level with CIE Lab Delta E explanation

https://claude.ai/code/session_01C2v2vuCbmKoHnXZyAAPtRg
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @alexey1312, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the WebP snapshot testing library by centralizing image encoding logic, improving error reporting, and enhancing API robustness. The changes aim to reduce code duplication, provide clearer feedback during snapshot comparisons, and prevent runtime crashes due to invalid input, while also offering new insights into compression performance.

Highlights

  • Shared WebP Encoding Logic Extraction: Extracted common WebP encoding functionality from UIImage+WebP and NSImage+WebP into a new, dedicated WebPEncoder.swift file, significantly reducing code duplication.
  • WebP Encoding Statistics API: Introduced a new WebPEncodingStatistics API to track metrics such as original image size, encoded WebP size, compression ratio, and encoding duration for each operation.
  • Unified NSImage Diffing Error Handling: Refactored NSImage comparison to return descriptive String? error messages instead of a boolean, providing more informative feedback on snapshot mismatches.
  • Graceful Error Handling for Zero-Sized Views: Replaced fatalError calls in NSView and NSViewController snapshot strategies with a graceful error image display when attempting to render views with zero dimensions, preventing crashes.
  • CompressionQuality Value Clamping: Implemented clamping for custom CompressionQuality values to ensure they remain within the valid 0.0 to 1.0 range during initialization and retrieval.
  • Enhanced CompressionQuality Documentation: Added comprehensive documentation to the CompressionQuality enum, including recommended precision thresholds and an explanation of CIE Lab Delta E for perceptual comparison.
Changelog
  • Sources/SnapshotTestingWebP/CompressionQuality.swift
    • Added extensive documentation detailing recommended precision thresholds and perceptual precision based on CIE Lab Delta E.
    • Clamped the rawValue input in the init?(rawValue:) initializer to ensure it stays within 0.0 and 1.0.
    • Clamped the returned value for the .custom case in the rawValue getter to prevent out-of-range values.
  • Sources/SnapshotTestingWebP/NSImage.swift
    • Modified the compareWebP function to return a descriptive String? error message instead of a Bool, indicating specific reasons for image comparison failures.
    • Introduced detailed error messages for various failure scenarios, including image loading, empty snapshots, size mismatches, and precision/perceptual precision thresholds being exceeded.
    • Removed generic error message generation, now relying on the specific messages from compareWebP.
  • Sources/SnapshotTestingWebP/NSView.swift
    • Replaced fatalError with a call to a new errorImage helper function when a view has zero width or height, returning an image with an error message.
    • Added a private errorImage function to generate an NSImage displaying an error message on a red background.
  • Sources/SnapshotTestingWebP/NSViewController.swift
    • Replaced fatalError with a call to the errorImage helper function (from NSView extension) when a view controller's view has zero width or height, returning an image with an error message.
  • Sources/SnapshotTestingWebP/WebP/NSImage+WebP.swift
    • Removed the direct import of libwebp, as encoding logic is now centralized.
    • Refactored the webpData function to delegate WebP encoding to the new WebPEncoder utility, removing duplicated encoding implementation.
  • Sources/SnapshotTestingWebP/WebP/UIImage+WebP.swift
    • Removed the direct import of libwebp, as encoding logic is now centralized.
    • Refactored the webpData function to delegate WebP encoding to the new WebPEncoder utility, removing duplicated encoding implementation.
  • Sources/SnapshotTestingWebP/WebP/WebPEncoder.swift
    • Added a new file containing the shared WebP encoding logic.
    • Introduced WebPEncodingStatistics struct to capture encoding metrics like original size, encoded size, and duration.
    • Implemented a static WebPEncoder utility with a thread-safe lastStatistics property to store the most recent encoding results.
    • Centralized the core WebP encoding functionality, including pixel data extraction and libwebp configuration, into static methods.
Activity
  • The author, alexey1312, initiated this pull request to refactor and improve the WebP snapshot testing library.
  • The pull request description includes a link to a Claude AI code session, indicating that AI assistance was used in the development or review of these changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is an excellent pull request that significantly refactors the codebase by extracting shared WebP encoding logic, which eliminates a large amount of duplicated code. The error handling is also greatly improved by providing descriptive error messages instead of booleans or fatal errors. The addition of clamping for compression quality and replacing fatalError with graceful error images are also welcome improvements to robustness and user experience.

My main feedback concerns the new WebPEncodingStatistics API. The current implementation relies on shared global state (WebPEncoder.lastStatistics), which is an anti-pattern that can lead to race conditions in concurrent environments. I've provided suggestions to refactor this into a stateless API by having the encode function return the statistics directly. This will make the API safer and more predictable.

Overall, great work on this refactoring!

Comment on lines +29 to +35
static var lastStatistics: WebPEncodingStatistics? {
get { lock.lock(); defer { lock.unlock() }; return _lastStatistics }
set { lock.lock(); defer { lock.unlock() }; _lastStatistics = newValue }
}

private static var _lastStatistics: WebPEncodingStatistics?
private static let lock = NSLock()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The use of a static, mutable lastStatistics property introduces global state, which can lead to race conditions and unpredictable behavior in concurrent environments, such as when running tests in parallel. A function's output should not depend on when it was called relative to other calls.

This property and the associated lock should be removed. The encode function should be modified to return the statistics directly.

Comment on lines +43 to +76
static func encode(_ cgImage: CGImage, compressionQuality: CGFloat) -> Data? {
let width = cgImage.width
let height = cgImage.height
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width

guard let pixelData = extractPixelData(from: cgImage) else {
return nil
}
defer { pixelData.deallocate() }

let qualityValue = Float(min(max(compressionQuality, 0), 1))
let originalSize = width * height * bytesPerPixel
let start = CFAbsoluteTimeGetCurrent()

guard let data = encodeWebP(
pixelData: pixelData,
width: width,
height: height,
bytesPerRow: bytesPerRow,
quality: qualityValue
) else {
return nil
}

let duration = CFAbsoluteTimeGetCurrent() - start
lastStatistics = WebPEncodingStatistics(
originalSize: originalSize,
encodedSize: data.count,
encodingDuration: duration
)

return data
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

To eliminate global state and make the API safer for concurrent use, this function should be modified to return the statistics as part of its result, rather than setting a global variable as a side effect.

        static func encode(_ cgImage: CGImage, compressionQuality: CGFloat) -> (data: Data, statistics: WebPEncodingStatistics)? {
            let width = cgImage.width
            let height = cgImage.height
            let bytesPerPixel = 4
            let bytesPerRow = bytesPerPixel * width

            guard let pixelData = extractPixelData(from: cgImage) else {
                return nil
            }
            defer { pixelData.deallocate() }

            let qualityValue = Float(min(max(compressionQuality, 0), 1))
            let originalSize = width * height * bytesPerPixel
            let start = CFAbsoluteTimeGetCurrent()

            guard let data = encodeWebP(
                pixelData: pixelData,
                width: width,
                height: height,
                bytesPerRow: bytesPerRow,
                quality: qualityValue
            ) else {
                return nil
            }

            let duration = CFAbsoluteTimeGetCurrent() - start
            let statistics = WebPEncodingStatistics(
                originalSize: originalSize,
                encodedSize: data.count,
                encodingDuration: duration
            )

            return (data: data, statistics: statistics)
        }

}

return Data(bytes: writer.mem, count: writer.size)
return WebPEncoder.encode(cgImage, compressionQuality: compressionQuality)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Following the suggested change in WebPEncoder.swift to make it stateless, this call site needs to be updated to handle the new return type (a tuple (data: Data, statistics: WebPEncodingStatistics)?). Since this function only needs the data, you can access the .data property of the tuple.

Suggested change
return WebPEncoder.encode(cgImage, compressionQuality: compressionQuality)
return WebPEncoder.encode(cgImage, compressionQuality: compressionQuality)?.data

}

return Data(bytes: writer.mem, count: writer.size)
return WebPEncoder.encode(cgImage, compressionQuality: compressionQuality)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Following the suggested change in WebPEncoder.swift to make it stateless, this call site needs to be updated to handle the new return type (a tuple (data: Data, statistics: WebPEncodingStatistics)?). Since this function only needs the data, you can access the .data property of the tuple.

Suggested change
return WebPEncoder.encode(cgImage, compressionQuality: compressionQuality)
return WebPEncoder.encode(cgImage, compressionQuality: compressionQuality)?.data

@alexey1312 alexey1312 merged commit 5eca1da into main Feb 12, 2026
2 checks passed
@alexey1312 alexey1312 deleted the claude/explore-library-improvements-iiasQ branch February 12, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants