Commit 25711e9
2
files
+50
added
-10
deleted
@@ -130,6 +130,8 @@ pub enum DrawCommand {
130
130
FillPolygon { points: Vec<Point>, color: Color },
131
131
/// Ogranicza kolejne polecenia do prostokąta (zastosowanie kumulacyjne).
132
132
PushClip { rect: Rect },
133
+ /// Ogranicza kolejne polecenia do **zaokrąglonego** prostokąta (kumulacyjnie).
134
+ PushClipRounded { rect: Rect, radius: f64 },
133
135
/// Zdejmuje ostatnie ograniczenie.
134
136
PopClip,
135
137
/// Tekst.
@@ -262,6 +264,13 @@ impl Scene {
262
264
self
263
265
}
264
266
267
+ /// Ogranicza kolejne polecenia do zaokrąglonego prostokąta (np. narożniki okna).
268
+ pub fn push_clip_rounded(&mut self, rect: Rect, radius: f64) -> &mut Self {
269
+ self.commands
270
+ .push(DrawCommand::PushClipRounded { rect, radius });
271
+ self
272
+ }
273
+
265
274
pub fn pop_clip(&mut self) -> &mut Self {
266
275
self.commands.push(DrawCommand::PopClip);
267
276
self
@@ -79,7 +79,7 @@ impl SoftwareRenderer {
79
79
pixmap.fill(ts_color(background));
80
80
}
81
81
82
- let mut clip_stack: Vec<Rect> = Vec::new();
82
+ let mut clip_stack: Vec<(Rect, f64)> = Vec::new();
83
83
let mut mask: Option<Mask> = None;
84
84
85
85
for cmd in scene.commands() {
@@ -166,25 +166,38 @@ impl SoftwareRenderer {
166
166
}
167
167
}
168
168
DrawCommand::PushClip { rect } => {
169
- let effective = match clip_stack.last() {
170
- Some(current) => current.intersection(rect).unwrap_or(Rect::ZERO),
171
- None => *rect,
169
+ let (effective, radius) = match clip_stack.last() {
170
+ Some((current, _)) => {
171
+ (current.intersection(rect).unwrap_or(Rect::ZERO), 0.0)
172
+ }
173
+ None => (*rect, 0.0),
174
+ };
175
+ clip_stack.push((effective, radius));
176
+ mask = build_mask(pixmap, effective, radius, transform);
177
+ }
178
+ DrawCommand::PushClipRounded { rect, radius } => {
179
+ let (effective, effective_radius) = match clip_stack.last() {
180
+ Some((current, current_radius)) => (
181
+ current.intersection(rect).unwrap_or(Rect::ZERO),
182
+ radius.min(*current_radius),
183
+ ),
184
+ None => (*rect, *radius),
172
185
};
173
- clip_stack.push(effective);
174
- mask = build_mask(pixmap, effective, transform);
186
+ clip_stack.push((effective, effective_radius));
187
+ mask = build_mask(pixmap, effective, effective_radius, transform);
175
188
}
176
189
DrawCommand::PopClip => {
177
190
clip_stack.pop();
178
191
mask = clip_stack
179
192
.last()
180
193
.copied()
181
- .and_then(|r| build_mask(pixmap, r, transform));
194
+ .and_then(|(r, rad)| build_mask(pixmap, r, rad, transform));
182
195
}
183
196
DrawCommand::Text(run) => {
184
197
let (w, h) = (pixmap.width() as usize, pixmap.height() as usize);
185
198
// Tekst nie korzysta z maski kształtów — obcinamy wprost piksele.
186
199
let k = scale.factor();
187
- let clip = clip_stack.last().map(|r| crate::text::ClipRect {
200
+ let clip = clip_stack.last().map(|(r, _)| crate::text::ClipRect {
188
201
x0: (r.x() * k).floor() as i64,
189
202
y0: (r.y() * k).floor() as i64,
190
203
x1: (r.right() * k).ceil() as i64,
@@ -382,9 +395,9 @@ fn stroke(
382
395
pixmap.stroke_path(path, &paint(color), &stroke, transform, mask);
383
396
}
384
397
385
-fn build_mask(pixmap: &Pixmap, rect: Rect, transform: Transform) -> Option<Mask> {
398
+fn build_mask(pixmap: &Pixmap, rect: Rect, radius: f64, transform: Transform) -> Option<Mask> {
386
399
let mut mask = Mask::new(pixmap.width(), pixmap.height())?;
387
- if let Some(path) = rect_path(rect, 0.0) {
400
+ if let Some(path) = rect_path(rect, radius) {
388
401
mask.fill_path(&path, FillRule::Winding, true, transform);
389
402
}
390
403
Some(mask)
@@ -570,6 +583,24 @@ mod tests {
570
583
);
571
584
}
572
585
586
+ #[test]
587
+ fn rounded_clip_cuts_corners() {
588
+ let mut r = SoftwareRenderer::new(40, 40);
589
+ let mut scene = Scene::new();
590
+ scene
591
+ .push_clip_rounded(Rect::from_xywh(0.0, 0.0, 40.0, 40.0), 12.0)
592
+ .fill_rect(
593
+ Rect::from_xywh(0.0, 0.0, 40.0, 40.0),
594
+ Color::from_rgba8(255, 0, 0, 255),
595
+ )
596
+ .pop_clip();
597
+ r.draw(&scene, 1.0, Color::WHITE);
598
+
599
+ // Narożnik poza promieniem pozostaje tłem, środek jest wypełniony.
600
+ assert_eq!(pixel(&r, 1, 1), [255, 255, 255, 255]);
601
+ assert_eq!(pixel(&r, 20, 20), [255, 0, 0, 255]);
602
+ }
603
+
573
604
#[test]
574
605
fn clip_limits_drawing() {
575
606
let mut r = SoftwareRenderer::new(40, 40);