use std::path::{Path, PathBuf};
use image::{DynamicImage, ImageFormat, RgbaImage, imageops::FilterType};
use tempfile::Builder;
use crate::{
context::AppContext,
error::{DoomError, Result},
};
#[derive(Debug, Clone, Copy)]
struct LogoAsset {
source: &'static str,
output: &'static str,
width: u32,
height: u32,
padding: u32,
}
const LOGOS: &[LogoAsset] = &[
LogoAsset {
source: "XYLEX Group_xylex-group-white-logo-transparent_10.png",
output: "xylex-wordmark-white.png",
width: 2048,
height: 512,
padding: 64,
},
LogoAsset {
source: "XYLEX_GROUP_ICON_TRANSPARENT_WHITE.png",
output: "xylex-icon-white.png",
width: 1024,
height: 1024,
padding: 96,
},
LogoAsset {
source: "XYLEX_Group_xylex_icon_black_zoom.png",
output: "xylex-icon-black.png",
width: 1024,
height: 1024,
padding: 96,
},
LogoAsset {
source: "XYLEX Group_xylex_0.png",
output: "xylex-square-logo.png",
width: 1024,
height: 1024,
padding: 64,
},
];
pub fn prepare_logo_assets(
ctx: &AppContext,
source_dir: impl AsRef<Path>,
output_dir: impl AsRef<Path>,
) -> Result<()> {
let source_root = ctx.repo().repo_path(source_dir);
let output_root = ctx.repo().repo_path(output_dir);
for logo in LOGOS {
let input_path = source_root.join(logo.source);
let output_path = output_root.join(logo.output);
save_normalized_png(&input_path, &output_path, logo.width, logo.height, logo.padding)?;
println!("Wrote {}", output_path.display());
}
Ok(())
}
pub fn ensure_default_logo_assets(ctx: &AppContext, output_dir: impl AsRef<Path>) -> Result<()> {
prepare_logo_assets(ctx, PathBuf::from("assets/source/logos"), output_dir)
}
fn save_normalized_png(
input_path: &Path,
output_path: &Path,
canvas_width: u32,
canvas_height: u32,
padding: u32,
) -> Result<()> {
if !input_path.exists() {
return Err(DoomError::message(format!(
"Missing source logo: {}",
input_path.display()
)));
}
let source = image::open(input_path)?.to_rgba8();
let usable_width = canvas_width.saturating_sub(padding.saturating_mul(2)).max(1);
let usable_height = canvas_height.saturating_sub(padding.saturating_mul(2)).max(1);
let scale = f32::min(
usable_width as f32 / source.width() as f32,
usable_height as f32 / source.height() as f32,
);
let draw_width = (source.width() as f32 * scale).round().max(1.0) as u32;
let draw_height = (source.height() as f32 * scale).round().max(1.0) as u32;
let resized = DynamicImage::ImageRgba8(source)
.resize(draw_width, draw_height, FilterType::Lanczos3)
.to_rgba8();
let mut canvas = RgbaImage::new(canvas_width, canvas_height);
let draw_x = ((canvas_width as i64 - draw_width as i64) / 2).max(0);
let draw_y = ((canvas_height as i64 - draw_height as i64) / 2).max(0);
image::imageops::overlay(&mut canvas, &resized, draw_x, draw_y);
let Some(parent) = output_path.parent() else {
return Err(DoomError::message(format!(
"Output path has no parent directory: {}",
output_path.display()
)));
};
std::fs::create_dir_all(parent)?;
let temp_file = Builder::new()
.prefix(".doom-eternal-logo-")
.suffix(".png")
.tempfile_in(parent)?;
DynamicImage::ImageRgba8(canvas).save_with_format(temp_file.path(), ImageFormat::Png)?;
temp_file.persist(output_path).map_err(|error| DoomError::Io(error.error))?;
Ok(())
}