Skip to main content

gcrecomp_runtime/texture/
formats.rs

1// GameCube texture format support
2use anyhow::Result;
3use image::{DynamicImage, RgbaImage};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum GameCubeTextureFormat {
7    CMPR,   // Compressed (S3TC/DXT1)
8    I4,     // 4-bit intensity
9    I8,     // 8-bit intensity
10    IA4,    // 4-bit intensity + alpha
11    IA8,    // 8-bit intensity + alpha
12    RGB565, // 16-bit RGB
13    RGB5A3, // 16-bit RGB + alpha
14    RGBA8,  // 32-bit RGBA
15}
16
17impl GameCubeTextureFormat {
18    pub fn from_gx_format(format: u8) -> Option<Self> {
19        match format {
20            0x00 => Some(Self::I4),
21            0x01 => Some(Self::I8),
22            0x02 => Some(Self::IA4),
23            0x03 => Some(Self::IA8),
24            0x04 => Some(Self::RGB565),
25            0x05 => Some(Self::RGB5A3),
26            0x06 => Some(Self::RGBA8),
27            0x08 => Some(Self::CMPR),
28            _ => None,
29        }
30    }
31
32    pub fn decode(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
33        match self {
34            Self::CMPR => Self::decode_cmpr(data, width, height),
35            Self::I4 => Self::decode_i4(data, width, height),
36            Self::I8 => Self::decode_i8(data, width, height),
37            Self::IA4 => Self::decode_ia4(data, width, height),
38            Self::IA8 => Self::decode_ia8(data, width, height),
39            Self::RGB565 => Self::decode_rgb565(data, width, height),
40            Self::RGB5A3 => Self::decode_rgb5a3(data, width, height),
41            Self::RGBA8 => Self::decode_rgba8(data, width, height),
42        }
43    }
44
45    fn decode_cmpr(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
46        // CMPR is DXT1/S3TC compression
47        // Would need DXT decoder
48        let mut image = RgbaImage::new(width, height);
49        // Placeholder - would decode DXT1
50        Ok(image)
51    }
52
53    fn decode_i4(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
54        let mut image = RgbaImage::new(width, height);
55        let pixels_per_byte = 2;
56        let mut data_idx = 0;
57
58        for y in 0..height {
59            for x in 0..width {
60                let byte_idx = (y * width + x) / pixels_per_byte;
61                if (byte_idx as usize) < data.len() {
62                    let byte = data[byte_idx as usize];
63                    let pixel_idx = (x % pixels_per_byte) as usize;
64                    let intensity = if pixel_idx == 0 {
65                        ((byte >> 4) & 0xF) * 17
66                    } else {
67                        (byte & 0xF) * 17
68                    };
69
70                    image.put_pixel(x, y, image::Rgba([intensity, intensity, intensity, 255]));
71                }
72                data_idx += 1;
73            }
74        }
75
76        Ok(image)
77    }
78
79    fn decode_i8(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
80        let mut image = RgbaImage::new(width, height);
81        let mut data_idx = 0;
82
83        for y in 0..height {
84            for x in 0..width {
85                if data_idx < data.len() {
86                    let intensity = data[data_idx];
87                    image.put_pixel(x, y, image::Rgba([intensity, intensity, intensity, 255]));
88                    data_idx += 1;
89                }
90            }
91        }
92
93        Ok(image)
94    }
95
96    fn decode_ia4(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
97        let mut image = RgbaImage::new(width, height);
98        let pixels_per_byte = 2;
99
100        for y in 0..height {
101            for x in 0..width {
102                let byte_idx = ((y * width + x) / pixels_per_byte) as usize;
103                if byte_idx < data.len() {
104                    let byte = data[byte_idx];
105                    let pixel_idx = (x % pixels_per_byte) as usize;
106                    let (intensity, alpha) = if pixel_idx == 0 {
107                        (((byte >> 4) & 0xF) * 17, ((byte >> 7) & 0x1) * 255)
108                    } else {
109                        ((byte & 0xF) * 17, ((byte >> 3) & 0x1) * 255)
110                    };
111
112                    image.put_pixel(x, y, image::Rgba([intensity, intensity, intensity, alpha]));
113                }
114            }
115        }
116
117        Ok(image)
118    }
119
120    fn decode_ia8(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
121        let mut image = RgbaImage::new(width, height);
122        let mut data_idx = 0;
123
124        for y in 0..height {
125            for x in 0..width {
126                if data_idx + 1 < data.len() {
127                    let intensity = data[data_idx];
128                    let alpha = data[data_idx + 1];
129                    image.put_pixel(x, y, image::Rgba([intensity, intensity, intensity, alpha]));
130                    data_idx += 2;
131                }
132            }
133        }
134
135        Ok(image)
136    }
137
138    fn decode_rgb565(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
139        let mut image = RgbaImage::new(width, height);
140        let mut data_idx = 0;
141
142        for y in 0..height {
143            for x in 0..width {
144                if data_idx + 1 < data.len() {
145                    let word = u16::from_be_bytes([data[data_idx], data[data_idx + 1]]);
146                    let r = ((word >> 11) & 0x1F) as u8 * 8;
147                    let g = ((word >> 5) & 0x3F) as u8 * 4;
148                    let b = (word & 0x1F) as u8 * 8;
149                    image.put_pixel(x, y, image::Rgba([r, g, b, 255]));
150                    data_idx += 2;
151                }
152            }
153        }
154
155        Ok(image)
156    }
157
158    fn decode_rgb5a3(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
159        let mut image = RgbaImage::new(width, height);
160        let mut data_idx = 0;
161
162        for y in 0..height {
163            for x in 0..width {
164                if data_idx + 1 < data.len() {
165                    let word = u16::from_be_bytes([data[data_idx], data[data_idx + 1]]);
166                    if (word & 0x8000) != 0 {
167                        // RGB5 mode
168                        let r = ((word >> 10) & 0x1F) as u8 * 8;
169                        let g = ((word >> 5) & 0x1F) as u8 * 8;
170                        let b = (word & 0x1F) as u8 * 8;
171                        image.put_pixel(x, y, image::Rgba([r, g, b, 255]));
172                    } else {
173                        // RGB4A3 mode
174                        let a = ((word >> 12) & 0x7) as u8 * 32;
175                        let r = ((word >> 8) & 0xF) as u8 * 16;
176                        let g = ((word >> 4) & 0xF) as u8 * 16;
177                        let b = (word & 0xF) as u8 * 16;
178                        image.put_pixel(x, y, image::Rgba([r, g, b, a]));
179                    }
180                    data_idx += 2;
181                }
182            }
183        }
184
185        Ok(image)
186    }
187
188    fn decode_rgba8(data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
189        let mut image = RgbaImage::new(width, height);
190        let mut data_idx = 0;
191
192        for y in 0..height {
193            for x in 0..width {
194                if data_idx + 3 < data.len() {
195                    let r = data[data_idx];
196                    let g = data[data_idx + 1];
197                    let b = data[data_idx + 2];
198                    let a = data[data_idx + 3];
199                    image.put_pixel(x, y, image::Rgba([r, g, b, a]));
200                    data_idx += 4;
201                }
202            }
203        }
204
205        Ok(image)
206    }
207}