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