swift-cross-ui icon indicating copy to clipboard operation
swift-cross-ui copied to clipboard

Implement view to image rendering (snapshotting)

Open stackotter opened this issue 7 months ago • 0 comments

Motivation

  • Snapshot testing
  • Generating 2d infographics with SwiftCrossUI
  • Generating screenshots of apps for docs etc

Implementation approach

This approach is just a suggestion, and any specifics such as method names are just sensible placeholders which could be changed with sensible justification.

  1. Add func render(_ widget: Widget) throws -> Image<RGBA> requirement to AppBackend.
  2. Implement render requirement for your preferred backend to have something concrete to test against.
  3. Implement a public API for rendering views to images using the new AppBackend method.
  4. Implement the new backend method for the rest of the backends. Feel free to update the backend method with additional parameters etc if you find that any of the backends require additional information to correctly render the view to a bitmap.

Rough API design (subject to feedback!)

// Could just be a method? This method would construct a view graph,
// run an initial update, then snapshot the resulting root widget
// using the new backend method.
let image = try MyView().render(inFrame: SIMD2(100, 100))

// If there are additional configuration parameters we want users to
// be able to control then we could use a ViewRenderer struct or
// something along those lines.
let renderer = ViewRenderer(frame: SIMD2(100, 100))
renderer.blah = foo
let image = try renderer.render(MyView())

AppKitBackend implementation

I've already got something pretty reliable going in SwiftCrossUI unit tests: https://github.com/stackotter/swift-cross-ui/blob/d43805398f66de52ad0950c7a7eec7fe983fa8f0/Tests/SwiftCrossUITests/SwiftCrossUITests.swift#L68

Gtk3Backend implementation

You can use Widget.draw (not exposed in our bindings, but easy enough to add) to render to a cairo context: https://docs.gtk.org/gtk3/method.Widget.draw.html

WinUIBackend implementation

You can render to a bitmap: https://stackoverflow.com/q/50142401

UIKitBackend implementation

Source: https://stackoverflow.com/a/41288197

extension UIView {
    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

GtkBackend

I'm yet to look into Gtk 4. I'll update this issue with any leads I find.

stackotter avatar May 27 '25 08:05 stackotter