1use std::collections::HashMap;
2
3use tiny_skia::{FillRule, GradientStop, LinearGradient, Mask, Paint, PathBuilder, Pixmap, SpreadMode, Stroke, Transform};
4use rosace_core::types::{Point, Rect, Size};
5
6const KAPPA: f32 = 0.552_285;
8
9fn text_gamma(cov: u32) -> u32 {
14 text_gamma_lut()[cov as usize] as u32
15}
16
17#[inline(always)]
19fn d255(x: u32) -> u32 {
20 let t = x + 128;
21 (t + (t >> 8)) >> 8
22}
23
24pub struct SkiaCanvas {
30 pixmap: Pixmap,
31 scale: f32,
35 has_drawn: bool,
38 frame_dirty: bool,
43 clip: Option<(i32, i32, i32, i32)>,
46 clip_masks: HashMap<(i32, i32, i32, i32), Mask>,
50 shadow_cache: HashMap<(u32, u32, u32, u32), ShadowMask>,
53 pending_shader_quads: Vec<ShaderQuadCmd>,
61 gpu_shapes: bool,
69 pending_frame_items: Vec<CanvasFrameItem>,
72 seg_bbox: Option<(f32, f32, f32, f32)>,
75}
76
77#[derive(Clone)]
81pub struct GlyphQuad {
82 pub key: u64,
84 pub bitmap: crate::font::CachedGlyph,
87 pub x: f32,
89 pub y: f32,
90 pub w: u32,
91 pub h: u32,
92 pub color: [u8; 4],
94}
95
96impl PartialEq for GlyphQuad {
99 fn eq(&self, other: &Self) -> bool {
100 self.key == other.key
101 && self.x == other.x && self.y == other.y
102 && self.w == other.w && self.h == other.h
103 && self.color == other.color
104 }
105}
106impl std::fmt::Debug for GlyphQuad {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(f, "GlyphQuad(key={:#x} at {},{} {}x{})", self.key, self.x, self.y, self.w, self.h)
109 }
110}
111
112pub fn text_gamma_lut() -> &'static [u8; 256] {
118 use std::sync::OnceLock;
119 static LUT: OnceLock<[u8; 256]> = OnceLock::new();
120 LUT.get_or_init(|| {
121 let mut t = [0u8; 256];
122 for (i, v) in t.iter_mut().enumerate() {
130 *v = ((i as f32 / 255.0).powf(1.0 / 1.55) * 255.0).round() as u8;
131 }
132 t
133 })
134}
135
136#[derive(Clone)]
141pub struct ImagePixels(pub std::sync::Arc<Vec<u8>>);
142
143impl PartialEq for ImagePixels {
144 fn eq(&self, other: &Self) -> bool {
145 std::sync::Arc::ptr_eq(&self.0, &other.0)
146 }
147}
148impl std::fmt::Debug for ImagePixels {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 write!(f, "ImagePixels({} bytes)", self.0.len())
151 }
152}
153
154pub fn blit_key(pixels: &[u8], w: u32, h: u32) -> u64 {
160 let mut hash: u64 = 0xcbf29ce484222325;
161 let mut eat = |b: u8| {
162 hash ^= b as u64;
163 hash = hash.wrapping_mul(0x100000001b3);
164 };
165 for v in [w, h, pixels.len() as u32] {
166 for b in v.to_le_bytes() { eat(b); }
167 }
168 let n = pixels.len();
169 for &start in &[0usize, n / 2, n.saturating_sub(32)] {
170 for &b in &pixels[start..(start + 32).min(n)] { eat(b); }
171 }
172 hash
173}
174
175#[derive(Debug, Clone, PartialEq)]
180pub enum CanvasFrameItem {
181 Shader(ShaderQuadCmd),
182 Segment { x: u32, y: u32, w: u32, h: u32, pixels: Vec<u8> },
183 Glyphs { glyphs: Vec<GlyphQuad>, clip: Option<(f32, f32, f32, f32)> },
184 Image {
185 key: u64,
186 pixels: ImagePixels,
187 src_w: u32,
188 src_h: u32,
189 dest: (f32, f32, f32, f32),
191 opacity: f32,
192 clip: Option<(f32, f32, f32, f32)>,
193 },
194 Backdrop {
198 rect: (f32, f32, f32, f32),
199 radius: f32,
200 blur: f32,
201 tint: [u8; 4],
202 },
203}
204
205#[derive(Debug, Clone, PartialEq)]
209pub struct ShaderQuadCmd {
210 pub pipeline_id: u64,
211 pub rect: (f32, f32, f32, f32),
213 pub uniforms: Vec<u8>,
214 pub clip: Option<(f32, f32, f32, f32)>,
216 pub animate_time: bool,
220}
221
222struct ShadowMask {
224 w: usize,
225 h: usize,
226 margin: i32,
228 data: Vec<u8>,
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct Color {
234 pub r: u8,
236 pub g: u8,
238 pub b: u8,
240 pub a: u8,
242}
243
244impl Color {
245 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
247 Self { r, g, b, a: 255 }
248 }
249
250 pub const fn rgba_bytes(self) -> [u8; 4] {
253 [self.r, self.g, self.b, self.a]
254 }
255
256 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
258 Self { r, g, b, a }
259 }
260
261 pub const WHITE: Color = Color::rgb(255, 255, 255);
263 pub const BLACK: Color = Color::rgb(0, 0, 0);
265 pub const RED: Color = Color::rgb(255, 0, 0);
267 pub const GREEN: Color = Color::rgb(0, 255, 0);
269 pub const BLUE: Color = Color::rgb(0, 0, 255);
271 pub const TRANSPARENT: Color = Color::rgba(0, 0, 0, 0);
273}
274
275#[inline]
280fn clip_xywh(
281 x: f32, y: f32, w: f32, h: f32,
282 clip: (i32, i32, i32, i32),
283) -> Option<(f32, f32, f32, f32)> {
284 let (cx, cy, cr, cb) = clip;
285 let x0 = x.max(cx as f32);
286 let y0 = y.max(cy as f32);
287 let x1 = (x + w).min(cr as f32);
288 let y1 = (y + h).min(cb as f32);
289 if x1 > x0 && y1 > y0 { Some((x0, y0, x1 - x0, y1 - y0)) } else { None }
290}
291
292#[inline]
294fn overlaps_clip(x: f32, y: f32, w: f32, h: f32, clip: (i32, i32, i32, i32)) -> bool {
295 let (cx, cy, cr, cb) = clip;
296 x + w > cx as f32 && y + h > cy as f32 && x < cr as f32 && y < cb as f32
297}
298
299fn ensure_clip_mask(
304 masks: &mut HashMap<(i32, i32, i32, i32), Mask>,
305 clip: (i32, i32, i32, i32),
306 width: u32,
307 height: u32,
308) {
309 if masks.contains_key(&clip) {
310 return;
311 }
312 let Some(mut mask) = Mask::new(width, height) else { return };
313 let (x0, y0, x1, y1) = clip;
314 let mut pb = PathBuilder::new();
315 if let Some(r) = tiny_skia::Rect::from_ltrb(x0 as f32, y0 as f32, x1 as f32, y1 as f32) {
316 pb.push_rect(r);
317 }
318 if let Some(path) = pb.finish() {
319 mask.fill_path(&path, FillRule::Winding, false, Transform::identity());
320 masks.insert(clip, mask);
321 }
322}
323
324fn rounded_rect_path(x: f32, y: f32, w: f32, h: f32, r: f32) -> Option<tiny_skia::Path> {
326 let k = KAPPA * r;
327 let (x1, y1) = (x + w, y + h);
328 let mut pb = PathBuilder::new();
329 pb.move_to(x + r, y);
330 pb.line_to(x1 - r, y);
331 pb.cubic_to(x1 - r + k, y, x1, y + r - k, x1, y + r);
332 pb.line_to(x1, y1 - r);
333 pb.cubic_to(x1, y1 - r + k, x1 - r + k, y1, x1 - r, y1);
334 pb.line_to(x + r, y1);
335 pb.cubic_to(x + r - k, y1, x, y1 - r + k, x, y1 - r);
336 pb.line_to(x, y + r);
337 pb.cubic_to(x, y + r - k, x + r - k, y, x + r, y);
338 pb.close();
339 pb.finish()
340}
341
342fn box_blur_h(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) {
344 let norm = (2 * r + 1) as u32;
345 for y in 0..h {
346 let row = y * w;
347 let mut acc: u32 = src[row] as u32 * r as u32;
348 for i in 0..=r {
349 acc += src[row + i.min(w - 1)] as u32;
350 }
351 for x in 0..w {
352 dst[row + x] = (acc / norm) as u8;
353 let add = src[row + (x + r + 1).min(w - 1)] as u32;
354 let sub = src[row + x.saturating_sub(r)] as u32;
355 acc = acc + add - sub;
356 }
357 }
358}
359
360fn box_blur_v(src: &[u8], dst: &mut [u8], w: usize, h: usize, r: usize) {
362 let norm = (2 * r + 1) as u32;
363 for x in 0..w {
364 let mut acc: u32 = src[x] as u32 * r as u32;
365 for i in 0..=r {
366 acc += src[i.min(h - 1) * w + x] as u32;
367 }
368 for y in 0..h {
369 dst[y * w + x] = (acc / norm) as u8;
370 let add = src[(y + r + 1).min(h - 1) * w + x] as u32;
371 let sub = src[y.saturating_sub(r) * w + x] as u32;
372 acc = acc + add - sub;
373 }
374 }
375}
376
377fn build_shadow_mask(w: u32, h: u32, blur: u32, radius: u32) -> ShadowMask {
384 let margin = (2 * blur) as i32 + 1;
385 let mw = w as usize + 2 * margin as usize;
386 let mh = h as usize + 2 * margin as usize;
387 let mut data = vec![0u8; mw * mh];
388 for row in margin as usize..margin as usize + h as usize {
389 let s = row * mw + margin as usize;
390 data[s..s + w as usize].fill(255);
391 }
392
393 let r = (radius as f32).min(w as f32 / 2.0).min(h as f32 / 2.0);
396 if r >= 1.0 {
397 let m = margin as f32;
398 let centers = [
399 (m + r, m + r),
400 (m + w as f32 - r, m + r),
401 (m + r, m + h as f32 - r),
402 (m + w as f32 - r, m + h as f32 - r),
403 ];
404 let corners = [
405 (m, m, m + r, m + r),
406 (m + w as f32 - r, m, m + w as f32, m + r),
407 (m, m + h as f32 - r, m + r, m + h as f32),
408 (m + w as f32 - r, m + h as f32 - r, m + w as f32, m + h as f32),
409 ];
410 for (i, &(x0, y0, x1, y1)) in corners.iter().enumerate() {
411 let (cx, cy) = centers[i];
412 for py in y0 as usize..(y1.ceil() as usize).min(mh) {
413 for px in x0 as usize..(x1.ceil() as usize).min(mw) {
414 let dx = px as f32 + 0.5 - cx;
415 let dy = py as f32 + 0.5 - cy;
416 let d = (dx * dx + dy * dy).sqrt();
417 let coverage = (r + 0.5 - d).clamp(0.0, 1.0);
418 data[py * mw + px] = (coverage * 255.0) as u8;
419 }
420 }
421 }
422 }
423
424 let br = (blur as usize / 2).max(1);
425 let mut tmp = vec![0u8; mw * mh];
426 for _ in 0..3 {
427 box_blur_h(&data, &mut tmp, mw, mh, br);
428 box_blur_v(&tmp, &mut data, mw, mh, br);
429 }
430 ShadowMask { w: mw, h: mh, margin, data }
431}
432
433impl SkiaCanvas {
434 pub fn new(width: u32, height: u32) -> Self {
436 Self::new_hidpi(width, height, 1.0)
437 }
438
439 pub fn new_hidpi(phys_width: u32, phys_height: u32, scale: f32) -> Self {
446 Self {
447 pixmap: Pixmap::new(phys_width, phys_height).expect("failed to create pixmap"),
448 scale: scale.max(1.0),
449 has_drawn: false,
450 frame_dirty: true,
451 clip: None,
452 clip_masks: HashMap::new(),
453 shadow_cache: HashMap::new(),
454 pending_shader_quads: Vec::new(),
455 gpu_shapes: false,
456 pending_frame_items: Vec::new(),
457 seg_bbox: None,
458 }
459 }
460
461 pub fn take_shader_quads(&mut self) -> Vec<ShaderQuadCmd> {
466 std::mem::take(&mut self.pending_shader_quads)
467 }
468
469 pub fn set_gpu_shapes(&mut self, on: bool) {
474 self.gpu_shapes = on;
475 }
476
477 pub fn gpu_shapes(&self) -> bool {
478 self.gpu_shapes
479 }
480
481 pub fn take_frame_items(&mut self) -> Vec<CanvasFrameItem> {
485 std::mem::take(&mut self.pending_frame_items)
486 }
487
488 fn cut_segment(&mut self) {
493 let Some((x0, y0, x1, y1)) = self.seg_bbox.take() else { return; };
494 let pw = self.pixmap.width() as i32;
495 let ph = self.pixmap.height() as i32;
496 let ix0 = (x0.floor() as i32).clamp(0, pw);
497 let iy0 = (y0.floor() as i32).clamp(0, ph);
498 let ix1 = (x1.ceil() as i32).clamp(0, pw);
499 let iy1 = (y1.ceil() as i32).clamp(0, ph);
500 if ix1 <= ix0 || iy1 <= iy0 { return; }
501 let (w, h) = ((ix1 - ix0) as u32, (iy1 - iy0) as u32);
502
503 let stride = pw as usize * 4;
504 let data = self.pixmap.data_mut();
505 let mut pixels = vec![0u8; (w * h * 4) as usize];
506 for row in 0..h as usize {
507 let src = (iy0 as usize + row) * stride + ix0 as usize * 4;
508 let dst = row * w as usize * 4;
509 pixels[dst..dst + w as usize * 4]
510 .copy_from_slice(&data[src..src + w as usize * 4]);
511 data[src..src + w as usize * 4].fill(0);
512 }
513 self.pending_frame_items.push(CanvasFrameItem::Segment {
514 x: ix0 as u32, y: iy0 as u32, w, h, pixels,
515 });
516 }
517
518 #[allow(dead_code)]
527 fn grow_segment(&mut self, x0: f32, y0: f32, x1: f32, y1: f32) {
528 let (mut x0, mut y0, mut x1, mut y1) = (x0, y0, x1, y1);
529 if let Some((cx, cy, cr, cb)) = self.clip {
530 x0 = x0.max(cx as f32);
531 y0 = y0.max(cy as f32);
532 x1 = x1.min(cr as f32);
533 y1 = y1.min(cb as f32);
534 }
535 if x1 <= x0 || y1 <= y0 { return; }
536 self.seg_bbox = Some(match self.seg_bbox {
537 Some((a, b, c, d)) => (a.min(x0), b.min(y0), c.max(x1), d.max(y1)),
538 None => (x0, y0, x1, y1),
539 });
540 }
541
542 fn push_builtin_quad(
545 &mut self,
546 pipeline_id: u64,
547 quad: (f32, f32, f32, f32),
548 uniforms: Vec<u8>,
549 widget_clip: Option<(f32, f32, f32, f32)>,
550 ) {
551 self.cut_segment();
552 self.pending_frame_items.push(CanvasFrameItem::Shader(ShaderQuadCmd {
553 pipeline_id, rect: quad, uniforms, clip: widget_clip, animate_time: false,
554 }));
555 }
556
557 pub fn width(&self) -> u32 {
559 self.pixmap.width()
560 }
561
562 pub fn height(&self) -> u32 {
564 self.pixmap.height()
565 }
566
567 pub fn logical_width(&self) -> u32 {
569 (self.pixmap.width() as f32 / self.scale).round() as u32
570 }
571
572 pub fn logical_height(&self) -> u32 {
574 (self.pixmap.height() as f32 / self.scale).round() as u32
575 }
576
577 pub fn scale(&self) -> f32 {
579 self.scale
580 }
581
582 pub fn has_drawn(&self) -> bool {
587 self.has_drawn
588 }
589
590 pub fn mark_frame_dirty(&mut self) {
594 self.frame_dirty = true;
595 }
596
597 pub fn take_frame_dirty(&mut self) -> bool {
600 std::mem::replace(&mut self.frame_dirty, false)
601 }
602
603 pub fn clear(&mut self, color: Color) {
605 if self.gpu_shapes {
606 self.pixmap.fill(tiny_skia::Color::TRANSPARENT);
610 self.pending_frame_items.clear();
611 self.seg_bbox = None;
612 let (w, h) = (self.pixmap.width() as f32, self.pixmap.height() as f32);
613 let (quad, uniforms) = crate::gpu_shapes::fill_rrect_quad(
614 (0.0, 0.0, w, h), 0.0, [color.r, color.g, color.b, color.a],
615 );
616 self.pending_frame_items.push(CanvasFrameItem::Shader(ShaderQuadCmd {
617 pipeline_id: crate::gpu_shapes::FILL_RRECT_ID,
618 rect: quad,
619 uniforms,
620 clip: None,
621 animate_time: false,
622 }));
623 self.has_drawn = true;
624 return;
625 }
626 self.pixmap.fill(
627 tiny_skia::Color::from_rgba8(color.r, color.g, color.b, color.a),
628 );
629 self.has_drawn = true;
630 }
631
632 pub fn clear_transparent(&mut self) {
636 self.pixmap.fill(tiny_skia::Color::TRANSPARENT);
637 self.has_drawn = false;
638 }
639
640 pub fn fill_rect(&mut self, rect: Rect, color: Color) {
646 if color.a == 0 { return; }
647 let (mut x, mut y, mut w, mut h) = (rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
648 if w < 0.5 || h < 0.5 { return; }
649
650 if let Some(clip) = self.clip {
651 match clip_xywh(x, y, w, h, clip) {
652 Some((cx, cy, cw, ch)) => { x = cx; y = cy; w = cw; h = ch; }
653 None => return,
654 }
655 }
656
657 let x0 = x.round();
660 let y0 = y.round();
661 let x1 = (x + w).round().max(x0 + 1.0);
662 let y1 = (y + h).round().max(y0 + 1.0);
663
664 let mut paint = Paint::default();
665 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
666 paint.anti_alias = false;
667 if let Some(r) = tiny_skia::Rect::from_ltrb(x0, y0, x1, y1) {
668 self.pixmap.fill_rect(r, &paint, Transform::identity(), None);
669 }
670 self.has_drawn = true;
671 }
672
673 pub fn stroke_rect(&mut self, rect: Rect, color: Color, stroke_width: f32) {
675 if let Some(clip) = self.clip {
677 if !overlaps_clip(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, clip) {
678 return;
679 }
680 ensure_clip_mask(&mut self.clip_masks, clip, self.pixmap.width(), self.pixmap.height());
681 }
682 let mut paint = Paint::default();
683 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
684 paint.anti_alias = true;
685 let Some(skia_rect) = tiny_skia::Rect::from_xywh(
686 rect.origin.x,
687 rect.origin.y,
688 rect.size.width,
689 rect.size.height,
690 ) else {
691 return;
692 };
693 let path = PathBuilder::from_rect(skia_rect);
694 let stroke = tiny_skia::Stroke {
695 width: stroke_width,
696 ..Default::default()
697 };
698 let mask = self.clip.and_then(|c| self.clip_masks.get(&c));
699 self.pixmap
700 .stroke_path(&path, &paint, &stroke, Transform::identity(), mask);
701 self.has_drawn = true;
702 }
703
704 pub fn fill_circle(&mut self, center: Point, radius: f32, color: Color) {
706 if color.a == 0 || radius < 0.5 { return; }
707 if let Some(clip) = self.clip {
708 if !overlaps_clip(center.x - radius, center.y - radius, radius * 2.0, radius * 2.0, clip) {
709 return;
710 }
711 ensure_clip_mask(&mut self.clip_masks, clip, self.pixmap.width(), self.pixmap.height());
712 }
713 let mut paint = Paint::default();
714 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
715 paint.anti_alias = true;
716 let mut pb = PathBuilder::new();
717 pb.push_circle(center.x, center.y, radius);
718 if let Some(path) = pb.finish() {
719 let mask = self.clip.and_then(|c| self.clip_masks.get(&c));
720 self.pixmap.fill_path(
721 &path,
722 &paint,
723 FillRule::Winding,
724 Transform::identity(),
725 mask,
726 );
727 }
728 self.has_drawn = true;
729 }
730
731 pub fn draw_text_placeholder(&mut self, text: &str, origin: Point, color: Color) {
733 let width = text.len() as f32 * 8.0;
734 let height = 16.0;
735 self.fill_rect(
736 Rect {
737 origin,
738 size: Size { width, height },
739 },
740 color,
741 );
742 }
743
744 pub fn draw_text(&mut self, text: &str, origin: Point, color: Color, font: &crate::font::FontCache, px: f32) {
751 self.draw_text_weighted(text, origin, color, font, px, crate::font::FontWeight::Regular);
752 }
753
754 pub fn draw_text_weighted(&mut self, text: &str, origin: Point, color: Color, font: &crate::font::FontCache, px: f32, weight: crate::font::FontWeight) {
758 if color.a == 0 || text.is_empty() { return; }
759
760 let canvas_w = self.pixmap.width() as i32;
761 let canvas_h = self.pixmap.height() as i32;
762 let ascender = font.ascender(px);
763
764 let (clip_x0, clip_y0, clip_x1, clip_y1) = match self.clip {
767 Some((cx, cy, cr, cb)) => (cx.max(0), cy.max(0), cr.min(canvas_w), cb.min(canvas_h)),
768 None => (0, 0, canvas_w, canvas_h),
769 };
770 if clip_x1 <= clip_x0 || clip_y1 <= clip_y0 { return; }
771
772 let _ = ascender; let color_a = color.a as u32;
774
775 let placed = crate::font::layout_glyphs(font, text, origin.x, origin.y, px, weight);
778
779 let dst = self.pixmap.data_mut();
783
784 for pg in &placed {
785 let (gx, gy) = (pg.x, pg.y);
786
787 if let Some(cg) = &pg.color_rgba {
793 for row in 0..cg.height {
794 let py = gy + row as i32;
795 if py < clip_y0 || py >= clip_y1 { continue; }
796 let row_base = (py * canvas_w) as usize * 4;
797 let src_row = (row * cg.width) as usize * 4;
798 for col in 0..cg.width {
799 let px_xi = gx + col as i32;
800 if px_xi < clip_x0 || px_xi >= clip_x1 { continue; }
801 let si = src_row + col as usize * 4;
802 let src_a = cg.rgba[si + 3] as u32;
803 if src_a == 0 { continue; }
804 let di = row_base + px_xi as usize * 4;
805 let inv = 255 - src_a;
806 dst[di] = (cg.rgba[si] as u32 + d255(dst[di] as u32 * inv)) as u8;
807 dst[di + 1] = (cg.rgba[si + 1] as u32 + d255(dst[di + 1] as u32 * inv)) as u8;
808 dst[di + 2] = (cg.rgba[si + 2] as u32 + d255(dst[di + 2] as u32 * inv)) as u8;
809 dst[di + 3] = (src_a + d255(dst[di + 3] as u32 * inv)) as u8;
810 }
811 }
812 continue;
813 }
814
815 let (metrics, bitmap) = (&pg.glyph.0, &pg.glyph.1);
816
817 for row in 0..metrics.height {
818 let py = gy + row as i32;
819 if py < clip_y0 || py >= clip_y1 { continue; }
820 let row_base = (py * canvas_w) as usize * 4;
821 let src_row = row * metrics.width;
822
823 for col in 0..metrics.width {
824 let coverage = text_gamma(bitmap[src_row + col] as u32);
825 if coverage == 0 { continue; }
826
827 let px_xi = gx + col as i32;
828 if px_xi < clip_x0 || px_xi >= clip_x1 { continue; }
829
830 let di = row_base + px_xi as usize * 4;
831 if coverage == 255 && color_a == 255 {
832 dst[di] = color.r;
834 dst[di + 1] = color.g;
835 dst[di + 2] = color.b;
836 dst[di + 3] = 255;
837 } else {
838 let src_a = d255(coverage * color_a);
840 let inv = 255 - src_a;
841 dst[di] = (d255(color.r as u32 * src_a) + d255(dst[di] as u32 * inv)) as u8;
842 dst[di + 1] = (d255(color.g as u32 * src_a) + d255(dst[di + 1] as u32 * inv)) as u8;
843 dst[di + 2] = (d255(color.b as u32 * src_a) + d255(dst[di + 2] as u32 * inv)) as u8;
844 dst[di + 3] = (src_a + d255(dst[di + 3] as u32 * inv)) as u8;
845 }
846 }
847 }
848 }
849 self.has_drawn = true;
850 }
851
852 pub fn fill_rrect(&mut self, rect: Rect, radius: f32, color: Color) {
857 if color.a == 0 { return; }
858 if let Some(clip) = self.clip {
859 if !overlaps_clip(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, clip) {
860 return;
861 }
862 ensure_clip_mask(&mut self.clip_masks, clip, self.pixmap.width(), self.pixmap.height());
863 }
864 let r = radius.min(rect.size.width / 2.0).min(rect.size.height / 2.0);
865 if r < 0.5 {
866 self.fill_rect(rect, color);
867 return;
868 }
869 let mut paint = Paint::default();
870 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
871 paint.anti_alias = true;
872 if let Some(path) = rounded_rect_path(
873 rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, r,
874 ) {
875 let mask = self.clip.and_then(|c| self.clip_masks.get(&c));
876 self.pixmap.fill_path(
877 &path,
878 &paint,
879 FillRule::Winding,
880 Transform::identity(),
881 mask,
882 );
883 }
884 self.has_drawn = true;
885 }
886
887 pub fn stroke_rrect(&mut self, rect: Rect, radius: f32, color: Color, stroke_width: f32) {
890 if color.a == 0 { return; }
891 if let Some(clip) = self.clip {
892 if !overlaps_clip(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, clip) {
893 return;
894 }
895 ensure_clip_mask(&mut self.clip_masks, clip, self.pixmap.width(), self.pixmap.height());
896 }
897 let r = radius.min(rect.size.width / 2.0).min(rect.size.height / 2.0);
898 if r < 0.5 {
899 self.stroke_rect(rect, color, stroke_width);
900 return;
901 }
902 let mut paint = Paint::default();
903 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
904 paint.anti_alias = true;
905 if let Some(path) = rounded_rect_path(
906 rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, r,
907 ) {
908 let stroke = tiny_skia::Stroke { width: stroke_width, ..Default::default() };
909 let mask = self.clip.and_then(|c| self.clip_masks.get(&c));
910 self.pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask);
911 }
912 self.has_drawn = true;
913 }
914
915 pub fn draw_shadow(&mut self, rect: Rect, radius: f32, color: Color, blur: f32) {
922 if color.a == 0 { return; }
923 let blur = blur.max(0.0);
924 if blur < 0.5 {
925 self.fill_rrect(rect, radius, color);
926 return;
927 }
928 let w = rect.size.width.round().max(1.0) as u32;
929 let h = rect.size.height.round().max(1.0) as u32;
930 let b = blur.round() as u32;
931 let rad = radius.max(0.0).round() as u32;
932 let key = (w, h, b, rad);
933 self.shadow_cache
934 .entry(key)
935 .or_insert_with(|| build_shadow_mask(w, h, b, rad));
936
937 let canvas_w = self.pixmap.width() as i32;
938 let canvas_h = self.pixmap.height() as i32;
939 let (clip_x0, clip_y0, clip_x1, clip_y1) = match self.clip {
940 Some((cx, cy, cr, cb)) => (cx.max(0), cy.max(0), cr.min(canvas_w), cb.min(canvas_h)),
941 None => (0, 0, canvas_w, canvas_h),
942 };
943 if clip_x1 <= clip_x0 || clip_y1 <= clip_y0 { return; }
944
945 let mask = &self.shadow_cache[&key];
946 let ox = rect.origin.x.round() as i32 - mask.margin;
947 let oy = rect.origin.y.round() as i32 - mask.margin;
948 let color_a = color.a as u32;
949 let dst = self.pixmap.data_mut();
950
951 for row in 0..mask.h {
952 let py = oy + row as i32;
953 if py < clip_y0 || py >= clip_y1 { continue; }
954 let row_base = (py * canvas_w) as usize * 4;
955 let src_row = row * mask.w;
956
957 for col in 0..mask.w {
958 let coverage = mask.data[src_row + col] as u32;
959 if coverage == 0 { continue; }
960
961 let px_xi = ox + col as i32;
962 if px_xi < clip_x0 || px_xi >= clip_x1 { continue; }
963
964 let src_a = d255(coverage * color_a);
965 if src_a == 0 { continue; }
966 let inv = 255 - src_a;
967 let di = row_base + px_xi as usize * 4;
968 dst[di] = (d255(color.r as u32 * src_a) + d255(dst[di] as u32 * inv)) as u8;
969 dst[di + 1] = (d255(color.g as u32 * src_a) + d255(dst[di + 1] as u32 * inv)) as u8;
970 dst[di + 2] = (d255(color.b as u32 * src_a) + d255(dst[di + 2] as u32 * inv)) as u8;
971 dst[di + 3] = (src_a + d255(dst[di + 3] as u32 * inv)) as u8;
972 }
973 }
974 self.has_drawn = true;
975 }
976
977 pub fn fill_gradient(&mut self, rect: Rect, radius: f32, from: Color, to: Color, vertical: bool) {
979 if let Some(clip) = self.clip {
980 if !overlaps_clip(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, clip) { return; }
981 ensure_clip_mask(&mut self.clip_masks, clip, self.pixmap.width(), self.pixmap.height());
982 }
983 let (x, y, w, h) = (rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
984 let (p0, p1) = if vertical {
985 (tiny_skia::Point::from_xy(x, y), tiny_skia::Point::from_xy(x, y + h))
986 } else {
987 (tiny_skia::Point::from_xy(x, y), tiny_skia::Point::from_xy(x + w, y))
988 };
989 let stops = vec![
990 GradientStop::new(0.0, tiny_skia::Color::from_rgba8(from.r, from.g, from.b, from.a)),
991 GradientStop::new(1.0, tiny_skia::Color::from_rgba8(to.r, to.g, to.b, to.a)),
992 ];
993 let Some(shader) = LinearGradient::new(p0, p1, stops, SpreadMode::Pad, Transform::identity()) else { return; };
994 let paint = Paint { shader, anti_alias: true, ..Paint::default() };
995 let mask = self.clip.and_then(|c| self.clip_masks.get(&c));
996 let r = radius.min(w / 2.0).min(h / 2.0);
997 if r < 0.5 {
998 if let Some(rr) = tiny_skia::Rect::from_xywh(x, y, w, h) {
999 self.pixmap.fill_rect(rr, &paint, Transform::identity(), mask);
1000 }
1001 } else if let Some(path) = rounded_rect_path(x, y, w, h, r) {
1002 self.pixmap.fill_path(&path, &paint, FillRule::Winding, Transform::identity(), mask);
1003 }
1004 self.has_drawn = true;
1005 }
1006
1007 pub fn fill_arc(&mut self, center: Point, radius: f32, thickness: f32, start_deg: f32, sweep_deg: f32, color: Color) {
1010 if color.a == 0 || radius < 0.5 || thickness < 0.3 { return; }
1011 if let Some(clip) = self.clip {
1012 let r = radius + thickness;
1013 if !overlaps_clip(center.x - r, center.y - r, r * 2.0, r * 2.0, clip) { return; }
1014 ensure_clip_mask(&mut self.clip_masks, clip, self.pixmap.width(), self.pixmap.height());
1015 }
1016 let segs = ((sweep_deg.abs() / 6.0).ceil() as usize).max(2);
1017 let mut pb = PathBuilder::new();
1018 for i in 0..=segs {
1019 let t = i as f32 / segs as f32;
1020 let a = (start_deg + sweep_deg * t).to_radians();
1021 let (px, py) = (center.x + radius * a.cos(), center.y + radius * a.sin());
1022 if i == 0 { pb.move_to(px, py); } else { pb.line_to(px, py); }
1023 }
1024 let Some(path) = pb.finish() else { return; };
1025 let mut paint = Paint::default();
1026 paint.set_color_rgba8(color.r, color.g, color.b, color.a);
1027 paint.anti_alias = true;
1028 let stroke = Stroke { width: thickness, line_cap: tiny_skia::LineCap::Round, ..Default::default() };
1029 let mask = self.clip.and_then(|c| self.clip_masks.get(&c));
1030 self.pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), mask);
1031 self.has_drawn = true;
1032 }
1033
1034 pub fn play_picture(&mut self, picture: &crate::picture::Picture, font: &crate::font::FontCache) {
1044 use crate::draw_command::DrawCommand;
1045 let s = self.scale;
1046 let sr = |r: Rect| Rect {
1047 origin: Point { x: r.origin.x * s, y: r.origin.y * s },
1048 size: Size { width: r.size.width * s, height: r.size.height * s },
1049 };
1050 let sp = |p: Point| Point { x: p.x * s, y: p.y * s };
1051
1052 let mut clip_stack: Vec<Option<(i32, i32, i32, i32)>> = Vec::new();
1054 let outer_clip = self.clip;
1056
1057 let mut widget_clip: Option<(f32, f32, f32, f32)> = None;
1063 let mut widget_clip_stack: Vec<Option<(f32, f32, f32, f32)>> = Vec::new();
1064
1065 for cmd in &picture.commands {
1066 match cmd {
1067 DrawCommand::PushClip { rect } => {
1068 let r = sr(*rect);
1069 let x0 = r.origin.x as i32;
1070 let y0 = r.origin.y as i32;
1071 let x1 = (r.origin.x + r.size.width) as i32;
1072 let y1 = (r.origin.y + r.size.height) as i32;
1073 let new_clip = if let Some((cx, cy, cr, cb)) = self.clip {
1074 let ix0 = x0.max(cx);
1076 let iy0 = y0.max(cy);
1077 let ix1 = x1.min(cr);
1078 let iy1 = y1.min(cb);
1079 if ix1 > ix0 && iy1 > iy0 { Some((ix0, iy0, ix1, iy1)) } else { None }
1080 } else {
1081 if x1 > x0 && y1 > y0 { Some((x0, y0, x1, y1)) } else { None }
1082 };
1083 clip_stack.push(self.clip);
1084 self.clip = new_clip;
1085
1086 widget_clip_stack.push(widget_clip);
1087 widget_clip = match widget_clip {
1088 Some((wx, wy, ww, wh)) => {
1089 let ix0 = r.origin.x.max(wx);
1090 let iy0 = r.origin.y.max(wy);
1091 let ix1 = (r.origin.x + r.size.width).min(wx + ww);
1092 let iy1 = (r.origin.y + r.size.height).min(wy + wh);
1093 if ix1 > ix0 && iy1 > iy0 {
1094 Some((ix0, iy0, ix1 - ix0, iy1 - iy0))
1095 } else {
1096 Some((ix0, iy0, 0.0, 0.0))
1099 }
1100 }
1101 None => Some((r.origin.x, r.origin.y, r.size.width, r.size.height)),
1102 };
1103 }
1104
1105 DrawCommand::PopClip => {
1106 self.clip = clip_stack.pop().unwrap_or(None);
1108 widget_clip = widget_clip_stack.pop().unwrap_or(None);
1109 }
1110
1111 DrawCommand::FillRect { rect, color } => {
1112 if self.gpu_shapes {
1113 let r = sr(*rect);
1114 let (q, u) = crate::gpu_shapes::fill_rrect_quad(
1115 (r.origin.x, r.origin.y, r.size.width, r.size.height),
1116 0.0, color.rgba_bytes(),
1117 );
1118 self.push_builtin_quad(crate::gpu_shapes::FILL_RRECT_ID, q, u, widget_clip);
1119 } else {
1120 self.fill_rect(sr(*rect), *color);
1121 }
1122 }
1123 DrawCommand::StrokeRect { rect, color, width } => {
1124 if self.gpu_shapes {
1125 let r = sr(*rect);
1126 let (q, u) = crate::gpu_shapes::stroke_rrect_quad(
1127 (r.origin.x, r.origin.y, r.size.width, r.size.height),
1128 0.0, *width * s, color.rgba_bytes(),
1129 );
1130 self.push_builtin_quad(crate::gpu_shapes::STROKE_RRECT_ID, q, u, widget_clip);
1131 } else {
1132 self.stroke_rect(sr(*rect), *color, *width * s);
1133 }
1134 }
1135 DrawCommand::FillRRect { rect, radius, color } => {
1136 if self.gpu_shapes {
1137 let r = sr(*rect);
1138 let (q, u) = crate::gpu_shapes::fill_rrect_quad(
1139 (r.origin.x, r.origin.y, r.size.width, r.size.height),
1140 *radius * s, color.rgba_bytes(),
1141 );
1142 self.push_builtin_quad(crate::gpu_shapes::FILL_RRECT_ID, q, u, widget_clip);
1143 } else {
1144 self.fill_rrect(sr(*rect), *radius * s, *color);
1145 }
1146 }
1147 DrawCommand::StrokeRRect { rect, radius, color, width } => {
1148 if self.gpu_shapes {
1149 let r = sr(*rect);
1150 let (q, u) = crate::gpu_shapes::stroke_rrect_quad(
1151 (r.origin.x, r.origin.y, r.size.width, r.size.height),
1152 *radius * s, *width * s, color.rgba_bytes(),
1153 );
1154 self.push_builtin_quad(crate::gpu_shapes::STROKE_RRECT_ID, q, u, widget_clip);
1155 } else {
1156 self.stroke_rrect(sr(*rect), *radius * s, *color, *width * s);
1157 }
1158 }
1159 DrawCommand::FillCircle { center, radius, color } => {
1160 if self.gpu_shapes {
1161 let c = sp(*center);
1163 let r = *radius * s;
1164 let (q, u) = crate::gpu_shapes::fill_rrect_quad(
1165 (c.x - r, c.y - r, r * 2.0, r * 2.0), r, color.rgba_bytes(),
1166 );
1167 self.push_builtin_quad(crate::gpu_shapes::FILL_RRECT_ID, q, u, widget_clip);
1168 } else {
1169 self.fill_circle(sp(*center), *radius * s, *color);
1170 }
1171 }
1172 DrawCommand::FillGradient { rect, radius, from, to, vertical } => {
1173 if self.gpu_shapes {
1174 let r = sr(*rect);
1175 let (q, u) = crate::gpu_shapes::gradient_quad(
1176 (r.origin.x, r.origin.y, r.size.width, r.size.height),
1177 *radius * s, from.rgba_bytes(), to.rgba_bytes(), *vertical,
1178 );
1179 self.push_builtin_quad(crate::gpu_shapes::GRADIENT_ID, q, u, widget_clip);
1180 } else {
1181 self.fill_gradient(sr(*rect), *radius * s, *from, *to, *vertical);
1182 }
1183 }
1184 DrawCommand::FillArc { center, radius, thickness, start_deg, sweep_deg, color } => {
1185 if self.gpu_shapes {
1186 let c = sp(*center);
1187 let (q, u) = crate::gpu_shapes::arc_quad(
1188 (c.x, c.y), *radius * s, *thickness * s,
1189 *start_deg, *sweep_deg, color.rgba_bytes(),
1190 );
1191 self.push_builtin_quad(crate::gpu_shapes::ARC_ID, q, u, widget_clip);
1192 } else {
1193 self.fill_arc(sp(*center), *radius * s, *thickness * s, *start_deg, *sweep_deg, *color);
1194 }
1195 }
1196 DrawCommand::DrawShadow { rect, radius, color, blur } => {
1197 if self.gpu_shapes {
1198 let r = sr(*rect);
1199 let (q, u) = crate::gpu_shapes::shadow_quad(
1200 (r.origin.x, r.origin.y, r.size.width, r.size.height),
1201 *radius * s, *blur * s, color.rgba_bytes(),
1202 );
1203 self.push_builtin_quad(crate::gpu_shapes::SHADOW_ID, q, u, widget_clip);
1204 } else {
1205 self.draw_shadow(sr(*rect), *radius * s, *color, *blur * s);
1206 }
1207 }
1208 DrawCommand::DrawText { text, origin, color, px, weight } => {
1209 let o = sp(*origin);
1210 let pxp = *px * s;
1211 if self.gpu_shapes {
1212 if color.a == 0 || text.is_empty() { continue; }
1219 let placed = crate::font::layout_glyphs(
1220 font, text, o.x, o.y, pxp, *weight,
1221 );
1222 let rgba = color.rgba_bytes();
1223 let (color_glyphs, plain): (Vec<_>, Vec<_>) =
1231 placed.into_iter().partition(|pg| pg.color_rgba.is_some());
1232 let quads = plain.into_iter().map(|pg| GlyphQuad {
1233 key: pg.key,
1234 x: pg.x as f32,
1235 y: pg.y as f32,
1236 w: pg.glyph.0.width as u32,
1237 h: pg.glyph.0.height as u32,
1238 bitmap: pg.glyph,
1239 color: rgba,
1240 });
1241 self.cut_segment();
1242 match self.pending_frame_items.last_mut() {
1243 Some(CanvasFrameItem::Glyphs { glyphs, clip })
1244 if *clip == widget_clip =>
1245 {
1246 glyphs.extend(quads);
1247 }
1248 _ => {
1249 self.pending_frame_items.push(CanvasFrameItem::Glyphs {
1250 glyphs: quads.collect(),
1251 clip: widget_clip,
1252 });
1253 }
1254 }
1255 for pg in color_glyphs {
1256 let cg = pg.color_rgba.unwrap();
1257 self.pending_frame_items.push(CanvasFrameItem::Image {
1258 key: (pg.key << 1) | 1, pixels: ImagePixels(std::sync::Arc::clone(&cg.rgba)),
1260 src_w: cg.width,
1261 src_h: cg.height,
1262 dest: (pg.x as f32, pg.y as f32, cg.width as f32, cg.height as f32),
1263 opacity: 1.0,
1264 clip: widget_clip,
1265 });
1266 }
1267 } else {
1268 self.draw_text_weighted(text, o, *color, font, pxp, *weight);
1269 }
1270 }
1271 DrawCommand::BlitRgba { pixels, src_width, src_height, dest_rect, opacity } => {
1272 let d = sr(*dest_rect);
1273 if self.gpu_shapes {
1274 self.cut_segment();
1280 self.pending_frame_items.push(CanvasFrameItem::Image {
1281 key: blit_key(pixels, *src_width, *src_height),
1282 pixels: ImagePixels(pixels.clone()),
1283 src_w: *src_width,
1284 src_h: *src_height,
1285 dest: (d.origin.x, d.origin.y, d.size.width, d.size.height),
1286 opacity: *opacity,
1287 clip: widget_clip,
1288 });
1289 } else {
1290 self.blit_rgba(pixels, *src_width, *src_height, d, *opacity);
1291 }
1292 }
1293 DrawCommand::BackdropBlur { rect, radius, blur, tint } => {
1294 let r = sr(*rect);
1295 if self.gpu_shapes {
1296 self.cut_segment();
1297 self.pending_frame_items.push(CanvasFrameItem::Backdrop {
1298 rect: (r.origin.x, r.origin.y, r.size.width, r.size.height),
1299 radius: *radius * s,
1300 blur: *blur * s,
1301 tint: tint.rgba_bytes(),
1302 });
1303 } else {
1304 let a = ((tint.a as f32 * 0.75) as u8).max(90);
1307 self.fill_rrect(r, *radius * s, Color { r: tint.r, g: tint.g, b: tint.b, a });
1308 }
1309 }
1310 DrawCommand::ShaderFill { pipeline_id, rect, uniforms, animate_time } => {
1311 let r = sr(*rect);
1315 let quad = (r.origin.x, r.origin.y, r.size.width, r.size.height);
1316 if self.gpu_shapes {
1317 self.cut_segment();
1318 self.pending_frame_items.push(CanvasFrameItem::Shader(ShaderQuadCmd {
1319 pipeline_id: *pipeline_id,
1320 rect: quad,
1321 uniforms: uniforms.clone(),
1322 clip: widget_clip,
1323 animate_time: *animate_time,
1324 }));
1325 } else {
1326 self.pending_shader_quads.push(ShaderQuadCmd {
1327 pipeline_id: *pipeline_id,
1328 rect: quad,
1329 uniforms: uniforms.clone(),
1330 clip: widget_clip,
1331 animate_time: *animate_time,
1332 });
1333 }
1334 }
1335 }
1336 }
1337 self.cut_segment();
1340
1341 self.clip = outer_clip;
1343 }
1344
1345 pub fn blit_rgba(&mut self, pixels: &[u8], src_w: u32, src_h: u32, dest: Rect, opacity: f32) {
1353 if src_w == 0 || src_h == 0 || opacity <= 0.0 { return; }
1354 let opacity = opacity.min(1.0);
1355 let cw = self.pixmap.width() as i32;
1356 let ch = self.pixmap.height() as i32;
1357
1358 let dx = dest.origin.x.round() as i32;
1359 let dy = dest.origin.y.round() as i32;
1360 let dw = dest.size.width.round() as i32;
1361 let dh = dest.size.height.round() as i32;
1362 if dw <= 0 || dh <= 0 { return; }
1363
1364 let (cx0, cy0, cx1, cy1) = match self.clip {
1366 Some((cx, cy, cr, cb)) => (cx.max(0), cy.max(0), cr.min(cw), cb.min(ch)),
1367 None => (0, 0, cw, ch),
1368 };
1369 if cx1 <= cx0 || cy1 <= cy0 { return; }
1370
1371 let exact = dw == src_w as i32 && dh == src_h as i32;
1372 let dst = self.pixmap.data_mut();
1373
1374 for row in 0..dh {
1375 let py = dy + row;
1376 if py < cy0 || py >= cy1 { continue; }
1377 let row_base = (py * cw) as usize * 4;
1378
1379 let (sy0, sy1, wy) = if exact {
1381 (row as usize, row as usize, 0.0f32)
1382 } else {
1383 let fy = ((row as f32 + 0.5) * src_h as f32 / dh as f32 - 0.5)
1384 .clamp(0.0, (src_h - 1) as f32);
1385 let y0 = fy as usize;
1386 (y0, (y0 + 1).min(src_h as usize - 1), fy - y0 as f32)
1387 };
1388
1389 for col in 0..dw {
1390 let px = dx + col;
1391 if px < cx0 || px >= cx1 { continue; }
1392
1393 let (r, g, b, a) = if exact {
1394 let si = (sy0 * src_w as usize + col as usize) * 4;
1395 (pixels[si] as f32, pixels[si + 1] as f32, pixels[si + 2] as f32, pixels[si + 3] as f32)
1396 } else {
1397 let fx = ((col as f32 + 0.5) * src_w as f32 / dw as f32 - 0.5)
1399 .clamp(0.0, (src_w - 1) as f32);
1400 let x0 = fx as usize;
1401 let x1 = (x0 + 1).min(src_w as usize - 1);
1402 let wx = fx - x0 as f32;
1403
1404 let idx = |sx: usize, sy: usize| (sy * src_w as usize + sx) * 4;
1405 let (i00, i10, i01, i11) = (idx(x0, sy0), idx(x1, sy0), idx(x0, sy1), idx(x1, sy1));
1406 let lerp2 = |c: usize| {
1407 let top = pixels[i00 + c] as f32 * (1.0 - wx) + pixels[i10 + c] as f32 * wx;
1408 let bot = pixels[i01 + c] as f32 * (1.0 - wx) + pixels[i11 + c] as f32 * wx;
1409 top * (1.0 - wy) + bot * wy
1410 };
1411 (lerp2(0), lerp2(1), lerp2(2), lerp2(3))
1412 };
1413
1414 let a = a * opacity;
1415 let alpha = a as u32;
1416 if alpha == 0 { continue; }
1417 let inv = 255 - alpha;
1418 let di = row_base + px as usize * 4;
1419 dst[di] = d255(r as u32 * alpha + dst[di] as u32 * inv) as u8;
1420 dst[di + 1] = d255(g as u32 * alpha + dst[di + 1] as u32 * inv) as u8;
1421 dst[di + 2] = d255(b as u32 * alpha + dst[di + 2] as u32 * inv) as u8;
1422 dst[di + 3] = 255;
1423 }
1424 }
1425 self.has_drawn = true;
1426 }
1427
1428 pub fn set_logical_clip(&mut self, r: Option<Rect>) {
1432 let s = self.scale;
1433 self.clip = r.map(|r| (
1434 (r.origin.x * s).floor() as i32,
1435 (r.origin.y * s).floor() as i32,
1436 ((r.origin.x + r.size.width) * s).ceil() as i32,
1437 ((r.origin.y + r.size.height) * s).ceil() as i32,
1438 ));
1439 }
1440
1441 pub fn fill_logical_rect(&mut self, r: Rect, color: Color) {
1443 let s = self.scale;
1444 self.fill_rect(Rect {
1445 origin: Point { x: r.origin.x * s, y: r.origin.y * s },
1446 size: Size { width: r.size.width * s, height: r.size.height * s },
1447 }, color);
1448 }
1449
1450 pub fn pixels(&self) -> &[u8] {
1452 self.pixmap.data()
1453 }
1454
1455 pub fn pixels_mut(&mut self) -> &mut [u8] {
1457 self.pixmap.data_mut()
1458 }
1459
1460 pub fn encode_png(&self) -> Option<Vec<u8>> {
1462 self.pixmap.encode_png().ok()
1463 }
1464}