unity_asset_decode/texture/decoders/
compressed.rs

1//! Compressed texture format decoders
2//!
3//! This module handles compressed texture formats like DXT1, DXT5, BC7, 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 compressed texture formats
13pub struct CompressedDecoder;
14
15impl CompressedDecoder {
16    /// Create a new compressed decoder
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// Decode DXT1 format
22    #[cfg(feature = "texture-advanced")]
23    fn decode_dxt1(&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_bc1(data, width as usize, height as usize, &mut output) {
29            Ok(_) => {
30                // Convert u32 RGBA to u8 RGBA
31                let rgba_data: Vec<u8> = output
32                    .iter()
33                    .flat_map(|&pixel| {
34                        [
35                            (pixel & 0xFF) as u8,         // R
36                            ((pixel >> 8) & 0xFF) as u8,  // G
37                            ((pixel >> 16) & 0xFF) as u8, // B
38                            ((pixel >> 24) & 0xFF) as u8, // A
39                        ]
40                    })
41                    .collect();
42
43                create_rgba_image(rgba_data, width, height)
44            }
45            Err(e) => Err(BinaryError::generic(format!("DXT1 decoding failed: {}", e))),
46        }
47    }
48
49    /// Decode DXT5 format
50    #[cfg(feature = "texture-advanced")]
51    fn decode_dxt5(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
52        validate_dimensions(width, height)?;
53
54        let mut output = vec![0u32; (width * height) as usize];
55
56        match texture2ddecoder::decode_bc3(data, width as usize, height as usize, &mut output) {
57            Ok(_) => {
58                // Convert u32 RGBA to u8 RGBA
59                let rgba_data: Vec<u8> = output
60                    .iter()
61                    .flat_map(|&pixel| {
62                        [
63                            (pixel & 0xFF) as u8,         // R
64                            ((pixel >> 8) & 0xFF) as u8,  // G
65                            ((pixel >> 16) & 0xFF) as u8, // B
66                            ((pixel >> 24) & 0xFF) as u8, // A
67                        ]
68                    })
69                    .collect();
70
71                create_rgba_image(rgba_data, width, height)
72            }
73            Err(e) => Err(BinaryError::generic(format!("DXT5 decoding failed: {}", e))),
74        }
75    }
76
77    /// Decode BC7 format
78    #[cfg(feature = "texture-advanced")]
79    fn decode_bc7(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
80        validate_dimensions(width, height)?;
81
82        let mut output = vec![0u32; (width * height) as usize];
83
84        match texture2ddecoder::decode_bc7(data, width as usize, height as usize, &mut output) {
85            Ok(_) => {
86                // Convert u32 RGBA to u8 RGBA
87                let rgba_data: Vec<u8> = output
88                    .iter()
89                    .flat_map(|&pixel| {
90                        [
91                            (pixel & 0xFF) as u8,         // R
92                            ((pixel >> 8) & 0xFF) as u8,  // G
93                            ((pixel >> 16) & 0xFF) as u8, // B
94                            ((pixel >> 24) & 0xFF) as u8, // A
95                        ]
96                    })
97                    .collect();
98
99                create_rgba_image(rgba_data, width, height)
100            }
101            Err(e) => Err(BinaryError::generic(format!("BC7 decoding failed: {}", e))),
102        }
103    }
104
105    /// Decode BC4 format (single channel)
106    #[cfg(feature = "texture-advanced")]
107    fn decode_bc4(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
108        validate_dimensions(width, height)?;
109
110        let mut output = vec![0u32; (width * height) as usize];
111
112        match texture2ddecoder::decode_bc4(data, width as usize, height as usize, &mut output) {
113            Ok(_) => {
114                // Convert u32 to u8 RGBA (BC4 is single channel, so replicate to RGB)
115                let rgba_data: Vec<u8> = output
116                    .iter()
117                    .flat_map(|&pixel| {
118                        let value = (pixel & 0xFF) as u8;
119                        [value, value, value, 255] // Replicate to RGB, full alpha
120                    })
121                    .collect();
122
123                create_rgba_image(rgba_data, width, height)
124            }
125            Err(e) => Err(BinaryError::generic(format!("BC4 decoding failed: {}", e))),
126        }
127    }
128
129    /// Decode BC5 format (two channel)
130    #[cfg(feature = "texture-advanced")]
131    fn decode_bc5(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
132        validate_dimensions(width, height)?;
133
134        let mut output = vec![0u32; (width * height) as usize];
135
136        match texture2ddecoder::decode_bc5(data, width as usize, height as usize, &mut output) {
137            Ok(_) => {
138                // Convert u32 to u8 RGBA (BC5 has RG channels)
139                let rgba_data: Vec<u8> = output
140                    .iter()
141                    .flat_map(|&pixel| {
142                        [
143                            (pixel & 0xFF) as u8,        // R
144                            ((pixel >> 8) & 0xFF) as u8, // G
145                            0,                           // B (not present in BC5)
146                            255,                         // A (full alpha)
147                        ]
148                    })
149                    .collect();
150
151                create_rgba_image(rgba_data, width, height)
152            }
153            Err(e) => Err(BinaryError::generic(format!("BC5 decoding failed: {}", e))),
154        }
155    }
156
157    /// Fallback for when texture-advanced feature is not enabled
158    #[cfg(not(feature = "texture-advanced"))]
159    fn decode_unsupported(&self, format: TextureFormat) -> Result<RgbaImage> {
160        Err(BinaryError::unsupported(format!(
161            "Compressed format {:?} requires texture-advanced feature",
162            format
163        )))
164    }
165}
166
167impl Decoder for CompressedDecoder {
168    fn decode(&self, texture: &Texture2D) -> Result<RgbaImage> {
169        let width = texture.width as u32;
170        let height = texture.height as u32;
171        let data = &texture.image_data;
172
173        match texture.format {
174            #[cfg(feature = "texture-advanced")]
175            TextureFormat::DXT1 => self.decode_dxt1(data, width, height),
176            #[cfg(feature = "texture-advanced")]
177            TextureFormat::DXT5 => self.decode_dxt5(data, width, height),
178            #[cfg(feature = "texture-advanced")]
179            TextureFormat::BC7 => self.decode_bc7(data, width, height),
180            #[cfg(feature = "texture-advanced")]
181            TextureFormat::BC4 => self.decode_bc4(data, width, height),
182            #[cfg(feature = "texture-advanced")]
183            TextureFormat::BC5 => self.decode_bc5(data, width, height),
184
185            #[cfg(not(feature = "texture-advanced"))]
186            format if format.is_compressed_format() => self.decode_unsupported(format),
187
188            _ => Err(BinaryError::unsupported(format!(
189                "Format {:?} is not a compressed format",
190                texture.format
191            ))),
192        }
193    }
194
195    fn can_decode(&self, format: TextureFormat) -> bool {
196        #[cfg(feature = "texture-advanced")]
197        {
198            matches!(
199                format,
200                TextureFormat::DXT1
201                    | TextureFormat::DXT5
202                    | TextureFormat::BC4
203                    | TextureFormat::BC5
204                    | TextureFormat::BC7
205            )
206        }
207
208        #[cfg(not(feature = "texture-advanced"))]
209        {
210            false
211        }
212    }
213
214    fn supported_formats(&self) -> Vec<TextureFormat> {
215        #[cfg(feature = "texture-advanced")]
216        {
217            vec![
218                TextureFormat::DXT1,
219                TextureFormat::DXT5,
220                TextureFormat::BC4,
221                TextureFormat::BC5,
222                TextureFormat::BC7,
223            ]
224        }
225
226        #[cfg(not(feature = "texture-advanced"))]
227        {
228            vec![]
229        }
230    }
231}
232
233impl Default for CompressedDecoder {
234    fn default() -> Self {
235        Self::new()
236    }
237}