Skip to main content

spritebox/
background_export.rs

1//! Native layer files and reconstruction metadata, independent of atlas packing.
2use 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}