1pub mod compositor;
2pub mod stroke;
3pub mod text_style;
4
5use core::marker::PhantomData;
6
7use embedded_graphics_core::{
8 Pixel,
9 draw_target::DrawTarget,
10 geometry::Point,
11 pixelcolor::{Rgb565, RgbColor},
12};
13
14#[cfg(not(feature = "std"))]
15use crate::math::F32Ext as _;
16use crate::{
17 font::{FontId, glyph_rows},
18 geometry::Rect,
19 image::{ImageFit, ImageRef, TileMode, TileRef},
20 palette::{DisplayPalette, InkRole},
21 style::{AlphaLinearGradient, AlphaRadialGradient, Border, GradientDirection, LinearGradient},
22 text,
23};
24
25pub const CHAR_WIDTH: u32 = 4;
26pub const CHAR_HEIGHT: u32 = 6;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum TextAlign {
30 Left,
31 Center,
32 Right,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum VerticalAlign {
37 Top,
38 Middle,
39 Bottom,
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum TextWrap {
44 None,
45 Character,
46 Word,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum TextOverflow {
51 Clip,
52 Ellipsis,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum EllipsisMode {
57 ThreeDots,
58 SingleGlyph,
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum TextOverflowPolicy {
63 Global(TextOverflow),
64 WrapThenEllipsis { max_lines: u8 },
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct TextStyle {
69 pub color: Rgb565,
70 pub font: FontId,
71 pub opacity: u8,
72 pub align: TextAlign,
73 pub vertical_align: VerticalAlign,
74 pub wrap: TextWrap,
75 pub overflow: TextOverflow,
76 pub overflow_policy: TextOverflowPolicy,
77 pub kerning: bool,
78 pub max_lines: Option<u8>,
79 pub ellipsis: EllipsisMode,
80 pub line_spacing: u8,
81}
82
83impl TextStyle {
84 pub const fn new(color: Rgb565) -> Self {
85 Self {
86 color,
87 font: FontId::Tiny3x5,
88 opacity: 255,
89 align: TextAlign::Left,
90 vertical_align: VerticalAlign::Top,
91 wrap: TextWrap::None,
92 overflow: TextOverflow::Clip,
93 overflow_policy: TextOverflowPolicy::Global(TextOverflow::Clip),
94 kerning: false,
95 max_lines: None,
96 ellipsis: EllipsisMode::ThreeDots,
97 line_spacing: 1,
98 }
99 }
100
101 pub const fn centered(mut self) -> Self {
102 self.align = TextAlign::Center;
103 self.vertical_align = VerticalAlign::Middle;
104 self
105 }
106
107 pub const fn with_align(mut self, align: TextAlign) -> Self {
108 self.align = align;
109 self
110 }
111
112 pub const fn with_vertical_align(mut self, align: VerticalAlign) -> Self {
113 self.vertical_align = align;
114 self
115 }
116
117 pub const fn with_wrap(mut self, wrap: TextWrap) -> Self {
118 self.wrap = wrap;
119 self
120 }
121
122 pub const fn with_line_spacing(mut self, spacing: u8) -> Self {
123 self.line_spacing = spacing;
124 self
125 }
126
127 pub const fn with_overflow(mut self, overflow: TextOverflow) -> Self {
128 self.overflow = overflow;
129 self.overflow_policy = TextOverflowPolicy::Global(overflow);
130 self
131 }
132
133 pub const fn with_kerning(mut self, kerning: bool) -> Self {
134 self.kerning = kerning;
135 self
136 }
137
138 pub const fn with_max_lines(mut self, max_lines: Option<u8>) -> Self {
139 self.max_lines = max_lines;
140 self
141 }
142
143 pub const fn with_ellipsis_mode(mut self, ellipsis: EllipsisMode) -> Self {
144 self.ellipsis = ellipsis;
145 self
146 }
147
148 pub const fn with_overflow_policy(mut self, policy: TextOverflowPolicy) -> Self {
149 self.overflow_policy = policy;
150 self
151 }
152
153 pub const fn with_opacity(mut self, opacity: u8) -> Self {
154 self.opacity = opacity;
155 self
156 }
157
158 pub const fn with_font_id(mut self, font: FontId) -> Self {
159 self.font = font;
160 self
161 }
162
163 pub fn with_font(mut self, font: impl Into<FontId>) -> Self {
164 self.font = font.into();
165 self
166 }
167}
168
169#[cfg(feature = "embedded-graphics")]
170impl From<&embedded_graphics::mono_font::MonoTextStyle<'static, Rgb565>> for TextStyle {
171 fn from(mono_style: &embedded_graphics::mono_font::MonoTextStyle<'static, Rgb565>) -> Self {
172 let mut style = TextStyle::new(mono_style.text_color.unwrap_or(Rgb565::WHITE));
173 style.font = FontId::MonoFont(mono_style.font);
174 style
175 }
176}
177
178#[cfg(feature = "embedded-graphics")]
179impl From<embedded_graphics::mono_font::MonoTextStyle<'static, Rgb565>> for TextStyle {
180 fn from(mono_style: embedded_graphics::mono_font::MonoTextStyle<'static, Rgb565>) -> Self {
181 Self::from(&mono_style)
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub struct TextMetrics {
187 pub width: u32,
188 pub height: u32,
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq)]
192pub enum RenderQuality {
193 Low,
194 Medium,
195 High,
196}
197
198#[derive(Clone, Copy, Debug, PartialEq, Eq)]
199pub enum AntiAliasMode {
200 None,
201 Coverage,
202 Subpixel,
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206pub struct StrokeStyle {
207 pub color: Rgb565,
208 pub width: u8,
209 pub antialias: bool,
210 pub antialias_mode: AntiAliasMode,
211 pub cap: StrokeCap,
212 pub join: StrokeJoin,
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub enum StrokeCap {
217 Butt,
218 Round,
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq)]
222pub enum StrokeJoin {
223 Miter,
224 Round,
225}
226
227#[derive(Clone, Copy, Debug, PartialEq)]
228pub struct Transform2D {
229 pub m11: f32,
230 pub m12: f32,
231 pub m21: f32,
232 pub m22: f32,
233 pub tx: f32,
234 pub ty: f32,
235}
236
237impl Transform2D {
238 pub const IDENTITY: Self = Self {
239 m11: 1.0,
240 m12: 0.0,
241 m21: 0.0,
242 m22: 1.0,
243 tx: 0.0,
244 ty: 0.0,
245 };
246
247 pub const fn translation(x: f32, y: f32) -> Self {
248 Self {
249 tx: x,
250 ty: y,
251 ..Self::IDENTITY
252 }
253 }
254
255 pub const fn scale(x: f32, y: f32) -> Self {
256 Self {
257 m11: x,
258 m22: y,
259 ..Self::IDENTITY
260 }
261 }
262
263 pub fn rotation(deg: f32) -> Self {
264 let r = deg.to_radians();
265 Self {
266 m11: r.cos(),
267 m12: -r.sin(),
268 m21: r.sin(),
269 m22: r.cos(),
270 ..Self::IDENTITY
271 }
272 }
273
274 pub fn skew(x_deg: f32, y_deg: f32) -> Self {
275 Self {
276 m12: x_deg.to_radians().tan(),
277 m21: y_deg.to_radians().tan(),
278 ..Self::IDENTITY
279 }
280 }
281
282 pub fn then(self, rhs: Self) -> Self {
283 Self {
284 m11: self.m11 * rhs.m11 + self.m12 * rhs.m21,
285 m12: self.m11 * rhs.m12 + self.m12 * rhs.m22,
286 m21: self.m21 * rhs.m11 + self.m22 * rhs.m21,
287 m22: self.m21 * rhs.m12 + self.m22 * rhs.m22,
288 tx: self.m11 * rhs.tx + self.m12 * rhs.ty + self.tx,
289 ty: self.m21 * rhs.tx + self.m22 * rhs.ty + self.ty,
290 }
291 }
292
293 #[inline(always)]
294 pub fn is_identity(self) -> bool {
295 self.m11 == 1.0
296 && self.m12 == 0.0
297 && self.m21 == 0.0
298 && self.m22 == 1.0
299 && self.tx == 0.0
300 && self.ty == 0.0
301 }
302
303 #[inline(always)]
304 pub fn apply(self, x: i32, y: i32) -> (i32, i32) {
305 if self.is_identity() {
306 (x, y)
307 } else {
308 let xf = x as f32;
309 let yf = y as f32;
310 (
311 (self.m11 * xf + self.m12 * yf + self.tx).round() as i32,
312 (self.m21 * xf + self.m22 * yf + self.ty).round() as i32,
313 )
314 }
315 }
316
317 #[inline(always)]
318 pub fn apply_f32(self, x: f32, y: f32) -> (f32, f32) {
319 if self.is_identity() {
320 (x, y)
321 } else {
322 (
323 self.m11 * x + self.m12 * y + self.tx,
324 self.m21 * x + self.m22 * y + self.ty,
325 )
326 }
327 }
328
329 pub fn inverse(self) -> Option<Self> {
330 let det = self.m11 * self.m22 - self.m12 * self.m21;
331 if det.abs() < 1e-7 {
332 return None;
333 }
334 let inv_det = 1.0 / det;
335 let m11 = self.m22 * inv_det;
336 let m12 = -self.m12 * inv_det;
337 let m21 = -self.m21 * inv_det;
338 let m22 = self.m11 * inv_det;
339 let tx = (self.m12 * self.ty - self.m22 * self.tx) * inv_det;
340 let ty = (self.m21 * self.tx - self.m11 * self.ty) * inv_det;
341 Some(Self {
342 m11,
343 m12,
344 m21,
345 m22,
346 tx,
347 ty,
348 })
349 }
350}
351
352#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub enum BlendMode {
354 Normal,
355 Add,
356 Multiply,
357 Screen,
358}
359
360pub use embedded_draw_target::PixelRead;
372
373pub use embedded_draw_target::WindowedDrawTarget;
380
381pub trait Compositor<D: DrawTarget<Color = Rgb565>> {
394 fn plot(
395 target: &mut D,
396 x: i32,
397 y: i32,
398 color: Rgb565,
399 opacity: u8,
400 blend: BlendMode,
401 backdrop: Rgb565,
402 ) -> Result<(), D::Error>;
403}
404
405#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
407pub struct Dither;
408
409#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
411pub struct Blend;
412
413impl<D: DrawTarget<Color = Rgb565>> Compositor<D> for Dither {
414 fn plot(
415 target: &mut D,
416 x: i32,
417 y: i32,
418 color: Rgb565,
419 opacity: u8,
420 blend: BlendMode,
421 backdrop: Rgb565,
422 ) -> Result<(), D::Error> {
423 if !should_draw_at_opacity(x, y, opacity) {
424 return Ok(());
425 }
426 let color = apply_blend_mode(color, blend, backdrop);
427 target.draw_iter([Pixel(Point::new(x, y), color)])
428 }
429}
430
431impl<D: DrawTarget<Color = Rgb565> + PixelRead> Compositor<D> for Blend {
432 fn plot(
433 target: &mut D,
434 x: i32,
435 y: i32,
436 color: Rgb565,
437 opacity: u8,
438 blend: BlendMode,
439 backdrop: Rgb565,
440 ) -> Result<(), D::Error> {
441 if opacity == 0 {
442 return Ok(());
443 }
444 let bg = target.get_pixel(Point::new(x, y));
445 let blended = lerp_rgb565(bg, color, opacity);
446 let blended = apply_blend_mode(blended, blend, backdrop);
447 target.draw_iter([Pixel(Point::new(x, y), blended)])
448 }
449}
450
451#[derive(Clone, Copy, Debug, PartialEq, Eq)]
452pub enum ColorFormat {
453 Rgb565,
454 Rgb888,
455 Argb8888,
456}
457
458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459pub struct RenderBackendCaps {
460 pub color_format: ColorFormat,
461 pub supports_layers: bool,
462 pub supports_subpixel: bool,
463}
464
465impl RenderBackendCaps {
466 pub const fn software_rgb565() -> Self {
467 Self {
468 color_format: ColorFormat::Rgb565,
469 supports_layers: true,
470 supports_subpixel: false,
471 }
472 }
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
476pub struct LayerState {
477 pub opacity: u8,
478 pub blend: BlendMode,
479 pub backdrop: Rgb565,
480}
481
482impl LayerState {
483 pub const fn normal() -> Self {
484 Self {
485 opacity: 255,
486 blend: BlendMode::Normal,
487 backdrop: Rgb565::BLACK,
488 }
489 }
490}
491
492impl StrokeStyle {
493 pub const fn new(color: Rgb565) -> Self {
494 Self {
495 color,
496 width: 1,
497 antialias: false,
498 antialias_mode: AntiAliasMode::None,
499 cap: StrokeCap::Butt,
500 join: StrokeJoin::Miter,
501 }
502 }
503
504 pub const fn with_width(mut self, width: u8) -> Self {
505 self.width = if width == 0 { 1 } else { width };
506 self
507 }
508
509 pub const fn with_antialias(mut self, antialias: bool) -> Self {
510 self.antialias = antialias;
511 if antialias {
512 if let AntiAliasMode::None = self.antialias_mode {
513 self.antialias_mode = AntiAliasMode::Coverage;
514 }
515 }
516 if !antialias {
517 self.antialias_mode = AntiAliasMode::None;
518 }
519 self
520 }
521
522 pub const fn with_antialias_mode(mut self, mode: AntiAliasMode) -> Self {
523 self.antialias_mode = mode;
524 self.antialias = !matches!(mode, AntiAliasMode::None);
525 self
526 }
527
528 pub const fn with_cap(mut self, cap: StrokeCap) -> Self {
529 self.cap = cap;
530 self
531 }
532
533 pub const fn with_join(mut self, join: StrokeJoin) -> Self {
534 self.join = join;
535 self
536 }
537}
538
539pub struct RenderCtx<'a, D, C = Dither>
540where
541 D: DrawTarget<Color = Rgb565>,
542{
543 target: &'a mut D,
544 clip: Rect,
545 dirty: Option<Rect>,
546 quality: RenderQuality,
547 backend_caps: RenderBackendCaps,
548 transform_stack: [Transform2D; 8],
549 transform_len: usize,
550 layer_stack: [LayerState; 8],
551 layer_len: usize,
552 palette: Option<DisplayPalette>,
553 _compositor: PhantomData<C>,
554}
555
556impl<'a, D> RenderCtx<'a, D, Dither>
557where
558 D: DrawTarget<Color = Rgb565>,
559{
560 pub fn new(target: &'a mut D, viewport: Rect) -> Self {
561 Self {
562 target,
563 clip: viewport,
564 dirty: None,
565 quality: RenderQuality::High,
566 backend_caps: RenderBackendCaps::software_rgb565(),
567 transform_stack: [Transform2D::IDENTITY; 8],
568 transform_len: 1,
569 layer_stack: [LayerState::normal(); 8],
570 layer_len: 1,
571 palette: None,
572 _compositor: PhantomData,
573 }
574 }
575
576 pub fn with_palette(mut self, palette: DisplayPalette) -> Self {
577 self.palette = Some(palette);
578 self
579 }
580
581 pub fn ink(&self, role: InkRole) -> Rgb565 {
583 self.palette
584 .map(|palette| palette.resolve(role))
585 .unwrap_or(Rgb565::WHITE)
586 }
587
588 pub fn with_dirty(target: &'a mut D, viewport: Rect, dirty: Rect) -> Self {
589 Self {
590 target,
591 clip: viewport,
592 dirty: Some(dirty),
593 quality: RenderQuality::High,
594 backend_caps: RenderBackendCaps::software_rgb565(),
595 transform_stack: [Transform2D::IDENTITY; 8],
596 transform_len: 1,
597 layer_stack: [LayerState::normal(); 8],
598 layer_len: 1,
599 palette: None,
600 _compositor: PhantomData,
601 }
602 }
603}
604
605impl<'a, D> RenderCtx<'a, D, Blend>
606where
607 D: DrawTarget<Color = Rgb565> + PixelRead,
608{
609 pub fn compositing(target: &'a mut D, viewport: Rect) -> Self {
614 Self {
615 target,
616 clip: viewport,
617 dirty: None,
618 quality: RenderQuality::High,
619 backend_caps: RenderBackendCaps::software_rgb565(),
620 transform_stack: [Transform2D::IDENTITY; 8],
621 transform_len: 1,
622 layer_stack: [LayerState::normal(); 8],
623 layer_len: 1,
624 palette: None,
625 _compositor: PhantomData,
626 }
627 }
628
629 pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) -> Result<(), D::Error> {
631 let draw = self.visible_rect(rect);
632 if draw.is_empty() || blur_degree == 0 {
633 return Ok(());
634 }
635 let x0 = draw.x;
636 let y0 = draw.y;
637 let x1 = draw.right();
638 let y1 = draw.bottom();
639 let alpha = 256 - (blur_degree as i32);
640
641 let mut row_buf = [Rgb565::BLACK; 1024];
642 let w_buf = ((x1 - x0) as usize).min(1024);
643
644 for y in y0..y1 {
646 let r_len = ((x1 - x0) as usize).min(w_buf);
647 if r_len == 0 {
648 continue;
649 }
650 for (i, x) in (x0..x1).take(r_len).enumerate() {
651 row_buf[i] = self.target.get_pixel(Point::new(x, y));
652 }
653
654 let p0 = row_buf[0];
656 let (r5, g6, b5) = (p0.r(), p0.g(), p0.b());
657 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
658 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
659 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
660
661 for p in row_buf[..r_len].iter_mut() {
662 let (r5, g6, b5) = (p.r(), p.g(), p.b());
663 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
664 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
665 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
666 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
667 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
668 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
669 *p = Rgb565::new(
670 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
671 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
672 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
673 );
674 }
675
676 let p_last = row_buf[r_len - 1];
678 let (r5, g6, b5) = (p_last.r(), p_last.g(), p_last.b());
679 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
680 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
681 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
682
683 for p in row_buf[..r_len].iter_mut().rev() {
684 let (r5, g6, b5) = (p.r(), p.g(), p.b());
685 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
686 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
687 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
688 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
689 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
690 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
691 *p = Rgb565::new(
692 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
693 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
694 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
695 );
696 }
697
698 for (i, x) in (x0..x1).take(r_len).enumerate() {
699 self.target
700 .draw_iter([Pixel(Point::new(x, y), row_buf[i])])?;
701 }
702 }
703
704 let mut col_buf = [Rgb565::BLACK; 1024];
706 let h_buf = ((y1 - y0) as usize).min(1024);
707
708 for x in x0..x1 {
709 let c_len = ((y1 - y0) as usize).min(h_buf);
710 if c_len == 0 {
711 continue;
712 }
713 for (i, y) in (y0..y1).take(c_len).enumerate() {
714 col_buf[i] = self.target.get_pixel(Point::new(x, y));
715 }
716
717 let p0 = col_buf[0];
719 let (r5, g6, b5) = (p0.r(), p0.g(), p0.b());
720 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
721 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
722 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
723
724 for p in col_buf[..c_len].iter_mut() {
725 let (r5, g6, b5) = (p.r(), p.g(), p.b());
726 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
727 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
728 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
729 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
730 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
731 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
732 *p = Rgb565::new(
733 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
734 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
735 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
736 );
737 }
738
739 let p_last = col_buf[c_len - 1];
741 let (r5, g6, b5) = (p_last.r(), p_last.g(), p_last.b());
742 let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
743 let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
744 let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
745
746 for p in col_buf[..c_len].iter_mut().rev() {
747 let (r5, g6, b5) = (p.r(), p.g(), p.b());
748 let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
749 let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
750 let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
751 acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
752 acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
753 acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
754 *p = Rgb565::new(
755 ((acc_r >> 8).clamp(0, 255) as u8) >> 3,
756 ((acc_g >> 8).clamp(0, 255) as u8) >> 2,
757 ((acc_b >> 8).clamp(0, 255) as u8) >> 3,
758 );
759 }
760
761 for (i, y) in (y0..y1).take(c_len).enumerate() {
762 self.target
763 .draw_iter([Pixel(Point::new(x, y), col_buf[i])])?;
764 }
765 }
766
767 Ok(())
768 }
769
770 pub fn reverse_colour_rect(&mut self, rect: Rect) -> Result<(), D::Error> {
772 let bounds = self.clip.intersection(rect);
773 if bounds.is_empty() {
774 return Ok(());
775 }
776 let x0 = bounds.x;
777 let y0 = bounds.y;
778 let x1 = bounds.right();
779 let y1 = bounds.bottom();
780
781 for y in y0..y1 {
782 for x in x0..x1 {
783 let pt = Point::new(x, y);
784 let c = self.target.get_pixel(pt);
785 let inv = Rgb565::new(31 - c.r(), 63 - c.g(), 31 - c.b());
786 self.target.draw_iter([Pixel(pt, inv)])?;
787 }
788 }
789 Ok(())
790 }
791
792 pub fn fill_rect_horizontal_line_mask(
794 &mut self,
795 rect: Rect,
796 mask: &[u8],
797 color: Rgb565,
798 opacity: u8,
799 ) -> Result<(), D::Error> {
800 if mask.is_empty() || opacity == 0 {
801 return Ok(());
802 }
803 let bounds = self.clip.intersection(rect);
804 if bounds.is_empty() {
805 return Ok(());
806 }
807
808 for y in bounds.y..bounds.bottom() {
809 for x in bounds.x..bounds.right() {
810 let mask_x = ((x - rect.x) as usize) % mask.len();
811 let alpha = ((mask[mask_x] as u32 * opacity as u32) >> 8) as u8;
812 if alpha == 0 {
813 continue;
814 }
815 let pt = Point::new(x, y);
816 let bg = self.target.get_pixel(pt);
817 let blended = lerp_rgb565(bg, color, alpha);
818 self.target.draw_iter([Pixel(pt, blended)])?;
819 }
820 }
821 Ok(())
822 }
823
824 pub fn fill_rect_vertical_line_mask(
826 &mut self,
827 rect: Rect,
828 mask: &[u8],
829 color: Rgb565,
830 opacity: u8,
831 ) -> Result<(), D::Error> {
832 if mask.is_empty() || opacity == 0 {
833 return Ok(());
834 }
835 let bounds = self.clip.intersection(rect);
836 if bounds.is_empty() {
837 return Ok(());
838 }
839
840 for y in bounds.y..bounds.bottom() {
841 let mask_y = ((y - rect.y) as usize) % mask.len();
842 let alpha = ((mask[mask_y] as u32 * opacity as u32) >> 8) as u8;
843 if alpha == 0 {
844 continue;
845 }
846 for x in bounds.x..bounds.right() {
847 let pt = Point::new(x, y);
848 let bg = self.target.get_pixel(pt);
849 let blended = lerp_rgb565(bg, color, alpha);
850 self.target.draw_iter([Pixel(pt, blended)])?;
851 }
852 }
853 Ok(())
854 }
855}
856
857impl<'a, D, C> RenderCtx<'a, D, C>
858where
859 D: DrawTarget<Color = Rgb565>,
860 C: Compositor<D>,
861{
862 pub const fn clip(&self) -> Rect {
863 self.clip
864 }
865
866 pub fn set_clip(&mut self, clip: Rect) {
867 self.clip = clip;
868 }
869
870 #[cfg(any(
875 feature = "embedded-text",
876 feature = "embedded-layout",
877 feature = "embedded-graphics"
878 ))]
879 pub fn draw_embedded_graphics<T>(&mut self, drawable: &T) -> Result<T::Output, D::Error>
880 where
881 T: embedded_graphics::Drawable<Color = Rgb565>,
882 {
883 use embedded_graphics::draw_target::DrawTargetExt;
884 use embedded_graphics::geometry::{Point, Size};
885 use embedded_graphics::primitives::Rectangle;
886
887 let clip_rect = Rectangle::new(
888 Point::new(self.clip.x, self.clip.y),
889 Size::new(self.clip.w, self.clip.h),
890 );
891 let mut clipped = self.target.clipped(&clip_rect);
892 drawable.draw(&mut clipped)
893 }
894
895 pub const fn quality(&self) -> RenderQuality {
896 self.quality
897 }
898
899 pub fn set_quality(&mut self, quality: RenderQuality) {
900 self.quality = quality;
901 }
902
903 pub const fn backend_caps(&self) -> RenderBackendCaps {
904 self.backend_caps
905 }
906
907 pub fn set_backend_caps(&mut self, caps: RenderBackendCaps) {
908 self.backend_caps = caps;
909 }
910
911 pub fn push_transform(&mut self, transform: Transform2D) {
912 if self.transform_len >= self.transform_stack.len() {
913 return;
914 }
915 let current = self.current_transform();
916 self.transform_stack[self.transform_len] = current.then(transform);
917 self.transform_len += 1;
918 }
919
920 pub fn pop_transform(&mut self) {
921 if self.transform_len > 1 {
922 self.transform_len -= 1;
923 }
924 }
925
926 pub fn translate(&mut self, x: f32, y: f32) {
927 self.push_transform(Transform2D::translation(x, y));
928 }
929
930 pub fn scale(&mut self, x: f32, y: f32) {
931 self.push_transform(Transform2D::scale(x, y));
932 }
933
934 pub fn rotate(&mut self, deg: f32) {
935 self.push_transform(Transform2D::rotation(deg));
936 }
937
938 pub fn skew(&mut self, x_deg: f32, y_deg: f32) {
939 self.push_transform(Transform2D::skew(x_deg, y_deg));
940 }
941
942 pub fn push_layer(&mut self, layer: LayerState) {
943 if self.layer_len >= self.layer_stack.len() {
944 return;
945 }
946 let current = self.current_layer();
947 self.layer_stack[self.layer_len] = LayerState {
948 opacity: ((current.opacity as u16 * layer.opacity as u16) / 255) as u8,
949 blend: layer.blend,
950 backdrop: layer.backdrop,
951 };
952 self.layer_len += 1;
953 }
954
955 pub fn pop_layer(&mut self) {
956 if self.layer_len > 1 {
957 self.layer_len -= 1;
958 }
959 }
960
961 pub const fn shadow_spread_for(&self, spread: u8) -> u8 {
962 match self.quality {
963 RenderQuality::Low => 0,
964 RenderQuality::Medium => {
965 if spread > 1 {
966 1
967 } else {
968 spread
969 }
970 }
971 RenderQuality::High => spread,
972 }
973 }
974
975 pub fn fill_rect(&mut self, rect: impl Into<Rect>, color: Rgb565) -> Result<(), D::Error> {
976 self.fill_rect_alpha(rect, color, 255)
977 }
978
979 pub fn fill_rect_alpha(
980 &mut self,
981 rect: impl Into<Rect>,
982 color: Rgb565,
983 opacity: u8,
984 ) -> Result<(), D::Error> {
985 self.fill_rounded_rect_alpha(rect, 0, color, opacity)
986 }
987
988 pub fn fill_rounded_rect(
989 &mut self,
990 rect: impl Into<Rect>,
991 radius: u8,
992 color: Rgb565,
993 ) -> Result<(), D::Error> {
994 self.fill_rounded_rect_alpha(rect, radius, color, 255)
995 }
996
997 pub fn fill_rounded_rect_alpha(
998 &mut self,
999 rect: impl Into<Rect>,
1000 radius: u8,
1001 color: Rgb565,
1002 opacity: u8,
1003 ) -> Result<(), D::Error> {
1004 let rect = rect.into();
1005 let draw = self.visible_rect(rect);
1006 if draw.is_empty() || opacity == 0 {
1007 return Ok(());
1008 }
1009 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
1010
1011 let layer = self.current_layer();
1012 let combined_opacity = ((opacity as u16 * layer.opacity as u16) / 255) as u8;
1013
1014 if radius == 0
1017 && combined_opacity == 255
1018 && self.current_transform().is_identity()
1019 && layer.blend == BlendMode::Normal
1020 {
1021 let eg_rect = embedded_graphics_core::primitives::Rectangle::new(
1022 embedded_graphics_core::geometry::Point::new(draw.x, draw.y),
1023 embedded_graphics_core::geometry::Size::new(draw.w, draw.h),
1024 );
1025 return self.target.fill_solid(&eg_rect, color);
1026 }
1027
1028 for y in draw.y..draw.bottom() {
1029 for x in draw.x..draw.right() {
1030 if !in_rounded_rect(x, y, rect, radius) {
1031 continue;
1032 }
1033 self.pixel(x, y, color, opacity)?;
1034 }
1035 }
1036 Ok(())
1037 }
1038
1039 pub fn fill_rounded_rect_gradient_alpha(
1040 &mut self,
1041 rect: impl Into<Rect>,
1042 radius: u8,
1043 gradient: LinearGradient,
1044 opacity: u8,
1045 ) -> Result<(), D::Error> {
1046 let rect = rect.into();
1047 let draw = self.visible_rect(rect);
1048 if draw.is_empty() || opacity == 0 {
1049 return Ok(());
1050 }
1051 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
1052 let denom = match gradient.direction {
1053 GradientDirection::Horizontal => rect.w.saturating_sub(1).max(1),
1054 GradientDirection::Vertical => rect.h.saturating_sub(1).max(1),
1055 };
1056
1057 for y in draw.y..draw.bottom() {
1058 for x in draw.x..draw.right() {
1059 if !in_rounded_rect(x, y, rect, radius) {
1060 continue;
1061 }
1062 let numer = match gradient.direction {
1063 GradientDirection::Horizontal => (x - rect.x).max(0) as u32,
1064 GradientDirection::Vertical => (y - rect.y).max(0) as u32,
1065 }
1066 .min(denom);
1067 let mut t = ((numer * 255) / denom) as u8;
1068 t = match self.quality {
1069 RenderQuality::Low => 128,
1070 RenderQuality::Medium => (t / 64) * 64,
1071 RenderQuality::High => t,
1072 };
1073 let color = lerp_rgb565(gradient.start, gradient.end, t);
1074 self.pixel(x, y, color, opacity)?;
1075 }
1076 }
1077 Ok(())
1078 }
1079
1080 pub fn stroke_rect(&mut self, rect: impl Into<Rect>, border: Border) -> Result<(), D::Error> {
1081 self.stroke_rect_alpha(rect, border, 255)
1082 }
1083
1084 pub fn stroke_rect_alpha(
1085 &mut self,
1086 rect: impl Into<Rect>,
1087 border: Border,
1088 opacity: u8,
1089 ) -> Result<(), D::Error> {
1090 let rect = rect.into();
1091 if border.width == 0 || rect.is_empty() {
1092 return Ok(());
1093 }
1094
1095 for i in 0..border.width as i32 {
1096 let w = rect.w.saturating_sub((i as u32).saturating_mul(2));
1097 let h = rect.h.saturating_sub((i as u32).saturating_mul(2));
1098 if w == 0 || h == 0 {
1099 break;
1100 }
1101 let r = Rect::new(rect.x + i, rect.y + i, w, h);
1102 self.fill_rect_alpha(Rect::new(r.x, r.y, r.w, 1), border.color, opacity)?;
1103 if r.h > 1 {
1104 self.fill_rect_alpha(
1105 Rect::new(r.x, r.bottom() - 1, r.w, 1),
1106 border.color,
1107 opacity,
1108 )?;
1109 }
1110 if r.h > 2 {
1111 self.fill_rect_alpha(Rect::new(r.x, r.y + 1, 1, r.h - 2), border.color, opacity)?;
1112 if r.w > 1 {
1113 self.fill_rect_alpha(
1114 Rect::new(r.right() - 1, r.y + 1, 1, r.h - 2),
1115 border.color,
1116 opacity,
1117 )?;
1118 }
1119 }
1120 }
1121 Ok(())
1122 }
1123
1124 pub fn stroke_rounded_rect(
1125 &mut self,
1126 rect: impl Into<Rect>,
1127 radius: u8,
1128 border: Border,
1129 ) -> Result<(), D::Error> {
1130 self.stroke_rounded_rect_alpha(rect, radius, border, 255)
1131 }
1132
1133 pub fn stroke_rounded_rect_alpha(
1134 &mut self,
1135 rect: impl Into<Rect>,
1136 radius: u8,
1137 border: Border,
1138 opacity: u8,
1139 ) -> Result<(), D::Error> {
1140 let rect = rect.into();
1141 if border.width == 0 || rect.is_empty() || opacity == 0 {
1142 return Ok(());
1143 }
1144
1145 let draw = self.visible_rect(rect);
1146 if draw.is_empty() {
1147 return Ok(());
1148 }
1149
1150 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
1151 for y in draw.y..draw.bottom() {
1152 for x in draw.x..draw.right() {
1153 if !in_rounded_rect(x, y, rect, radius) {
1154 continue;
1155 }
1156
1157 let mut inner_hit = false;
1158 let mut i = 1u8;
1159 while i < border.width {
1160 let inset = i as i32;
1161 let inner = Rect::new(
1162 rect.x + inset,
1163 rect.y + inset,
1164 rect.w.saturating_sub((i as u32) * 2),
1165 rect.h.saturating_sub((i as u32) * 2),
1166 );
1167 let inner_radius = radius.saturating_sub(i);
1168 if !inner.is_empty() && in_rounded_rect(x, y, inner, inner_radius) {
1169 inner_hit = true;
1170 break;
1171 }
1172 i += 1;
1173 }
1174
1175 if !inner_hit {
1176 self.pixel(x, y, border.color, opacity)?;
1177 }
1178 }
1179 }
1180 Ok(())
1181 }
1182
1183 pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Rgb565) -> Result<(), D::Error> {
1184 self.draw_text_with_font(x, y, text, color, FontId::Tiny3x5)
1185 }
1186
1187 pub fn draw_text_with_font(
1188 &mut self,
1189 x: i32,
1190 y: i32,
1191 text: &str,
1192 color: Rgb565,
1193 font: impl Into<FontId>,
1194 ) -> Result<(), D::Error> {
1195 let font = font.into();
1196 let advance = font.advance() as i32;
1197 let line_h = font.line_height() as i32;
1198 let mut cursor_x = x;
1199 let mut cursor_y = y;
1200 for ch in text.chars() {
1201 if ch == '\n' {
1202 cursor_x = x;
1203 cursor_y += line_h;
1204 continue;
1205 }
1206 self.draw_char_with_font(cursor_x, cursor_y, ch, color, 255, font)?;
1207 cursor_x += advance;
1208 }
1209 Ok(())
1210 }
1211
1212 pub fn draw_text_in(
1213 &mut self,
1214 rect: impl Into<Rect>,
1215 text: &str,
1216 style: TextStyle,
1217 ) -> Result<(), D::Error> {
1218 self.draw_text_in_with_font(rect, text, style, style.font)
1219 }
1220
1221 pub fn draw_text_shaped_in<S, const N: usize>(
1222 &mut self,
1223 rect: Rect,
1224 text: &str,
1225 style: TextStyle,
1226 shaper: &S,
1227 config: crate::text::ShapingConfig,
1228 ) -> Result<(), D::Error>
1229 where
1230 S: crate::text::TextShaper,
1231 {
1232 if rect.is_empty() {
1233 return Ok(());
1234 }
1235 let mut shaped = heapless::Vec::<crate::text::ShapedGlyph, N>::new();
1236 shaper.shape(text, config, &mut shaped);
1237 if shaped.is_empty() {
1238 return Ok(());
1239 }
1240 let mut x = rect.x;
1241 let y = rect.y + rect.h.saturating_sub(style.font.line_height()) as i32 / 2;
1242 for glyph in shaped {
1243 self.draw_char_with_font(x, y, glyph.ch, style.color, style.opacity, style.font)?;
1244 x += (glyph.x_advance as i32).max(1) * style.font.advance() as i32;
1245 if x >= rect.right() {
1246 break;
1247 }
1248 }
1249 Ok(())
1250 }
1251
1252 pub fn draw_text_in_with_font(
1253 &mut self,
1254 rect: impl Into<Rect>,
1255 text: &str,
1256 style: TextStyle,
1257 font: impl Into<FontId>,
1258 ) -> Result<(), D::Error> {
1259 let rect = rect.into();
1260 let font = font.into();
1261 if rect.is_empty() {
1262 return Ok(());
1263 }
1264
1265 let advance = font.advance();
1266 let line_h = font.line_height();
1267 let max_chars = (rect.w / advance).max(1) as usize;
1268 let char_count = text.chars().count();
1269 let line_count = count_lines(text, max_chars, style.wrap).max(1);
1270 let line_step = line_h + style.line_spacing as u32;
1271 let total_h = line_count as u32 * line_h
1272 + line_count.saturating_sub(1) as u32 * style.line_spacing as u32;
1273 let mut y = match style.vertical_align {
1274 VerticalAlign::Top => rect.y,
1275 VerticalAlign::Middle => rect.y + rect.h.saturating_sub(total_h) as i32 / 2,
1276 VerticalAlign::Bottom => rect.y + rect.h.saturating_sub(total_h) as i32,
1277 };
1278
1279 let mut start = 0;
1280 let mut rendered_lines = 0u8;
1281 let max_lines = match style.overflow_policy {
1282 TextOverflowPolicy::WrapThenEllipsis { max_lines } => max_lines.max(1),
1283 TextOverflowPolicy::Global(_) => style.max_lines.unwrap_or(u8::MAX),
1284 };
1285 while start < char_count {
1286 if rendered_lines >= max_lines {
1287 break;
1288 }
1289 let (len, consumed_newline) = line_len_at(text, start, max_chars, style.wrap);
1290 let mut draw_len = len;
1291 let is_last_allowed_line = rendered_lines.saturating_add(1) >= max_lines;
1292 let use_ellipsis = match style.overflow_policy {
1293 TextOverflowPolicy::WrapThenEllipsis { .. } => is_last_allowed_line,
1294 TextOverflowPolicy::Global(mode) => mode == TextOverflow::Ellipsis,
1295 };
1296 if use_ellipsis
1297 && ((!consumed_newline && start + len < char_count) || is_last_allowed_line)
1298 {
1299 let ellipsis_width = match style.ellipsis {
1300 EllipsisMode::ThreeDots => 3usize,
1301 EllipsisMode::SingleGlyph => 1usize,
1302 };
1303 if len > ellipsis_width {
1304 draw_len = len - ellipsis_width;
1305 }
1306 }
1307 let line_w = self.substring_width(text, start, draw_len, font, style.kerning);
1308 let x = match style.align {
1309 TextAlign::Left => rect.x,
1310 TextAlign::Center => rect.x + rect.w.saturating_sub(line_w) as i32 / 2,
1311 TextAlign::Right => rect.x + rect.w.saturating_sub(line_w) as i32,
1312 };
1313 self.draw_chars_with_font(
1314 x,
1315 y,
1316 text,
1317 start,
1318 draw_len,
1319 style.color,
1320 style.opacity,
1321 font,
1322 style.kerning,
1323 )?;
1324 if draw_len < len && use_ellipsis {
1325 let token = match style.ellipsis {
1326 EllipsisMode::ThreeDots => "...",
1327 EllipsisMode::SingleGlyph => ".",
1328 };
1329 self.draw_text_with_font(x + line_w as i32, y, token, style.color, font)?;
1330 }
1331 y += line_step as i32;
1332 rendered_lines = rendered_lines.saturating_add(1);
1333 start += len + usize::from(consumed_newline);
1334 if style.wrap == TextWrap::Word && start < char_count {
1335 while text.chars().nth(start).is_some_and(|ch| ch == ' ') {
1336 start += 1;
1337 }
1338 }
1339 if len == 0 && !consumed_newline {
1340 break;
1341 }
1342 }
1343
1344 Ok(())
1345 }
1346
1347 pub fn draw_line_in(&mut self, rect: Rect, line: text::Line<'_>) -> Result<(), D::Error> {
1348 if rect.is_empty() {
1349 return Ok(());
1350 }
1351
1352 self.draw_line_segment_in(rect, line, 0, line.width_chars())
1353 }
1354
1355 pub fn draw_line(
1356 &mut self,
1357 x0: i32,
1358 y0: i32,
1359 x1: i32,
1360 y1: i32,
1361 color: Rgb565,
1362 ) -> Result<(), D::Error> {
1363 self.draw_line_styled(x0, y0, x1, y1, StrokeStyle::new(color))
1364 }
1365
1366 pub fn draw_line_styled(
1367 &mut self,
1368 x0: i32,
1369 y0: i32,
1370 x1: i32,
1371 y1: i32,
1372 style: StrokeStyle,
1373 ) -> Result<(), D::Error> {
1374 let mut x = x0;
1375 let mut y = y0;
1376 let dx = (x1 - x0).abs();
1377 let sx = if x0 < x1 { 1 } else { -1 };
1378 let dy = -(y1 - y0).abs();
1379 let sy = if y0 < y1 { 1 } else { -1 };
1380 let mut err = dx + dy;
1381 let half = (style.width as i32 / 2).max(0);
1382 let opacity = self.stroke_opacity(style);
1383
1384 loop {
1385 for oy in -half..=half {
1386 for ox in -half..=half {
1387 self.pixel(x + ox, y + oy, style.color, opacity)?;
1388 }
1389 }
1390 if style.cap == StrokeCap::Round {
1391 self.fill_circle(x0, y0, half.max(1) as u32, style.color)?;
1392 self.fill_circle(x1, y1, half.max(1) as u32, style.color)?;
1393 }
1394 if x == x1 && y == y1 {
1395 break;
1396 }
1397 let e2 = 2 * err;
1398 if e2 >= dy {
1399 err += dy;
1400 x += sx;
1401 }
1402 if e2 <= dx {
1403 err += dx;
1404 y += sy;
1405 }
1406 }
1407 Ok(())
1408 }
1409
1410 pub fn fill_circle(
1411 &mut self,
1412 center_x: i32,
1413 center_y: i32,
1414 radius: u32,
1415 color: Rgb565,
1416 ) -> Result<(), D::Error> {
1417 let radius = radius as i32;
1418 if radius <= 0 {
1419 return Ok(());
1420 }
1421 let r_sq = radius * radius;
1422 for dy in -radius..=radius {
1423 let dx = ((r_sq - dy * dy) as f32).sqrt() as i32;
1424 if dx >= 0 {
1425 let w = (dx * 2 + 1) as u32;
1426 self.fill_rect(Rect::new(center_x - dx, center_y + dy, w, 1), color)?;
1427 }
1428 }
1429 Ok(())
1430 }
1431
1432 pub fn stroke_circle(
1433 &mut self,
1434 center_x: i32,
1435 center_y: i32,
1436 radius: u32,
1437 color: Rgb565,
1438 ) -> Result<(), D::Error> {
1439 let radius = radius as i32;
1440 if radius <= 0 {
1441 return Ok(());
1442 }
1443 let mut x = radius;
1444 let mut y = 0;
1445 let mut err = 1 - x;
1446 while x >= y {
1447 self.pixel(center_x + x, center_y + y, color, 255)?;
1448 self.pixel(center_x + y, center_y + x, color, 255)?;
1449 self.pixel(center_x - y, center_y + x, color, 255)?;
1450 self.pixel(center_x - x, center_y + y, color, 255)?;
1451 self.pixel(center_x - x, center_y - y, color, 255)?;
1452 self.pixel(center_x - y, center_y - x, color, 255)?;
1453 self.pixel(center_x + y, center_y - x, color, 255)?;
1454 self.pixel(center_x + x, center_y - y, color, 255)?;
1455 y += 1;
1456 if err < 0 {
1457 err += 2 * y + 1;
1458 } else {
1459 x -= 1;
1460 err += 2 * (y - x) + 1;
1461 }
1462 }
1463 Ok(())
1464 }
1465
1466 pub fn stroke_arc(
1467 &mut self,
1468 center_x: i32,
1469 center_y: i32,
1470 radius: u32,
1471 start_deg: i32,
1472 end_deg: i32,
1473 color: Rgb565,
1474 ) -> Result<(), D::Error> {
1475 self.stroke_arc_styled(
1476 center_x,
1477 center_y,
1478 radius,
1479 start_deg,
1480 end_deg,
1481 StrokeStyle::new(color),
1482 )
1483 }
1484
1485 pub fn stroke_arc_styled(
1486 &mut self,
1487 center_x: i32,
1488 center_y: i32,
1489 radius: u32,
1490 start_deg: i32,
1491 end_deg: i32,
1492 style: StrokeStyle,
1493 ) -> Result<(), D::Error> {
1494 let mut start = start_deg;
1495 let mut end = end_deg;
1496 if end < start {
1497 core::mem::swap(&mut start, &mut end);
1498 }
1499 let mut deg = start;
1500 let step = match self.quality {
1501 RenderQuality::Low => 8,
1502 RenderQuality::Medium => 4,
1503 RenderQuality::High => 2,
1504 };
1505 while deg <= end {
1506 let rad = (deg as f32).to_radians();
1507 let x = center_x + (radius as f32 * rad.cos()) as i32;
1508 let y = center_y + (radius as f32 * rad.sin()) as i32;
1509 let half = (style.width as i32 / 2).max(0);
1510 let opacity = self.stroke_opacity(style);
1511 for oy in -half..=half {
1512 for ox in -half..=half {
1513 self.pixel(x + ox, y + oy, style.color, opacity)?;
1514 }
1515 }
1516 if style.join == StrokeJoin::Round {
1517 self.fill_circle(x, y, half.max(1) as u32, style.color)?;
1518 }
1519 deg += step;
1520 }
1521 Ok(())
1522 }
1523
1524 pub fn fill_sector_sweep(
1528 &mut self,
1529 center_x: i32,
1530 center_y: i32,
1531 radius: u32,
1532 start_deg: f32,
1533 sweep_deg: f32,
1534 color: Rgb565,
1535 ) -> Result<(), D::Error> {
1536 if radius == 0 {
1537 return Ok(());
1538 }
1539
1540 let draw = self.visible_rect(Rect::new(
1541 center_x - radius as i32,
1542 center_y - radius as i32,
1543 radius.saturating_mul(2).saturating_add(1),
1544 radius.saturating_mul(2).saturating_add(1),
1545 ));
1546 if draw.is_empty() {
1547 return Ok(());
1548 }
1549
1550 let max_sweep = sweep_deg.abs().min(360.0);
1551 if max_sweep <= 0.0 {
1552 return Ok(());
1553 }
1554
1555 let rr = (radius as i32) * (radius as i32);
1556 let start = normalize_angle_deg(start_deg);
1557 let ccw = sweep_deg >= 0.0;
1558
1559 let (lo_deg, hi_deg) = if ccw {
1569 (start, start + max_sweep)
1570 } else {
1571 (start - max_sweep, start)
1572 };
1573 let (lo_c, lo_s) = cardinal_unit(lo_deg)
1574 .unwrap_or_else(|| (lo_deg.to_radians().cos(), lo_deg.to_radians().sin()));
1575 let (hi_c, hi_s) = cardinal_unit(hi_deg)
1576 .unwrap_or_else(|| (hi_deg.to_radians().cos(), hi_deg.to_radians().sin()));
1577 let full_circle = max_sweep >= 360.0;
1583 let reflex = max_sweep > 180.0;
1584
1585 for y in draw.y..draw.bottom() {
1586 for x in draw.x..draw.right() {
1587 let dx = x - center_x;
1588 let dy = y - center_y;
1589 let d2 = dx * dx + dy * dy;
1590 if d2 > rr {
1591 continue;
1592 }
1593
1594 let in_sweep = if full_circle {
1595 true
1596 } else {
1597 let (fx, fy) = (dx as f32, dy as f32);
1598 if !reflex {
1599 cross(lo_c, lo_s, fx, fy) >= 0.0 && cross(fx, fy, hi_c, hi_s) >= 0.0
1600 } else {
1601 !(cross(hi_c, hi_s, fx, fy) >= 0.0 && cross(fx, fy, lo_c, lo_s) >= 0.0)
1602 }
1603 };
1604 if in_sweep {
1605 self.pixel(x, y, color, 255)?;
1606 }
1607 }
1608 }
1609 Ok(())
1610 }
1611
1612 pub fn fill_polygon(&mut self, points: &[Point], color: Rgb565) -> Result<(), D::Error> {
1613 if points.len() < 3 {
1614 return Ok(());
1615 }
1616 let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
1617 let max_y = points.iter().map(|p| p.y).max().unwrap_or(-1);
1618 for y in min_y..=max_y {
1619 let mut intersections = [i32::MIN; 16];
1620 let mut count = 0usize;
1621 for i in 0..points.len() {
1622 let p1 = points[i];
1623 let p2 = points[(i + 1) % points.len()];
1624 let (y1, y2) = if p1.y <= p2.y {
1625 (p1.y, p2.y)
1626 } else {
1627 (p2.y, p1.y)
1628 };
1629 if y < y1 || y >= y2 || y1 == y2 {
1630 continue;
1631 }
1632 if count >= intersections.len() {
1633 break;
1634 }
1635 let x = p1.x + ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y);
1636 intersections[count] = x;
1637 count += 1;
1638 }
1639 intersections[..count].sort_unstable();
1640 let mut i = 0;
1641 while i + 1 < count {
1642 let x0 = intersections[i];
1643 let x1 = intersections[i + 1];
1644 for x in x0..=x1 {
1645 self.pixel(x, y, color, 255)?;
1646 }
1647 i += 2;
1648 }
1649 }
1650 Ok(())
1651 }
1652
1653 pub fn draw_image(
1654 &mut self,
1655 rect: Rect,
1656 image: ImageRef<'_>,
1657 fit: ImageFit,
1658 ) -> Result<(), D::Error> {
1659 self.draw_image_region(rect, image, fit, Rect::new(0, 0, image.width, image.height))
1660 }
1661
1662 pub fn draw_image_region(
1663 &mut self,
1664 rect: Rect,
1665 image: ImageRef<'_>,
1666 fit: ImageFit,
1667 src_rect: Rect,
1668 ) -> Result<(), D::Error> {
1669 let bounds = image.bounds_at(rect, fit);
1670 if bounds.is_empty() || image.width == 0 || image.height == 0 {
1671 return Ok(());
1672 }
1673 let src_w = image.width as usize;
1674 for y in 0..bounds.h {
1675 let src_y = match fit {
1676 ImageFit::Stretch => {
1677 src_rect.y.max(0) as usize
1678 + ((y as u64 * src_rect.h as u64) / bounds.h as u64) as usize
1679 }
1680 ImageFit::Center => src_rect.y.max(0) as usize + y as usize,
1681 };
1682 for x in 0..bounds.w {
1683 let src_x = match fit {
1684 ImageFit::Stretch => {
1685 src_rect.x.max(0) as usize
1686 + ((x as u64 * src_rect.w as u64) / bounds.w as u64) as usize
1687 }
1688 ImageFit::Center => src_rect.x.max(0) as usize + x as usize,
1689 };
1690 let idx = src_y.saturating_mul(src_w).saturating_add(src_x);
1691 if let Some(raw) = image.pixels.get(idx) {
1692 let color = Rgb565::new(
1693 ((raw >> 11) & 0x1F) as u8,
1694 ((raw >> 5) & 0x3F) as u8,
1695 (raw & 0x1F) as u8,
1696 );
1697 self.pixel(bounds.x + x as i32, bounds.y + y as i32, color, 255)?;
1698 }
1699 }
1700 }
1701 Ok(())
1702 }
1703
1704 pub fn draw_image_transformed(
1705 &mut self,
1706 rect: Rect,
1707 image: ImageRef<'_>,
1708 scale: f32,
1709 rotation_deg: f32,
1710 ) -> Result<(), D::Error> {
1711 if rect.is_empty() || image.width == 0 || image.height == 0 || scale <= 0.0 {
1712 return Ok(());
1713 }
1714 let cx = rect.x + rect.w as i32 / 2;
1715 let cy = rect.y + rect.h as i32 / 2;
1716 let rad = rotation_deg.to_radians();
1717 let cos_r = rad.cos();
1718 let sin_r = rad.sin();
1719 let src_w = image.width as usize;
1720 let src_cx = image.width as f32 / 2.0;
1721 let src_cy = image.height as f32 / 2.0;
1722 for y in rect.y..rect.bottom() {
1723 for x in rect.x..rect.right() {
1724 let dx = (x - cx) as f32 / scale;
1725 let dy = (y - cy) as f32 / scale;
1726 let sx = cos_r * dx + sin_r * dy + src_cx;
1727 let sy = -sin_r * dx + cos_r * dy + src_cy;
1728 if sx < 0.0 || sy < 0.0 || sx >= image.width as f32 || sy >= image.height as f32 {
1729 continue;
1730 }
1731 let idx = (sy as usize)
1732 .saturating_mul(src_w)
1733 .saturating_add(sx as usize);
1734 if let Some(raw) = image.pixels.get(idx) {
1735 let color = Rgb565::new(
1736 ((raw >> 11) & 0x1F) as u8,
1737 ((raw >> 5) & 0x3F) as u8,
1738 (raw & 0x1F) as u8,
1739 );
1740 self.pixel(x, y, color, 255)?;
1741 }
1742 }
1743 }
1744 Ok(())
1745 }
1746
1747 pub fn fill_rect_masked(
1748 &mut self,
1749 rect: Rect,
1750 color: Rgb565,
1751 mask: fn(i32, i32) -> bool,
1752 ) -> Result<(), D::Error> {
1753 let draw = self.visible_rect(rect);
1754 if draw.is_empty() {
1755 return Ok(());
1756 }
1757 for y in draw.y..draw.bottom() {
1758 for x in draw.x..draw.right() {
1759 if mask(x, y) {
1760 self.pixel(x, y, color, 255)?;
1761 }
1762 }
1763 }
1764 Ok(())
1765 }
1766
1767 pub fn draw_text_model_in(&mut self, rect: Rect, text: text::Text<'_>) -> Result<(), D::Error> {
1768 if rect.is_empty() || text.lines.is_empty() {
1769 return Ok(());
1770 }
1771
1772 let metrics = text.metrics(rect.w);
1773 let max_line_height = text
1774 .lines
1775 .iter()
1776 .map(|line| line.max_line_height())
1777 .max()
1778 .unwrap_or(CHAR_HEIGHT);
1779 let line_step = max_line_height + text.line_spacing as u32;
1780 let mut y = match text.vertical_align {
1781 VerticalAlign::Top => rect.y,
1782 VerticalAlign::Middle => rect.y + rect.h.saturating_sub(metrics.height) as i32 / 2,
1783 VerticalAlign::Bottom => rect.y + rect.h.saturating_sub(metrics.height) as i32,
1784 };
1785 for line in text.lines {
1786 let align = if line.align == TextAlign::Left {
1787 text.align
1788 } else {
1789 line.align
1790 };
1791 let line = text::Line { align, ..*line };
1792
1793 let mut start = 0;
1794 let char_count = line.char_count();
1795 if char_count == 0 {
1796 y += line_step as i32;
1797 continue;
1798 }
1799 while start < char_count {
1800 if y >= rect.bottom() {
1801 return Ok(());
1802 }
1803 let (len, consumed_newline) = line.segment_len_at(start, rect.w, text.wrap);
1804 self.draw_line_segment_in(
1805 Rect::new(rect.x, y, rect.w, max_line_height),
1806 line,
1807 start,
1808 len,
1809 )?;
1810 y += line_step as i32;
1811 start += len + usize::from(consumed_newline);
1812 if len == 0 && !consumed_newline {
1813 break;
1814 }
1815 }
1816 }
1817
1818 Ok(())
1819 }
1820
1821 pub fn text_metrics(text: &str) -> TextMetrics {
1822 Self::text_metrics_with_font(text, FontId::Tiny3x5)
1823 }
1824
1825 pub fn text_metrics_with_font(text: &str, font: impl Into<FontId>) -> TextMetrics {
1826 let font = font.into();
1827 TextMetrics {
1828 width: text.chars().count() as u32 * font.advance(),
1829 height: font.line_height(),
1830 }
1831 }
1832
1833 pub fn text_metrics_wrapped(text: &str, max_width: u32, wrap: TextWrap) -> TextMetrics {
1834 Self::text_metrics_wrapped_with_font(text, max_width, wrap, FontId::Tiny3x5)
1835 }
1836
1837 pub fn text_metrics_wrapped_with_font(
1838 text: &str,
1839 max_width: u32,
1840 wrap: TextWrap,
1841 font: impl Into<FontId>,
1842 ) -> TextMetrics {
1843 let font = font.into();
1844 let max_chars = (max_width / font.advance()).max(1) as usize;
1845 let lines = count_lines(text, max_chars, wrap).max(1);
1846 let widest = widest_line(text, max_chars, wrap) as u32 * font.advance();
1847 TextMetrics {
1848 width: widest.min(max_width),
1849 height: lines as u32 * font.line_height() + lines.saturating_sub(1) as u32,
1850 }
1851 }
1852
1853 #[allow(clippy::too_many_arguments)]
1854 fn draw_chars_with_font(
1855 &mut self,
1856 x: i32,
1857 y: i32,
1858 text: &str,
1859 start: usize,
1860 len: usize,
1861 color: Rgb565,
1862 opacity: u8,
1863 font: FontId,
1864 kerning: bool,
1865 ) -> Result<(), D::Error> {
1866 let advance = font.advance() as i32;
1867 let mut cursor_x = x;
1868 let mut prev: Option<char> = None;
1869 for ch in text.chars().skip(start).take(len) {
1870 self.draw_char_with_font(cursor_x, y, ch, color, opacity, font)?;
1871 cursor_x += advance + kerning_adjust(prev, ch, kerning);
1872 prev = Some(ch);
1873 }
1874 Ok(())
1875 }
1876
1877 fn substring_width(
1878 &self,
1879 text: &str,
1880 start: usize,
1881 len: usize,
1882 font: FontId,
1883 kerning: bool,
1884 ) -> u32 {
1885 let mut width = 0u32;
1886 let mut prev = None;
1887 for ch in text.chars().skip(start).take(len) {
1888 width = width.saturating_add(font.advance());
1889 let adjust = kerning_adjust(prev, ch, kerning);
1890 if adjust < 0 {
1891 width = width.saturating_sub((-adjust) as u32);
1892 } else {
1893 width = width.saturating_add(adjust as u32);
1894 }
1895 prev = Some(ch);
1896 }
1897 width
1898 }
1899
1900 fn draw_line_segment_in(
1901 &mut self,
1902 rect: Rect,
1903 line: text::Line<'_>,
1904 start: usize,
1905 len: usize,
1906 ) -> Result<(), D::Error> {
1907 if rect.is_empty() || len == 0 {
1908 return Ok(());
1909 }
1910
1911 let line_w = self.line_segment_width(line, start, len);
1912 let x = match line.align {
1913 TextAlign::Left => rect.x,
1914 TextAlign::Center => rect.x + rect.w.saturating_sub(line_w) as i32 / 2,
1915 TextAlign::Right => rect.x + rect.w.saturating_sub(line_w) as i32,
1916 };
1917
1918 let old_clip = self.clip;
1919 self.clip = self.clip.intersection(rect);
1920 let result = self.draw_span_chars(x, rect.y, line, start, len);
1921 self.clip = old_clip;
1922 result
1923 }
1924
1925 fn draw_span_chars(
1926 &mut self,
1927 x: i32,
1928 y: i32,
1929 line: text::Line<'_>,
1930 start: usize,
1931 len: usize,
1932 ) -> Result<(), D::Error> {
1933 let mut cursor_x = x;
1934 for (idx, (ch, style)) in line
1935 .spans
1936 .iter()
1937 .flat_map(|span| span.content.chars().map(move |ch| (ch, span.style)))
1938 .enumerate()
1939 {
1940 if idx < start {
1941 continue;
1942 }
1943 if idx >= start + len {
1944 break;
1945 }
1946 if ch != '\n' {
1947 self.draw_char_with_font(cursor_x, y, ch, style.color, 255, style.font)?;
1948 cursor_x += style.font.advance() as i32;
1949 }
1950 }
1951 Ok(())
1952 }
1953
1954 fn line_segment_width(&self, line: text::Line<'_>, start: usize, len: usize) -> u32 {
1955 line.spans
1956 .iter()
1957 .flat_map(|span| span.content.chars().map(move |ch| (ch, span.style.font)))
1958 .enumerate()
1959 .filter_map(|(idx, (ch, font))| {
1960 if idx < start || idx >= start + len || ch == '\n' {
1961 None
1962 } else {
1963 Some(font.advance())
1964 }
1965 })
1966 .sum()
1967 }
1968
1969 fn draw_char_with_font(
1970 &mut self,
1971 x: i32,
1972 y: i32,
1973 ch: char,
1974 color: Rgb565,
1975 opacity: u8,
1976 font: FontId,
1977 ) -> Result<(), D::Error> {
1978 let glyph = glyph_rows(font, ch);
1979 let layer = self.current_layer();
1980 let fast_spans = opacity == 255
1981 && layer.opacity == 255
1982 && self.current_transform().is_identity()
1983 && layer.blend == BlendMode::Normal;
1984
1985 match font {
1986 FontId::Tiny3x5 | FontId::Medium4x7 | FontId::Custom(_) => {
1987 for (row, bits) in glyph.iter().enumerate() {
1988 let ry = y + row as i32;
1989 if fast_spans && *bits == 0b111 {
1990 self.fill_rect(Rect::new(x, ry, 3, 1), color)?;
1991 } else if fast_spans && *bits == 0b110 {
1992 self.fill_rect(Rect::new(x, ry, 2, 1), color)?;
1993 } else if fast_spans && *bits == 0b011 {
1994 self.fill_rect(Rect::new(x + 1, ry, 2, 1), color)?;
1995 } else {
1996 for col in 0..3 {
1997 if bits & (1 << (2 - col)) != 0 {
1998 self.pixel(x + col, ry, color, opacity)?;
1999 }
2000 }
2001 }
2002 }
2003 }
2004 FontId::Scaled6x10 => {
2005 for (row, bits) in glyph.iter().enumerate() {
2006 for col in 0..3 {
2007 if bits & (1 << (2 - col)) != 0 {
2008 let px = x + (col * 2);
2009 let py = y + (row as i32 * 2);
2010 self.pixel(px, py, color, opacity)?;
2011 self.pixel(px + 1, py, color, opacity)?;
2012 self.pixel(px, py + 1, color, opacity)?;
2013 self.pixel(px + 1, py + 1, color, opacity)?;
2014 }
2015 }
2016 }
2017 }
2018 FontId::Vector(scale) => {
2019 let glyph = crate::font::get_vector_glyph(ch);
2020 let mut last_point: Option<(i32, i32)> = None;
2021 let scale_f = scale as f32;
2022 for &(px, py) in glyph {
2023 if px == 0xFF && py == 0xFF {
2024 last_point = None;
2025 continue;
2026 }
2027 let draw_x = x + (px as f32 * scale_f) as i32;
2028 let draw_y = y + (py as f32 * scale_f) as i32;
2029 if let Some((lx, ly)) = last_point {
2030 self.draw_line_styled(
2031 lx,
2032 ly,
2033 draw_x,
2034 draw_y,
2035 StrokeStyle::new(color).with_width(1).with_antialias(true),
2036 )?;
2037 }
2038 last_point = Some((draw_x, draw_y));
2039 }
2040 }
2041 FontId::Bitmap(bitmap_font) => {
2042 use crate::font::GlyphOp;
2043 let mut draw_err = Ok(());
2044 bitmap_font.draw_glyph_to(ch, |op| {
2045 if draw_err.is_ok() {
2046 match op {
2047 GlyphOp::Pixel(dx, dy) => {
2048 if let Err(e) = self.pixel(x + dx, y + dy, color, opacity) {
2049 draw_err = Err(e);
2050 }
2051 }
2052 GlyphOp::Span(dx, dy, len) => {
2053 if fast_spans {
2054 if let Err(e) =
2055 self.fill_rect(Rect::new(x + dx, y + dy, len, 1), color)
2056 {
2057 draw_err = Err(e);
2058 }
2059 } else {
2060 for col in 0..len {
2061 if let Err(e) =
2062 self.pixel(x + dx + col as i32, y + dy, color, opacity)
2063 {
2064 draw_err = Err(e);
2065 break;
2066 }
2067 }
2068 }
2069 }
2070 }
2071 }
2072 });
2073 draw_err?;
2074 }
2075 FontId::Dynamic(dyn_font) => {
2076 let mut draw_err = Ok(());
2077 dyn_font.draw_glyph(ch, &mut |dx, dy| {
2078 if draw_err.is_ok() {
2079 if let Err(e) = self.pixel(x + dx, y + dy, color, opacity) {
2080 draw_err = Err(e);
2081 }
2082 }
2083 });
2084 draw_err?;
2085 }
2086 #[cfg(feature = "embedded-graphics")]
2087 FontId::MonoFont(font) => {
2088 use embedded_graphics::Drawable;
2089 use embedded_graphics::draw_target::DrawTarget;
2090 use embedded_graphics::geometry::{OriginDimensions, Point, Size};
2091 use embedded_graphics::mono_font::MonoTextStyle;
2092 use embedded_graphics::pixelcolor::BinaryColor;
2093 use embedded_graphics::text::Text;
2094
2095 struct GlyphPixelCollector<'a, F> {
2096 x: i32,
2097 y: i32,
2098 f: &'a mut F,
2099 }
2100
2101 impl<F> OriginDimensions for GlyphPixelCollector<'_, F> {
2102 fn size(&self) -> Size {
2103 Size::new(u32::MAX, u32::MAX)
2104 }
2105 }
2106
2107 impl<F: FnMut(i32, i32)> DrawTarget for GlyphPixelCollector<'_, F> {
2108 type Color = BinaryColor;
2109 type Error = core::convert::Infallible;
2110
2111 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
2112 where
2113 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
2114 {
2115 for embedded_graphics::Pixel(pos, color) in pixels {
2116 if color.is_on() {
2117 (self.f)(self.x + pos.x, self.y + pos.y);
2118 }
2119 }
2120 Ok(())
2121 }
2122 }
2123
2124 let mut collector_err = Ok(());
2125 let mut pixel_cb = |px: i32, py: i32| {
2126 if collector_err.is_ok() {
2127 if let Err(e) = self.pixel(px, py, color, opacity) {
2128 collector_err = Err(e);
2129 }
2130 }
2131 };
2132
2133 let mut collector = GlyphPixelCollector {
2134 x,
2135 y,
2136 f: &mut pixel_cb,
2137 };
2138
2139 let text_style = MonoTextStyle::new(font, BinaryColor::On);
2140 let mut buf = [0u8; 4];
2141 let ch_str = ch.encode_utf8(&mut buf);
2142 let _ = Text::new(ch_str, Point::zero(), text_style).draw(&mut collector);
2143 collector_err?;
2144 }
2145 }
2146 Ok(())
2147 }
2148
2149 fn pixel(&mut self, x: i32, y: i32, color: Rgb565, opacity: u8) -> Result<(), D::Error> {
2150 let (x, y) = self.current_transform().apply(x, y);
2151 if !self.clip.contains(x, y) {
2152 return Ok(());
2153 }
2154 if let Some(dirty) = self.dirty {
2155 if !dirty.contains(x, y) {
2156 return Ok(());
2157 }
2158 }
2159 let layer = self.current_layer();
2160 let combined_opacity = ((opacity as u16 * layer.opacity as u16) / 255) as u8;
2161 C::plot(
2165 self.target,
2166 x,
2167 y,
2168 color,
2169 combined_opacity,
2170 layer.blend,
2171 layer.backdrop,
2172 )
2173 }
2174
2175 fn visible_rect(&self, rect: Rect) -> Rect {
2176 let mut draw = rect.intersection(self.clip);
2177 if let Some(dirty) = self.dirty {
2178 draw = draw.intersection(dirty);
2179 }
2180 draw
2181 }
2182
2183 fn current_transform(&self) -> Transform2D {
2184 self.transform_stack[self.transform_len - 1]
2185 }
2186
2187 fn current_layer(&self) -> LayerState {
2188 self.layer_stack[self.layer_len - 1]
2189 }
2190
2191 fn stroke_opacity(&self, style: StrokeStyle) -> u8 {
2192 if !style.antialias || matches!(style.antialias_mode, AntiAliasMode::None) {
2193 return 255;
2194 }
2195 match style.antialias_mode {
2196 AntiAliasMode::None => 255,
2197 AntiAliasMode::Coverage => match self.quality {
2198 RenderQuality::Low => 96,
2199 RenderQuality::Medium => 160,
2200 RenderQuality::High => 220,
2201 },
2202 AntiAliasMode::Subpixel => {
2203 if self.backend_caps.supports_subpixel {
2204 match self.quality {
2205 RenderQuality::Low => 128,
2206 RenderQuality::Medium => 192,
2207 RenderQuality::High => 240,
2208 }
2209 } else {
2210 match self.quality {
2211 RenderQuality::Low => 96,
2212 RenderQuality::Medium => 160,
2213 RenderQuality::High => 220,
2214 }
2215 }
2216 }
2217 }
2218 }
2219}
2220
2221impl<'a, D, C> RenderCtx<'a, D, C>
2222where
2223 D: DrawTarget<Color = Rgb565> + PixelRead,
2224 C: Compositor<D>,
2225{
2226 fn pixel_blended(&mut self, x: i32, y: i32, color: Rgb565, alpha: u8) -> Result<(), D::Error> {
2230 let (x, y) = self.current_transform().apply(x, y);
2231 if !self.clip.contains(x, y) {
2232 return Ok(());
2233 }
2234 if let Some(dirty) = self.dirty {
2235 if !dirty.contains(x, y) {
2236 return Ok(());
2237 }
2238 }
2239 let layer = self.current_layer();
2240 let combined_alpha = ((alpha as u16 * layer.opacity as u16) / 255) as u8;
2241 if combined_alpha == 0 {
2242 return Ok(());
2243 }
2244 let backdrop = self.target.get_pixel(Point::new(x, y));
2245 let blended = lerp_rgb565(backdrop, color, combined_alpha);
2246 let blended = apply_blend_mode(blended, layer.blend, layer.backdrop);
2247 self.target.draw_iter([Pixel(Point::new(x, y), blended)])
2248 }
2249
2250 pub fn fill_rect_true_alpha(
2253 &mut self,
2254 rect: Rect,
2255 color: Rgb565,
2256 alpha: u8,
2257 ) -> Result<(), D::Error> {
2258 self.fill_rounded_rect_true_alpha(rect, 0, color, alpha)
2259 }
2260
2261 pub fn fill_rounded_rect_true_alpha(
2264 &mut self,
2265 rect: Rect,
2266 radius: u8,
2267 color: Rgb565,
2268 alpha: u8,
2269 ) -> Result<(), D::Error> {
2270 let draw = self.visible_rect(rect);
2271 if draw.is_empty() || alpha == 0 {
2272 return Ok(());
2273 }
2274 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
2275
2276 for y in draw.y..draw.bottom() {
2277 for x in draw.x..draw.right() {
2278 if !in_rounded_rect(x, y, rect, radius) {
2279 continue;
2280 }
2281 self.pixel_blended(x, y, color, alpha)?;
2282 }
2283 }
2284 Ok(())
2285 }
2286
2287 pub fn fill_rect_alpha_mask(
2289 &mut self,
2290 rect: Rect,
2291 mask: &[u8],
2292 mask_stride: usize,
2293 color: Rgb565,
2294 opacity: u8,
2295 ) -> Result<(), D::Error> {
2296 let draw = self.visible_rect(rect);
2297 if draw.is_empty() || opacity == 0 || mask_stride == 0 {
2298 return Ok(());
2299 }
2300 for y in draw.y..draw.bottom() {
2301 let my = (y - rect.y) as usize;
2302 for x in draw.x..draw.right() {
2303 let mx = (x - rect.x) as usize;
2304 let idx = my * mask_stride + mx;
2305 if let Some(&m_val) = mask.get(idx) {
2306 if m_val > 0 {
2307 let pix_opacity = ((m_val as u16 * opacity as u16) / 255) as u8;
2308 self.pixel(x, y, color, pix_opacity)?;
2309 }
2310 }
2311 }
2312 }
2313 Ok(())
2314 }
2315
2316 pub fn fill_rounded_rect_alpha_gradient(
2318 &mut self,
2319 rect: Rect,
2320 radius: u8,
2321 gradient: &AlphaLinearGradient,
2322 opacity: u8,
2323 ) -> Result<(), D::Error> {
2324 let draw = self.visible_rect(rect);
2325 if draw.is_empty() || opacity == 0 {
2326 return Ok(());
2327 }
2328 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
2329 let denom = match gradient.direction {
2330 GradientDirection::Horizontal => rect.w.saturating_sub(1).max(1),
2331 GradientDirection::Vertical => rect.h.saturating_sub(1).max(1),
2332 };
2333
2334 for y in draw.y..draw.bottom() {
2335 for x in draw.x..draw.right() {
2336 if !in_rounded_rect(x, y, rect, radius) {
2337 continue;
2338 }
2339 let numer = match gradient.direction {
2340 GradientDirection::Horizontal => (x - rect.x).max(0) as u32,
2341 GradientDirection::Vertical => (y - rect.y).max(0) as u32,
2342 }
2343 .min(denom);
2344 let t = ((numer * 255) / denom) as u8;
2345 let (color, grad_alpha) = gradient.sample(t);
2346 let combined_alpha = ((grad_alpha as u16 * opacity as u16) / 255) as u8;
2347 self.pixel(x, y, color, combined_alpha)?;
2348 }
2349 }
2350 Ok(())
2351 }
2352
2353 pub fn fill_rounded_rect_radial_gradient(
2355 &mut self,
2356 rect: Rect,
2357 radius: u8,
2358 gradient: &AlphaRadialGradient,
2359 opacity: u8,
2360 ) -> Result<(), D::Error> {
2361 let draw = self.visible_rect(rect);
2362 if draw.is_empty() || opacity == 0 {
2363 return Ok(());
2364 }
2365 let radius = radius.min((rect.w.min(rect.h) / 2) as u8);
2366 let cx = rect.x as f32 + rect.w as f32 * gradient.center_x;
2367 let cy = rect.y as f32 + rect.h as f32 * gradient.center_y;
2368
2369 for y in draw.y..draw.bottom() {
2370 let dy = y as f32 - cy;
2371 for x in draw.x..draw.right() {
2372 if !in_rounded_rect(x, y, rect, radius) {
2373 continue;
2374 }
2375 let dx = x as f32 - cx;
2376 let dist = (dx * dx + dy * dy).sqrt();
2377 let (color, grad_alpha) = gradient.sample_at_dist(dist);
2378 let combined_alpha = ((grad_alpha as u16 * opacity as u16) / 255) as u8;
2379 self.pixel(x, y, color, combined_alpha)?;
2380 }
2381 }
2382 Ok(())
2383 }
2384
2385 pub fn draw_drop_shadow(
2387 &mut self,
2388 rect: Rect,
2389 _radius: u8,
2390 shadow_color: Rgb565,
2391 shadow_opacity: u8,
2392 shadow_spread: u8,
2393 blur_radius: u8,
2394 ) -> Result<(), D::Error> {
2395 if shadow_opacity == 0 {
2396 return Ok(());
2397 }
2398 let margin = (shadow_spread as i32) + (blur_radius as i32);
2399 let shadow_rect = Rect::new(
2400 rect.x - margin,
2401 rect.y - margin,
2402 rect.w + (margin as u32 * 2),
2403 rect.h + (margin as u32 * 2),
2404 );
2405 let draw = self.visible_rect(shadow_rect);
2406 if draw.is_empty() {
2407 return Ok(());
2408 }
2409
2410 for y in draw.y..draw.bottom() {
2411 for x in draw.x..draw.right() {
2412 let dx = if x < rect.x {
2413 rect.x - x
2414 } else if x >= rect.right() {
2415 x - rect.right() + 1
2416 } else {
2417 0
2418 };
2419 let dy = if y < rect.y {
2420 rect.y - y
2421 } else if y >= rect.bottom() {
2422 y - rect.bottom() + 1
2423 } else {
2424 0
2425 };
2426
2427 let dist = ((dx * dx + dy * dy) as f32).sqrt();
2428 if dist > margin as f32 {
2429 continue;
2430 }
2431
2432 let factor = (1.0 - (dist / (margin as f32 + 1.0))).clamp(0.0, 1.0);
2433 let alpha = (shadow_opacity as f32 * factor) as u8;
2434 if alpha > 0 {
2435 self.pixel(x, y, shadow_color, alpha)?;
2436 }
2437 }
2438 }
2439 Ok(())
2440 }
2441
2442 pub fn draw_card_fill(
2444 &mut self,
2445 rect: Rect,
2446 radius: u8,
2447 bg_gradient: &AlphaLinearGradient,
2448 shadow_color: Rgb565,
2449 shadow_opacity: u8,
2450 blur_radius: u8,
2451 ) -> Result<(), D::Error> {
2452 if shadow_opacity > 0 && blur_radius > 0 {
2453 self.draw_drop_shadow(rect, radius, shadow_color, shadow_opacity, 2, blur_radius)?;
2454 }
2455 self.fill_rounded_rect_alpha_gradient(rect, radius, bg_gradient, 255)
2456 }
2457
2458 pub fn draw_tile(
2460 &mut self,
2461 rect: Rect,
2462 tile: TileRef<'_>,
2463 opacity: u8,
2464 ) -> Result<(), D::Error> {
2465 self.draw_tile_transformed_ssaa(rect, tile, Transform2D::IDENTITY, opacity, false)
2466 }
2467
2468 pub fn draw_tile_transformed(
2470 &mut self,
2471 rect: Rect,
2472 tile: TileRef<'_>,
2473 transform: Transform2D,
2474 opacity: u8,
2475 ) -> Result<(), D::Error> {
2476 self.draw_tile_transformed_ssaa(rect, tile, transform, opacity, false)
2477 }
2478
2479 pub fn draw_tile_transformed_ssaa(
2481 &mut self,
2482 rect: Rect,
2483 tile: TileRef<'_>,
2484 transform: Transform2D,
2485 opacity: u8,
2486 enable_ssaa: bool,
2487 ) -> Result<(), D::Error> {
2488 let draw = self.visible_rect(rect);
2489 if draw.is_empty() || opacity == 0 || tile.width == 0 || tile.height == 0 {
2490 return Ok(());
2491 }
2492
2493 let inv_transform = match transform.inverse() {
2494 Some(inv) => inv,
2495 None => return Ok(()),
2496 };
2497
2498 let cx = rect.x as f32 + rect.w as f32 * 0.5;
2499 let cy = rect.y as f32 + rect.h as f32 * 0.5;
2500
2501 let offsets = [
2502 (0.25f32, 0.25f32),
2503 (0.75f32, 0.25f32),
2504 (0.25f32, 0.75f32),
2505 (0.75f32, 0.75f32),
2506 ];
2507
2508 for y in draw.y..draw.bottom() {
2509 for x in draw.x..draw.right() {
2510 if !enable_ssaa {
2511 let px = (x as f32 + 0.5) - cx;
2512 let py = (y as f32 + 0.5) - cy;
2513 let (tx, ty) = inv_transform.apply_f32(px, py);
2514 let u = (tx + tile.width as f32 * 0.5).floor() as i32;
2515 let v = (ty + tile.height as f32 * 0.5).floor() as i32;
2516 if let Some(col) = tile.get_pixel(u, v) {
2517 self.pixel(x, y, col, opacity)?;
2518 }
2519 } else {
2520 let mut r_sum = 0u32;
2521 let mut g_sum = 0u32;
2522 let mut b_sum = 0u32;
2523 let mut weight = 0u32;
2524
2525 for &(ox, oy) in &offsets {
2526 let px = (x as f32 + ox) - cx;
2527 let py = (y as f32 + oy) - cy;
2528 let (tx, ty) = inv_transform.apply_f32(px, py);
2529 let u = (tx + tile.width as f32 * 0.5).floor() as i32;
2530 let v = (ty + tile.height as f32 * 0.5).floor() as i32;
2531 if let Some(col) = tile.get_pixel(u, v) {
2532 r_sum += col.r() as u32;
2533 g_sum += col.g() as u32;
2534 b_sum += col.b() as u32;
2535 weight += 1;
2536 }
2537 }
2538
2539 if let Some(w) = core::num::NonZeroU32::new(weight) {
2540 let weight_val = w.get();
2541 let r_avg = (r_sum / weight_val) as u8;
2542 let g_avg = (g_sum / weight_val) as u8;
2543 let b_avg = (b_sum / weight_val) as u8;
2544 let color = Rgb565::new(r_avg, g_avg, b_avg);
2545 let pix_opacity = ((weight * opacity as u32 + 2) / 4) as u8;
2546 self.pixel(x, y, color, pix_opacity)?;
2547 }
2548 }
2549 }
2550 }
2551 Ok(())
2552 }
2553
2554 pub fn draw_image_transformed_ssaa(
2556 &mut self,
2557 rect: Rect,
2558 image: ImageRef<'_>,
2559 scale: f32,
2560 rotation_deg: f32,
2561 opacity: u8,
2562 enable_ssaa: bool,
2563 ) -> Result<(), D::Error> {
2564 let transform = Transform2D::rotation(rotation_deg).then(Transform2D::scale(scale, scale));
2565 let tile = TileRef::from_image(image, TileMode::None);
2566 self.draw_tile_transformed_ssaa(rect, tile, transform, opacity, enable_ssaa)
2567 }
2568}
2569
2570fn should_draw_at_opacity(x: i32, y: i32, opacity: u8) -> bool {
2571 if opacity == 255 {
2572 return true;
2573 }
2574 if opacity == 0 {
2575 return false;
2576 }
2577 let bayer4 = [
2578 [0u8, 8, 2, 10],
2579 [12, 4, 14, 6],
2580 [3, 11, 1, 9],
2581 [15, 7, 13, 5],
2582 ];
2583 let threshold = ((opacity as u16 * 16) / 255) as u8;
2584 let sample = bayer4[(y as usize) & 3][(x as usize) & 3];
2585 sample < threshold.max(1)
2586}
2587
2588fn lerp_rgb565(a: Rgb565, b: Rgb565, t: u8) -> Rgb565 {
2589 let t = t as u16;
2590 let inv = 255u16.saturating_sub(t);
2591 let r = ((a.r() as u16 * inv) + (b.r() as u16 * t)) / 255;
2592 let g = ((a.g() as u16 * inv) + (b.g() as u16 * t)) / 255;
2593 let bb = ((a.b() as u16 * inv) + (b.b() as u16 * t)) / 255;
2594 Rgb565::new(r as u8, g as u8, bb as u8)
2595}
2596
2597#[inline]
2598fn normalize_angle_deg(mut deg: f32) -> f32 {
2599 while deg < 0.0 {
2600 deg += 360.0;
2601 }
2602 while deg >= 360.0 {
2603 deg -= 360.0;
2604 }
2605 deg
2606}
2607
2608#[inline]
2617fn cardinal_unit(deg: f32) -> Option<(f32, f32)> {
2618 const EPS: f32 = 1e-4;
2619 let normalized = normalize_angle_deg(deg);
2620 if (normalized - 0.0).abs() < EPS {
2621 Some((1.0, 0.0))
2622 } else if (normalized - 90.0).abs() < EPS {
2623 Some((0.0, 1.0))
2624 } else if (normalized - 180.0).abs() < EPS {
2625 Some((-1.0, 0.0))
2626 } else if (normalized - 270.0).abs() < EPS {
2627 Some((0.0, -1.0))
2628 } else {
2629 None
2630 }
2631}
2632
2633#[inline]
2634fn cross(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
2635 ux * vy - uy * vx
2636}
2637
2638fn apply_blend_mode(src: Rgb565, mode: BlendMode, backdrop: Rgb565) -> Rgb565 {
2639 match mode {
2640 BlendMode::Normal => src,
2641 BlendMode::Add => Rgb565::new(
2642 src.r().saturating_add(backdrop.r()),
2643 src.g().saturating_add(backdrop.g()),
2644 src.b().saturating_add(backdrop.b()),
2645 ),
2646 BlendMode::Multiply => Rgb565::new(
2647 ((src.r() as u16 * backdrop.r() as u16) / 31) as u8,
2648 ((src.g() as u16 * backdrop.g() as u16) / 63) as u8,
2649 ((src.b() as u16 * backdrop.b() as u16) / 31) as u8,
2650 ),
2651 BlendMode::Screen => Rgb565::new(
2652 (31 - ((31 - src.r() as u16) * (31 - backdrop.r() as u16) / 31)) as u8,
2653 (63 - ((63 - src.g() as u16) * (63 - backdrop.g() as u16) / 63)) as u8,
2654 (31 - ((31 - src.b() as u16) * (31 - backdrop.b() as u16) / 31)) as u8,
2655 ),
2656 }
2657}
2658
2659fn in_rounded_rect(x: i32, y: i32, rect: Rect, radius: u8) -> bool {
2660 if rect.is_empty() {
2661 return false;
2662 }
2663 let radius = radius as i32;
2664 if radius <= 0 {
2665 return rect.contains(x, y);
2666 }
2667
2668 let left = rect.x;
2669 let top = rect.y;
2670 let right = rect.right() - 1;
2671 let bottom = rect.bottom() - 1;
2672 let inner_left = left + radius;
2673 let inner_right = right - radius;
2674 let inner_top = top + radius;
2675 let inner_bottom = bottom - radius;
2676
2677 if (x >= inner_left && x <= inner_right) || (y >= inner_top && y <= inner_bottom) {
2678 return rect.contains(x, y);
2679 }
2680
2681 let (cx, cy) = if x < inner_left && y < inner_top {
2682 (inner_left, inner_top)
2683 } else if x > inner_right && y < inner_top {
2684 (inner_right, inner_top)
2685 } else if x < inner_left && y > inner_bottom {
2686 (inner_left, inner_bottom)
2687 } else if x > inner_right && y > inner_bottom {
2688 (inner_right, inner_bottom)
2689 } else {
2690 return rect.contains(x, y);
2691 };
2692
2693 let dx = x - cx;
2694 let dy = y - cy;
2695 dx * dx + dy * dy <= radius * radius
2696}
2697
2698fn line_len_at(text: &str, start: usize, max_chars: usize, wrap: TextWrap) -> (usize, bool) {
2699 let mut len = 0;
2700 let limit = match wrap {
2701 TextWrap::None => usize::MAX,
2702 TextWrap::Character => max_chars.max(1),
2703 TextWrap::Word => max_chars.max(1),
2704 };
2705 let mut last_ws_break = None;
2706
2707 for ch in text.chars().skip(start) {
2708 if ch == '\n' {
2709 return (len, true);
2710 }
2711 if matches!(wrap, TextWrap::Word) && ch.is_whitespace() {
2712 last_ws_break = Some(len + 1);
2713 }
2714 if len >= limit {
2715 if matches!(wrap, TextWrap::Word) {
2716 if let Some(idx) = last_ws_break {
2717 return (idx, false);
2718 }
2719 }
2720 return (len, false);
2721 }
2722 len += 1;
2723 }
2724
2725 (len, false)
2726}
2727
2728fn count_lines(text: &str, max_chars: usize, wrap: TextWrap) -> usize {
2729 if text.is_empty() {
2730 return 1;
2731 }
2732 let char_count = text.chars().count();
2733 let mut lines = 0;
2734 let mut start = 0;
2735 while start < char_count {
2736 let (len, consumed_newline) = line_len_at(text, start, max_chars, wrap);
2737 lines += 1;
2738 start += len + usize::from(consumed_newline);
2739 if len == 0 && !consumed_newline {
2740 break;
2741 }
2742 }
2743 lines
2744}
2745
2746fn widest_line(text: &str, max_chars: usize, wrap: TextWrap) -> usize {
2747 let char_count = text.chars().count();
2748 let mut widest = 0;
2749 let mut start = 0;
2750 while start < char_count {
2751 let (len, consumed_newline) = line_len_at(text, start, max_chars, wrap);
2752 widest = widest.max(len);
2753 start += len + usize::from(consumed_newline);
2754 if len == 0 && !consumed_newline {
2755 break;
2756 }
2757 }
2758 widest
2759}
2760
2761fn kerning_adjust(prev: Option<char>, next: char, enabled: bool) -> i32 {
2762 if !enabled {
2763 return 0;
2764 }
2765 match (prev, next) {
2766 (Some('A'), 'V') | (Some('A'), 'W') | (Some('T'), 'o') | (Some('L'), 'T') => -1,
2767 _ => 0,
2768 }
2769}
2770
2771#[cfg(test)]
2772mod tests {
2773 use super::*;
2774
2775 #[test]
2776 fn test_transform2d_is_identity() {
2777 let id = Transform2D::IDENTITY;
2778 assert!(id.is_identity());
2779 assert_eq!(id.apply(10, 20), (10, 20));
2780
2781 let tr = Transform2D::translation(5.0, 10.0);
2782 assert!(!tr.is_identity());
2783 assert_eq!(tr.apply(10, 20), (15, 30));
2784 }
2785
2786 #[test]
2787 fn test_fill_circle_scanline_spans_correctness() {
2788 let mut buf = crate::test_buffer::TestBuffer::new(50, 50);
2789 let mut ctx = RenderCtx::new(&mut buf, Rect::new(0, 0, 50, 50));
2790
2791 ctx.fill_circle(25, 25, 10, Rgb565::RED).unwrap();
2793
2794 assert_eq!(buf.pixel_at(25, 25), Some(Rgb565::RED));
2796
2797 assert_eq!(buf.pixel_at(32, 32), Some(Rgb565::RED));
2799
2800 assert_eq!(buf.pixel_at(37, 25), Some(Rgb565::BLACK));
2802 assert_eq!(buf.pixel_at(25, 37), Some(Rgb565::BLACK));
2803 }
2804
2805 #[test]
2806 fn test_cardinal_unit_exact_values_and_fallback() {
2807 assert_eq!(cardinal_unit(0.0), Some((1.0, 0.0)));
2808 assert_eq!(cardinal_unit(90.0), Some((0.0, 1.0)));
2809 assert_eq!(cardinal_unit(180.0), Some((-1.0, 0.0)));
2810 assert_eq!(cardinal_unit(270.0), Some((0.0, -1.0)));
2811 assert_eq!(cardinal_unit(-90.0), Some((0.0, -1.0)));
2814 assert_eq!(cardinal_unit(45.0), None);
2817 assert_eq!(cardinal_unit(89.99), None);
2818 }
2819
2820 #[test]
2821 fn test_cardinal_unit_agrees_with_real_trig_at_cardinal_angles() {
2822 for deg in [0.0_f32, 90.0, 180.0, 270.0, -90.0, 450.0] {
2828 let (fast_c, fast_s) = cardinal_unit(deg).expect("cardinal angle");
2829 let (real_c, real_s) = (deg.to_radians().cos(), deg.to_radians().sin());
2830 assert!(
2831 (fast_c - real_c).abs() < 1e-6,
2832 "cos mismatch at {deg}: fast={fast_c} real={real_c}"
2833 );
2834 assert!(
2835 (fast_s - real_s).abs() < 1e-6,
2836 "sin mismatch at {deg}: fast={fast_s} real={real_s}"
2837 );
2838 }
2839 }
2840
2841 #[test]
2842 fn test_fill_sector_sweep_cardinal_fast_path_renders() {
2843 let mut buf = crate::test_buffer::TestBuffer::new(50, 50);
2848 let mut ctx = RenderCtx::new(&mut buf, Rect::new(0, 0, 50, 50));
2849 ctx.fill_sector_sweep(25, 25, 20, -90.0, 90.0, Rgb565::RED)
2850 .unwrap();
2851 assert!(buf.count_color(Rgb565::RED) > 0);
2852 assert_eq!(buf.pixel_at(40, 25), Some(Rgb565::RED));
2857 assert_eq!(buf.pixel_at(25, 40), Some(Rgb565::BLACK));
2858 }
2859}