1use crate::{Color, Document, Error, Image, Limits, Position, Result, Sprite};
3
4#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct ScreenItem {
7 pub(crate) sprite: Sprite,
8 pub(crate) x: i32,
9 pub(crate) y: i32,
10 pub(crate) position: Position,
11}
12impl ScreenItem {
13 pub fn sprite(&self) -> &Sprite {
14 &self.sprite
15 }
16 pub fn position(&self) -> (i32, i32) {
17 (self.x, self.y)
18 }
19}
20
21#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct Screen {
24 pub(crate) name: String,
25 pub(crate) width: u32,
26 pub(crate) height: u32,
27 pub(crate) background: Option<(String, char)>,
28 pub(crate) grid: Option<u32>,
29 pub(crate) items: Vec<ScreenItem>,
30 pub(crate) position: Position,
31}
32impl Screen {
33 pub fn name(&self) -> &str {
34 &self.name
35 }
36 pub fn size(&self) -> (u32, u32) {
37 (self.width, self.height)
38 }
39 pub fn items(&self) -> &[ScreenItem] {
40 &self.items
41 }
42 pub fn grid(&self) -> Option<u32> {
43 self.grid
44 }
45 pub(crate) fn canvas(&self, document: &Document, limits: &Limits) -> Result<Image> {
46 let mut image = Image::new_with(self.width, self.height, limits)?;
47 let color = self
48 .background
49 .as_ref()
50 .and_then(|(p, k)| document.palette(p)?.color(*k))
51 .unwrap_or(Color::TRANSPARENT);
52 for y in 0..self.height {
53 for x in 0..self.width {
54 image.set_pixel(x, y, color);
55 }
56 }
57 Ok(image)
58 }
59 pub(crate) fn render(&self, document: &Document, limits: &Limits) -> Result<Image> {
60 let mut image = self.canvas(document, limits)?;
61 for item in &self.items {
62 image.over_from(&item.sprite.render(), item.x as i64, item.y as i64);
63 }
64 Ok(image)
65 }
66}
67impl Document {
68 pub fn screens(&self) -> &[Screen] {
69 &self.screens
70 }
71 pub fn screen(&self, name: &str) -> Option<Image> {
73 self.screen_with(name, &Limits::default()).ok().flatten()
74 }
75 pub fn screen_with(&self, name: &str, limits: &Limits) -> Result<Option<Image>> {
77 self.screens
78 .iter()
79 .find(|s| s.name == name)
80 .map(|s| s.render(self, limits))
81 .transpose()
82 }
83}
84impl Image {
85 pub fn over_from(&mut self, source: &Image, x: i64, y: i64) {
87 let (left, top, right, bottom) = placement_bounds(
88 self.width(),
89 self.height(),
90 source.width(),
91 source.height(),
92 x,
93 y,
94 );
95 for ty in top..bottom {
96 for tx in left..right {
97 let color = source.pixel((tx - x) as u32, (ty - y) as u32).unwrap();
98 let below = self.pixel(tx as u32, ty as u32).unwrap();
99 self.set_pixel(tx as u32, ty as u32, color.over(below));
100 }
101 }
102 }
103 pub fn scaled_with(&self, scale: u32, limits: &Limits) -> Result<Image> {
105 if !(1..=64).contains(&scale) {
106 return Err(Error::invalid("Scale must be 1..=64"));
107 }
108 let w = self
109 .width()
110 .checked_mul(scale)
111 .ok_or_else(|| Error::limit("Scaled width overflow"))?;
112 let h = self
113 .height()
114 .checked_mul(scale)
115 .ok_or_else(|| Error::limit("Scaled height overflow"))?;
116 let mut image = Image::new_with(w, h, limits)?;
117 for y in 0..h {
118 for x in 0..w {
119 image.set_pixel(x, y, self.pixel(x / scale, y / scale).unwrap());
120 }
121 }
122 Ok(image)
123 }
124}
125
126pub(crate) fn placement_bounds(
127 width: u32,
128 height: u32,
129 source_width: u32,
130 source_height: u32,
131 x: i64,
132 y: i64,
133) -> (i64, i64, i64, i64) {
134 (
135 x.clamp(0, width as i64),
136 y.clamp(0, height as i64),
137 x.saturating_add(source_width as i64).clamp(0, width as i64),
138 y.saturating_add(source_height as i64)
139 .clamp(0, height as i64),
140 )
141}