unity_asset_decode/texture/decoders/
crunch.rs

1//! Crunch compressed texture format decoders
2//!
3//! This module handles Crunch-compressed texture formats.
4//! Crunch is Unity's proprietary compression that can wrap other formats like DXT.
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 Crunch compressed texture formats
13pub struct CrunchDecoder;
14
15impl CrunchDecoder {
16    /// Create a new Crunch decoder
17    pub fn new() -> Self {
18        Self
19    }
20
21    /// Decompress Crunch compressed data
22    #[cfg(feature = "texture-advanced")]
23    fn decompress_crunch(&self, data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
24        validate_dimensions(width, height)?;
25
26        let mut output = vec![0u32; (width * height) as usize];
27
28        match texture2ddecoder::decode_crunch(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                Ok(rgba_data)
43            }
44            Err(e) => Err(BinaryError::generic(format!(
45                "Crunch decompression failed: {}",
46                e
47            ))),
48        }
49    }
50
51    /// Decode DXT1 Crunched format
52    #[cfg(feature = "texture-advanced")]
53    fn decode_dxt1_crunched(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
54        let rgba_data = self.decompress_crunch(data, width, height)?;
55        create_rgba_image(rgba_data, width, height)
56    }
57
58    /// Decode DXT5 Crunched format
59    #[cfg(feature = "texture-advanced")]
60    fn decode_dxt5_crunched(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
61        let rgba_data = self.decompress_crunch(data, width, height)?;
62        create_rgba_image(rgba_data, width, height)
63    }
64
65    /// Decode ETC RGB4 Crunched format
66    #[cfg(feature = "texture-advanced")]
67    fn decode_etc_rgb4_crunched(&self, data: &[u8], width: u32, height: u32) -> Result<RgbaImage> {
68        let rgba_data = self.decompress_crunch(data, width, height)?;
69        create_rgba_image(rgba_data, width, height)
70    }
71
72    /// Decode ETC2 RGBA8 Crunched format
73    #[cfg(feature = "texture-advanced")]
74    fn decode_etc2_rgba8_crunched(
75        &self,
76        data: &[u8],
77        width: u32,
78        height: u32,
79    ) -> Result<RgbaImage> {
80        let rgba_data = self.decompress_crunch(data, width, height)?;
81        create_rgba_image(rgba_data, width, height)
82    }
83
84    /// Fallback for when texture-advanced feature is not enabled
85    #[cfg(not(feature = "texture-advanced"))]
86    fn decode_unsupported(&self, format: TextureFormat) -> Result<RgbaImage> {
87        Err(BinaryError::unsupported(format!(
88            "Crunch format {:?} requires texture-advanced feature",
89            format
90        )))
91    }
92}
93
94impl Decoder for CrunchDecoder {
95    fn decode(&self, texture: &Texture2D) -> Result<RgbaImage> {
96        let width = texture.width as u32;
97        let height = texture.height as u32;
98        let data = &texture.image_data;
99
100        match texture.format {
101            #[cfg(feature = "texture-advanced")]
102            TextureFormat::DXT1Crunched => self.decode_dxt1_crunched(data, width, height),
103            #[cfg(feature = "texture-advanced")]
104            TextureFormat::DXT5Crunched => self.decode_dxt5_crunched(data, width, height),
105            #[cfg(feature = "texture-advanced")]
106            TextureFormat::ETC_RGB4Crunched => self.decode_etc_rgb4_crunched(data, width, height),
107            #[cfg(feature = "texture-advanced")]
108            TextureFormat::ETC2_RGBA8Crunched => {
109                self.decode_etc2_rgba8_crunched(data, width, height)
110            }
111
112            #[cfg(not(feature = "texture-advanced"))]
113            format if format.is_crunch_compressed() => self.decode_unsupported(format),
114
115            _ => Err(BinaryError::unsupported(format!(
116                "Format {:?} is not a Crunch format",
117                texture.format
118            ))),
119        }
120    }
121
122    fn can_decode(&self, format: TextureFormat) -> bool {
123        #[cfg(feature = "texture-advanced")]
124        {
125            format.is_crunch_compressed()
126        }
127
128        #[cfg(not(feature = "texture-advanced"))]
129        {
130            false
131        }
132    }
133
134    fn supported_formats(&self) -> Vec<TextureFormat> {
135        #[cfg(feature = "texture-advanced")]
136        {
137            vec![
138                TextureFormat::DXT1Crunched,
139                TextureFormat::DXT5Crunched,
140                TextureFormat::ETC_RGB4Crunched,
141                TextureFormat::ETC2_RGBA8Crunched,
142            ]
143        }
144
145        #[cfg(not(feature = "texture-advanced"))]
146        {
147            vec![]
148        }
149    }
150}
151
152impl Default for CrunchDecoder {
153    fn default() -> Self {
154        Self::new()
155    }
156}