unity_asset_decode/texture/decoders/
mod.rs

1//! Texture decoders module
2//!
3//! This module provides specialized decoders for different texture formats,
4//! organized by format category for better maintainability.
5
6mod basic;
7mod compressed;
8mod crunch;
9mod mobile;
10
11pub use basic::BasicDecoder;
12pub use compressed::CompressedDecoder;
13pub use crunch::CrunchDecoder;
14pub use mobile::MobileDecoder;
15
16use super::formats::TextureFormat;
17use super::types::Texture2D;
18use crate::error::{BinaryError, Result};
19use image::RgbaImage;
20
21/// Main texture decoder dispatcher
22///
23/// This struct coordinates between different specialized decoders
24/// based on the texture format type.
25pub struct TextureDecoder {
26    basic: BasicDecoder,
27    compressed: CompressedDecoder,
28    mobile: MobileDecoder,
29    crunch: CrunchDecoder,
30}
31
32impl TextureDecoder {
33    /// Create a new texture decoder
34    pub fn new() -> Self {
35        Self {
36            basic: BasicDecoder::new(),
37            compressed: CompressedDecoder::new(),
38            mobile: MobileDecoder::new(),
39            crunch: CrunchDecoder::new(),
40        }
41    }
42
43    /// Decode texture to RGBA image
44    ///
45    /// This method dispatches to the appropriate specialized decoder
46    /// based on the texture format.
47    pub fn decode(&self, texture: &Texture2D) -> Result<RgbaImage> {
48        // Validate texture first
49        texture
50            .validate()
51            .map_err(|e| BinaryError::invalid_data(&e))?;
52
53        // Handle Crunch compression first (it can wrap other formats)
54        if texture.format.is_crunch_compressed() {
55            return self.crunch.decode(texture);
56        }
57
58        // Dispatch to appropriate decoder based on format category
59        if texture.format.is_basic_format() {
60            self.basic.decode(texture)
61        } else if texture.format.is_compressed_format() {
62            self.compressed.decode(texture)
63        } else if texture.format.is_mobile_format() {
64            self.mobile.decode(texture)
65        } else {
66            Err(BinaryError::unsupported(format!(
67                "Unsupported texture format: {:?}",
68                texture.format
69            )))
70        }
71    }
72
73    /// Check if a format can be decoded
74    pub fn can_decode(&self, format: TextureFormat) -> bool {
75        format.is_basic_format()
76            || format.is_compressed_format()
77            || format.is_mobile_format()
78            || format.is_crunch_compressed()
79    }
80
81    /// Get list of supported formats
82    pub fn supported_formats(&self) -> Vec<TextureFormat> {
83        vec![
84            // Basic formats
85            TextureFormat::Alpha8,
86            TextureFormat::RGB24,
87            TextureFormat::RGBA32,
88            TextureFormat::ARGB32,
89            TextureFormat::BGRA32,
90            TextureFormat::RGBA4444,
91            TextureFormat::ARGB4444,
92            TextureFormat::RGB565,
93            // Compressed formats (when texture-advanced feature is enabled)
94            #[cfg(feature = "texture-advanced")]
95            TextureFormat::DXT1,
96            #[cfg(feature = "texture-advanced")]
97            TextureFormat::DXT5,
98            #[cfg(feature = "texture-advanced")]
99            TextureFormat::BC7,
100            // Mobile formats (when texture-advanced feature is enabled)
101            #[cfg(feature = "texture-advanced")]
102            TextureFormat::ETC2_RGB,
103            #[cfg(feature = "texture-advanced")]
104            TextureFormat::ETC2_RGBA8,
105            #[cfg(feature = "texture-advanced")]
106            TextureFormat::ASTC_RGBA_4x4,
107            // Crunch formats (when texture-advanced feature is enabled)
108            #[cfg(feature = "texture-advanced")]
109            TextureFormat::DXT1Crunched,
110            #[cfg(feature = "texture-advanced")]
111            TextureFormat::DXT5Crunched,
112        ]
113    }
114}
115
116impl Default for TextureDecoder {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122/// Common decoder trait
123///
124/// This trait defines the interface that all specialized decoders must implement.
125pub trait Decoder {
126    /// Decode texture data to RGBA image
127    fn decode(&self, texture: &Texture2D) -> Result<RgbaImage>;
128
129    /// Check if this decoder can handle the given format
130    fn can_decode(&self, format: TextureFormat) -> bool;
131
132    /// Get list of formats supported by this decoder
133    fn supported_formats(&self) -> Vec<TextureFormat>;
134}
135
136/// Helper function to create RGBA image from raw data
137pub(crate) fn create_rgba_image(data: Vec<u8>, width: u32, height: u32) -> Result<RgbaImage> {
138    if data.len() != (width * height * 4) as usize {
139        return Err(BinaryError::invalid_data(format!(
140            "Invalid data size: expected {}, got {}",
141            width * height * 4,
142            data.len()
143        )));
144    }
145
146    RgbaImage::from_raw(width, height, data)
147        .ok_or_else(|| BinaryError::invalid_data("Failed to create RGBA image from raw data"))
148}
149
150/// Helper function to validate dimensions
151pub(crate) fn validate_dimensions(width: u32, height: u32) -> Result<()> {
152    if width == 0 || height == 0 {
153        return Err(BinaryError::invalid_data("Invalid texture dimensions"));
154    }
155
156    // Reasonable size limits to prevent memory issues
157    if width > 16384 || height > 16384 {
158        return Err(BinaryError::invalid_data("Texture dimensions too large"));
159    }
160
161    Ok(())
162}