use base64::{engine::general_purpose, Engine as _};
use image::{imageops::FilterType, DynamicImage};
use image_dds::ddsfile;
use std::io::Cursor;
use webp::{Encoder, WebPMemory};
pub mod errors;
pub mod files;
pub mod structs;
#[cfg_attr(test, derive(Debug, PartialEq, Eq, PartialOrd, Ord))]
pub struct ImageFile {
pub base_game: Option<String>,
pub local_file: Option<String>,
pub original: Option<String>
}
impl ImageFile {
fn fail() -> Self {
ImageFile {
base_game: None,
local_file: None,
original: None,
}
}
fn base(name: String) -> Self {
ImageFile {
original: Some(name.clone()),
base_game: Some(name),
local_file: None,
}
}
fn local(name: String) -> Self {
ImageFile {
original: Some(name.clone()),
base_game: None,
local_file: Some(name),
}
}
}
#[must_use]
pub fn extract_and_normalize_image(xml_tree: &roxmltree::Document, tag_name: &str) -> ImageFile {
normalize_image_file(
xml_tree
.descendants()
.find(|n| n.has_tag_name(tag_name))
.and_then(|n| n.text()),
)
}
#[must_use]
#[inline]
pub fn normalize_image_file(file_node: Option<&str>) -> ImageFile {
if let Some(entry_text) = file_node {
if entry_text.starts_with("$data") {
return ImageFile::base(entry_text.to_owned());
}
let mut entry_text = entry_text.to_owned().replace('\\', "/");
if std::path::Path::new(&entry_text)
.extension()
.map_or(false, |ext| ext.eq_ignore_ascii_case("png"))
{
entry_text.replace_range(entry_text.len() - 4.., ".dds");
}
return ImageFile::local(entry_text);
}
ImageFile::fail()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_image_file_base() {
let response = normalize_image_file(Some("$data/something.dds"));
let expected = ImageFile {
original: Some(String::from("$data/something.dds")),
base_game: Some(String::from("$data/something.dds")),
local_file: None,
};
assert_eq!(response, expected);
}
#[test]
fn test_image_file_local_dds() {
let response = normalize_image_file(Some("./data/something.dds"));
let expected = ImageFile {
original: Some(String::from("./data/something.dds")),
base_game: None,
local_file: Some(String::from("./data/something.dds")),
};
assert_eq!(response, expected);
}
#[test]
fn test_image_file_local_png() {
let response = normalize_image_file(Some("./data/something.PNG"));
let expected = ImageFile {
original: Some(String::from("./data/something.dds")),
base_game: None,
local_file: Some(String::from("./data/something.dds")),
};
assert_eq!(response, expected);
}
#[test]
fn test_image_file_fail() {
let response = normalize_image_file(None);
let expected = ImageFile {
base_game: None,
local_file: None,
original: None,
};
assert_eq!(response, expected);
}
}
#[must_use]
pub fn convert_mod_icon(bin_file: Vec<u8>) -> Option<String> {
let input_vector: Cursor<Vec<u8>> = Cursor::new(bin_file);
let dds = ddsfile::Dds::read(input_vector).ok()?;
let original_image = image_dds::image_from_dds(&dds, 0).ok()?;
let unscaled_image = DynamicImage::ImageRgba8(original_image);
let encoder: Encoder = Encoder::from_image(&unscaled_image).ok()?;
let webp: WebPMemory = encoder.encode(75_f32);
let b64 = general_purpose::STANDARD.encode(webp.as_ref());
Some(format!("data:image/webp;base64, {b64}"))
}
#[must_use]
pub fn convert_map_image(bin_file: Vec<u8>) -> Option<String> {
let input_vector = Cursor::new(bin_file);
let dds = ddsfile::Dds::read(input_vector).ok()?;
let original_image = image_dds::image_from_dds(&dds, 0).ok()?;
let unscaled_image = DynamicImage::ImageRgba8(original_image);
let cropped_image = unscaled_image
.resize(1024, 1024, FilterType::Nearest)
.crop(256, 256, 512, 512);
let encoder: Encoder = Encoder::from_image(&cropped_image).ok()?;
let webp: WebPMemory = encoder.encode(75_f32);
let b64 = general_purpose::STANDARD.encode(webp.as_ref());
Some(format!("data:image/webp;base64, {b64}"))
}