1use std::{
2 io,
3 path::{Path, PathBuf},
4};
5
6use image::{GenericImageView, ImageError, ImageReader, Rgba, RgbaImage};
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, Default)]
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 banner.title(language).map(|title| title.to_string())
64 } else {
65 None
66 }
67 }
68
69 pub fn load_raw(banner: &raw::Banner) -> Self {
71 let version = banner.version();
72 Self {
73 version,
74 title: BannerTitle {
75 japanese: Self::load_title(banner, version, Language::Japanese).unwrap(),
76 english: Self::load_title(banner, version, Language::English).unwrap(),
77 french: Self::load_title(banner, version, Language::French).unwrap(),
78 german: Self::load_title(banner, version, Language::German).unwrap(),
79 italian: Self::load_title(banner, version, Language::Italian).unwrap(),
80 spanish: Self::load_title(banner, version, Language::Spanish).unwrap(),
81 chinese: Self::load_title(banner, version, Language::Chinese),
82 korean: Self::load_title(banner, version, Language::Korean),
83 },
84 images: BannerImages::from_bitmap(*banner.bitmap(), *banner.palette()),
85 keyframes: None,
86 }
87 }
88
89 fn crc(&self, banner: &mut raw::Banner, version: BannerVersion) {
90 if self.version >= version {
91 *banner.crc_mut(version.crc_index()) = CRC_16_MODBUS.checksum(&banner.full_data()[version.crc_range()]);
92 }
93 }
94
95 pub fn build(&self) -> Result<raw::Banner<'_>, BannerError> {
102 if self.version > BannerVersion::Korea {
108 return VersionNotSupportedSnafu { max: BannerVersion::Korea, actual: self.version }.fail();
109 }
110
111 let mut banner = raw::Banner::new(self.version);
112 self.title.copy_to_banner(&mut banner);
113
114 *banner.bitmap_mut() = self.images.bitmap;
115 *banner.palette_mut() = self.images.palette;
116
117 if let Some(keyframes) = &self.keyframes {
118 if keyframes.len() > 64 {
119 TooManyKeyframesSnafu { max: 64usize, actual: keyframes.len() }.fail()?;
120 }
121
122 let animation = banner.animation_mut().unwrap();
123 for i in 0..keyframes.len() {
124 animation.keyframes[i] = keyframes[i].build();
125 }
126 for i in keyframes.len()..64 {
127 animation.keyframes[i] = raw::BannerKeyframe::new();
128 }
129 }
130
131 self.crc(&mut banner, BannerVersion::Original);
132 self.crc(&mut banner, BannerVersion::China);
133 self.crc(&mut banner, BannerVersion::Korea);
134 self.crc(&mut banner, BannerVersion::Animated);
135
136 Ok(banner)
137 }
138}
139
140#[derive(Default, Serialize, Deserialize)]
142pub struct BannerImages {
143 #[serde(skip)]
145 pub bitmap: BannerBitmap,
146 #[serde(skip)]
148 pub palette: BannerPalette,
149 #[serde(skip)]
151 pub animation_bitmaps: Option<Box<[BannerBitmap]>>,
152 #[serde(skip)]
154 pub animation_palettes: Option<Box<[BannerPalette]>>,
155
156 pub bitmap_path: PathBuf,
158 pub palette_path: PathBuf,
160}
161
162#[derive(Debug, Snafu)]
164pub enum BannerImageError {
165 #[snafu(transparent)]
167 Io {
168 source: io::Error,
170 },
171 #[snafu(transparent)]
173 Image {
174 source: ImageError,
176 },
177 #[snafu(display("banner icon must be {expected} pixels but got {actual} pixels:\n{backtrace}"))]
179 WrongSize {
180 expected: ImageSize,
182 actual: ImageSize,
184 backtrace: Backtrace,
186 },
187 #[snafu(display("banner icon {bitmap:?} contains a pixel at {x},{y} which is not present in the palette:\n{backtrace}"))]
189 InvalidPixel {
190 bitmap: PathBuf,
192 x: u32,
194 y: u32,
196 backtrace: Backtrace,
198 },
199}
200
201impl BannerImages {
202 pub fn from_bitmap(bitmap: BannerBitmap, palette: BannerPalette) -> Self {
204 Self {
205 bitmap,
206 palette,
207 animation_bitmaps: None,
208 animation_palettes: None,
209 bitmap_path: "bitmap.png".into(),
210 palette_path: "palette.png".into(),
211 }
212 }
213
214 pub fn load(&mut self, path: &Path) -> Result<(), BannerImageError> {
221 let bitmap_image = ImageReader::open(path.join(&self.bitmap_path))?.decode()?;
222 if bitmap_image.width() != 32 || bitmap_image.height() != 32 {
223 return WrongSizeSnafu {
224 expected: ImageSize { width: 32, height: 32 },
225 actual: ImageSize { width: bitmap_image.width(), height: bitmap_image.height() },
226 }
227 .fail();
228 }
229
230 let palette_image = ImageReader::open(path.join(&self.palette_path))?.decode()?;
231 if palette_image.width() != 16 || palette_image.height() != 1 {
232 return WrongSizeSnafu {
233 expected: ImageSize { width: 16, height: 1 },
234 actual: ImageSize { width: palette_image.width(), height: palette_image.height() },
235 }
236 .fail();
237 }
238
239 let mut bitmap = BannerBitmap([0u8; 0x200]);
240 for (x, y, color) in bitmap_image.pixels() {
241 let alpha = color.0[3];
242 let index = if alpha == 0 {
243 0
244 } else {
245 let Some(index) = palette_image.pixels().find_map(|(i, _, c)| (color == c).then_some(i)) else {
246 return InvalidPixelSnafu { bitmap: path.join(&self.bitmap_path), x, y }.fail();
247 };
248 index
249 };
250 bitmap.set_pixel(x as usize, y as usize, index as u8);
251 }
252
253 let mut palette = BannerPalette([0u16; 16]);
254 for (i, _, color) in palette_image.pixels() {
255 let [r, g, b, _] = color.0;
256 palette.set_color(i as usize, r, g, b);
257 }
258
259 self.bitmap = bitmap;
260 self.palette = palette;
261 Ok(())
262 }
263
264 pub fn save_bitmap_file(&self, path: &Path) -> Result<(), BannerImageError> {
270 let mut bitmap_image = RgbaImage::new(32, 32);
271 for y in 0..32 {
272 for x in 0..32 {
273 let index = self.bitmap.get_pixel(x, y);
274 let color = self.palette.get_color(index);
275 bitmap_image.put_pixel(x as u32, y as u32, Rgba(color));
276 }
277 }
278
279 let mut palette_image = RgbaImage::new(16, 1);
280 for index in 0..16 {
281 let color = self.palette.get_color(index);
282 palette_image.put_pixel(index as u32, 0, Rgba(color));
283 }
284
285 bitmap_image.save(path.join(&self.bitmap_path))?;
286 palette_image.save(path.join(&self.palette_path))?;
287 Ok(())
288 }
289}
290
291#[derive(Serialize, Deserialize, Default)]
293pub struct BannerTitle {
294 pub japanese: String,
296 pub english: String,
298 pub french: String,
300 pub german: String,
302 pub italian: String,
304 pub spanish: String,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub chinese: Option<String>,
309 #[serde(skip_serializing_if = "Option::is_none")]
310 pub korean: Option<String>,
312}
313
314macro_rules! copy_title {
315 ($banner:ident, $language:expr, $title:expr) => {
316 if let Some(title) = $banner.title_mut($language) {
317 *title = Unicode16Array::from($title.as_str());
318 }
319 };
320}
321
322impl BannerTitle {
323 fn copy_to_banner(&self, banner: &mut raw::Banner) {
324 copy_title!(banner, Language::Japanese, &self.japanese);
325 copy_title!(banner, Language::English, &self.english);
326 copy_title!(banner, Language::French, &self.french);
327 copy_title!(banner, Language::German, &self.german);
328 copy_title!(banner, Language::Italian, &self.italian);
329 copy_title!(banner, Language::Spanish, &self.spanish);
330 if let Some(chinese) = &self.chinese {
331 copy_title!(banner, Language::Chinese, chinese);
332 }
333 if let Some(korean) = &self.korean {
334 copy_title!(banner, Language::Korean, korean);
335 }
336 }
337}
338
339#[derive(Serialize, Deserialize)]
341pub struct BannerKeyframe {
342 pub flip_vertically: bool,
344 pub flip_horizontally: bool,
346 pub palette: usize,
348 pub bitmap: usize,
350 pub frame_duration: usize,
352}
353
354impl BannerKeyframe {
355 pub fn build(&self) -> raw::BannerKeyframe {
361 raw::BannerKeyframe::new()
362 .with_frame_duration(self.frame_duration.try_into().unwrap())
363 .with_bitmap_index(self.bitmap.try_into().unwrap())
364 .with_palette_index(self.palette.try_into().unwrap())
365 .with_flip_horizontally(self.flip_horizontally)
366 .with_flip_vertically(self.flip_vertically)
367 }
368}