wow_sharedmedia/converter/
image.rs1use std::path::Path;
4
5use base64::Engine;
6use image::ImageEncoder;
7
8use crate::Error;
9
10const MAX_DIMENSION: u32 = 4096;
12
13pub fn convert_to_tga(input: &Path, output: &Path) -> Result<ImageConvertResult, Error> {
18 let img = image::open(input).map_err(|e| Error::InvalidImage(e.to_string()))?;
19 write_dynamic_image_as_tga(&img, output)
20}
21
22pub fn convert_to_preview_data_uri(input: &Path) -> Result<String, Error> {
27 let img = image::open(input).map_err(|e| Error::InvalidImage(e.to_string()))?;
28
29 let mut buf = std::io::Cursor::new(Vec::new());
31 img.write_to(&mut buf, image::ImageFormat::Png)
32 .map_err(|e| Error::ImageConversion(e.to_string()))?;
33
34 Ok(format!(
35 "data:image/png;base64,{}",
36 base64::engine::general_purpose::STANDARD.encode(buf.into_inner())
37 ))
38}
39
40#[allow(dead_code)]
45pub(crate) fn convert_blp_to_tga(input: &Path, output: &Path) -> Result<ImageConvertResult, Error> {
46 let dynamic = super::blp::read_blp(input)?;
47 write_dynamic_image_as_tga(&dynamic, output)
48}
49
50fn write_dynamic_image_as_tga(img: &image::DynamicImage, output: &Path) -> Result<ImageConvertResult, Error> {
54 let original_width = img.width();
55 let original_height = img.height();
56
57 let width = original_width.next_power_of_two().min(MAX_DIMENSION);
58 let height = original_height.next_power_of_two().min(MAX_DIMENSION);
59 let was_resized = width != original_width || height != original_height;
60
61 let resized = if was_resized {
62 img.resize_exact(width, height, image::imageops::FilterType::Triangle)
63 } else {
64 img.clone()
65 };
66
67 let rgba = resized.to_rgba8();
68 let (w, h) = rgba.dimensions();
69 let pixels = rgba.as_raw();
70
71 if let Some(parent) = output.parent() {
72 std::fs::create_dir_all(parent).map_err(|e| Error::Io {
73 source: e,
74 path: parent.to_path_buf(),
75 })?;
76 }
77 let file = std::fs::File::create(output).map_err(|e| Error::Io {
78 source: e,
79 path: output.to_path_buf(),
80 })?;
81 let writer = std::io::BufWriter::new(file);
82 let encoder = image::codecs::tga::TgaEncoder::new(writer);
83 encoder
84 .write_image(pixels, w, h, image::ExtendedColorType::Rgba8)
85 .map_err(|e| Error::ImageConversion(e.to_string()))?;
86
87 Ok(ImageConvertResult {
88 width: w,
89 height: h,
90 original_width,
91 original_height,
92 was_resized,
93 })
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ImageConvertResult {
99 pub width: u32,
101 pub height: u32,
103 pub original_width: u32,
105 pub original_height: u32,
107 pub was_resized: bool,
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use tempfile::TempDir;
115
116 fn create_test_image(dir: &std::path::Path, name: &str, w: u32, h: u32) -> std::path::PathBuf {
117 let mut img = image::RgbImage::new(w, h);
118 for pixel in img.pixels_mut() {
119 *pixel = image::Rgb([100, 200, 50]);
120 }
121 let path = dir.join(name);
122 img.save(&path).unwrap();
123 path
124 }
125
126 #[test]
127 fn test_convert_png_to_tga() {
128 let tmp = TempDir::new().unwrap();
129 let input = create_test_image(tmp.path(), "test.png", 64, 32);
130 let output = tmp.path().join("test.tga");
131 let result = convert_to_tga(&input, &output).unwrap();
132 assert_eq!(result.width, 64);
133 assert_eq!(result.height, 32);
134 assert!(!result.was_resized);
135 assert!(output.exists());
136 }
137
138 #[test]
139 fn test_resize_non_power_of_two() {
140 let tmp = TempDir::new().unwrap();
141 let input = create_test_image(tmp.path(), "np2.png", 100, 50);
142 let output = tmp.path().join("np2.tga");
143 let result = convert_to_tga(&input, &output).unwrap();
144 assert_eq!(result.width, 128);
145 assert_eq!(result.height, 64);
146 assert!(result.was_resized);
147 }
148
149 #[test]
150 fn test_preview_data_uri() {
151 let tmp = TempDir::new().unwrap();
152 let input = create_test_image(tmp.path(), "preview.png", 32, 32);
153 let uri = convert_to_preview_data_uri(&input).unwrap();
154 assert!(uri.starts_with("data:image/png;base64,"));
155 }
156
157 #[test]
158 fn test_zero_dimensions() {
159 let tmp = TempDir::new().unwrap();
160 let mut img = image::RgbImage::new(1, 1);
161 for pixel in img.pixels_mut() {
162 *pixel = image::Rgb([0; 3]);
163 }
164 let path = tmp.path().join("tiny.png");
165 img.save(&path).unwrap();
166 let result = convert_to_tga(&path, &tmp.path().join("out.tga"));
167 assert!(result.is_ok());
168 }
169}