Implement view to image rendering (snapshotting)
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.
- Add
func render(_ widget: Widget) throws -> Image<RGBA>requirement toAppBackend. - Implement
renderrequirement for your preferred backend to have something concrete to test against. - Implement a public API for rendering views to images using the new
AppBackendmethod. - 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.