pkmn_rom_extract/
graphics.rs1use alloc::vec;
3use alloc::vec::Vec;
4use core::fmt;
5use core::result;
6
7#[cfg(feature = "std")]
8#[allow(unused_imports)]
9use std::io::Write;
10
11#[cfg(not(feature = "std"))]
12#[allow(unused_imports)]
13use no_std_io2::io::Write;
14
15#[cfg(feature = "png")]
16use png::{BitDepth, ColorType, Encoder};
17
18const BYTES_PER_TILE: usize = 32;
19
20pub const PALETTE_SIZE: usize = 16;
22
23pub const PALETTE_SIZE_BYTES: usize = PALETTE_SIZE * 2;
25
26pub const SPRITE_BYTES_32PX: usize = 4 * 4 * BYTES_PER_TILE;
28
29pub const SPRITE_BYTES_64PX: usize = 8 * 8 * BYTES_PER_TILE;
31
32#[derive(Clone, Debug)]
34pub struct GbaPalette {
35 colors: [u16; PALETTE_SIZE],
36}
37
38#[derive(Clone, Debug)]
40pub struct GbaSprite {
41 width: u32, height: u32,
43 tiles: Vec<u8>,
44 palette: GbaPalette,
45}
46
47#[derive(Clone, Debug)]
49pub struct GbaBackground {
50 width: u32, height: u32,
52 pal_offset: u32,
53 tiles: Vec<u8>,
54 palettes: Vec<GbaPalette>,
55 tilemap: Vec<u16>,
56}
57
58#[derive(Debug)]
60pub enum GraphicsError {
61 DimensionMismatch,
62 InvalidPalette,
63 #[cfg(feature = "png")]
64 PngError(png::EncodingError),
65}
66
67pub type Result<T> = result::Result<T, GraphicsError>;
69
70impl fmt::Display for GraphicsError {
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 match self {
73 GraphicsError::DimensionMismatch => write!(f, "Graphics data has invalid size."),
74 GraphicsError::InvalidPalette => write!(f, "Palette data is invalid."),
75 #[cfg(feature = "png")]
76 GraphicsError::PngError(e) => write!(f, "PNG Encoding Error: {e}."),
77 }
78 }
79}
80
81#[cfg(feature = "png")]
82impl From<png::EncodingError> for GraphicsError {
83 fn from(err: png::EncodingError) -> Self {
84 GraphicsError::PngError(err)
85 }
86}
87
88impl From<&[u8; 32]> for GbaPalette {
89 fn from(bytes: &[u8; PALETTE_SIZE_BYTES]) -> Self {
90 let mut out = GbaPalette {
91 colors: [0; PALETTE_SIZE],
92 };
93 for i in 0..PALETTE_SIZE {
94 out.colors[i] = u16::from_le_bytes(bytes[i * 2..i * 2 + 2].try_into().unwrap());
95 }
96 out
97 }
98}
99
100impl GbaPalette {
101 pub fn from_slice(slice: &[u8]) -> Result<Self> {
105 let bytes: &[u8; PALETTE_SIZE_BYTES] =
106 slice.try_into().or(Err(GraphicsError::InvalidPalette))?;
107 Ok(Self::from(bytes))
108 }
109}
110
111impl GbaSprite {
112 fn build(tile_width: u32, tiles: &[u8], palette: GbaPalette) -> Result<Self> {
113 let row_size = (tile_width as usize) * BYTES_PER_TILE;
114 if tiles.len() % row_size != 0 {
115 return Err(GraphicsError::DimensionMismatch);
116 }
117 Ok(GbaSprite {
118 width: tile_width,
119 height: (tiles.len() / row_size) as u32,
120 tiles: tiles.to_vec(),
121 palette: palette.clone(),
122 })
123 }
124
125 pub fn build_24px(tiles: &[u8], palette: GbaPalette) -> Result<Self> {
131 Self::build(3, tiles, palette)
132 }
133
134 pub fn build_32px(tiles: &[u8], palette: GbaPalette) -> Result<Self> {
140 Self::build(4, tiles, palette)
141 }
142
143 pub fn build_64px(tiles: &[u8], palette: GbaPalette) -> Result<Self> {
149 Self::build(8, tiles, palette)
150 }
151
152 pub fn dimensions(&self) -> (u32, u32) {
154 (self.width * 8, self.height * 8)
155 }
156
157 pub fn get_palette(&self) -> &GbaPalette {
159 &self.palette
160 }
161
162 pub fn convert_palette(&self) -> [u32; PALETTE_SIZE] {
164 let mut out = [0; PALETTE_SIZE];
165 for (dest, c) in out.iter_mut().zip(self.palette.colors.iter()) {
166 *dest = rgb15_to_32(*c);
167 }
168 out[0] &= 0xFFFFFF;
170 out
171 }
172
173 pub fn pixels_4bpp(&self) -> Vec<u8> {
183 let size_bytes = (self.width * self.height) as usize * BYTES_PER_TILE;
184 let mut out = Vec::with_capacity(size_bytes);
185 for tile_y in 0..self.height as usize {
186 let tile_row = tile_y * BYTES_PER_TILE * self.width as usize;
187 for pix_y in 0..8usize {
188 for tile_x in 0..self.width as usize {
189 let off = tile_row + tile_x * BYTES_PER_TILE + pix_y * 4;
190 out.extend_from_slice(&self.tiles[off..off + 4]);
191 }
192 }
193 }
194 out
195 }
196
197 pub fn pixels_16bpp(&self) -> Vec<u16> {
202 let pal = &self.palette.colors;
203 self.pixels_4bpp()
204 .iter()
205 .flat_map(|b| {
206 let bo = *b as usize;
207 [pal[bo & 0xF], pal[bo >> 4]]
208 })
209 .collect()
210 }
211
212 #[cfg(feature = "png")]
219 pub fn write_png<T: Write>(&self, file: T) -> Result<()> {
220 let pal_bytes: Vec<u8> = self
221 .palette
222 .colors
223 .iter()
224 .flat_map(|c| rgb15_to_24(*c))
225 .collect();
226 let alpha = {
227 let mut alpha = [0xFFu8; 16];
228 alpha[0] = 0;
229 alpha
230 };
231 let data: Vec<u8> = self
234 .pixels_4bpp()
235 .iter()
236 .map(|b| b.rotate_right(4))
237 .collect();
238
239 let mut encoder = Encoder::new(file, self.width * 8, self.height * 8);
240 encoder.set_color(ColorType::Indexed);
241 encoder.set_depth(BitDepth::Four);
242 encoder.set_trns(&alpha);
243 encoder.set_palette(pal_bytes);
244
245 let mut writer = encoder.write_header()?;
246 Ok(writer.write_image_data(&data)?)
247 }
248}
249
250impl GbaBackground {
251 pub fn build(
258 tile_width: u32,
259 pal_offset: u32,
260 tiles: &[u8],
261 palettes: &[GbaPalette],
262 tilemap: &[u16],
263 ) -> Result<Self> {
264 if tilemap.len() % tile_width as usize != 0 {
265 return Err(GraphicsError::DimensionMismatch);
266 }
267 Ok(Self {
268 width: tile_width,
269 height: tilemap.len() as u32 / tile_width,
270 pal_offset,
271 tiles: tiles.to_vec(),
272 palettes: palettes.to_vec(),
273 tilemap: tilemap.to_vec(),
274 })
275 }
276
277 pub fn dimensions(&self) -> (u32, u32) {
279 (self.width * 8, self.height * 8)
280 }
281
282 pub fn convert_palette(&self) -> Vec<u32> {
284 let mut out = Vec::with_capacity(self.palettes.len() * 16);
285 for pal in &self.palettes {
286 for (idx, color) in pal.colors.iter().enumerate() {
287 if idx == 0 {
288 out.push(rgb15_to_32(*color) & 0xFFFFFF);
290 } else {
291 out.push(rgb15_to_32(*color));
292 }
293 }
294 }
295 out
296 }
297
298 pub fn pixels_8bpp(&self) -> Vec<u8> {
303 let size_bytes = (self.width * self.height) as usize * 8 * 8;
304 let mut out = vec![0; size_bytes];
305 let pal_limit = self.palettes.len() as u32 + self.pal_offset;
306 for ty in 0..self.height as usize {
307 for tx in 0..self.width as usize {
308 let tspec = self.tilemap[ty * self.width as usize + tx];
309 let pal = (tspec >> 12) as u8;
310 let tile_idx = (tspec & 0x3FF) as usize;
311 let xflip = (tspec & 0x400) != 0;
312 let yflip = (tspec & 0x800) != 0;
313 let dx = if xflip { -1 } else { 1 };
314 if (pal as u32) < self.pal_offset || (pal as u32) >= pal_limit {
315 continue;
316 }
317 if tile_idx * BYTES_PER_TILE > self.tiles.len() {
318 continue;
319 }
320 let pal = (pal - self.pal_offset as u8) * 16;
321 for py in 0..8 {
322 let y = ty * 8 + if yflip { 7 - py } else { py };
323 for px in 0..4 {
324 let x = tx * 8 + if xflip { 7 - px * 2 } else { px * 2 };
325 let dst = y * self.width as usize * 8 + x;
326 let cur_byte = self.tiles[tile_idx * 32 + py * 4 + px];
327 out[dst] = pal | cur_byte & 0xF;
328 out[(dst as isize + dx) as usize] = pal | cur_byte >> 4;
329 }
330 }
331 }
332 }
333 out
334 }
335
336 #[cfg(feature = "png")]
343 pub fn write_png<T: Write>(&self, file: T) -> Result<()> {
344 let pal_bytes: Vec<u8> = {
345 let mut bytes: Vec<u8> = Vec::with_capacity(self.palettes.len() * PALETTE_SIZE_BYTES);
346 for pal in &self.palettes {
347 bytes.extend(pal.colors.iter().flat_map(|c| rgb15_to_24(*c)));
348 }
349 bytes
350 };
351 let alpha = {
352 let mut alpha: Vec<u8> = vec![0xFFu8; 16 * self.palettes.len()];
353 for idx in 0..self.palettes.len() {
355 alpha[idx * 16] = 0;
356 }
357 alpha
358 };
359
360 let data: Vec<u8> = self.pixels_8bpp();
361 let mut encoder = Encoder::new(file, self.width * 8, self.height * 8);
362 encoder.set_color(ColorType::Indexed);
363 encoder.set_depth(BitDepth::Eight);
364 encoder.set_trns(&alpha);
365 encoder.set_palette(pal_bytes);
366
367 let mut writer = encoder.write_header()?;
368 writer.write_image_data(&data)?;
369 Ok(())
370 }
371}
372
373pub fn rgb15_to_32(color: u16) -> u32 {
379 let c32 = color as u32;
380 let r = (c32 & 31) * 255 / 31;
381 let g = ((c32 >> 5) & 31) * 255 / 31;
382 let b = ((c32 >> 10) & 31) * 255 / 31;
383
384 0xFF << 24 | r << 16 | g << 8 | b
385}
386
387pub fn rgb15_to_24(color: u16) -> [u8; 3] {
391 let c32 = color as u32;
392 let r = (c32 & 31) * 255 / 31;
393 let g = ((c32 >> 5) & 31) * 255 / 31;
394 let b = ((c32 >> 10) & 31) * 255 / 31;
395
396 [r as u8, g as u8, b as u8]
397}