1use std::{
2 io,
3 path::{Path, PathBuf},
4};
5
6use image::{io::Reader, GenericImageView, ImageError, Rgb, RgbImage};
7use serde::{Deserialize, Serialize};
8use snafu::{Backtrace, Snafu};
9
10use super::{
11 raw::{self, BannerBitmap, BannerPalette, BannerVersion, Language},
12 ImageSize,
13};
14use crate::{crc::CRC_16_MODBUS, str::Unicode16Array};
15
16#[derive(Serialize, Deserialize)]
18pub struct Banner {
19 version: BannerVersion,
20 pub title: BannerTitle,
22 pub images: BannerImages,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub keyframes: Option<Vec<BannerKeyframe>>,
27}
28
29#[derive(Debug, Snafu)]
31pub enum BannerError {
32 #[snafu(transparent)]
34 BannerFile {
35 source: BannerImageError,
37 },
38 #[snafu(display("maximum keyframe count is {max} but got {actual}:\n{backtrace}"))]
40 TooManyKeyframes {
41 max: usize,
43 actual: usize,
45 backtrace: Backtrace,
47 },
48 #[snafu(display("maximum supported banner version is currently {max} but got {actual}:\n{backtrace}"))]
50 VersionNotSupported {
51 max: BannerVersion,
53 actual: BannerVersion,
55 backtrace: Backtrace,
57 },
58}
59
60impl Banner {
61 fn load_title(banner: &raw::Banner, version: BannerVersion, language: Language) -> Option<String> {
62 if version.supports_language(language) {
63 if let Some(title) = banner.title(language) {
64 Some(title.to_string())
65 } else {
66 None
67 }
68 } else {
69 None
70 }
71 }
72
73 pub fn load_raw(banner: &raw::Banner) -> Self {
75 let version = banner.version();
76 Self {
77 version,
78 title: BannerTitle {
79 japanese: Self::load_title(banner, version, Language::Japanese).unwrap(),
80 english: Self::load_title(banner, version, Language::English).unwrap(),
81 french: Self::load_title(banner, version, Language::French).unwrap(),
82 german: Self::load_title(banner, version, Language::German).unwrap(),
83 italian: Self::load_title(banner, version, Language::Italian).unwrap(),
84 spanish: Self::load_title(banner, version, Language::Spanish).unwrap(),
85 chinese: Self::load_title(banner, version, Language::Chinese),
86 korean: Self::load_title(banner, version, Language::Korean),
87 },
88 images: BannerImages::from_bitmap(*banner.bitmap(), *banner.palette()),
89 keyframes: None,
90 }
91 }
92
93 fn crc(&self, banner: &mut raw::Banner, version: BannerVersion) {
94 if self.version >= version {
95 *banner.crc_mut(version.crc_index()) = CRC_16_MODBUS.checksum(&banner.full_data()[version.crc_range()]);
96 }
97 }
98
99 pub fn build(&self) -> Result<raw::Banner, BannerError> {
106 if self.version > BannerVersion::Korea {
112 return VersionNotSupportedSnafu { max: BannerVersion::Korea, actual: self.version }.fail();
113 }
114
115 let mut banner = raw::Banner::new(self.version);
116 self.title.copy_to_banner(&mut banner);
117
118 *banner.bitmap_mut() = self.images.bitmap;
119 *banner.palette_mut() = self.images.palette;
120
121 if let Some(keyframes) = &self.keyframes {
122 if keyframes.len() > 64 {
123 TooManyKeyframesSnafu { max: 64usize, actual: keyframes.len() }.fail()?;
124 }
125
126 let animation = banner.animation_mut().unwrap();
127 for i in 0..keyframes.len() {
128 animation.keyframes[i] = keyframes[i].build();
129 }
130 for i in keyframes.len()..64 {
131 animation.keyframes[i] = raw::BannerKeyframe::new();
132 }
133 }
134
135 self.crc(&mut banner, BannerVersion::Original);
136 self.crc(&mut banner, BannerVersion::China);
137 self.crc(&mut banner, BannerVersion::Korea);
138 self.crc(&mut banner, BannerVersion::Animated);
139
140 Ok(banner)
141 }
142}
143
144#[derive(Default, Serialize, Deserialize)]
146pub struct BannerImages {
147 #[serde(skip)]
149 pub bitmap: BannerBitmap,
150 #[serde(skip)]
152 pub palette: BannerPalette,
153 #[serde(skip)]
155 pub animation_bitmaps: Option<Box<[BannerBitmap]>>,
156 #[serde(skip)]
158 pub animation_palettes: Option<Box<[BannerPalette]>>,
159
160 pub bitmap_path: PathBuf,
162 pub palette_path: PathBuf,
164}
165
166#[derive(Debug, Snafu)]
168pub enum BannerImageError {
169 #[snafu(transparent)]
171 Io {
172 source: io::Error,
174 },
175 #[snafu(transparent)]
177 Image {
178 source: ImageError,
180 },
181 #[snafu(display("banner icon must be {expected} pixels but got {actual} pixels:\n{backtrace}"))]
183 WrongSize {
184 expected: ImageSize,
186 actual: ImageSize,
188 backtrace: Backtrace,
190 },
191 #[snafu(display("banner icon {bitmap:?} contains a pixel at {x},{y} which is not present in the palette:\n{backtrace}"))]
193 InvalidPixel {
194 bitmap: PathBuf,
196 x: u32,
198 y: u32,
200 backtrace: Backtrace,
202 },
203}
204
205impl BannerImages {
206 pub fn from_bitmap(bitmap: BannerBitmap, palette: BannerPalette) -> Self {
208 Self {
209 bitmap,
210 palette,
211 animation_bitmaps: None,
212 animation_palettes: None,
213 bitmap_path: "bitmap.png".into(),
214 palette_path: "palette.png".into(),
215 }
216 }
217
218 pub fn load(&mut self, path: &Path) -> Result<(), BannerImageError> {
225 let bitmap_image = Reader::open(path.join(&self.bitmap_path))?.decode()?;
226 if bitmap_image.width() != 32 || bitmap_image.height() != 32 {
227 return WrongSizeSnafu {
228 expected: ImageSize { width: 32, height: 32 },
229 actual: ImageSize { width: bitmap_image.width(), height: bitmap_image.height() },
230 }
231 .fail();
232 }
233
234 let palette_image = Reader::open(path.join(&self.palette_path))?.decode()?;
235 if palette_image.width() != 16 || palette_image.height() != 1 {
236 return WrongSizeSnafu {
237 expected: ImageSize { width: 16, height: 1 },
238 actual: ImageSize { width: palette_image.width(), height: palette_image.height() },
239 }
240 .fail();
241 }
242
243 let mut bitmap = BannerBitmap([0u8; 0x200]);
244 for (x, y, color) in bitmap_image.pixels() {
245 let index = palette_image.pixels().find_map(|(i, _, c)| (color == c).then_some(i));
246 let Some(index) = index else {
247 return InvalidPixelSnafu { bitmap: path.join(&self.bitmap_path), x, y }.fail();
248 };
249 bitmap.set_pixel(x as usize, y as usize, index as u8);
250 }
251
252 let mut palette = BannerPalette([0u16; 16]);
253 for (i, _, color) in palette_image.pixels() {
254 let [r, g, b, _] = color.0;
255 palette.set_color(i as usize, r, g, b);
256 }
257
258 self.bitmap = bitmap;
259 self.palette = palette;
260 Ok(())
261 }
262
263 pub fn save_bitmap_file(&self, path: &Path) -> Result<(), BannerImageError> {
269 let mut bitmap_image = RgbImage::new(32, 32);
270 for y in 0..32 {
271 for x in 0..32 {
272 let index = self.bitmap.get_pixel(x, y);
273 let (r, g, b) = self.palette.get_color(index);
274 bitmap_image.put_pixel(x as u32, y as u32, Rgb([r, g, b]));
275 }
276 }
277
278 let mut palette_image = RgbImage::new(16, 1);
279 for index in 0..16 {
280 let (r, g, b) = self.palette.get_color(index);
281 palette_image.put_pixel(index as u32, 0, Rgb([r, g, b]));
282 }
283
284 bitmap_image.save(path.join(&self.bitmap_path))?;
285 palette_image.save(path.join(&self.palette_path))?;
286 Ok(())
287 }
288}
289
290#[derive(Serialize, Deserialize)]
292pub struct BannerTitle {
293 pub japanese: String,
295 pub english: String,
297 pub french: String,
299 pub german: String,
301 pub italian: String,
303 pub spanish: String,
305 #[serde(skip_serializing_if = "Option::is_none")]
306 pub chinese: Option<String>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub korean: Option<String>,
311}
312
313macro_rules! copy_title {
314 ($banner:ident, $language:expr, $title:expr) => {
315 if let Some(title) = $banner.title_mut($language) {
316 *title = Unicode16Array::from_str($title);
317 }
318 };
319}
320
321impl BannerTitle {
322 fn copy_to_banner(&self, banner: &mut raw::Banner) {
323 copy_title!(banner, Language::Japanese, &self.japanese);
324 copy_title!(banner, Language::English, &self.english);
325 copy_title!(banner, Language::French, &self.french);
326 copy_title!(banner, Language::German, &self.german);
327 copy_title!(banner, Language::Italian, &self.italian);
328 copy_title!(banner, Language::Spanish, &self.spanish);
329 if let Some(chinese) = &self.chinese {
330 copy_title!(banner, Language::Chinese, chinese);
331 }
332 if let Some(korean) = &self.korean {
333 copy_title!(banner, Language::Korean, korean);
334 }
335 }
336}
337
338#[derive(Serialize, Deserialize)]
340pub struct BannerKeyframe {
341 pub flip_vertically: bool,
343 pub flip_horizontally: bool,
345 pub palette: usize,
347 pub bitmap: usize,
349 pub frame_duration: usize,
351}
352
353impl BannerKeyframe {
354 pub fn build(&self) -> raw::BannerKeyframe {
360 raw::BannerKeyframe::new()
361 .with_frame_duration(self.frame_duration.try_into().unwrap())
362 .with_bitmap_index(self.bitmap.try_into().unwrap())
363 .with_palette_index(self.palette.try_into().unwrap())
364 .with_flip_horizontally(self.flip_horizontally)
365 .with_flip_vertically(self.flip_vertically)
366 }
367}