unity_asset_decode/texture/helpers/
export.rs

1//! Texture export utilities
2//!
3//! This module provides functionality for exporting textures to various image formats.
4
5use crate::error::{BinaryError, Result};
6use image::{ImageFormat, RgbaImage};
7use std::path::Path;
8
9/// Texture exporter utility
10///
11/// This struct provides methods for exporting texture data to various image formats.
12pub struct TextureExporter;
13
14impl TextureExporter {
15    /// Export texture as PNG
16    ///
17    /// This is the most common export format, providing lossless compression
18    /// with full alpha channel support.
19    pub fn export_png<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
20        image
21            .save_with_format(path, ImageFormat::Png)
22            .map_err(|e| BinaryError::generic(format!("Failed to save PNG: {}", e)))
23    }
24
25    /// Export texture as JPEG
26    ///
27    /// Note: JPEG does not support alpha channel, so alpha will be lost.
28    pub fn export_jpeg<P: AsRef<Path>>(image: &RgbaImage, path: P, quality: u8) -> Result<()> {
29        // Convert RGBA to RGB for JPEG (no alpha support)
30        let rgb_image = image::DynamicImage::ImageRgba8(image.clone()).to_rgb8();
31
32        let mut output = std::fs::File::create(path)
33            .map_err(|e| BinaryError::generic(format!("Failed to create output file: {}", e)))?;
34
35        let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut output, quality);
36        encoder
37            .encode_image(&rgb_image)
38            .map_err(|e| BinaryError::generic(format!("Failed to encode JPEG: {}", e)))
39    }
40
41    /// Export texture as BMP
42    pub fn export_bmp<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
43        image
44            .save_with_format(path, ImageFormat::Bmp)
45            .map_err(|e| BinaryError::generic(format!("Failed to save BMP: {}", e)))
46    }
47
48    /// Export texture as TIFF
49    pub fn export_tiff<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
50        image
51            .save_with_format(path, ImageFormat::Tiff)
52            .map_err(|e| BinaryError::generic(format!("Failed to save TIFF: {}", e)))
53    }
54
55    /// Export texture with automatic format detection based on file extension
56    pub fn export_auto<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
57        let path_ref = path.as_ref();
58        let extension = path_ref
59            .extension()
60            .and_then(|ext| ext.to_str())
61            .unwrap_or("")
62            .to_lowercase();
63
64        match extension.as_str() {
65            "png" => Self::export_png(image, path),
66            "jpg" | "jpeg" => Self::export_jpeg(image, path, 90), // Default quality 90
67            "bmp" => Self::export_bmp(image, path),
68            "tif" | "tiff" => Self::export_tiff(image, path),
69            _ => {
70                // Default to PNG for unknown extensions
71                Self::export_png(image, path)
72            }
73        }
74    }
75
76    /// Export texture with custom format and options
77    pub fn export_with_format<P: AsRef<Path>>(
78        image: &RgbaImage,
79        path: P,
80        format: ImageFormat,
81    ) -> Result<()> {
82        image.save_with_format(path, format).map_err(|e| {
83            BinaryError::generic(format!(
84                "Failed to save image with format {:?}: {}",
85                format, e
86            ))
87        })
88    }
89
90    /// Get supported export formats
91    pub fn supported_formats() -> Vec<&'static str> {
92        vec!["png", "jpg", "jpeg", "bmp", "tiff", "tif"]
93    }
94
95    /// Check if a format is supported for export
96    pub fn is_format_supported(extension: &str) -> bool {
97        Self::supported_formats().contains(&extension.to_lowercase().as_str())
98    }
99
100    /// Create a filename with the given base name and format extension
101    pub fn create_filename(base_name: &str, format: &str) -> String {
102        let clean_base =
103            base_name.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-');
104        format!("{}.{}", clean_base, format.to_lowercase())
105    }
106
107    /// Validate that the image has valid dimensions for export
108    pub fn validate_for_export(image: &RgbaImage) -> Result<()> {
109        let (width, height) = image.dimensions();
110
111        if width == 0 || height == 0 {
112            return Err(BinaryError::invalid_data("Image has zero dimensions"));
113        }
114
115        // Check for reasonable size limits (prevent memory issues)
116        if width > 32768 || height > 32768 {
117            return Err(BinaryError::invalid_data(
118                "Image dimensions too large for export",
119            ));
120        }
121
122        Ok(())
123    }
124
125    /// Export with validation
126    pub fn export_validated<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
127        Self::validate_for_export(image)?;
128        Self::export_auto(image, path)
129    }
130}
131
132/// Export options for advanced export scenarios
133#[derive(Debug, Clone)]
134pub struct ExportOptions {
135    pub format: ImageFormat,
136    pub quality: Option<u8>,     // For JPEG
137    pub compression: Option<u8>, // For PNG
138}
139
140impl Default for ExportOptions {
141    fn default() -> Self {
142        Self {
143            format: ImageFormat::Png,
144            quality: Some(90),
145            compression: Some(6),
146        }
147    }
148}
149
150impl ExportOptions {
151    /// Create PNG export options
152    pub fn png() -> Self {
153        Self {
154            format: ImageFormat::Png,
155            quality: None,
156            compression: Some(6),
157        }
158    }
159
160    /// Create JPEG export options with quality
161    pub fn jpeg(quality: u8) -> Self {
162        Self {
163            format: ImageFormat::Jpeg,
164            quality: Some(quality.clamp(1, 100)),
165            compression: None,
166        }
167    }
168
169    /// Create BMP export options
170    pub fn bmp() -> Self {
171        Self {
172            format: ImageFormat::Bmp,
173            quality: None,
174            compression: None,
175        }
176    }
177
178    /// Export with these options
179    pub fn export<P: AsRef<Path>>(&self, image: &RgbaImage, path: P) -> Result<()> {
180        match self.format {
181            ImageFormat::Jpeg => {
182                let quality = self.quality.unwrap_or(90);
183                TextureExporter::export_jpeg(image, path, quality)
184            }
185            _ => TextureExporter::export_with_format(image, path, self.format),
186        }
187    }
188}