shuyi_ui/frame/onebit/
roles.rs1use core::fmt;
9
10use alloc::collections::BTreeMap;
11
12use shuyi_assets::onebit::{PatternMapDef, dither};
13
14use super::{FRAME_BYTES, Font, Frame, HEIGHT, Images, ROW_BYTES, Shade, Shades, WIDTH};
15use crate::frame::color::Rgb;
16use crate::frame::glyph_buffer::GlyphBuffer;
17use crate::prelude::*;
18
19#[allow(clippy::cast_possible_truncation)] pub const fn luminance(rgb: Rgb) -> u8 {
23 ((77 * rgb.r as u32 + 150 * rgb.g as u32 + 29 * rgb.b as u32) >> 8) as u8
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28struct Role {
29 name: String,
30 fg: Shade,
31 bg: Shade,
32}
33
34#[derive(Clone, Debug, Default, PartialEq, Eq)]
40pub struct PatternMap {
41 roles: BTreeMap<Rgb, Role>,
43}
44
45impl PatternMap {
46 pub fn new<'a>(palette: impl IntoIterator<Item = (&'a str, Rgb)>, map: &PatternMapDef) -> Self {
54 let mut roles = BTreeMap::new();
55 for (name, rgb) in palette {
56 if let Some(patterns) = map.get(name) {
57 roles.entry(rgb).or_insert_with(|| Role {
58 name: name.to_owned(),
59 fg: patterns.fg.into(),
60 bg: patterns.background().into(),
61 });
62 }
63 }
64 Self { roles }
65 }
66
67 pub fn role_of(&self, rgb: Rgb) -> Option<&str> {
69 self.roles.get(&rgb).map(|role| role.name.as_str())
70 }
71
72 fn dithered(rgb: Rgb) -> Shade {
74 Shade::rows(dither(255 - luminance(rgb)))
75 }
76}
77
78impl Shades for PatternMap {
79 fn ground(&self, rgb: Rgb) -> Shade {
81 self.roles
82 .get(&rgb)
83 .map_or_else(|| Self::dithered(rgb), |role| role.bg)
84 }
85
86 fn glyph(&self, rgb: Rgb) -> Shade {
89 self.roles.get(&rgb).map_or_else(
90 || {
91 let solid = if luminance(rgb) >= 128 { u8::MAX } else { 0 };
92 Shade::rows([solid; 8])
93 },
94 |role| role.fg,
95 )
96 }
97
98 fn shape(&self, rgb: Rgb) -> Shade {
100 self.roles
101 .get(&rgb)
102 .map_or_else(|| Self::dithered(rgb), |role| role.fg)
103 }
104}
105
106#[derive(Clone, PartialEq, Eq)]
111pub struct OneBitFrame {
112 bytes: Box<Frame>,
113}
114
115impl OneBitFrame {
116 pub fn white() -> Self {
118 Self {
119 bytes: Box::new([u8::MAX; FRAME_BYTES]),
120 }
121 }
122
123 pub const fn width(&self) -> i32 {
125 WIDTH
126 }
127
128 pub const fn height(&self) -> i32 {
130 HEIGHT
131 }
132
133 pub fn bytes(&self) -> &Frame {
135 &self.bytes
136 }
137
138 pub fn bytes_mut(&mut self) -> &mut Frame {
140 &mut self.bytes
141 }
142
143 pub fn is_white(&self, x: i32, y: i32) -> bool {
145 super::is_white(&self.bytes, x, y)
146 }
147
148 pub fn changed_rows(&self, previous: &Self) -> RowMask {
150 changed_rows(&previous.bytes, &self.bytes)
151 }
152}
153
154impl fmt::Debug for OneBitFrame {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 write!(f, "OneBitFrame({WIDTH} × {HEIGHT})")
157 }
158}
159
160pub fn to_one_bit(
166 frame: &GlyphBuffer,
167 map: &PatternMap,
168 font: &Font<'_>,
169 images: &impl Images,
170) -> OneBitFrame {
171 let mut out = OneBitFrame::white();
172 super::render_with(frame, font, images, map, out.bytes_mut());
173 out
174}
175
176#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
178pub struct RowMask([u32; RowMask::WORDS]);
179
180impl RowMask {
181 const WORDS: usize = (HEIGHT as usize).div_ceil(32);
183
184 #[allow(clippy::cast_sign_loss)] pub fn insert(&mut self, y: i32) {
187 if (0..HEIGHT).contains(&y) {
188 self.0[y as usize / 32] |= 1 << (y % 32);
189 }
190 }
191
192 #[allow(clippy::cast_sign_loss)] pub fn contains(&self, y: i32) -> bool {
195 (0..HEIGHT).contains(&y) && self.0[y as usize / 32] >> (y % 32) & 1 == 1
196 }
197
198 pub fn count(&self) -> u32 {
200 self.0.iter().map(|word| word.count_ones()).sum()
201 }
202
203 pub fn is_empty(&self) -> bool {
205 self.0.iter().all(|&word| word == 0)
206 }
207
208 pub fn rows(&self) -> impl Iterator<Item = i32> + '_ {
210 (0..HEIGHT).filter(|&y| self.contains(y))
211 }
212}
213
214pub fn changed_rows(old: &Frame, new: &Frame) -> RowMask {
216 let mut mask = RowMask::default();
217 let rows = old.as_chunks::<ROW_BYTES>().0.iter().zip(new.as_chunks().0);
218 for (y, (a, b)) in (0..).zip(rows) {
219 if a != b {
220 mask.insert(y);
221 }
222 }
223 mask
224}
225
226#[cfg(test)]
227mod tests;