use crate::error::{BinaryError, Result};
use image::{ImageFormat, RgbaImage};
use std::path::Path;
pub struct TextureExporter;
impl TextureExporter {
pub fn export_png<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
image
.save_with_format(path, ImageFormat::Png)
.map_err(|e| BinaryError::generic(format!("Failed to save PNG: {}", e)))
}
pub fn export_jpeg<P: AsRef<Path>>(image: &RgbaImage, path: P, quality: u8) -> Result<()> {
let rgb_image = image::DynamicImage::ImageRgba8(image.clone()).to_rgb8();
let mut output = std::fs::File::create(path)
.map_err(|e| BinaryError::generic(format!("Failed to create output file: {}", e)))?;
let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut output, quality);
encoder
.encode_image(&rgb_image)
.map_err(|e| BinaryError::generic(format!("Failed to encode JPEG: {}", e)))
}
pub fn export_bmp<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
image
.save_with_format(path, ImageFormat::Bmp)
.map_err(|e| BinaryError::generic(format!("Failed to save BMP: {}", e)))
}
pub fn export_tiff<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
image
.save_with_format(path, ImageFormat::Tiff)
.map_err(|e| BinaryError::generic(format!("Failed to save TIFF: {}", e)))
}
pub fn export_auto<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
let path_ref = path.as_ref();
let extension = path_ref
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
.to_lowercase();
match extension.as_str() {
"png" => Self::export_png(image, path),
"jpg" | "jpeg" => Self::export_jpeg(image, path, 90), "bmp" => Self::export_bmp(image, path),
"tif" | "tiff" => Self::export_tiff(image, path),
_ => {
Self::export_png(image, path)
}
}
}
pub fn export_with_format<P: AsRef<Path>>(
image: &RgbaImage,
path: P,
format: ImageFormat,
) -> Result<()> {
image.save_with_format(path, format).map_err(|e| {
BinaryError::generic(format!(
"Failed to save image with format {:?}: {}",
format, e
))
})
}
pub fn supported_formats() -> Vec<&'static str> {
vec!["png", "jpg", "jpeg", "bmp", "tiff", "tif"]
}
pub fn is_format_supported(extension: &str) -> bool {
Self::supported_formats().contains(&extension.to_lowercase().as_str())
}
pub fn create_filename(base_name: &str, format: &str) -> String {
let clean_base =
base_name.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-');
format!("{}.{}", clean_base, format.to_lowercase())
}
pub fn validate_for_export(image: &RgbaImage) -> Result<()> {
let (width, height) = image.dimensions();
if width == 0 || height == 0 {
return Err(BinaryError::invalid_data("Image has zero dimensions"));
}
if width > 32768 || height > 32768 {
return Err(BinaryError::invalid_data(
"Image dimensions too large for export",
));
}
Ok(())
}
pub fn export_validated<P: AsRef<Path>>(image: &RgbaImage, path: P) -> Result<()> {
Self::validate_for_export(image)?;
Self::export_auto(image, path)
}
}
#[derive(Debug, Clone)]
pub struct ExportOptions {
pub format: ImageFormat,
pub quality: Option<u8>, pub compression: Option<u8>, }
impl Default for ExportOptions {
fn default() -> Self {
Self {
format: ImageFormat::Png,
quality: Some(90),
compression: Some(6),
}
}
}
impl ExportOptions {
pub fn png() -> Self {
Self {
format: ImageFormat::Png,
quality: None,
compression: Some(6),
}
}
pub fn jpeg(quality: u8) -> Self {
Self {
format: ImageFormat::Jpeg,
quality: Some(quality.clamp(1, 100)),
compression: None,
}
}
pub fn bmp() -> Self {
Self {
format: ImageFormat::Bmp,
quality: None,
compression: None,
}
}
pub fn export<P: AsRef<Path>>(&self, image: &RgbaImage, path: P) -> Result<()> {
match self.format {
ImageFormat::Jpeg => {
let quality = self.quality.unwrap_or(90);
TextureExporter::export_jpeg(image, path, quality)
}
_ => TextureExporter::export_with_format(image, path, self.format),
}
}
}