1use crate::palette::{GbColor, Palette};
8use dotzuki_engine::render::Rgba;
9
10pub const BYTES_PER_TILE_ROW: usize = 2;
12pub const BYTES_PER_TILE: usize = 16;
14pub const TILE_PIXELS: usize = 8;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum TileFormat {
20 Gb2bpp,
22 Gba4bpp,
25 FullColor,
27}
28
29#[derive(Debug, Clone)]
31pub struct Tile {
32 pub pixels: [[u8; TILE_PIXELS]; TILE_PIXELS],
35}
36
37impl Tile {
38 pub fn from_2bpp(data: &[u8]) -> Self {
44 assert!(
45 data.len() >= BYTES_PER_TILE,
46 "Need {} bytes for a tile, got {}",
47 BYTES_PER_TILE,
48 data.len()
49 );
50 let mut pixels = [[0u8; TILE_PIXELS]; TILE_PIXELS];
51 for row in 0..TILE_PIXELS {
52 let lo = data[row * 2];
53 let hi = data[row * 2 + 1];
54 for col in 0..TILE_PIXELS {
55 let bit = 7 - col;
56 let color_index = ((hi >> bit) & 1) << 1 | ((lo >> bit) & 1);
57 pixels[row][col] = color_index;
58 }
59 }
60 Self { pixels }
61 }
62
63 pub fn blank() -> Self {
65 Self {
66 pixels: [[0; TILE_PIXELS]; TILE_PIXELS],
67 }
68 }
69
70 #[inline]
72 pub fn get(&self, row: usize, col: usize) -> u8 {
73 self.pixels[row][col]
74 }
75
76 pub fn render_row(&self, row: usize, palette: &Palette) -> [Rgba; TILE_PIXELS] {
79 let mut out = [Rgba::TRANSPARENT; TILE_PIXELS];
80 for col in 0..TILE_PIXELS {
81 let color_idx = GbColor::from_u8(self.pixels[row][col]);
82 out[col] = palette.color(color_idx);
83 }
84 out
85 }
86
87 pub fn flip_y(&self) -> Tile {
89 let mut pixels = [[0u8; TILE_PIXELS]; TILE_PIXELS];
90 for row in 0..TILE_PIXELS {
91 pixels[row] = self.pixels[TILE_PIXELS - 1 - row];
92 }
93 Tile { pixels }
94 }
95
96 pub fn flip_x(&self) -> Tile {
98 let mut pixels = [[0u8; TILE_PIXELS]; TILE_PIXELS];
99 for row in 0..TILE_PIXELS {
100 for col in 0..TILE_PIXELS {
101 pixels[row][col] = self.pixels[row][TILE_PIXELS - 1 - col];
102 }
103 }
104 Tile { pixels }
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct TileSet {
111 tiles: Vec<Tile>,
112 format: TileFormat,
113 rgba_tiles: Vec<RgbaTile>,
114}
115
116impl TileSet {
117 pub fn from_2bpp(data: &[u8]) -> Self {
120 assert!(
121 data.len() % BYTES_PER_TILE == 0,
122 "Tile data length {} is not a multiple of {}",
123 data.len(),
124 BYTES_PER_TILE,
125 );
126 let count = data.len() / BYTES_PER_TILE;
127 let mut tiles = Vec::with_capacity(count);
128 for i in 0..count {
129 let start = i * BYTES_PER_TILE;
130 tiles.push(Tile::from_2bpp(&data[start..start + BYTES_PER_TILE]));
131 }
132 Self {
133 tiles,
134 format: TileFormat::Gb2bpp,
135 rgba_tiles: Vec::new(),
136 }
137 }
138
139 pub fn blank(count: usize) -> Self {
141 Self {
142 tiles: vec![Tile::blank(); count],
143 format: TileFormat::Gb2bpp,
144 rgba_tiles: Vec::new(),
145 }
146 }
147
148 pub fn get(&self, index: usize) -> &Tile {
150 if index < self.tiles.len() {
151 &self.tiles[index]
152 } else {
153 static BLANK: Tile = Tile {
156 pixels: [[0; TILE_PIXELS]; TILE_PIXELS],
157 };
158 &BLANK
159 }
160 }
161
162 pub fn len(&self) -> usize {
164 self.tiles.len()
165 }
166
167 pub fn is_empty(&self) -> bool {
169 self.tiles.is_empty()
170 }
171
172 pub fn set(&mut self, index: usize, tile: Tile) {
174 if index < self.tiles.len() {
175 self.tiles[index] = tile;
176 }
177 }
178
179 pub fn load_2bpp_at(&mut self, start_tile: usize, data: &[u8]) {
182 let count = data.len() / BYTES_PER_TILE;
183 for i in 0..count {
184 let tile_idx = start_tile + i;
185 if tile_idx >= self.tiles.len() {
186 break;
187 }
188 let start = i * BYTES_PER_TILE;
189 self.tiles[tile_idx] = Tile::from_2bpp(&data[start..start + BYTES_PER_TILE]);
190 }
191 }
192
193 pub fn from_1bpp(data: &[u8]) -> Self {
196 let bytes_per_tile_1bpp = 8;
197 assert!(
198 data.len() % bytes_per_tile_1bpp == 0,
199 "1bpp tile data length {} is not a multiple of {}",
200 data.len(),
201 bytes_per_tile_1bpp,
202 );
203 let count = data.len() / bytes_per_tile_1bpp;
204 let mut tiles = Vec::with_capacity(count);
205 for i in 0..count {
206 let mut pixels = [[0u8; TILE_PIXELS]; TILE_PIXELS];
207 for row in 0..TILE_PIXELS {
208 let byte = data[i * bytes_per_tile_1bpp + row];
209 for col in 0..TILE_PIXELS {
210 let bit = 7 - col;
211 pixels[row][col] = if (byte >> bit) & 1 == 1 { 3 } else { 0 };
213 }
214 }
215 tiles.push(Tile { pixels });
216 }
217 Self {
218 tiles,
219 format: TileFormat::Gb2bpp,
220 rgba_tiles: Vec::new(),
221 }
222 }
223
224 pub fn from_4bpp(data: &[u8]) -> Self {
232 const BYTES_PER_TILE_4BPP: usize = 32;
233 assert!(
234 data.len() % BYTES_PER_TILE_4BPP == 0,
235 "4bpp tile data length {} is not a multiple of {}",
236 data.len(),
237 BYTES_PER_TILE_4BPP,
238 );
239 let count = data.len() / BYTES_PER_TILE_4BPP;
240 let mut tiles = Vec::with_capacity(count);
241 for ti in 0..count {
242 let base = ti * BYTES_PER_TILE_4BPP;
243 let mut pixels = [[0u8; TILE_PIXELS]; TILE_PIXELS];
244 for row in 0..TILE_PIXELS {
245 let p0_lo = data[base + row * 2];
246 let p0_hi = data[base + row * 2 + 1];
247 let p1_lo = data[base + 16 + row * 2];
248 let p1_hi = data[base + 16 + row * 2 + 1];
249 for col in 0..TILE_PIXELS {
250 let bit = 7 - col;
251 let p0_color = ((p0_hi >> bit) & 1) << 1 | ((p0_lo >> bit) & 1);
253 let p1_color = ((p1_hi >> bit) & 1) << 1 | ((p1_lo >> bit) & 1);
255 pixels[row][col] = (p1_color << 2) | p0_color;
257 }
258 }
259 tiles.push(Tile { pixels });
260 }
261 Self {
262 tiles,
263 format: TileFormat::Gba4bpp,
264 rgba_tiles: Vec::new(),
265 }
266 }
267
268 pub fn from_rgba(pixels: &[Rgba], tile_count: usize) -> Self {
276 assert!(
277 pixels.len() >= tile_count * TILE_PIXELS * TILE_PIXELS,
278 "RGBA pixel data length {} is insufficient for {} tiles (need {})",
279 pixels.len(),
280 tile_count,
281 tile_count * TILE_PIXELS * TILE_PIXELS,
282 );
283 let mut rgba_tiles = Vec::with_capacity(tile_count);
284 for ti in 0..tile_count {
285 let base = ti * TILE_PIXELS * TILE_PIXELS;
286 let mut rgba_pixels = [[Rgba::TRANSPARENT; TILE_PIXELS]; TILE_PIXELS];
287 for row in 0..TILE_PIXELS {
288 for col in 0..TILE_PIXELS {
289 rgba_pixels[row][col] = pixels[base + row * TILE_PIXELS + col];
290 }
291 }
292 rgba_tiles.push(RgbaTile { pixels: rgba_pixels });
293 }
294 Self {
295 tiles: vec![Tile::blank(); tile_count],
296 format: TileFormat::FullColor,
297 rgba_tiles,
298 }
299 }
300
301 pub fn tile_format(&self) -> TileFormat {
303 self.format
304 }
305
306 pub fn get_rgba(&self, index: usize) -> Option<&RgbaTile> {
309 if self.format != TileFormat::FullColor {
310 return None;
311 }
312 self.rgba_tiles.get(index)
313 }
314}
315
316pub fn decode_2bpp_row(lo: u8, hi: u8) -> [u8; TILE_PIXELS] {
318 let mut out = [0u8; TILE_PIXELS];
319 for col in 0..TILE_PIXELS {
320 let bit = 7 - col;
321 out[col] = ((hi >> bit) & 1) << 1 | ((lo >> bit) & 1);
322 }
323 out
324}
325
326#[derive(Debug, Clone)]
336pub struct RgbaTile {
337 pub pixels: [[Rgba; TILE_PIXELS]; TILE_PIXELS],
340}
341
342impl RgbaTile {
343 pub fn blank() -> Self {
345 Self {
346 pixels: [[Rgba::TRANSPARENT; TILE_PIXELS]; TILE_PIXELS],
347 }
348 }
349
350 #[inline]
352 pub fn get(&self, row: usize, col: usize) -> Rgba {
353 self.pixels[row][col]
354 }
355
356 #[inline]
358 pub fn render_row(&self, row: usize) -> [Rgba; TILE_PIXELS] {
359 self.pixels[row]
360 }
361}
362
363#[derive(Debug, Clone)]
368pub struct RgbaTileSet {
369 tiles: Vec<RgbaTile>,
370}
371
372static BLANK_RGBA_TILE: RgbaTile = RgbaTile {
374 pixels: [[Rgba::TRANSPARENT; TILE_PIXELS]; TILE_PIXELS],
375};
376
377impl RgbaTileSet {
378 #[cfg(feature = "gpu")]
386 pub fn from_rgba_png(png_data: &[u8]) -> Result<Self, String> {
387 use image::GenericImageView;
388
389 let img = image::load_from_memory(png_data)
390 .map_err(|e| format!("Failed to decode PNG: {}", e))?;
391 let (w, h) = img.dimensions();
392 if w % TILE_PIXELS as u32 != 0 || h % TILE_PIXELS as u32 != 0 {
393 return Err(format!(
394 "PNG dimensions {}×{} are not multiples of {}",
395 w, h, TILE_PIXELS
396 ));
397 }
398
399 let rgba = img.to_rgba8();
400 let tiles_x = (w / TILE_PIXELS as u32) as usize;
401 let tiles_y = (h / TILE_PIXELS as u32) as usize;
402 let mut tiles = Vec::with_capacity(tiles_x * tiles_y);
403
404 for ty in 0..tiles_y {
405 for tx in 0..tiles_x {
406 let mut pixels = [[Rgba::TRANSPARENT; TILE_PIXELS]; TILE_PIXELS];
407 let base_x = (tx * TILE_PIXELS) as u32;
408 let base_y = (ty * TILE_PIXELS) as u32;
409 for row in 0..TILE_PIXELS {
410 for col in 0..TILE_PIXELS {
411 let px = rgba.get_pixel(base_x + col as u32, base_y + row as u32);
412 pixels[row][col] = Rgba::from([px[0], px[1], px[2], px[3]]);
413 }
414 }
415 tiles.push(RgbaTile { pixels });
416 }
417 }
418
419 Ok(Self { tiles })
420 }
421
422 pub fn get(&self, index: usize) -> &RgbaTile {
424 if index < self.tiles.len() {
425 &self.tiles[index]
426 } else {
427 &BLANK_RGBA_TILE
428 }
429 }
430
431 pub fn len(&self) -> usize {
433 self.tiles.len()
434 }
435
436 pub fn is_empty(&self) -> bool {
438 self.tiles.is_empty()
439 }
440}