A Rust desktop client and library for pixels, a collaborative, r/place-style pixel canvas built for a community event.
You can run the full GUI client to draw on the shared canvas, or pull in pixels-canvas as a plain library and script the canvas yourself with no window and no rendering, just the API.
- Live canvas rendered with macroquad, auto-refreshing from the server every few seconds
- Four tools: move/pan, brush (paint a pixel), color picker (sample a pixel), and image placer (stamp a local image)
- egui side panel for color selection and tool switching, with a tooltip while you're on cooldown
- Layered canvas where the live server layer and a translucent image-preview layer are composited via alpha blending
- Server cooldown handled end-to-end, dimming the canvas and surfacing the remaining wait
- Reusable core so the networking, color, and canvas logic live in dependency-light library crates
The project is a Cargo workspace of three crates, layered bottom-up:
| Crate | Kind | Purpose |
|---|---|---|
pixels-util | library | Core primitives: Color (conversions, hex, alpha blend), the Pixels grid, and Cooldown |
pixels-canvas | library | Canvas, Layer, Element, and the HTTP Client that talks to the server |
pixels-interface | binary | The GUI app: macroquad (render) + egui (panel) + bevy_ecs (systems) |
cargo run -p pixels-interface -- <refresh-token>On launch you're asked whether to select a PNG/JPEG to paste onto the canvas; pick one to enable the image placer tool.
Controls
| Action | Input |
|---|---|
| Move / pan | M, then drag |
| Brush | B, then click |
| Pick color | I, then click |
| Place image | P, then click |
| Zoom | scroll wheel |
pixels-canvas has no GUI dependencies. It authenticates, fetches the canvas, and sets pixels on its own. Add it as a path (or git) dependency and drive the canvas directly:
use pixels_canvas::prelude::*;use pixels_util::color::Color;fnmain() -> Result<(),CanvasError>{// Authenticates with your refresh token and pulls the current canvas.letmut canvas = Canvas::new(std::env::var("REFRESH_TOKEN").unwrap())?;println!("canvas is {}x{}", canvas.width(), canvas.height());// Read a pixel.ifletSome(color) = canvas.get_pixel(10,10){println!("pixel (10,10) = #{}", color.to_hex(ColorMode::RGB));}// Write a pixel (respects the server-side cooldown).match canvas.set_pixel(10,10,Color::from_rgb(255,0,0)){Ok(()) => println!("placed!"),Err(CanvasError::Cooldown(secs)) => println!("wait {secs}s"),Err(CanvasError::Client(e)) => eprintln!("request failed: {e}"),}// Refresh the local copy from the server.
canvas.update_main_layer()?;Ok(())}Key entry points:
Canvas::new(refresh)authenticates and loads the canvasCanvas::get_pixel/set_pixelread and write single pixelsCanvas::update_main_layerre-fetches the live canvas from the serverCanvas::get_layers_mergedreturns the composited canvas for renderingColor/Pixels(frompixels-util) work with pixel data offline
cargo build # all crates
cargo run -p pixels-interface -- <refresh-token>See License.md.