1use core::ops::Range;
41
42use shuyi_assets::bitmap::Bitmap;
43use shuyi_assets::bitmap::dither::brightness;
44use shuyi_assets::font::FALLBACK_GLYPH;
45use shuyi_assets::font::FontAtlasDef;
46use shuyi_assets::image::ImageId;
47use shuyi_assets::ink::Ink;
48pub use shuyi_assets::onebit::{Base, Pattern, PatternMapDef, RolePatterns, Rows};
49
50use crate::frame::color::Rgb;
51use crate::frame::glyph_buffer::{Cell, GlyphBuffer, Item, Layer, Paint, PxRect, Rect, Sprite};
52use crate::frame::painted::{Painted, PaintedKind, PaintedSprite, PaintedTable};
53
54mod roles;
55pub use roles::{OneBitFrame, PatternMap, RowMask, changed_rows, luminance, to_one_bit};
56
57#[cfg(any(test, feature = "onebit-reference"))]
58pub mod reference;
59
60pub const WIDTH: i32 = 400;
62pub const HEIGHT: i32 = 240;
64pub const ROW_BYTES: usize = 52;
67pub const FRAME_BYTES: usize = ROW_BYTES * HEIGHT as usize;
69
70pub type Frame = [u8; FRAME_BYTES];
74
75pub fn is_white(frame: &Frame, x: i32, y: i32) -> bool {
77 if !(0..WIDTH).contains(&x) || !(0..HEIGHT).contains(&y) {
78 return false;
79 }
80 let byte = frame[byte_index(x, y)];
81 byte >> (7 - (x & 7)) & 1 == 1
82}
83
84pub fn ink_of(rgb: Rgb) -> Ink {
88 Ink::from_code([rgb.r, rgb.g, rgb.b]).unwrap_or_else(|| {
89 let luma = 299 * u32::from(rgb.r) + 587 * u32::from(rgb.g) + 114 * u32::from(rgb.b);
90 if luma >= 128 * 1000 {
91 Ink::White
92 } else {
93 Ink::Black
94 }
95 })
96}
97
98pub const fn ink_is_white(ink: Ink, x: i32, y: i32) -> bool {
102 match ink {
103 Ink::White => true,
104 Ink::Black | Ink::BlackEdged | Ink::Invert => false,
105 Ink::Grey => x.rem_euclid(2) != y.rem_euclid(2),
107 Ink::Dots => !(y.rem_euclid(2) == 0 && x.rem_euclid(4) == y.rem_euclid(4)),
109 Ink::Diagonals => (x - y).rem_euclid(4) != 0,
111 Ink::Dashes => x.div_euclid(2).rem_euclid(2) != y.div_euclid(2).rem_euclid(2),
113 }
114}
115
116#[derive(Copy, Clone, Debug, PartialEq, Eq)]
119pub struct Shade {
120 pub rows: Rows,
124 pub edged: bool,
127 pub flips: bool,
130}
131
132impl Shade {
133 pub const fn rows(rows: Rows) -> Self {
135 Self {
136 rows,
137 edged: false,
138 flips: false,
139 }
140 }
141
142 #[allow(clippy::cast_sign_loss)] pub const fn of_ink(ink: Ink) -> Self {
145 let mut rows = [0; 8];
146 let mut y = 0;
147 while y < 8 {
148 let mut x = 0;
149 while x < 8 {
150 if ink_is_white(ink, x, y) {
151 rows[y as usize] |= 0x80 >> x;
152 }
153 x += 1;
154 }
155 y += 1;
156 }
157 Self {
158 rows,
159 edged: matches!(ink, Ink::BlackEdged),
160 flips: matches!(ink, Ink::Invert),
161 }
162 }
163
164 #[allow(clippy::cast_sign_loss)] pub const fn row(self, y: i32) -> u8 {
168 self.rows[(y & 7) as usize]
169 }
170
171 pub const fn is_white(self, x: i32, y: i32) -> bool {
173 self.row(y) >> (7 - (x & 7)) & 1 == 1
174 }
175}
176
177impl From<Pattern> for Shade {
178 fn from(pattern: Pattern) -> Self {
179 Self::rows(pattern.rows())
180 }
181}
182
183pub trait Shades {
185 fn ground(&self, rgb: Rgb) -> Shade;
187 fn glyph(&self, rgb: Rgb) -> Shade;
189 fn shape(&self, rgb: Rgb) -> Shade;
191}
192
193#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
196pub struct InkShades;
197
198const INK_SHADES: [Shade; Ink::ALL.len()] = {
200 let mut table = [Shade::rows([0; 8]); Ink::ALL.len()];
201 let mut i = 0;
202 while i < Ink::ALL.len() {
203 table[Ink::ALL[i] as usize] = Shade::of_ink(Ink::ALL[i]);
204 i += 1;
205 }
206 table
207};
208
209impl InkShades {
210 const fn of(ink: Ink) -> Shade {
212 INK_SHADES[ink as usize]
213 }
214}
215
216impl Shades for InkShades {
217 fn ground(&self, rgb: Rgb) -> Shade {
218 Self::of(ink_of(rgb))
219 }
220
221 fn glyph(&self, rgb: Rgb) -> Shade {
222 Self::of(ink_of(rgb))
223 }
224
225 fn shape(&self, rgb: Rgb) -> Shade {
226 Self::of(ink_of(rgb))
227 }
228}
229
230#[allow(clippy::cast_sign_loss)] const fn byte_index(x: i32, y: i32) -> usize {
234 y as usize * ROW_BYTES + (x >> 3) as usize
235}
236
237const NO_ROWS: u32 = u32::MAX;
239
240#[derive(Copy, Clone, Debug)]
243pub struct Font<'a> {
244 atlas: &'a FontAtlasDef,
245 bits: &'a [u8],
246 ascii: [u32; 128],
250}
251
252impl<'a> Font<'a> {
253 pub fn new(atlas: &'a FontAtlasDef, bits: &'a [u8]) -> Result<Self, &'static str> {
256 if atlas.cell_w > 8 {
257 return Err("the one-bit font has cells at most 8 pixels wide");
259 }
260 let rows = atlas.cell_h as usize;
261 if atlas.cell_end() as usize * rows > bits.len() {
262 return Err("the one-bit atlas is shorter than its glyph map");
264 }
265 let mut ascii = [NO_ROWS; 128];
266 for (code, start) in (0u8..).zip(&mut ascii) {
267 let found = rows_start(atlas, char::from(code));
268 *start = found
269 .and_then(|at| u32::try_from(at).ok())
270 .unwrap_or(NO_ROWS);
271 }
272 Ok(Self { atlas, bits, ascii })
273 }
274
275 pub fn rows(&self, glyph: char) -> Option<&'a [u8]> {
278 let start = match self.ascii.get(glyph as usize) {
279 Some(&NO_ROWS) => return None,
280 Some(&start) => start as usize,
281 None => rows_start(self.atlas, glyph)?,
282 };
283 self.bits.get(start..start + self.atlas.cell_h as usize)
284 }
285
286 fn wide_rows(&self, glyph: char, half: usize, set: Option<&str>) -> Option<&'a [u8]> {
290 let rows = self.atlas.cell_h as usize;
291 let start = (self.atlas.wide_cell(set, glyph)? as usize + half) * rows;
292 self.bits.get(start..start + rows)
293 }
294
295 pub fn cell_rows(&self, buf: &GlyphBuffer, x: i32, y: i32) -> Option<&'a [u8]> {
301 let cell = buf.get(x, y)?;
302 if cell.is_continuation() {
303 let first = buf.get(x.checked_sub(1)?, y)?;
304 return self.wide_rows(first.glyph, 1, buf.widths().wide_font());
305 }
306 if cell.glyph == ' ' {
307 return None;
308 }
309 let wide = buf.get(x + 1, y).is_some_and(Cell::is_continuation);
310 match self
311 .wide_rows(cell.glyph, 0, buf.widths().wide_font())
312 .filter(|_| wide)
313 {
314 Some(rows) => Some(rows),
315 None => self.rows(cell.glyph),
316 }
317 }
318
319 fn height(&self, scale: i32) -> i32 {
321 i32::try_from(self.atlas.cell_h).map_or(i32::MAX, |h| h.saturating_mul(scale))
322 }
323}
324
325fn rows_start(atlas: &FontAtlasDef, glyph: char) -> Option<usize> {
328 let index = atlas
329 .glyphs
330 .get(&glyph)
331 .or_else(|| atlas.glyphs.get(&FALLBACK_GLYPH))?;
332 Some(*index as usize * atlas.cell_h as usize)
333}
334
335#[derive(Copy, Clone, Debug, PartialEq, Eq)]
346pub struct Image<'a> {
347 width: u16,
348 height: u16,
349 source_width: u16,
350 source_height: u16,
351 data: &'a [u8],
352 mask: Option<&'a [u8]>,
353}
354
355impl<'a> Image<'a> {
356 pub fn new(width: u16, height: u16, data: &'a [u8], mask: Option<&'a [u8]>) -> Option<Self> {
359 let size = Self::stride(width) * usize::from(height);
360 let fits = |bytes: &[u8]| bytes.len() >= size;
361 (fits(data) && mask.is_none_or(fits)).then_some(Self {
362 width,
363 height,
364 source_width: width,
365 source_height: height,
366 data,
367 mask,
368 })
369 }
370
371 pub fn from_bitmap(bitmap: &Bitmap<'a>, style: usize) -> Option<Self> {
374 let data = bitmap.style(style)?;
375 let image = Self::new(bitmap.width(), bitmap.height(), data, bitmap.mask())?;
376 let (width, height) = bitmap.source_size();
377 Some(image.with_source(width, height))
378 }
379
380 #[must_use]
383 pub fn with_source(self, width: u16, height: u16) -> Self {
384 Self {
385 source_width: if width == 0 { self.source_width } else { width },
386 source_height: if height == 0 {
387 self.source_height
388 } else {
389 height
390 },
391 ..self
392 }
393 }
394
395 pub const fn stride(width: u16) -> usize {
397 (width as usize).div_ceil(8)
398 }
399
400 #[cfg(any(test, feature = "onebit-reference"))]
403 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn pixel(&self, x: i64, y: i64) -> Option<bool> {
405 if !(0..i64::from(self.width)).contains(&x) || !(0..i64::from(self.height)).contains(&y) {
406 return None;
407 }
408 let at = y as usize * Self::stride(self.width) + (x >> 3) as usize;
409 let bit = |bytes: &[u8]| bytes[at] >> (7 - (x & 7)) & 1 == 1;
410 match self.mask {
411 Some(mask) if !bit(mask) => None,
412 _ => Some(bit(self.data)),
413 }
414 }
415
416 fn sample(d: i64, start: i64, len: i64, dest: i64, size: u16, source: u16) -> i64 {
422 let (size, source) = (i64::from(size), i64::from(source.max(1)));
423 (start * 2 * dest + (2 * d + 1) * len) * size / (2 * dest * source)
424 }
425}
426
427pub trait Images {
429 fn image(&self, id: ImageId) -> Option<Image<'_>>;
432}
433
434impl<T: Images + ?Sized> Images for &T {
437 fn image(&self, id: ImageId) -> Option<Image<'_>> {
438 (**self).image(id)
439 }
440}
441
442impl Images for () {
444 fn image(&self, _: ImageId) -> Option<Image<'_>> {
445 None
446 }
447}
448
449impl Images for shuyi_assets::Bundle {
453 fn image(&self, id: ImageId) -> Option<Image<'_>> {
454 let bitmap = Bitmap::parse(self.bytes(id.path())?).ok()?;
455 Image::from_bitmap(&bitmap, 0)
456 }
457}
458
459#[derive(Copy, Clone, Debug, PartialEq, Eq)]
462pub enum Phase {
463 Ground,
465 Backdrop,
467 Backgrounds,
469 Under,
471 Edges,
473 Glyphs,
475 Over,
477 Invert,
479}
480
481impl Phase {
482 pub const ALL: [Self; 8] = [
484 Self::Ground,
485 Self::Backdrop,
486 Self::Backgrounds,
487 Self::Under,
488 Self::Edges,
489 Self::Glyphs,
490 Self::Over,
491 Self::Invert,
492 ];
493
494 pub const fn name(self) -> &'static str {
497 match self {
498 Self::Ground => "ground",
499 Self::Backdrop => "backdrop",
500 Self::Backgrounds => "backgrounds",
501 Self::Under => "under items",
502 Self::Edges => "edges",
503 Self::Glyphs => "glyphs",
504 Self::Over => "over items",
505 Self::Invert => "invert",
506 }
507 }
508}
509
510pub fn render(buf: &GlyphBuffer, font: &Font<'_>, images: &impl Images, out: &mut Frame) {
514 render_with(buf, font, images, &InkShades, out);
515}
516
517pub fn render_marked(
520 buf: &GlyphBuffer,
521 font: &Font<'_>,
522 images: &impl Images,
523 out: &mut Frame,
524 mark: &mut impl FnMut(Phase),
525) {
526 render_marked_with(buf, font, images, &InkShades, out, mark);
527}
528
529pub fn render_with(
531 buf: &GlyphBuffer,
532 font: &Font<'_>,
533 images: &impl Images,
534 shades: &impl Shades,
535 out: &mut Frame,
536) {
537 render_marked_with(buf, font, images, shades, out, &mut |_| {});
538}
539
540pub fn render_marked_with(
542 buf: &GlyphBuffer,
543 font: &Font<'_>,
544 images: &impl Images,
545 shades: &impl Shades,
546 out: &mut Frame,
547 mark: &mut impl FnMut(Phase),
548) {
549 out.fill(u8::MAX);
551 mark(Phase::Ground);
552 let look = Look {
553 font,
554 images,
555 shades,
556 pictures: buf.pictures(),
557 };
558 draw(buf, &look, out, Clip::SCREEN, mark);
559}
560
561pub fn render_changed(
568 old: &GlyphBuffer,
569 new: &GlyphBuffer,
570 font: &Font<'_>,
571 images: &impl Images,
572 out: &mut Frame,
573) {
574 render_changed_with(old, new, font, images, &InkShades, out);
575}
576
577pub fn render_changed_with(
580 old: &GlyphBuffer,
581 new: &GlyphBuffer,
582 font: &Font<'_>,
583 images: &impl Images,
584 shades: &impl Shades,
585 out: &mut Frame,
586) {
587 if old.spec() != new.spec() {
588 render_with(new, font, images, shades, out);
589 return;
590 }
591 let mut dirty = Tiles::default();
592 let place = Place::ORIGIN;
593 let reach = Reach::of(new, font, 1);
594 let changed = |x: i32, y: i32| old.get(x, y) != new.get(x, y);
595 let continues = |x: i32, y: i32| new.get(x, y).is_some_and(Cell::is_continuation);
600 for y in 0..i32::from(new.height()) {
601 for x in 0..i32::from(new.width()) {
602 if changed(x, y) || (continues(x, y) && changed(x - 1, y)) {
603 dirty.add(reach.around(place, x, y));
604 }
605 }
606 }
607 let (before, after) = (old.items(), new.items());
608 let repainted = |items: &[Item]| {
610 items.iter().any(|item| match item {
611 Item::Painted(p) => old.pictures().get(p.picture) != new.pictures().get(p.picture),
612 _ => false,
613 })
614 };
615 for i in 0..before.len().max(after.len()) {
616 let (was, is) = (before.get(i), after.get(i));
617 if was != is || is.is_some_and(|item| repainted(core::slice::from_ref(item))) {
618 for item in was.into_iter().chain(is) {
619 dirty.add(place.screen(item.visible()));
620 }
621 }
622 }
623 let scene_repainted = new
624 .backdrop()
625 .is_some_and(|backdrop| repainted(backdrop.scene().items()));
626 if old.backdrop() != new.backdrop() || scene_repainted {
627 for backdrop in old.backdrop().into_iter().chain(new.backdrop()) {
628 dirty.add(place.screen(backdrop.clip_px()));
629 }
630 }
631 let look = Look {
632 font,
633 images,
634 shades,
635 pictures: new.pictures(),
636 };
637 for region in dirty.regions() {
638 Target {
639 frame: &mut *out,
640 clip: region,
641 }
642 .fill(region, Pen::WHITE);
643 draw(new, &look, out, region, &mut |_| {});
644 }
645}
646
647struct Look<'a, I, S> {
651 font: &'a Font<'a>,
652 images: &'a I,
653 shades: &'a S,
654 pictures: &'a PaintedTable,
655}
656
657#[derive(Default)]
660struct Tiles([u64; Tiles::ROWS]);
661
662impl Tiles {
663 const W: i32 = 8;
664 const H: i32 = 16;
665 const ROWS: usize = (HEIGHT / Self::H) as usize;
666
667 #[allow(clippy::cast_sign_loss)] fn add(&mut self, rect: Clip) {
670 let r = rect.within(Clip::SCREEN);
671 if r.is_empty() {
672 return;
673 }
674 let (first, last) = (r.x0 / Self::W, (r.x1 - 1) / Self::W);
675 let columns = (u64::MAX >> (63 - last)) & (u64::MAX << first);
676 for row in &mut self.0[(r.y0 / Self::H) as usize..=((r.y1 - 1) / Self::H) as usize] {
677 *row |= columns;
678 }
679 }
680
681 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] fn regions(&self) -> impl Iterator<Item = Clip> + '_ {
685 (0i32..).zip(self.0).flat_map(|(row, bits)| {
686 let starts = bits & !(bits << 1);
688 (0..u64::BITS)
689 .filter(move |&column| starts >> column & 1 == 1)
690 .map(move |first| {
691 let len = (bits >> first).trailing_ones();
692 Clip {
693 x0: first as i32 * Self::W,
694 y0: row * Self::H,
695 x1: (first + len) as i32 * Self::W,
696 y1: (row + 1) * Self::H,
697 }
698 })
699 })
700 }
701}
702
703fn draw<I: Images, S: Shades>(
706 buf: &GlyphBuffer,
707 look: &Look<'_, I, S>,
708 out: &mut Frame,
709 region: Clip,
710 mark: &mut impl FnMut(Phase),
711) {
712 let place = Place::ORIGIN;
713 if let Some(backdrop) = buf.backdrop() {
714 let window = place.screen(backdrop.clip_px());
715 let clip = region.within(window);
716 if !clip.is_empty() {
717 let (dx, dy) = backdrop.scene_offset(1);
718 let mut scene = Target {
719 frame: &mut *out,
720 clip,
721 };
722 let at = Place {
723 x: window.x0 + offset(dx),
724 y: window.y0 + offset(dy),
725 scale: i32::from(backdrop.zoom()),
726 };
727 draw_layers(&mut scene, backdrop.scene(), at, look, &mut |_| {});
728 }
729 }
730 mark(Phase::Backdrop);
731 let mut target = Target {
732 frame: out,
733 clip: region,
734 };
735 draw_layers(&mut target, buf, place, look, mark);
736}
737
738#[allow(clippy::cast_possible_truncation)] fn offset(d: i64) -> i32 {
742 d.clamp(-(1 << 30), 1 << 30) as i32
743}
744
745#[derive(Copy, Clone, Debug, PartialEq, Eq)]
747struct Clip {
748 x0: i32,
749 y0: i32,
750 x1: i32,
751 y1: i32,
752}
753
754impl Clip {
755 const SCREEN: Self = Self {
757 x0: 0,
758 y0: 0,
759 x1: WIDTH,
760 y1: HEIGHT,
761 };
762
763 fn within(self, other: Self) -> Self {
765 Self {
766 x0: self.x0.max(other.x0),
767 y0: self.y0.max(other.y0),
768 x1: self.x1.min(other.x1),
769 y1: self.y1.min(other.y1),
770 }
771 }
772
773 const fn is_empty(self) -> bool {
775 self.x0 >= self.x1 || self.y0 >= self.y1
776 }
777}
778
779#[derive(Copy, Clone, Debug)]
782struct Place {
783 x: i32,
784 y: i32,
785 scale: i32,
786}
787
788impl Place {
789 const ORIGIN: Self = Self {
791 x: 0,
792 y: 0,
793 scale: 1,
794 };
795
796 fn screen(self, r: PxRect) -> Clip {
798 let x0 = self.x + r.x * self.scale;
799 let y0 = self.y + r.y * self.scale;
800 Clip {
801 x0,
802 y0,
803 x1: x0 + r.w * self.scale,
804 y1: y0 + r.h * self.scale,
805 }
806 }
807}
808
809#[derive(Copy, Clone)]
813struct Reach {
814 cell_w: i32,
816 cell_h: i32,
817 right: i32,
819 below: i32,
820}
821
822impl Reach {
823 fn of(buf: &GlyphBuffer, font: &Font<'_>, scale: i32) -> Self {
824 let spec = buf.spec();
825 let (cell_w, cell_h) = (
826 i32::from(spec.cell_w) * scale,
827 i32::from(spec.cell_h) * scale,
828 );
829 Self {
830 cell_w,
831 cell_h,
832 right: cell_w.max(8 * scale) + 1,
833 below: cell_h.max(font.height(scale)).saturating_add(1),
834 }
835 }
836
837 fn around(self, at: Place, x: i32, y: i32) -> Clip {
839 let (x0, y0) = (at.x + x * self.cell_w, at.y + y * self.cell_h);
840 Clip {
841 x0: x0 - 1,
842 y0: y0 - 1,
843 x1: x0.saturating_add(self.right),
844 y1: y0.saturating_add(self.below),
845 }
846 }
847
848 fn span(lo: i32, hi: i32, origin: i32, size: i32, reach: i32, count: u16) -> Range<i32> {
852 if size <= 0 {
853 return 0..0;
854 }
855 let first = (lo - origin).saturating_sub(reach).div_euclid(size);
856 let end = (hi - origin).saturating_add(size).div_euclid(size);
857 first.max(0)..end.min(i32::from(count))
858 }
859}
860
861struct Target<'f> {
863 frame: &'f mut Frame,
864 clip: Clip,
865}
866
867#[derive(Copy, Clone)]
869enum Pen {
870 Paint(Shade),
872 Flip,
874}
875
876impl Pen {
877 const WHITE: Self = Self::ink(Ink::White);
879
880 const fn ink(ink: Ink) -> Self {
882 Self::Paint(InkShades::of(ink))
883 }
884
885 const fn apply(self, byte: u8, mask: u8, y: i32) -> u8 {
888 match self {
889 Pen::Paint(shade) => byte ^ ((byte ^ shade.row(y)) & mask),
891 Pen::Flip => byte ^ mask,
892 }
893 }
894}
895
896impl Target<'_> {
897 #[allow(clippy::cast_possible_truncation)] fn whole_byte(&self, x: i32, bits: u64) -> Option<u8> {
902 let one_byte = x.trailing_zeros() >= 3 && bits << 8 == 0;
903 let inside = x >= self.clip.x0 && x + 8 <= self.clip.x1;
904 (one_byte && inside).then_some((bits >> 56) as u8)
905 }
906
907 #[allow(
910 clippy::cast_possible_truncation,
911 clippy::cast_sign_loss,
912 clippy::cast_possible_wrap
913 )] fn run(&mut self, y: i32, x: i32, bits: u64, pen: Pen) {
915 let clip = self.clip;
916 if bits == 0 || y < clip.y0 || y >= clip.y1 {
917 return;
918 }
919 if let Some(mask) = self.whole_byte(x, bits) {
920 let byte = &mut self.frame[byte_index(x, y)];
921 *byte = pen.apply(*byte, mask, y);
922 return;
923 }
924 let (lo, hi) = (x.max(clip.x0), x.saturating_add(64).min(clip.x1));
925 if lo >= hi {
926 return;
927 }
928 let left = u64::MAX >> (lo - x);
930 let right = u64::MAX.checked_shr((hi - x) as u32).unwrap_or(0);
931 let bits = bits & left & !right;
932 let row = y as usize * ROW_BYTES;
933 for column in (lo >> 3)..=((hi - 1) >> 3) {
934 let shift = column * 8 - x;
937 let mask = if shift >= 0 {
938 (bits << shift >> 56) as u8
939 } else {
940 (bits >> 56 >> -shift) as u8
941 };
942 let byte = &mut self.frame[row + column as usize];
943 *byte = pen.apply(*byte, mask, y);
944 }
945 }
946
947 #[allow(clippy::cast_sign_loss)] fn fill(&mut self, rect: Clip, pen: Pen) {
951 let r = rect.within(self.clip);
952 if r.is_empty() {
953 return;
954 }
955 let (first, last) = ((r.x0 >> 3) as usize, ((r.x1 - 1) >> 3) as usize);
956 let left = u8::MAX >> (r.x0 & 7);
957 let right = u8::MAX << (7 - ((r.x1 - 1) & 7));
958 let rows = &mut self.frame[r.y0 as usize * ROW_BYTES..r.y1 as usize * ROW_BYTES];
959 for (y, row) in (r.y0..).zip(rows.as_chunks_mut::<ROW_BYTES>().0) {
960 if first == last {
961 row[first] = pen.apply(row[first], left & right, y);
962 continue;
963 }
964 row[first] = pen.apply(row[first], left, y);
965 let whole = &mut row[first + 1..last];
966 match pen {
967 Pen::Paint(shade) => whole.fill(shade.row(y)),
968 Pen::Flip => whole.iter_mut().for_each(|byte| *byte = !*byte),
969 }
970 row[last] = pen.apply(row[last], right, y);
971 }
972 }
973}
974
975fn draw_layers<I: Images, S: Shades>(
980 target: &mut Target<'_>,
981 buf: &GlyphBuffer,
982 at: Place,
983 look: &Look<'_, I, S>,
984 mark: &mut impl FnMut(Phase),
985) {
986 let (font, shades) = (look.font, look.shades);
987 let reach = Reach::of(buf, font, at.scale);
988 let clip = target.clip;
989 let columns = Reach::span(
990 clip.x0,
991 clip.x1,
992 at.x,
993 reach.cell_w,
994 reach.right,
995 buf.width(),
996 );
997 let rows = Reach::span(
998 clip.y0,
999 clip.y1,
1000 at.y,
1001 reach.cell_h,
1002 reach.below,
1003 buf.height(),
1004 );
1005 let span = |x0: i32, x1: i32, y: i32| Clip {
1007 x0: at.x + x0 * reach.cell_w,
1008 y0: at.y + y * reach.cell_h,
1009 x1: at.x + x1 * reach.cell_w,
1010 y1: at.y + (y + 1) * reach.cell_h,
1011 };
1012 let (columns, rows) = (&columns, &rows);
1013 let cells = || {
1016 rows.clone().flat_map(move |y| {
1017 columns.clone().filter_map(move |x| {
1018 let top_left = (at.x + x * reach.cell_w, at.y + y * reach.cell_h);
1019 let cell = buf.get(x, y).filter(|c| c.glyph != ' ')?;
1020 Some((top_left, (x, y), cell))
1021 })
1022 })
1023 };
1024 let mut grounds = Remembered::new(|rgb| shades.ground(rgb));
1025 for y in rows.clone() {
1027 let mut from = (columns.start, None);
1028 for x in columns.start..=columns.end {
1029 let cell = (x < columns.end).then(|| buf.get(x, y)).flatten();
1030 let shade = cell.filter(|c| !c.see_through).map(|c| grounds.of(c.bg));
1031 if shade != from.1 {
1032 if let Some(shade) = from.1 {
1033 target.fill(span(from.0, x, y), Pen::Paint(shade));
1034 }
1035 from = (x, shade);
1036 }
1037 }
1038 }
1039 mark(Phase::Backgrounds);
1040 draw_items(target, buf, Layer::Under, at, look);
1041 mark(Phase::Under);
1042 let mut glyphs = Remembered::new(|rgb| shades.glyph(rgb));
1043 for (top_left, (x, y), cell) in cells() {
1044 if glyphs.of(cell.fg).edged
1045 && let Some(rows) = font.cell_rows(buf, x, y)
1046 {
1047 draw_glyph(target, rows, top_left, at.scale, Glyph::Edge);
1048 }
1049 }
1050 mark(Phase::Edges);
1051 for (top_left, (x, y), cell) in cells() {
1052 if let Some(rows) = font.cell_rows(buf, x, y) {
1053 let shade = Glyph::Paint(glyphs.of(cell.fg));
1054 draw_glyph(target, rows, top_left, at.scale, shade);
1055 }
1056 }
1057 mark(Phase::Glyphs);
1058 draw_items(target, buf, Layer::Over, at, look);
1059 mark(Phase::Over);
1060 for item in buf.items() {
1061 if let Item::Rect(o) = item
1062 && shades.shape(o.color).flips
1063 {
1064 target.fill(at.screen(o.rect), Pen::Flip);
1065 }
1066 }
1067 mark(Phase::Invert);
1068}
1069
1070struct Remembered<F> {
1073 last: Option<(Rgb, Shade)>,
1074 shade: F,
1075}
1076
1077impl<F: Fn(Rgb) -> Shade> Remembered<F> {
1078 const fn new(shade: F) -> Self {
1079 Self { last: None, shade }
1080 }
1081
1082 fn of(&mut self, rgb: Rgb) -> Shade {
1084 match self.last {
1085 Some((last, shade)) if last == rgb => shade,
1086 _ => {
1087 let shade = (self.shade)(rgb);
1088 self.last = Some((rgb, shade));
1089 shade
1090 }
1091 }
1092 }
1093}
1094
1095fn draw_items<I: Images, S: Shades>(
1098 target: &mut Target<'_>,
1099 buf: &GlyphBuffer,
1100 layer: Layer,
1101 at: Place,
1102 look: &Look<'_, I, S>,
1103) {
1104 for item in buf.items().iter().filter(|item| item.layer() == layer) {
1105 match item {
1106 Item::Rect(o) => {
1107 let shade = look.shades.shape(o.color);
1108 if !shade.flips {
1109 target.fill(at.screen(o.rect), Pen::Paint(shade));
1110 }
1111 }
1112 Item::Sprite(s) => draw_sprite(target, s, at, look),
1113 Item::Painted(p) => draw_painted(target, p, at, look),
1114 Item::Gizmo(g) => {
1115 let pen = Pen::Paint(look.shades.shape(g.color));
1116 g.for_each_run(|y, x0, x1| {
1117 target.fill(at.screen(Rect::new(x0, y, x1 - x0, 1)), pen);
1118 });
1119 }
1120 }
1121 }
1122}
1123
1124#[derive(Copy, Clone)]
1126enum Glyph {
1127 Paint(Shade),
1129 Edge,
1131}
1132
1133#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] fn draw_glyph(
1138 target: &mut Target<'_>,
1139 rows: &[u8],
1140 (left, top): (i32, i32),
1141 scale: i32,
1142 part: Glyph,
1143) {
1144 match part {
1145 Glyph::Paint(shade) => {
1146 let mut y = top;
1147 for &byte in rows {
1148 let bits = widen(byte, scale);
1149 for _ in 0..scale {
1150 target.run(y, left, bits, Pen::Paint(shade));
1151 y += 1;
1152 }
1153 }
1154 }
1155 Glyph::Edge => {
1156 let height = rows.len() as i32 * scale;
1157 let row = |dy: i32| {
1159 if (0..height).contains(&dy) {
1160 widen(rows[(dy / scale) as usize], scale)
1161 } else {
1162 0
1163 }
1164 };
1165 for dy in -1..=height {
1166 let near = (row(dy - 1) | row(dy) | row(dy + 1)) >> 1;
1168 let edge = near | near << 1 | near >> 1;
1169 target.run(top + dy, left - 1, edge, Pen::WHITE);
1170 }
1171 }
1172 }
1173}
1174
1175#[allow(clippy::cast_sign_loss)] fn widen(byte: u8, scale: i32) -> u64 {
1179 if scale == 1 {
1180 return u64::from(byte) << 56;
1181 }
1182 let scale = scale as u32;
1183 let block = u64::MAX >> (64 - scale);
1184 let mut out = 0u64;
1185 for i in 0..8 {
1186 if byte >> (7 - i) & 1 == 1 {
1187 out |= block << (64 - scale * (i + 1));
1188 }
1189 }
1190 out
1191}
1192
1193const BAYER: [[u32; 4]; 4] = [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
1196
1197#[allow(clippy::cast_sign_loss)] fn shows(opacity: u8, x: i32, y: i32) -> bool {
1201 let rank = BAYER[(y & 3) as usize][(x & 3) as usize];
1202 u32::from(opacity) * 16 > rank * 255 + 127
1203}
1204
1205fn shows_byte(opacity: u8, y: i32) -> u8 {
1208 (0..8)
1209 .filter(|&x| shows(opacity, x, y))
1210 .fold(0, |byte, x| byte + (0x80 >> x))
1211}
1212
1213#[allow(clippy::cast_possible_truncation)] fn ones(n: i32) -> u8 {
1216 (0xff00u16 >> n.clamp(0, 8)) as u8
1217}
1218
1219fn on_picture(u: i32, width: u16) -> u8 {
1223 ones(i32::from(width) - u)
1224}
1225
1226#[allow(clippy::cast_possible_truncation)] fn byte_at(row: &[u8], u: i32) -> u8 {
1230 let get = |i: i32| {
1231 usize::try_from(i)
1232 .ok()
1233 .and_then(|i| row.get(i))
1234 .map_or(0, |&byte| byte)
1235 };
1236 let pair = u16::from_be_bytes([get(u >> 3), get((u >> 3) + 1)]);
1237 (pair << (u & 7) >> 8) as u8
1238}
1239
1240pub(crate) const fn alpha_drawn(alpha: u8) -> bool {
1243 alpha >= 128
1244}
1245
1246pub(crate) fn darken(light: u8, brightness: u8) -> u8 {
1249 let product = (u32::from(light) * u32::from(brightness) + 127) / 255;
1250 u8::try_from(product).unwrap_or(u8::MAX)
1251}
1252
1253#[allow(clippy::cast_sign_loss)] pub fn dither_row(light: [u8; 8], y: i32) -> u8 {
1262 let order = &shuyi_assets::onebit::BAYER[(y & 7) as usize];
1263 let mut byte = 0;
1264 for (i, (&level, &rank)) in light.iter().zip(order).enumerate() {
1265 if u16::from(rank) * 4 + 2 >= u16::from(u8::MAX - level) {
1266 byte |= 0x80 >> i;
1267 }
1268 }
1269 byte
1270}
1271
1272#[derive(Copy, Clone)]
1275struct Placed {
1276 src: PxRect,
1277 dest: PxRect,
1278 flip_x: bool,
1279 opacity: u8,
1280 brightness: u8,
1281 paint: Paint,
1282}
1283
1284impl Placed {
1285 const fn of_sprite(s: &Sprite) -> Self {
1286 Self {
1287 src: s.src,
1288 dest: s.dest,
1289 flip_x: s.flip_x,
1290 opacity: s.opacity,
1291 brightness: s.brightness,
1292 paint: s.paint,
1293 }
1294 }
1295
1296 const fn of_painted(p: &PaintedSprite) -> Self {
1297 Self {
1298 src: p.src,
1299 dest: p.dest,
1300 flip_x: false,
1301 opacity: u8::MAX,
1302 brightness: p.brightness,
1303 paint: Paint::Image,
1304 }
1305 }
1306}
1307
1308#[derive(Copy, Clone)]
1311struct PictureRow<'a> {
1312 data: &'a [u8],
1313 mask: Option<&'a [u8]>,
1314 width: u16,
1315}
1316
1317#[allow(clippy::large_enum_variant)]
1320enum Columns {
1321 Run { k: i32, flipped: bool },
1324 Table {
1327 x0: i32,
1328 table: [u16; WIDTH as usize],
1329 },
1330}
1331
1332impl Columns {
1333 fn of_sprite(
1338 sprite: &Placed,
1339 (width, source_width): (u16, u16),
1340 at: Place,
1341 shown: Clip,
1342 ) -> Option<Self> {
1343 let (src, dest) = (sprite.src, sprite.dest);
1344 let (src_w, dest_w) = (i64::from(src.w), i64::from(dest.w));
1345 let column = |x: i32| {
1346 let mut dx = i64::from((x - at.x).div_euclid(at.scale)) - i64::from(dest.x);
1347 if sprite.flip_x {
1348 dx = dest_w - 1 - dx;
1349 }
1350 Image::sample(dx, src.x.into(), src_w, dest_w, width, source_width)
1351 };
1352 let steady = at.scale == 1 && src.x >= 0;
1355 Self::of(shown.x0..shown.x1, steady, sprite.flip_x, width, column)
1356 }
1357
1358 #[allow(clippy::cast_sign_loss)] fn column(&self, x: i32, width: u16) -> Option<u16> {
1362 let u = match *self {
1363 Self::Run { k, flipped } => {
1364 let u = if flipped { k - x } else { k + x };
1365 u16::try_from(u).ok()?
1366 }
1367 Self::Table { x0, ref table } => table[(x - x0) as usize],
1368 };
1369 (u < width).then_some(u)
1370 }
1371
1372 #[allow(clippy::cast_sign_loss)] fn of(
1388 shown: Range<i32>,
1389 steady: bool,
1390 flipped: bool,
1391 width: u16,
1392 column: impl Fn(i32) -> i64,
1393 ) -> Option<Self> {
1394 let (first, last) = (column(shown.start), column(shown.end - 1));
1395 let span = if flipped { first - last } else { last - first };
1396 if steady && span == i64::from(shown.end - shown.start - 1) {
1397 if first.max(last) < 0 || first.min(last) >= i64::from(width) {
1398 return None;
1399 }
1400 let first = i32::try_from(first).ok()?;
1402 let k = if flipped {
1403 first + shown.start
1404 } else {
1405 first - shown.start
1406 };
1407 return Some(Self::Run { k, flipped });
1408 }
1409 let mut table = [u16::MAX; WIDTH as usize];
1410 for (x, u) in shown.clone().zip(&mut table) {
1411 *u = u16::try_from(column(x)).unwrap_or(u16::MAX);
1412 }
1413 Some(Self::Table {
1414 x0: shown.start,
1415 table,
1416 })
1417 }
1418
1419 #[allow(clippy::cast_sign_loss)] fn bits(&self, row: PictureRow<'_>, x: i32, lanes: u8) -> (u8, u8) {
1424 let PictureRow { data, mask, width } = row;
1425 match *self {
1426 Self::Run { k, flipped } => {
1427 let u = if flipped { k - x - 7 } else { k + x };
1429 let white = byte_at(data, u);
1430 let drawn = on_picture(u, width) & mask.map_or(u8::MAX, |mask| byte_at(mask, u));
1431 if flipped {
1432 (white.reverse_bits(), drawn.reverse_bits())
1433 } else {
1434 (white, drawn)
1435 }
1436 }
1437 Self::Table { x0, ref table } => {
1438 let (mut white, mut drawn) = (0, 0);
1439 for i in 0..8 {
1440 let bit = 0x80 >> i;
1441 if lanes & bit == 0 {
1442 continue;
1443 }
1444 let u = table[(x + i - x0) as usize];
1445 if u >= width {
1446 continue;
1447 }
1448 let (at, shift) = (usize::from(u >> 3), 7 - (u & 7));
1449 if mask.is_none_or(|mask| mask[at] >> shift & 1 == 1) {
1450 drawn |= bit;
1451 }
1452 if data[at] >> shift & 1 == 1 {
1453 white |= bit;
1454 }
1455 }
1456 (white, drawn)
1457 }
1458 }
1459 }
1460}
1461
1462fn draw_sprite<I: Images, S: Shades>(
1473 target: &mut Target<'_>,
1474 sprite: &Sprite,
1475 at: Place,
1476 look: &Look<'_, I, S>,
1477) {
1478 let shown = at.screen(sprite.clip).within(target.clip);
1479 if shown.is_empty() {
1480 return;
1481 }
1482 let Some(image) = look.images.image(sprite.image) else {
1483 target.fill(shown, Pen::ink(Ink::Grey));
1484 return;
1485 };
1486 let placed = Placed::of_sprite(sprite);
1487 draw_one_bit(target, &placed, &image, at, look.shades, shown);
1488}
1489
1490fn draw_painted<I: Images, S: Shades>(
1495 target: &mut Target<'_>,
1496 painted: &PaintedSprite,
1497 at: Place,
1498 look: &Look<'_, I, S>,
1499) {
1500 let shown = at.screen(painted.clip).within(target.clip);
1501 if shown.is_empty() {
1502 return;
1503 }
1504 let Some(picture) = look.pictures.get(painted.picture) else {
1505 target.fill(shown, Pen::ink(Ink::Grey));
1506 return;
1507 };
1508 let placed = Placed::of_painted(painted);
1509 let (width, height) = picture.size();
1510 match picture.kind() {
1511 PaintedKind::OneBit => {
1512 if let Some(image) = Image::new(width, height, picture.pixels(), picture.mask()) {
1513 draw_one_bit(target, &placed, &image, at, look.shades, shown);
1514 }
1515 }
1516 PaintedKind::Rgba | PaintedKind::Indexed => {
1517 draw_dithered(target, &placed, picture, at, shown);
1518 }
1519 }
1520}
1521
1522#[allow(clippy::cast_sign_loss)] fn draw_dithered(
1529 target: &mut Target<'_>,
1530 placed: &Placed,
1531 picture: &Painted,
1532 at: Place,
1533 shown: Clip,
1534) {
1535 let (width, height) = picture.size();
1536 let Some(columns) = Columns::of_sprite(placed, (width, width), at, shown) else {
1537 return;
1538 };
1539 let mut palette = [0u8; 256];
1541 for (light, c) in palette.iter_mut().zip(picture.palette()) {
1542 *light = brightness([c.r, c.g, c.b, u8::MAX]);
1543 }
1544 let (stride, pixels) = (picture.stride(), picture.pixels());
1545 let indexed = picture.kind() == PaintedKind::Indexed;
1546 let light = |u: u16, v: u16| {
1547 if indexed {
1548 let index = pixels[usize::from(v) * stride + usize::from(u)];
1549 return Some(palette[usize::from(index)]);
1550 }
1551 let rgba = picture.rgba_at(u, v);
1552 alpha_drawn(rgba[3]).then(|| brightness(rgba))
1553 };
1554 let (src, dest) = (placed.src, placed.dest);
1555 for y in shown.y0..shown.y1 {
1556 let dy = i64::from((y - at.y).div_euclid(at.scale)) - i64::from(dest.y);
1557 let v = Image::sample(
1558 dy,
1559 src.y.into(),
1560 src.h.into(),
1561 dest.h.into(),
1562 height,
1563 height,
1564 );
1565 let Some(v) = u16::try_from(v).ok().filter(|&v| v < height) else {
1566 continue;
1567 };
1568 let line = &mut target.frame[y as usize * ROW_BYTES..][..ROW_BYTES];
1569 for x in ((shown.x0 & !7)..shown.x1).step_by(8) {
1570 let lanes = ones(shown.x1 - x) & !ones(shown.x0 - x);
1571 let (mut levels, mut drawn) = ([0u8; 8], 0u8);
1572 for (i, level) in (0..8).zip(&mut levels) {
1573 let bit = 0x80 >> i;
1574 let pixel = (lanes & bit != 0)
1575 .then(|| columns.column(x + i, width))
1576 .flatten()
1577 .and_then(|u| light(u, v));
1578 if let Some(pixel) = pixel {
1579 *level = darken(pixel, placed.brightness);
1580 drawn |= bit;
1581 }
1582 }
1583 let byte = &mut line[(x >> 3) as usize];
1584 *byte ^= (*byte ^ dither_row(levels, y)) & drawn;
1585 }
1586 }
1587}
1588
1589#[allow(clippy::cast_sign_loss)] fn draw_one_bit(
1594 target: &mut Target<'_>,
1595 sprite: &Placed,
1596 image: &Image<'_>,
1597 at: Place,
1598 shades: &impl Shades,
1599 shown: Clip,
1600) {
1601 let size = (image.width, image.source_width);
1602 let Some(columns) = Columns::of_sprite(sprite, size, at, shown) else {
1603 return;
1604 };
1605 let (src, dest) = (sprite.src, sprite.dest);
1606 let (src_h, dest_h) = (i64::from(src.h), i64::from(dest.h));
1607 let solid = match sprite.paint {
1608 Paint::Solid(color) => Some(shades.shape(color)),
1609 Paint::Image | Paint::Dimmed | Paint::Shaded(_) => None,
1610 };
1611 let opacity = [0, 1, 2, 3].map(|y| shows_byte(sprite.opacity, y));
1612 let brightness = [0, 1, 2, 3].map(|y| shows_byte(sprite.brightness, y));
1613 let stride = Image::stride(image.width);
1614 for y in shown.y0..shown.y1 {
1615 let dy = i64::from((y - at.y).div_euclid(at.scale)) - i64::from(dest.y);
1616 let v = Image::sample(
1617 dy,
1618 src.y.into(),
1619 src_h,
1620 dest_h,
1621 image.height,
1622 image.source_height,
1623 );
1624 let Some(v) = usize::try_from(v)
1625 .ok()
1626 .filter(|&v| v < usize::from(image.height))
1627 else {
1628 continue;
1629 };
1630 let start = v * stride;
1631 let row = PictureRow {
1632 data: &image.data[start..start + stride],
1633 mask: image.mask.map(|mask| &mask[start..start + stride]),
1634 width: image.width,
1635 };
1636 let (showing, light) = (opacity[(y & 3) as usize], brightness[(y & 3) as usize]);
1637 let line = &mut target.frame[y as usize * ROW_BYTES..][..ROW_BYTES];
1638 for x in ((shown.x0 & !7)..shown.x1).step_by(8) {
1639 let lanes = ones(shown.x1 - x) & !ones(shown.x0 - x);
1640 let (white, drawn) = columns.bits(row, x, lanes);
1641 let drawn = drawn & lanes & showing;
1642 let white = white & light;
1644 let bits = match (solid, sprite.paint) {
1645 (Some(shade), _) => shade.row(y),
1646 (None, Paint::Dimmed) => white | InkShades::of(Ink::Grey).row(y),
1649 (None, _) => white,
1650 };
1651 let byte = &mut line[(x >> 3) as usize];
1653 *byte ^= (*byte ^ bits) & drawn;
1654 }
1655 }
1656}
1657
1658#[cfg(test)]
1659mod painted_tests;
1660#[cfg(test)]
1661mod speed_tests;
1662#[cfg(test)]
1663mod tests;