A high-performance 2D graphics library for Rust, providing a complete HTML5 Canvas API implementation built on Google's Skia graphics engine.
This is a pure Rust port of the popular skia-canvas library, eliminating the need for Node.js bindings while maintaining full API compatibility.
- 🚀 High Performance: Hardware-accelerated rendering via Skia
- 🎨 Complete Canvas API: Full HTML5 Canvas 2D context implementation
- 🖼️ Advanced Graphics: Gradients, patterns, filters, and effects
- 📝 Rich Typography: Advanced text rendering with OpenType features
- 🔧 Path Operations: Boolean operations, interpolation, and effects
- 🖥️ GPU Acceleration: Vulkan (Linux/Windows) and Metal (macOS) backends
- 📦 Multiple Export Formats: PNG, JPEG, WebP, PDF, SVG
- ✅ Battle-tested: 290+ tests ported from the original implementation
Add this to your Cargo.toml:
[dependencies]
skia-graphics-rs = "0.1.0"default: Includes async supportasync: Async runtime support via Tokiowindow: Desktop windowing support (experimental)vulkan: Vulkan GPU backendmetal: Metal GPU backend (macOS)freetype: FreeType font rendering with WOFF2 supporthttp: HTTP image loading support
Example with specific features:
[dependencies]
skia-graphics-rs = { version = "0.1.0", features = ["vulkan", "freetype"] }Currently requires a local skia-bindings patch. Add to your Cargo.toml:
[patch.crates-io]
skia-bindings = { path = "/tmp/rust-skia/skia-bindings" }use skia_graphics_rs::{Canvas, Path2D};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a canvas
let mut canvas = Canvas::new(800.0, 600.0)?;
let ctx = canvas.get_context_2d();
// Draw a red rectangle
ctx.set_fill_style_color(255, 0, 0, 255);
ctx.fill_rect(50.0, 50.0, 200.0, 100.0);
// Draw a blue circle using Path2D
let mut path = Path2D::new();
path.arc(400.0, 300.0, 50.0, 0.0, std::f32::consts::PI * 2.0, false);
ctx.set_fill_style_color(0, 0, 255, 255);
ctx.fill_path(&path);
// Add some text
ctx.set_font_string("48px sans-serif");
ctx.set_fill_style_color(0, 0, 0, 255);
ctx.fill_text("Hello, Rust!", 250.0, 400.0);
// Save to file
canvas.save_as("output.png", 1.0)?;
Ok(())
}// Create a canvas with dimensions
let canvas = Canvas::new(width, height)?;
// Get the 2D rendering context
let ctx = canvas.get_context_2d();
// Save to various formats
canvas.save_as("output.png", 1.0)?; // PNG with quality
canvas.save_as("output.jpg", 0.9)?; // JPEG with quality
canvas.save_as("output.pdf", 1.0)?; // PDF
canvas.save_as("output.svg", 1.0)?; // SVG// Basic shapes
ctx.fill_rect(x, y, width, height);
ctx.stroke_rect(x, y, width, height);
ctx.clear_rect(x, y, width, height);
// Paths
ctx.begin_path();
ctx.move_to(x, y);
ctx.line_to(x, y);
ctx.arc(x, y, radius, start_angle, end_angle, anticlockwise);
ctx.bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
ctx.quadratic_curve_to(cpx, cpy, x, y);
ctx.close_path();
ctx.fill();
ctx.stroke();
// Styles
ctx.set_fill_style_color(r, g, b, a);
ctx.set_stroke_style_color(r, g, b, a);
ctx.set_line_width(width);
ctx.set_line_cap("round"); // "butt", "round", "square"
ctx.set_line_join("miter"); // "miter", "round", "bevel"let mut path1 = Path2D::new();
let mut path2 = Path2D::new();
// Boolean operations
let union = path1.union(&path2);
let intersection = path1.intersect(&path2);
let difference = path1.difference(&path2);
let xor = path1.xor(&path2);
// Path effects
let rounded = path.round(radius);
let trimmed = path.trim(start, end, false);
let simplified = path.simplify(FillType::Winding);
// Transformations
let transformed = path.transform(&matrix);
let offset = path.offset(dx, dy);
// Interpolation
let interpolated = path1.interpolate(&path2, 0.5);// Set font properties
ctx.set_font_string("bold 24px Arial");
ctx.set_text_align("center"); // "left", "right", "center", "start", "end"
ctx.set_text_baseline("middle"); // "top", "middle", "bottom", "alphabetic", etc.
// Draw text
ctx.fill_text("Hello", x, y);
ctx.stroke_text("World", x, y);
// Measure text
let metrics = ctx.measure_text("Sample text");
println!("Width: {}", metrics.width);// Linear gradient
let gradient = ctx.create_linear_gradient(x0, y0, x1, y1);
gradient.add_color_stop(0.0, "red");
gradient.add_color_stop(1.0, "blue");
ctx.set_fill_style_gradient(&gradient);
// Radial gradient
let gradient = ctx.create_radial_gradient(x0, y0, r0, x1, y1, r1);
gradient.add_color_stop(0.0, "yellow");
gradient.add_color_stop(1.0, "green");
// Pattern from image
let pattern = ctx.create_pattern(&image, "repeat")?;
ctx.set_fill_style_pattern(&pattern);// Load and draw images
let image = Image::from_file("image.png")?;
ctx.draw_image(&image, x, y);
ctx.draw_image_with_size(&image, x, y, width, height);
// Get/put image data
let image_data = ctx.get_image_data(x, y, width, height)?;
ctx.put_image_data(&image_data, x, y);// Transform the coordinate system
ctx.translate(x, y);
ctx.rotate(angle);
ctx.scale(x, y);
ctx.transform(a, b, c, d, e, f);
ctx.set_transform(a, b, c, d, e, f);
ctx.reset_transform();
// Save and restore state
ctx.save();
// ... drawing operations ...
ctx.restore();This library implements the complete HTML5 Canvas 2D API, including:
- ✅
fillRect(),strokeRect(),clearRect() - ✅
fillText(),strokeText(),measureText() - ✅
beginPath(),closePath(),moveTo(),lineTo() - ✅
arc(),arcTo(),ellipse() - ✅
bezierCurveTo(),quadraticCurveTo() - ✅
rect(),roundRect() - ✅
fill(),stroke(),clip() - ✅
drawImage()(with multiple overloads) - ✅
getImageData(),putImageData(),createImageData()
- ✅
fillStyle,strokeStyle - ✅
lineWidth,lineCap,lineJoin,miterLimit - ✅
lineDashOffset,setLineDash(),getLineDash() - ✅
font,textAlign,textBaseline,direction - ✅
globalAlpha,globalCompositeOperation - ✅
shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor - ✅
filter - ✅
imageSmoothingEnabled,imageSmoothingQuality
- ✅ All standard path construction methods
- ✅
addPath() - ✅ Boolean operations:
union(),intersect(),difference(),xor(),complement() - ✅ Effects:
round(),trim(),jitter(),simplify(),unwind() - ✅ Transformations:
offset(),transform() - ✅ Analysis:
bounds(),contains(),edges() - ✅ SVG:
dproperty getter/setter
use skia_graphics_rs::{Canvas, Path2D};
let mut canvas = Canvas::new(500.0, 500.0)?;
let ctx = canvas.get_context_2d();
// Create a star path
let mut star = Path2D::new();
let points = 5;
let outer_radius = 100.0;
let inner_radius = 50.0;
let cx = 250.0;
let cy = 250.0;
for i in 0..(points * 2) {
let radius = if i % 2 == 0 { outer_radius } else { inner_radius };
let angle = (i as f32) * std::f32::consts::PI / (points as f32);
let x = cx + angle.cos() * radius;
let y = cy + angle.sin() * radius;
if i == 0 {
star.move_to(x, y);
} else {
star.line_to(x, y);
}
}
star.close_path();
// Apply gradient fill
let gradient = ctx.create_radial_gradient(cx, cy, 0.0, cx, cy, outer_radius);
gradient.add_color_stop(0.0, "yellow");
gradient.add_color_stop(1.0, "orange");
ctx.set_fill_style_gradient(&gradient);
ctx.fill_path(&star);
// Add stroke
ctx.set_stroke_style_color(255, 0, 0, 255);
ctx.set_line_width(3.0);
ctx.stroke_path(&star);// Apply CSS filters
ctx.set_filter("blur(5px)");
ctx.draw_image(&image, 0.0, 0.0);
ctx.set_filter("contrast(200%) brightness(150%)");
ctx.draw_image(&image, 100.0, 0.0);
// Combine multiple filters
ctx.set_filter("sepia(100%) hue-rotate(90deg)");
ctx.draw_image(&image, 200.0, 0.0);Run the test suite:
# Run all tests
cargo test
# Run specific test module
cargo test path2d
# Run with verbose output
cargo test -- --nocaptureThe library includes 290+ tests ported from the original skia-canvas, ensuring complete API compatibility.
This library maintains API compatibility with skia-canvas, making migration straightforward:
- Pure Rust implementation (no Node.js required)
- Methods use snake_case instead of camelCase (Rust convention)
- Color values use RGBA tuples instead of CSS strings in some low-level APIs
- Async operations use Rust futures instead of JavaScript promises
JavaScript (skia-canvas):
const {Canvas, Path2D} = require('@samizdatco/skia-canvas')
const canvas = new Canvas(400, 400)
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'red'
ctx.fillRect(50, 50, 100, 100)
await canvas.saveAs('output.png')Rust (skia-graphics-rs):
use skia_graphics_rs::{Canvas, Path2D};
let mut canvas = Canvas::new(400.0, 400.0)?;
let ctx = canvas.get_context_2d();
ctx.set_fill_style_color(255, 0, 0, 255);
ctx.fill_rect(50.0, 50.0, 100.0, 100.0);
canvas.save_as("output.png", 1.0)?;The library leverages Skia's optimized rendering pipeline with:
- Hardware acceleration via GPU backends (Vulkan/Metal)
- Efficient path caching and reuse
- Multi-threaded rendering operations
- Optimized image decoding and caching
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
- Clone the repository:
git clone https://github.com/GrigoryEvko/skia-graphics-rs.git
cd skia-graphics-rs- Build the project:
cargo build --release- Run tests:
cargo testMIT License - see LICENSE file for details.
- Original skia-canvas by Christian Swinehart
- Skia Graphics Library by Google
- rust-skia bindings
This is an active port from the original skia-canvas library. All core Canvas 2D API functionality has been implemented and tested. The library is suitable for production use in projects requiring high-performance 2D graphics rendering with Canvas API compatibility.
- Complete GUI/windowing module
- WebAssembly support
- Additional export formats
- Performance optimizations
- Extended filter effects
- Animation utilities
For questions, issues, or contributions, please visit the GitHub repository.