1use crate::json::{Writer, list};
3use crate::scene::Budget;
4use crate::{
5 BackgroundRenderOptions, Document, Error, ErrorCode, Image, LayerOptions, Limits, Result,
6};
7
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9pub enum BackgroundMode {
10 None,
11 Flat,
12 Layers,
13 #[default]
14 Both,
15}
16impl BackgroundMode {
17 pub(crate) fn flat(self) -> bool {
18 matches!(self, Self::Flat | Self::Both)
19 }
20 pub(crate) fn layers(self) -> bool {
21 matches!(self, Self::Layers | Self::Both)
22 }
23}
24#[derive(Clone, Debug, Default, Eq, PartialEq)]
25pub struct BackgroundExportOptions {
26 pub mode: BackgroundMode,
27 pub selection: Option<String>,
28 pub camera: (i32, i32),
29}
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct BackgroundFlatExport {
32 pub file: String,
33 pub image: Image,
34 pub camera: (i32, i32),
35}
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub struct BackgroundLayerExport {
38 pub name: String,
39 pub file: String,
40 pub image: Image,
41 pub options: LayerOptions,
42}
43#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct BackgroundExport {
45 pub name: String,
46 pub size: (u32, u32),
47 pub flat: Option<BackgroundFlatExport>,
48 pub layers: Vec<BackgroundLayerExport>,
49}
50
51pub(crate) fn build(
52 document: &Document,
53 options: &BackgroundExportOptions,
54 limits: &Limits,
55 b: &mut Budget,
56) -> Result<Vec<BackgroundExport>> {
57 if let Some(name) = &options.selection {
58 if document.background(name).is_none() {
59 return Err(Error::new(
60 ErrorCode::UnknownName,
61 format!("Unknown background `{name}`"),
62 ));
63 }
64 }
65 if options.mode == BackgroundMode::None {
66 return Ok(Vec::new());
67 }
68 let mut result = Vec::new();
69 for (i, bg) in document
70 .backgrounds
71 .iter()
72 .enumerate()
73 .filter(|(_, bg)| options.selection.as_deref().is_none_or(|n| bg.name() == n))
74 {
75 let natives = bg.native_layers(document, limits, b)?;
76 let flat = if options.mode.flat() {
77 Some(BackgroundFlatExport {
78 file: format!("background-{i:04}.png"),
79 image: bg.flatten(
80 &natives,
81 &BackgroundRenderOptions {
82 camera: options.camera,
83 },
84 limits,
85 b,
86 )?,
87 camera: options.camera,
88 })
89 } else {
90 None
91 };
92 let mut layers = Vec::new();
93 for (j, (layer, image)) in bg.layers.iter().zip(natives).enumerate() {
94 if options.mode.layers() {
95 layers.push(BackgroundLayerExport {
96 name: layer.name().into(),
97 file: format!("background-{i:04}-layer-{j:04}.png"),
98 image,
99 options: layer.options.clone(),
100 });
101 } else {
102 b.pixels += image.width() as u64 * image.height() as u64;
103 }
104 }
105 result.push(BackgroundExport {
106 name: bg.name().into(),
107 size: bg.size(),
108 flat,
109 layers,
110 });
111 }
112 Ok(result)
113}
114pub(crate) fn file_names(doc: &Document, o: &BackgroundExportOptions) -> Vec<String> {
115 let mut names = Vec::new();
116 for (i, b) in doc
117 .backgrounds
118 .iter()
119 .enumerate()
120 .filter(|(_, b)| o.selection.as_deref().is_none_or(|n| b.name() == n))
121 {
122 if o.mode.flat() {
123 names.push(format!("background-{i:04}.png"));
124 }
125 if o.mode.layers() {
126 for j in 0..b.layers.len() {
127 names.push(format!("background-{i:04}-layer-{j:04}.png"));
128 }
129 }
130 }
131 names
132}
133pub(crate) fn write_json(w: &mut Writer, backgrounds: &[BackgroundExport]) {
134 if backgrounds.is_empty() {
135 return;
136 }
137 w.object(Some("backgrounds"));
138 w.raw(Some("schema"), "1");
139 w.string(Some("units"), "pixel");
140 w.array(Some("items"));
141 for bg in backgrounds {
142 w.object(None);
143 w.string(Some("name"), &bg.name);
144 w.raw(Some("size"), &list(&[bg.size.0, bg.size.1]));
145 if let Some(flat) = &bg.flat {
146 w.object(Some("flat"));
147 w.string(Some("image"), &flat.file);
148 w.raw(
149 Some("camera"),
150 &format!("[{}, {}]", flat.camera.0, flat.camera.1),
151 );
152 w.close('}');
153 }
154 if !bg.layers.is_empty() {
155 w.array(Some("layers"));
156 for layer in &bg.layers {
157 w.object(None);
158 w.string(Some("name"), &layer.name);
159 w.string(Some("image"), &layer.file);
160 w.raw(
161 Some("size"),
162 &list(&[layer.image.width(), layer.image.height()]),
163 );
164 w.raw(
165 Some("origin"),
166 &format!("[{}, {}]", layer.options.origin.0, layer.options.origin.1),
167 );
168 w.string(Some("repeat"), layer.options.repeat.as_str());
169 w.object(Some("parallax"));
170 for (axis, r) in [
171 ("x", layer.options.parallax.0),
172 ("y", layer.options.parallax.1),
173 ] {
174 w.object(Some(axis));
175 w.raw(Some("num"), &r.numerator().to_string());
176 w.raw(Some("den"), &r.denominator().to_string());
177 w.close('}');
178 }
179 w.close('}');
180 w.close('}');
181 }
182 w.close(']');
183 }
184 w.close('}');
185 }
186 w.close(']');
187 w.close('}');
188}
189pub(crate) fn add_files(files: &mut Vec<(String, Vec<u8>)>, backgrounds: &[BackgroundExport]) {
190 for bg in backgrounds {
191 if let Some(f) = &bg.flat {
192 files.push((f.file.clone(), crate::png::encode(&f.image)));
193 }
194 for l in &bg.layers {
195 files.push((l.file.clone(), crate::png::encode(&l.image)));
196 }
197 }
198}
199pub(crate) fn rename_themed(backgrounds: &mut [BackgroundExport], theme: usize) {
200 let rename = |file: &mut String| {
201 let stem = file.strip_suffix(".png").unwrap();
202 *file = format!("{stem}-theme-{theme:04}.png");
203 };
204 for bg in backgrounds {
205 if let Some(f) = &mut bg.flat {
206 rename(&mut f.file);
207 }
208 for l in &mut bg.layers {
209 rename(&mut l.file);
210 }
211 }
212}