Skip to main content

i_slint_renderer_software/
path.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Path rendering support for the software renderer using zeno
5
6use super::PhysicalRect;
7use super::draw_functions::{PremultipliedRgbaColor, TargetPixel};
8use alloc::vec::Vec;
9use zeno::{Fill, Mask, Stroke};
10
11pub use zeno::Command;
12
13/// Convert Slint's PathDataIterator to zeno's Command format
14#[cfg(feature = "std")]
15pub fn convert_path_data_to_zeno(
16    path_data: i_slint_core::graphics::PathDataIterator,
17    rotation: crate::RotationInfo,
18    scale_factor: i_slint_core::lengths::ScaleFactor,
19    offset: euclid::Vector2D<f32, i_slint_core::lengths::PhysicalPx>,
20) -> Vec<Command> {
21    use crate::Transform as _;
22    use i_slint_core::lengths::LogicalPoint;
23    use lyon_path::Event;
24    let mut commands = Vec::new();
25
26    let convert_point = |p| {
27        let p = (LogicalPoint::from_untyped(p) * scale_factor + offset).transformed(rotation);
28        zeno::Point::new(p.x, p.y)
29    };
30
31    for event in path_data.iter() {
32        match event {
33            Event::Begin { at } => {
34                commands.push(Command::MoveTo(convert_point(at)));
35            }
36            Event::Line { to, .. } => {
37                commands.push(Command::LineTo(convert_point(to)));
38            }
39            Event::Quadratic { ctrl, to, .. } => {
40                commands.push(Command::QuadTo(convert_point(ctrl), convert_point(to)));
41            }
42            Event::Cubic { ctrl1, ctrl2, to, .. } => {
43                commands.push(Command::CurveTo(
44                    convert_point(ctrl1),
45                    convert_point(ctrl2),
46                    convert_point(to),
47                ));
48            }
49            Event::End { close, .. } => {
50                if close {
51                    commands.push(Command::Close);
52                }
53            }
54        }
55    }
56
57    commands
58}
59
60/// Common rendering logic for both filled and stroked paths
61fn render_path_with_style<T: TargetPixel>(
62    commands: &[Command],
63    path_geometry: &PhysicalRect,
64    clip_geometry: &PhysicalRect,
65    color: PremultipliedRgbaColor,
66    style: zeno::Style,
67    buffer: &mut impl crate::target_pixel_buffer::TargetPixelBuffer<TargetPixel = T>,
68) {
69    // The mask needs to be rendered at the full path size
70    let path_width = path_geometry.size.width as usize;
71    let path_height = path_geometry.size.height as usize;
72
73    if path_width == 0 || path_height == 0 {
74        return;
75    }
76
77    // Create a buffer for the mask output
78    let mut mask_buffer = Vec::with_capacity(path_width * path_height);
79    mask_buffer.resize(path_width * path_height, 0u8);
80
81    // Render the full path into the mask
82    Mask::new(commands)
83        .size(path_width as u32, path_height as u32)
84        .style(style)
85        .render_into(&mut mask_buffer, None);
86
87    // Calculate the intersection region - only apply within clipped area
88    // clip_geometry is relative to screen, path_geometry is also relative to screen
89    let clip_x_start = clip_geometry.origin.x.max(0) as usize;
90    let clip_y_start = clip_geometry.origin.y.max(0) as usize;
91    let clip_x_end = (clip_geometry.max_x().max(0) as usize).min(buffer.line_slice(0).len());
92    let clip_y_end = (clip_geometry.max_y().max(0) as usize).min(buffer.num_lines());
93
94    let path_x_start = path_geometry.origin.x as isize;
95    let path_y_start = path_geometry.origin.y as isize;
96
97    // Apply the mask only within the clipped region
98    for screen_y in clip_y_start..clip_y_end {
99        let line = buffer.line_slice(screen_y);
100
101        // Calculate the y coordinate in the mask buffer
102        let mask_y = screen_y as isize - path_y_start;
103        if mask_y < 0 || mask_y >= path_height as isize {
104            continue;
105        }
106
107        for screen_x in clip_x_start..clip_x_end {
108            // Calculate the x coordinate in the mask buffer
109            let mask_x = screen_x as isize - path_x_start;
110            if mask_x < 0 || mask_x >= path_width as isize {
111                continue;
112            }
113
114            let mask_idx = (mask_y as usize) * path_width + (mask_x as usize);
115            let coverage = mask_buffer[mask_idx];
116
117            if coverage > 0 {
118                // Scale all color components by coverage to maintain premultiplication
119                let coverage_factor = coverage as u16;
120                let alpha_color = PremultipliedRgbaColor {
121                    red: ((color.red as u16 * coverage_factor) / 255) as u8,
122                    green: ((color.green as u16 * coverage_factor) / 255) as u8,
123                    blue: ((color.blue as u16 * coverage_factor) / 255) as u8,
124                    alpha: ((color.alpha as u16 * coverage_factor) / 255) as u8,
125                };
126                T::blend(&mut line[screen_x], alpha_color);
127            }
128        }
129    }
130}
131
132/// Render a filled path
133///
134/// * `commands` - The path commands to render
135/// * `path_geometry` - The full bounding box of the path in screen coordinates
136/// * `clip_geometry` - The clipped region where the path should be rendered (intersection of path and clip)
137/// * `color` - The color to render the path
138/// * `buffer` - The target pixel buffer
139pub fn render_filled_path<T: TargetPixel>(
140    commands: &[Command],
141    path_geometry: &PhysicalRect,
142    clip_geometry: &PhysicalRect,
143    color: PremultipliedRgbaColor,
144    buffer: &mut impl crate::target_pixel_buffer::TargetPixelBuffer<TargetPixel = T>,
145) {
146    render_path_with_style(
147        commands,
148        path_geometry,
149        clip_geometry,
150        color,
151        zeno::Style::Fill(Fill::NonZero),
152        buffer,
153    );
154}
155
156/// Render a stroked path
157///
158/// * `commands` - The path commands to render
159/// * `path_geometry` - The full bounding box of the path in screen coordinates
160/// * `clip_geometry` - The clipped region where the path should be rendered (intersection of path and clip)
161/// * `color` - The color to render the path
162/// * `stroke_width` - The width of the stroke
163/// * `buffer` - The target pixel buffer
164pub fn render_stroked_path<T: TargetPixel>(
165    commands: &[Command],
166    path_geometry: &PhysicalRect,
167    clip_geometry: &PhysicalRect,
168    color: PremultipliedRgbaColor,
169    stroke_width: f32,
170    buffer: &mut impl crate::target_pixel_buffer::TargetPixelBuffer<TargetPixel = T>,
171) {
172    render_path_with_style(
173        commands,
174        path_geometry,
175        clip_geometry,
176        color,
177        zeno::Style::Stroke(Stroke::new(stroke_width)),
178        buffer,
179    );
180}