unity_asset_decode/texture/decoders/
mobile.rs

1//! Mobile texture format decoders
2//!
3//! This module handles mobile-specific texture formats like ETC, ASTC, PVRTC, etc.
4//! Requires the texture-advanced feature for texture2ddecoder integration.
5
6use super::{Decoder, create_rgba_image, validate_dimensions};
7use crate::error::{BinaryError, Result};
8use crate::texture::formats::TextureFormat;
9use crate::texture::types::Texture2D;
10use image::RgbaImage;
11
12/// Decoder for mobile texture formats
13pub struct MobileDecoder;
14
15impl MobileDecoder {
16    /// Create a new mobile decoder
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// Decode ETC2 RGB format
22    #[cfg(feature = "texture-advanced")]
23    fn decode_etc2_rgb(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
24        validate_dimensions(width, height)?;
25
26        let mut output = vec![0u32; (width * height) as usize];
27
28        match texture2ddecoder::decode_etc2_rgb(data, width as usize, height as usize, &mut output)
29        {
30            Ok(_) => {
31                // Convert u32 RGBA to u8 RGBA
32                let rgba_data: Vec<u8> = output
33                    .iter()
34                    .flat_map(|&pixel| {
35                        [
36                            (pixel & 0xFF) as u8,         // R
37                            ((pixel >> 8) & 0xFF) as u8,  // G
38                            ((pixel >> 16) & 0xFF) as u8, // B
39                            255,                          // A (ETC2 RGB has no alpha)
40                        ]
41                    })
42                    .collect();
43
44                create_rgba_image(rgba_data, width, height)
45            }
46            Err(e) => Err(BinaryError::generic(format!(
47                "ETC2 RGB decoding failed: {}",
48                e
49            ))),
50        }
51    }
52
53    /// Decode ETC2 RGBA8 format
54    #[cfg(feature = "texture-advanced")]
55    fn decode_etc2_rgba8(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
56        validate_dimensions(width, height)?;
57
58        let mut output = vec![0u32; (width * height) as usize];
59
60        match texture2ddecoder::decode_etc2_rgba8(
61            data,
62            width as usize,
63            height as usize,
64            &mut output,
65        ) {
66            Ok(_) => {
67                // Convert u32 RGBA to u8 RGBA
68                let rgba_data: Vec<u8> = output
69                    .iter()
70                    .flat_map(|&pixel| {
71                        [
72                            (pixel & 0xFF) as u8,         // R
73                            ((pixel >> 8) & 0xFF) as u8,  // G
74                            ((pixel >> 16) & 0xFF) as u8, // B
75                            ((pixel >> 24) & 0xFF) as u8, // A
76                        ]
77                    })
78                    .collect();
79
80                create_rgba_image(rgba_data, width, height)
81            }
82            Err(e) => Err(BinaryError::generic(format!(
83                "ETC2 RGBA8 decoding failed: {}",
84                e
85            ))),
86        }
87    }
88
89    /// Decode ASTC 4x4 format
90    #[cfg(feature = "texture-advanced")]
91    fn decode_astc_4x4(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
92        validate_dimensions(width, height)?;
93
94        let mut output = vec![0u32; (width * height) as usize];
95
96        match texture2ddecoder::decode_astc(
97            data,
98            width as usize,
99            height as usize,
100            4,
101            4,
102            &mut output,
103        ) {
104            Ok(_) => {
105                // Convert u32 RGBA to u8 RGBA
106                let rgba_data: Vec<u8> = output
107                    .iter()
108                    .flat_map(|&pixel| {
109                        [
110                            (pixel & 0xFF) as u8,         // R
111                            ((pixel >> 8) & 0xFF) as u8,  // G
112                            ((pixel >> 16) & 0xFF) as u8, // B
113                            ((pixel >> 24) & 0xFF) as u8, // A
114                        ]
115                    })
116                    .collect();
117
118                create_rgba_image(rgba_data, width, height)
119            }
120            Err(e) => Err(BinaryError::generic(format!(
121                "ASTC 4x4 decoding failed: {}",
122                e
123            ))),
124        }
125    }
126
127    /// Decode ASTC 6x6 format
128    #[cfg(feature = "texture-advanced")]
129    fn decode_astc_6x6(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
130        validate_dimensions(width, height)?;
131
132        let mut output = vec![0u32; (width * height) as usize];
133
134        match texture2ddecoder::decode_astc(
135            data,
136            width as usize,
137            height as usize,
138            6,
139            6,
140            &mut output,
141        ) {
142            Ok(_) => {
143                // Convert u32 RGBA to u8 RGBA
144                let rgba_data: Vec<u8> = output
145                    .iter()
146                    .flat_map(|&pixel| {
147                        [
148                            (pixel & 0xFF) as u8,         // R
149                            ((pixel >> 8) & 0xFF) as u8,  // G
150                            ((pixel >> 16) & 0xFF) as u8, // B
151                            ((pixel >> 24) & 0xFF) as u8, // A
152                        ]
153                    })
154                    .collect();
155
156                create_rgba_image(rgba_data, width, height)
157            }
158            Err(e) => Err(BinaryError::generic(format!(
159                "ASTC 6x6 decoding failed: {}",
160                e
161            ))),
162        }
163    }
164
165    /// Decode ASTC 8x8 format
166    #[cfg(feature = "texture-advanced")]
167    fn decode_astc_8x8(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
168        validate_dimensions(width, height)?;
169
170        let mut output = vec![0u32; (width * height) as usize];
171
172        match texture2ddecoder::decode_astc(
173            data,
174            width as usize,
175            height as usize,
176            8,
177            8,
178            &mut output,
179        ) {
180            Ok(_) => {
181                // Convert u32 RGBA to u8 RGBA
182                let rgba_data: Vec<u8> = output
183                    .iter()
184                    .flat_map(|&pixel| {
185                        [
186                            (pixel & 0xFF) as u8,         // R
187                            ((pixel >> 8) & 0xFF) as u8,  // G
188                            ((pixel >> 16) & 0xFF) as u8, // B
189                            ((pixel >> 24) & 0xFF) as u8, // A
190                        ]
191                    })
192                    .collect();
193
194                create_rgba_image(rgba_data, width, height)
195            }
196            Err(e) => Err(BinaryError::generic(format!(
197                "ASTC 8x8 decoding failed: {}",
198                e
199            ))),
200        }
201    }
202
203    /// Fallback for when texture-advanced feature is not enabled
204    #[cfg(not(feature = "texture-advanced"))]
205    fn decode_unsupported(&self, format: TextureFormat) -> Result<RgbaImage> {
206        Err(BinaryError::unsupported(format!(
207            "Mobile format {:?} requires texture-advanced feature",
208            format
209        )))
210    }
211}
212
213impl Decoder for MobileDecoder {
214    fn decode(&self, texture: &Texture2D) -> Result<RgbaImage> {
215        let width = texture.width as u32;
216        let height = texture.height as u32;
217        let data = &texture.image_data;
218
219        match texture.format {
220            #[cfg(feature = "texture-advanced")]
221            TextureFormat::ETC2_RGB => self.decode_etc2_rgb(data, width, height),
222            #[cfg(feature = "texture-advanced")]
223            TextureFormat::ETC2_RGBA8 => self.decode_etc2_rgba8(data, width, height),
224            #[cfg(feature = "texture-advanced")]
225            TextureFormat::ASTC_RGBA_4x4 => self.decode_astc_4x4(data, width, height),
226            #[cfg(feature = "texture-advanced")]
227            TextureFormat::ASTC_RGBA_6x6 => self.decode_astc_6x6(data, width, height),
228            #[cfg(feature = "texture-advanced")]
229            TextureFormat::ASTC_RGBA_8x8 => self.decode_astc_8x8(data, width, height),
230
231            #[cfg(not(feature = "texture-advanced"))]
232            format if format.is_mobile_format() => self.decode_unsupported(format),
233
234            _ => Err(BinaryError::unsupported(format!(
235                "Format {:?} is not a mobile format",
236                texture.format
237            ))),
238        }
239    }
240
241    fn can_decode(&self, format: TextureFormat) -> bool {
242        #[cfg(feature = "texture-advanced")]
243        {
244            matches!(
245                format,
246                TextureFormat::ETC2_RGB
247                    | TextureFormat::ETC2_RGBA8
248                    | TextureFormat::ASTC_RGBA_4x4
249                    | TextureFormat::ASTC_RGBA_6x6
250                    | TextureFormat::ASTC_RGBA_8x8
251            )
252        }
253
254        #[cfg(not(feature = "texture-advanced"))]
255        {
256            false
257        }
258    }
259
260    fn supported_formats(&self) -> Vec<TextureFormat> {
261        #[cfg(feature = "texture-advanced")]
262        {
263            vec![
264                TextureFormat::ETC2_RGB,
265                TextureFormat::ETC2_RGBA8,
266                TextureFormat::ASTC_RGBA_4x4,
267                TextureFormat::ASTC_RGBA_6x6,
268                TextureFormat::ASTC_RGBA_8x8,
269            ]
270        }
271
272        #[cfg(not(feature = "texture-advanced"))]
273        {
274            vec![]
275        }
276    }
277}
278
279impl Default for MobileDecoder {
280    fn default() -> Self {
281        Self::new()
282    }
283}