1use alloc::collections::BTreeMap;
34use alloc::format;
35use alloc::rc::Rc;
36
37use shuyi_assets::font::{CellWidths, FALLBACK_GLYPH, FontAtlasDef};
38use shuyi_assets::image::{ImageId, ImageInfo, ImageTable};
39use shuyi_assets::lighting::Light;
40
41use super::{Image, Painter, test_card};
42use crate::frame::color::Rgb;
43use crate::frame::gizmo::{GizmoItem, GizmoRole, GizmoShape, LabelText};
44use crate::frame::glyph_buffer::{Cell, GlyphBuffer, Layer, Overlay, Paint, Rect, Sprite};
45use crate::frame::painted::{Painted, PaintedId, PaintedSprite};
46
47pub const COLUMNS: u16 = 32;
49pub const ROWS: u16 = 12;
51
52pub const TEST_CARD_PATH: &str = "card/test_card.png";
54pub const MISSING_PATH: &str = "card/missing.png";
57
58const CLEAR: Rgb = Rgb::new(20, 18, 28);
60const BACKGROUNDS: [Rgb; 4] = [
62 Rgb::new(40, 60, 120),
63 Rgb::new(120, 40, 50),
64 Rgb::new(30, 100, 60),
65 Rgb::new(150, 140, 40),
66];
67const FOREGROUNDS: [Rgb; 5] = [
69 Rgb::new(255, 255, 255),
70 Rgb::new(250, 200, 60),
71 Rgb::new(90, 220, 240),
72 Rgb::new(240, 90, 200),
73 Rgb::new(10, 10, 10),
74];
75const GLYPHS: [char; 4] = ['#', 'o', '-', '|'];
77const MISSING_GLYPH: char = 'Z';
79const WIDE_GLYPH: char = '田';
82const STRIPES: [Rgb; 2] = [Rgb::new(70, 70, 80), Rgb::new(200, 190, 170)];
84
85pub const PAINTED_RGBA: PaintedId = PaintedId::new("card/rgba");
87pub const PAINTED_INDEXED: PaintedId = PaintedId::new("card/indexed");
89pub const PAINTED_MASKED: PaintedId = PaintedId::new("card/masked");
91pub const PAINTED_ONE_BIT: PaintedId = PaintedId::new("card/one-bit");
93pub const PAINTED_SCENE: PaintedId = PaintedId::new("card/scene");
95pub const PAINTED_MISSING: PaintedId = PaintedId::new("card/painted-missing");
97
98#[derive(Debug, Clone, PartialEq)]
101pub struct RenderCard {
102 pub buffer: GlyphBuffer,
104 pub atlas: FontAtlasDef,
106 pub atlas_image: Image,
108 pub table: ImageTable,
110 pub images: BTreeMap<ImageId, Image>,
112 pub clear: Rgb,
114}
115
116impl RenderCard {
117 pub fn painter(&self) -> Painter {
119 Painter::new(
120 self.atlas.clone(),
121 self.atlas_image.clone(),
122 self.images.clone(),
123 self.clear,
124 )
125 }
126}
127
128pub fn build() -> RenderCard {
130 let table = ImageTable::new(BTreeMap::from([
131 (
132 TEST_CARD_PATH,
133 ImageInfo::new(test_card::SIZE, test_card::SIZE),
134 ),
135 (
136 MISSING_PATH,
137 ImageInfo::new(test_card::SIZE, test_card::SIZE),
138 ),
139 ]));
140 let ids = table.id(TEST_CARD_PATH).zip(table.id(MISSING_PATH));
141 let Some((card, missing)) = ids else {
142 unreachable!("the table names both pictures");
143 };
144 let (atlas, atlas_image) = atlas();
145 let card_image = Image {
146 width: test_card::SIZE,
147 height: test_card::SIZE,
148 rgba: test_card::pixels(),
149 };
150 RenderCard {
151 buffer: frame(card, missing),
152 atlas,
153 atlas_image,
154 images: BTreeMap::from([(card, card_image)]),
155 table,
156 clear: CLEAR,
157 }
158}
159
160fn atlas() -> (FontAtlasDef, Image) {
165 const CELL: (u32, u32) = (8, 16);
166 let order = [' ', '#', 'o', '-', '|', FALLBACK_GLYPH];
167 let glyphs: BTreeMap<char, u32> = order.iter().copied().zip(0..).collect();
168 let narrow = u32::try_from(order.len()).unwrap_or(0);
169 let columns = narrow + 2;
170 let (width, height) = (CELL.0 * columns, CELL.1);
171 let set = |glyph: char, x: u32, y: u32| match glyph {
174 '#' => (1..7).contains(&x) && (2..14).contains(&y),
175 'o' => {
176 let (across, down) = ((1..7).contains(&x), (3..13).contains(&y));
177 across && down && (x == 1 || x == 6 || y == 3 || y == 12)
178 }
179 '-' => (7..9).contains(&y),
180 '|' => (3..5).contains(&x),
181 FALLBACK_GLYPH => (1..7).contains(&x) && (2..14).contains(&y) && (x + y).is_multiple_of(2),
182 WIDE_GLYPH => {
183 let (across, down) = ((1..15).contains(&x), (2..14).contains(&y));
184 across && down && (x == 1 || x == 14 || x == 5 || y == 2 || y == 13 || y == 7)
185 }
186 _ => false,
187 };
188 let rgba = (0..height)
189 .flat_map(|y| (0..width).map(move |x| (x, y)))
190 .flat_map(|(x, y)| {
191 let cell = x / CELL.0;
192 let on = match order.get(cell as usize) {
193 Some(&glyph) => set(glyph, x % CELL.0, y),
194 None => set(WIDE_GLYPH, x - narrow * CELL.0, y),
195 };
196 [if on { 255 } else { 0 }; 4]
197 })
198 .collect();
199 let def = FontAtlasDef {
200 cell_w: CELL.0,
201 cell_h: CELL.1,
202 columns,
203 glyphs,
204 wide: BTreeMap::from([(WIDE_GLYPH, narrow)]),
205 wide_sets: BTreeMap::new(),
206 };
207 (
208 def,
209 Image {
210 width,
211 height,
212 rgba,
213 },
214 )
215}
216
217fn frame(card: ImageId, missing: ImageId) -> GlyphBuffer {
220 let mut buf = GlyphBuffer::new(COLUMNS, ROWS, Cell::new(' ', FOREGROUNDS[0], CLEAR))
221 .with_widths(CellWidths::wide([WIDE_GLYPH]));
222 cells(&mut buf);
223 rects(&mut buf);
224 sprites(&mut buf, card, missing);
225 painted(&mut buf);
226 backdrop(&mut buf, card);
227 gizmos(&mut buf);
228 buf
229}
230
231fn cells(buf: &mut GlyphBuffer) {
238 for x in 0..i32::from(COLUMNS) {
239 let i = nth(x);
240 let glyph = GLYPHS[i % GLYPHS.len()];
241 let fg = FOREGROUNDS[i % FOREGROUNDS.len()];
242 buf.set(x, 0, Cell::new(glyph, fg, BACKGROUNDS[i / 4 % 4]));
243 let glyph = if i.is_multiple_of(3) {
244 MISSING_GLYPH
245 } else {
246 glyph
247 };
248 buf.set(x, 1, Cell::new(glyph, fg, CLEAR));
249 }
250 for y in 2..11 {
251 for x in 0..20 {
252 let stripe = STRIPES[nth(x + y) % 2];
253 buf.set(x, y, Cell::new(' ', FOREGROUNDS[0], stripe));
254 }
255 }
256 for x in 0..8 {
257 let i = nth(x);
258 let stripe = STRIPES[nth(x + 6) % 2];
259 buf.set(x, 6, Cell::new(GLYPHS[i % 4], FOREGROUNDS[i % 5], stripe));
260 }
261 for x in 0..8 {
262 let glyph = if x % 2 == 0 { 'o' } else { ' ' };
263 buf.set(x, 11, Cell::see_through(glyph, FOREGROUNDS[2]));
264 }
265 buf.set(8, 11, Cell::see_through(MISSING_GLYPH, FOREGROUNDS[1]));
266 let wide = format!("{WIDE_GLYPH}#{WIDE_GLYPH}{WIDE_GLYPH}o");
267 buf.print(10, 11, &wide, FOREGROUNDS[1], CLEAR);
268 buf.print(
269 18,
270 11,
271 &format!("{WIDE_GLYPH}|"),
272 FOREGROUNDS[3],
273 BACKGROUNDS[0],
274 );
275 buf.set(22, 11, Cell::new(WIDE_GLYPH, FOREGROUNDS[2], CLEAR));
276}
277
278fn rects(buf: &mut GlyphBuffer) {
281 let red = Rgb::new(230, 40, 40);
282 buf.add_overlay(Overlay::new(Rect::new(0, 23, 256, 3), red, Layer::Under));
283 for (i, color) in BACKGROUNDS.iter().enumerate() {
284 let x = i32::try_from(i).unwrap_or(0) * 32 + 5;
285 buf.add_overlay(Overlay::new(Rect::new(x, 6, 5, 3), *color, Layer::Over));
286 }
287 let white = Rgb::new(255, 255, 255);
288 buf.add_overlay(Overlay::new(Rect::new(161, 33, 1, 140), white, Layer::Over));
289 let green = Rgb::new(40, 220, 90);
290 buf.add_overlay(Overlay::new(Rect::new(30, 40, 4, 7), green, Layer::Over));
291}
292
293fn sprites(buf: &mut GlyphBuffer, card: ImageId, missing: ImageId) {
301 let full = Rect::new(0, 0, 16, 16);
302 let dusk = Paint::Shaded(Light::new(0.4, [0.7, 0.75, 1.0]));
303 let gold = Paint::Solid(Rgb::new(250, 200, 60));
304 let at = |x: i32, y: i32, size: i32| Rect::new(x, y, size, size);
305 let small = [
307 (card, Paint::Image, Layer::Under, false, 255, 255),
308 (card, Paint::Image, Layer::Over, false, 255, 255),
309 (card, gold, Layer::Over, false, 255, 255),
310 (card, Paint::Dimmed, Layer::Over, false, 255, 255),
311 (card, dusk, Layer::Over, false, 255, 255),
312 (card, Paint::Image, Layer::Over, true, 255, 255),
313 (missing, Paint::Image, Layer::Over, false, 255, 255),
314 ];
315 let faded = [
316 (card, Paint::Image, Layer::Over, false, 128, 255),
317 (card, gold, Layer::Over, false, 128, 255),
318 (card, Paint::Image, Layer::Over, false, 255, 102),
319 (card, gold, Layer::Over, false, 128, 102),
320 (card, Paint::Dimmed, Layer::Over, false, 255, 102),
321 (card, Paint::Image, Layer::Under, true, 128, 255),
322 (card, dusk, Layer::Over, false, 128, 102),
323 ];
324 for (y, row) in [(36, small), (60, faded)] {
325 for (x, (image, paint, layer, flip_x, opacity, brightness)) in (4..).step_by(22).zip(row) {
326 let mut sprite = Sprite::new(image, full, at(x, y, 16), layer).painted(paint);
327 sprite.flip_x = flip_x;
328 sprite.opacity = opacity;
329 sprite.brightness = brightness;
330 buf.add_sprite(sprite);
331 }
332 }
333 let big = |x: i32, layer| Sprite::new(card, full, at(x, 98, 32), layer);
336 buf.add_sprite(big(4, Layer::Under));
337 let mut cut = big(42, Layer::Over);
338 cut.clip = Rect::new(47, 101, 21, 23);
339 buf.add_sprite(cut);
340 let mut cut = big(80, Layer::Over);
341 cut.clip = Rect::new(85, 101, 21, 23);
342 cut.flip_x = true;
343 buf.add_sprite(cut);
344 let mut cut = big(118, Layer::Under).painted(Paint::Solid(Rgb::new(90, 220, 240)));
345 cut.clip = Rect::new(121, 105, 25, 15);
346 buf.add_sprite(cut);
347}
348
349fn rgba_picture() -> Painted {
352 let mut picture = Painted::rgba(8, 8);
353 for (i, pixel) in picture
354 .pixels_mut()
355 .as_chunks_mut::<4>()
356 .0
357 .iter_mut()
358 .enumerate()
359 {
360 let (x, y) = (i % 8, i / 8);
361 let alpha = match (x, y) {
362 (0..2, 0..2) => 0,
363 (_, 7) => 128,
364 _ => 255,
365 };
366 let level = |v: usize| u8::try_from(v * 36).unwrap_or(u8::MAX);
367 let blue = if (x + y) % 2 == 0 { 200 } else { 40 };
368 pixel.copy_from_slice(&[level(x), level(y), blue, alpha]);
369 }
370 picture
371}
372
373fn indexed_picture() -> Painted {
376 let palette = [
377 BACKGROUNDS[1],
378 FOREGROUNDS[1],
379 FOREGROUNDS[2],
380 BACKGROUNDS[2],
381 ];
382 let mut picture = Painted::indexed(8, 8, &palette);
383 for (i, pixel) in picture.pixels_mut().iter_mut().enumerate() {
384 *pixel = u8::try_from((i % 8 + i / 8) % 3).unwrap_or(0);
385 }
386 picture
387}
388
389fn one_bit_picture(width: u16, masked: bool) -> Painted {
393 let mut picture = Painted::one_bit(width, 8);
394 if masked {
395 picture = picture.with_mask();
396 }
397 let stride = picture.stride();
398 let w = usize::from(width);
399 let white =
400 |x: usize, y: usize| x == 0 || y == 0 || x == w - 1 || y == 7 || x == y || x + y == w - 1;
401 let pixels = picture.pixels_mut();
402 for y in 0..8 {
403 for x in 0..w {
404 if white(x, y) {
405 pixels[y * stride + x / 8] |= 0x80 >> (x % 8);
406 }
407 }
408 }
409 if let Some(mask) = picture.mask_mut() {
410 for y in 0..8 {
411 for x in 0..w {
412 let corner = (x == 0 || x == w - 1) && (y == 0 || y == 7);
413 if corner || x == w / 2 {
414 mask[y * stride + x / 8] &= !(0x80 >> (x % 8));
415 }
416 }
417 }
418 }
419 picture
420}
421
422fn painted(buf: &mut GlyphBuffer) {
428 let pictures = buf.pictures_mut();
429 pictures.insert(PAINTED_RGBA, rgba_picture());
430 pictures.insert(PAINTED_INDEXED, indexed_picture());
431 pictures.insert(PAINTED_MASKED, one_bit_picture(8, true));
432 pictures.insert(PAINTED_ONE_BIT, one_bit_picture(16, false));
433 let eight = Rect::new(0, 0, 8, 8);
434 let at = |x: i32| Rect::new(x, 140, 16, 16);
435 let place = |id, x, layer| PaintedSprite::new(id, eight, at(x), layer);
436 buf.add_painted(place(PAINTED_RGBA, 4, Layer::Under));
437 let mut dim = place(PAINTED_RGBA, 26, Layer::Over);
438 dim.brightness = 102;
439 buf.add_painted(dim);
440 buf.add_painted(place(PAINTED_INDEXED, 48, Layer::Over));
441 let mut cut = place(PAINTED_INDEXED, 70, Layer::Under);
442 cut.clip = Rect::new(73, 143, 9, 11);
443 buf.add_painted(cut);
444 buf.add_painted(place(PAINTED_MASKED, 92, Layer::Over));
445 buf.add_painted(place(PAINTED_ONE_BIT, 114, Layer::Under));
446 buf.add_painted(place(PAINTED_MISSING, 136, Layer::Over));
447 let mut missing = place(PAINTED_MISSING, 4, Layer::Under);
448 missing.dest.y = 158;
449 missing.clip = missing.dest;
450 buf.add_painted(missing);
451}
452
453fn backdrop(buf: &mut GlyphBuffer, card: ImageId) {
458 let mut scene = GlyphBuffer::new(6, 5, Cell::new(' ', FOREGROUNDS[0], CLEAR));
459 for y in 0..5 {
460 for x in 0..6 {
461 let i = nth(x + y * 6);
462 if i % 5 == 4 {
463 continue; }
465 let glyph = if i.is_multiple_of(2) {
466 GLYPHS[i % 4]
467 } else {
468 ' '
469 };
470 let cell = Cell::new(glyph, FOREGROUNDS[i % 5], BACKGROUNDS[i % 4]);
471 scene.set(x, y, cell);
472 }
473 }
474 let full = Rect::new(0, 0, 16, 16);
475 scene.add_sprite(Sprite::new(
476 card,
477 full,
478 Rect::new(12, 20, 16, 16),
479 Layer::Over,
480 ));
481 let line = Overlay::new(Rect::new(3, 45, 30, 2), Rgb::new(230, 40, 40), Layer::Under);
482 scene.add_overlay(line);
483 scene
485 .pictures_mut()
486 .insert(PAINTED_SCENE, one_bit_picture(16, true));
487 let whole = Rect::new(0, 0, 16, 8);
488 let painted = PaintedSprite::new(PAINTED_SCENE, whole, Rect::new(30, 4, 16, 8), Layer::Over);
489 scene.add_painted(painted);
490 let clip = Rect::new(21, 2, 10, 9);
491 for y in clip.y..clip.y + clip.h {
492 for x in clip.x..clip.x + clip.w {
493 let i = nth(x + y);
494 let glyph = if i.is_multiple_of(3) {
495 GLYPHS[i % 4]
496 } else {
497 ' '
498 };
499 buf.set(x, y, Cell::see_through(glyph, FOREGROUNDS[i % 4]));
500 }
501 }
502 buf.set(23, 4, Cell::new('o', FOREGROUNDS[1], BACKGROUNDS[1]));
503 buf.set(24, 4, Cell::new('#', FOREGROUNDS[2], CLEAR));
504 buf.set(27, 8, Cell::new(' ', FOREGROUNDS[0], BACKGROUNDS[3]));
505 buf.set_backdrop(Rc::new(scene), clip, (0.5, 0.5), 2);
506}
507
508fn gizmos(buf: &mut GlyphBuffer) {
513 let ends = [
514 (238, 104),
515 (216, 126),
516 (200, 126),
517 (178, 104),
518 (178, 88),
519 (200, 66),
520 (216, 66),
521 (238, 88),
522 ];
523 let mut add = |role, shape, color| buf.add_gizmo(GizmoItem::new("card", role, shape, color));
524 for (i, to) in ends.into_iter().enumerate() {
525 let line = GizmoShape::Line {
526 from: (208, 96),
527 to,
528 };
529 add(GizmoRole::Path, line, FOREGROUNDS[i % FOREGROUNDS.len()]);
530 }
531 let rect = |x, y, w, h| GizmoShape::Rect {
532 rect: Rect::new(x, y, w, h),
533 };
534 add(GizmoRole::Bounds, rect(170, 36, 24, 14), FOREGROUNDS[0]);
535 let circle = |center, radius| GizmoShape::Circle { center, radius };
536 add(GizmoRole::Trigger, circle((226, 150), 10), FOREGROUNDS[2]);
537 add(GizmoRole::Bone, circle((250, 20), 3), FOREGROUNDS[3]);
538 add(GizmoRole::Bone, circle((164, 186), 0), FOREGROUNDS[4]);
539 let cross = |at, size| GizmoShape::Cross { at, size };
540 add(GizmoRole::Note, cross((186, 150), 4), FOREGROUNDS[1]);
541 add(GizmoRole::Note, cross((200, 150), 1), FOREGROUNDS[0]);
542 let mut cut = GizmoItem::new(
543 "card",
544 GizmoRole::Solid,
545 rect(226, 40, 18, 10),
546 FOREGROUNDS[1],
547 );
548 cut.clip = Rect::new(230, 38, 20, 6);
549 buf.add_gizmo(cut);
550 for (column, text) in [(24, "#o"), (29, "-|")] {
552 buf.print_fg(column, 11, text, FOREGROUNDS[2]);
553 let label = GizmoShape::Label {
554 at: (column * 8, 180),
555 text: LabelText::new(text),
556 };
557 let mut item = GizmoItem::new("card", GizmoRole::Note, label, FOREGROUNDS[2]);
558 item.clip = Rect::new(column * 8, 176, 16, 16);
559 buf.add_gizmo(item);
560 }
561}
562
563fn nth(v: i32) -> usize {
565 usize::try_from(v).unwrap_or(0)
566}
567
568#[cfg(test)]
569mod tests;