<div align="center">
<a href="https://darkly.art"><img src="https://github.com/user-attachments/assets/62115b89-ab63-453c-93ce-a513e500fad7" alt="Darkly — a GPU-native paint engine in Rust" width="600"></a>
**A GPU-native paint engine for Rust: brushes, layers, blend modes, masks, selections, and undo, on [wgpu](https://wgpu.rs) (WebGPU). Runs on the desktop or in the browser via WebAssembly.**
[](https://crates.io/crates/darkly)
[](https://docs.rs/darkly)
[](https://github.com/darkly-art/darkly)
[](https://github.com/darkly-art/darkly/blob/master/LICENSE)
[](https://discord.gg/kFz2FGhbpu)
</div>
---
`darkly` is the Rust core of [Darkly](https://darkly.art), a GPU-native paint program for artists. It provides all the essential features: pressure-sensitive brush strokes, layers and groups, blend modes, masks, selections, and undo/redo, all on the GPU.
To render, it needs a wgpu surface (a native window or an HTML canvas). It then builds the layer tree and the GPU textures behind it, and composites frames on demand. The [Darkly web app](https://demo.darkly.art) is this exact crate behind a thin WebAssembly bridge.
This crate can be used to power the backend for any drawing app, whiteboard, texture editor, etc. It includes only the engine: no frontend. The [Darkly](https://darkly.art) app is the reference frontend.
## Features
- **Node-graph brush engine.** Every brush (round, watercolor, smudge, liquify, clone) compiles to a single GPU pipeline.
- **Layers and groups.** A real layer tree with blend modes and per-layer opacity.
- **Masks and selections.** Rectangle, ellipse, lasso, polygon, and magic-wand modifiers.
- **First-class undo/redo.** Every edit is an undoable, serializable document operation.
- **GPU-native compositing.** wgpu/WebGPU pipelines, with the document as the source of truth and the compositor derived from it.
- **One engine, native or web.** The same code drives desktop builds and the in-browser editor.
## Example
Create a layer, paint a brush stroke across it, and composite a frame. The wgpu `Instance` and `Surface` come from a native window or an HTML canvas:
```rust
use darkly::engine::{DarklyEngine, types::StrokeOp};
use darkly::gpu::context::GpuContext;
async fn run(instance: wgpu::Instance, surface: wgpu::Surface<'static>) {
// GpuContext owns the wgpu device/queue + canvas surface; the engine owns
// the authoritative document, session state, and undo stack.
let gpu = GpuContext::new(instance, surface, wgpu::Limits::default(), 1920, 1080).await;
let mut engine = DarklyEngine::new(gpu, 1920, 1080);
// Add a raster layer and paint a horizontal brush stroke across it.
let layer = engine.add_raster_layer(None);
engine.begin_stroke(layer);
for i in 0..20 {
engine.stroke_to(StrokeOp::BrushStroke {
x: i as f32 * 96.0,
y: 540.0,
pressure: 1.0,
x_tilt: 0.0,
y_tilt: 0.0,
rotation: 0.0,
tangential_pressure: 0.0,
time_ms: i as f64 * 16.0,
cr: 0.1, cg: 0.6, cb: 1.0, ca: 1.0, // brush color, linear RGBA
});
}
engine.end_stroke();
// Composite the document to the surface. Call once per frame; the arg is the
// animation clock in seconds (drives time-based veils). Returns whether it drew.
engine.render(0.0);
// Undo is first-class: this rolls the stroke back off the document.
engine.undo();
}
```
> **Pre-1.0:** Darkly is in beta and this crate tracks the app's needs, so the API will change between releases. Pin an exact version if you depend on it.
## Project
- **Try it now:** [demo.darkly.art](https://demo.darkly.art)
- **Documentation:** [darkly.art/docs](https://darkly.art/docs)
- **Community:** [Discord](https://discord.gg/kFz2FGhbpu)
- **Issues and source:** [github.com/darkly-art/darkly](https://github.com/darkly-art/darkly)
## Acknowledgements
A special thank you to **[Nick Cameron](https://github.com/nrc)** for graciously gifting us the `darkly` crate on crates.io. 💜