wassily_core/
canvas.rs

1//! # Canvas
2//!
3//! The Canvas is the primary drawing surface for wassily. It provides a high-level
4//! abstraction over tiny-skia's Pixmap with additional features for scalable rendering.
5//!
6//! ## Key Features
7//!
8//! - **Scalable Rendering**: Create images at different sizes using scale factors
9//! - **Multiple Output Formats**: Save as PNG, JPEG, and other image formats
10//! - **Direct Pixel Access**: Paint individual pixels or work with image data
11//! - **Image Integration**: Load from and convert to standard image formats
12//!
13//! ## Usage Patterns
14//!
15//! ### Basic Drawing
16//! ```no_run
17//! use wassily_core::*;
18//! use tiny_skia::Color;
19//!
20//! let mut canvas = Canvas::new(400, 300);
21//! canvas.fill(Color::WHITE);
22//! canvas.save_png("output.png");
23//! ```
24//!
25//! ### Scalable Output
26//! ```no_run
27//! use wassily_core::*;
28//! 
29//! // Same drawing code works at different scales
30//! let mut small = Canvas::new(400, 300);
31//! let mut large = Canvas::with_scale(400, 300, 2.0); // 800x600 output
32//! ```
33
34use crate::points::pt;
35use image::{DynamicImage, ImageFormat, RgbImage, RgbaImage};
36use tiny_skia::*;
37
38/// A `Canvas` is an abstraction over a tiny-skia `Pixmap` that allows for drawing at different
39/// scales. This allows for using the same drawing commands to produce images of different sizes.
40///
41/// ## Example
42///
43/// ```no_run
44/// use wassily_core::*;
45/// use tiny_skia::Color;
46///
47/// fn draw(canvas: &mut Canvas) {
48///     canvas.fill(Color::from_rgba8(128, 128, 128, 255));
49///     let center = pt(canvas.width() / 2, canvas.height() / 2);
50///     Shape::new()
51///         .ellipse(center, 300.0, 200.0)
52///         .fill_color(Color::from_rgba8(75, 0, 130, 255))
53///         .stroke_color(Color::from_rgba8(255, 165, 0, 255))
54///         .stroke_weight(10.0)
55///         .draw(canvas);
56/// }
57///
58/// fn main() {
59///    let mut canvas1 = Canvas::new(500, 500);
60///     // Draw a 300x200 ellipse at the center of a 500 x 500 canvas.
61///     draw(&mut canvas1);
62///
63///     let mut canvas2 = Canvas::with_scale(500, 500, 2.0);
64///     // Draw a 600x400 ellipse at the center of a 1000 x 1000 canvas. That is,
65///     // the exact same image as above, but, double the width and height.
66///     draw(&mut canvas2);
67///
68///     canvas1.save_png("./scale1.png");
69///     canvas2.save_png("./scale2.png");
70/// }
71/// ```
72pub struct Canvas {
73    /// The underlying tiny-skia `Pixmap`.
74    pub pixmap: Pixmap,
75    // The width of the unscaled map, in pixels. You should use this with in any drawing commands
76    // so that it will be scaled correctly at differing scales.
77    width: u32,
78    // The height of the unscaled map, in pixels.
79    height: u32,
80    /// The scale factor of the canvas. This is the factor by which the width and height are
81    /// scaled. For example, a scale of 2.0 means that the width and height are doubled when
82    /// rendering.
83    pub scale: f32,
84}
85
86impl Canvas {
87    /// Create a new unscaled `Canvas`.
88    pub fn new(width: u32, height: u32) -> Canvas {
89        Canvas {
90            pixmap: Pixmap::new(width, height).unwrap(),
91            width,
92            height,
93            scale: 1.0,
94        }
95    }
96
97    pub fn from_image(image: &DynamicImage) -> Canvas {
98        let rgba_img = image.to_rgba8();
99
100        let img_width = rgba_img.width();
101        let img_height = rgba_img.height();
102        let mut pixmap = Pixmap::new(img_width, img_height).unwrap();
103        pixmap.data_mut().copy_from_slice(rgba_img.as_raw());
104
105        Canvas {
106            pixmap: pixmap.to_owned(),
107            width: image.width(),
108            height: image.height(),
109            scale: 1.0,
110        }
111    }
112
113    /// Create a new `Canvas` with a scale factor. The actual size of the canvas is the width times
114    /// the scale and the height times the scale. However, all drawing commands should use the
115    /// pre-scaled `width` and `height`.
116    pub fn with_scale(width: u32, height: u32, scale: f32) -> Canvas {
117        let w = scale * width as f32;
118        let h = scale * height as f32;
119        Canvas {
120            pixmap: Pixmap::new(w as u32, h as u32).unwrap(),
121            width,
122            height,
123            scale,
124        }
125    }
126
127    // The width of the unscaled map, in pixels. You should use this with in any drawing commands
128    // so that it will be scaled correctly at differing scales.
129    pub fn width(&self) -> u32 {
130        self.width
131    }
132
133    /// The pre-scaled height of the canvas in pixels as a u32.
134    pub fn height(&self) -> u32 {
135        self.height
136    }
137
138    ///The pre-scaled width of the canvas in pixels as a f32.
139    pub fn w_f32(&self) -> f32 {
140        self.width as f32
141    }
142
143    /// The pre-scaled height of the canvas in pixels as a f32.
144    pub fn h_f32(&self) -> f32 {
145        self.height as f32
146    }
147
148    /// The pre-scaled width of the canvas in pixels as a usize.
149    pub fn w_usize(&self) -> usize {
150        self.width as usize
151    }
152
153    /// The pre-scaled height of the canvas in pixels as a usize.
154    pub fn h_usize(&self) -> usize {
155        self.height as usize
156    }
157
158    /// Fill the entire canvas with a `Color`.
159    pub fn fill(&mut self, color: Color) {
160        self.pixmap.fill(color);
161    }
162
163    /// Fill the path with a `Paint`.
164    pub fn fill_path(
165        &mut self,
166        path: &Path,
167        paint: &mut Paint,
168        fill_rule: FillRule,
169        transform: Transform,
170        clip_mask: Option<&Mask>,
171    ) {
172        let mut transform = transform;
173        transform = transform.post_scale(self.scale, self.scale);
174        self.pixmap
175            .fill_path(path, paint, fill_rule, transform, clip_mask);
176    }
177
178    /// Fill a rectangle with a `Paint`.
179    pub fn fill_rect(
180        &mut self,
181        rect: Rect,
182        paint: &mut Paint,
183        transform: Transform,
184        clip_mask: Option<&Mask>,
185    ) {
186        let mut transform = transform;
187        transform = transform.post_scale(self.scale, self.scale);
188        self.pixmap.fill_rect(rect, paint, transform, clip_mask);
189    }
190
191    /// Stroke a path with a `Paint`.
192    pub fn stroke_path(
193        &mut self,
194        path: &Path,
195        paint: &mut Paint,
196        stroke: &Stroke,
197        transform: Transform,
198        clip_mask: Option<&Mask>,
199    ) {
200        let mut transform = transform;
201        transform = transform.post_scale(self.scale, self.scale);
202        self.pixmap
203            .stroke_path(path, paint, stroke, transform, clip_mask);
204    }
205
206    /// Color a single pixel on the canvas. !!! Warning this function will not scale with the
207    /// canvas.
208    pub fn dot(&mut self, x: f32, y: f32, color: Color) {
209        let width = self.pixmap.width();
210        let pixel_map = self.pixmap.pixels_mut();
211        let k = y as usize * width as usize + x as usize;
212        pixel_map[k] = color.premultiply().to_color_u8();
213    }
214
215    /// Save the `Canvas` as a png.
216    pub fn save_png<P: AsRef<std::path::Path>>(&self, path: P) {
217        self.pixmap.save_png(path).expect("Error writing png");
218    }
219
220    /// Save the `Canvas` as a jpg.
221    pub fn save_jpg<P: AsRef<std::path::Path>>(&self, path: P) {
222        let img: RgbaImage = self.into();
223        img.save_with_format(path, ImageFormat::Jpeg)
224            .expect("Error writing jpeg");
225    }
226
227    /// Save the `Canvas` as a tiff.
228    pub fn save_tiff<P: AsRef<std::path::Path>>(&self, path: P) {
229        let img: RgbaImage = self.into();
230        img.save_with_format(path, ImageFormat::Tiff)
231            .expect("Error writing tiff");
232    }
233
234    pub fn data(&self) -> &[u8] {
235        self.pixmap.data()
236    }
237
238    pub fn take(self) -> Vec<u8> {
239        self.pixmap.take()
240    }
241}
242
243/// Create a tiny-skia `Paint` from a solid `Color`.
244pub fn paint_solid<'a>(color: Color) -> Paint<'a> {
245    let mut paint = Paint {
246        anti_alias: true,
247        ..Default::default()
248    };
249    paint.set_color(color);
250    paint
251}
252
253/// Create a tiny-skia `Paint` from a `Shader`.
254pub fn paint_shader(shader: Shader) -> Paint {
255    Paint {
256        anti_alias: true,
257        shader,
258        ..Default::default()
259    }
260}
261
262/// Create a linear gradient paint.
263pub fn paint_lg<'a>(x0: f32, y0: f32, x1: f32, y1: f32, stops: Vec<GradientStop>) -> Paint<'a> {
264    let lg = LinearGradient::new(
265        pt(x0, y0),
266        pt(x1, y1),
267        stops,
268        SpreadMode::Pad,
269        Transform::identity(),
270    )
271    .unwrap();
272    paint_shader(lg)
273}
274
275pub fn paint_rg<'a>(
276    x0: f32,
277    y0: f32,
278    x1: f32,
279    y1: f32,
280    radius: f32,
281    stops: Vec<GradientStop>,
282) -> Paint<'a> {
283    let rg = RadialGradient::new(
284        pt(x0, y0),
285        pt(x1, y1),
286        radius,
287        stops,
288        SpreadMode::Pad,
289        Transform::identity(),
290    )
291    .unwrap();
292    paint_shader(rg)
293}
294
295fn image_to_canvas(width: u32, height: u32, data: Vec<u8>) -> Canvas {
296    let pixmap = PixmapRef::from_bytes(&data, width, height).unwrap();
297    Canvas {
298        pixmap: pixmap.to_owned(),
299        width,
300        height,
301        scale: 1.0,
302    }
303}
304
305/// Convert a `&RgbaImage` to a `Canvas`.
306impl From<&RgbaImage> for Canvas {
307    fn from(ib: &RgbaImage) -> Self {
308        image_to_canvas(ib.width(), ib.height(), ib.clone().into_vec())
309    }
310}
311
312/// Convert a `&RgbImage` to a `Canvas`.
313impl From<&RgbImage> for Canvas {
314    fn from(ib: &RgbImage) -> Self {
315        let mut data4: Vec<u8> = Vec::new();
316        let data = ib.clone().into_vec();
317        for d in data.chunks(3) {
318            data4.extend(d);
319            data4.push(255)
320        }
321        image_to_canvas(ib.width(), ib.height(), data4)
322    }
323}
324
325/// Convert a `RgbaImage` to a `Canvas`.
326impl From<RgbaImage> for Canvas {
327    fn from(ib: RgbaImage) -> Self {
328        image_to_canvas(ib.width(), ib.height(), ib.into_vec())
329    }
330}
331
332/// Convert a `RgbImage` to a `Canvas`.
333impl From<RgbImage> for Canvas {
334    fn from(ib: RgbImage) -> Self {
335        let mut data4: Vec<u8> = Vec::new();
336        let data = ib.clone().into_vec();
337        for d in data.chunks(3) {
338            data4.extend(d);
339            data4.push(255)
340        }
341        image_to_canvas(ib.width(), ib.height(), data4)
342    }
343}
344
345/// Convert a `Canvas` to a `RgbaImage`.
346impl From<Canvas> for RgbaImage {
347    fn from(canvas: Canvas) -> Self {
348        let data = canvas.pixmap.data().to_vec();
349        RgbaImage::from_vec(canvas.width(), canvas.height(), data).unwrap()
350    }
351}
352
353/// Convert a `&Canvas` to a `RgbaImage`.
354impl From<&Canvas> for RgbaImage {
355    fn from(canvas: &Canvas) -> Self {
356        let data = canvas.pixmap.data().to_vec();
357        RgbaImage::from_vec(canvas.width(), canvas.height(), data).unwrap()
358    }
359}