use std::path::Path;
use image::imageops::FilterType;
use crate::{Error, Result};
pub fn favicon(src: &Path, out: &Path, sizes: &[u32]) -> Result<()> {
favicon_inner(src, out, sizes).map_err(|e| Error::Icons(e.to_string()))
}
fn favicon_inner(
src: &Path,
out: &Path,
sizes: &[u32],
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let img = image::open(src)?;
let mut dir = ico::IconDir::new(ico::ResourceType::Icon);
for &size in sizes {
let rgba = img
.resize_exact(size, size, FilterType::Lanczos3)
.to_rgba8();
let icon = ico::IconImage::from_rgba_data(size, size, rgba.into_raw());
dir.add_entry(ico::IconDirEntry::encode(&icon)?);
}
if let Some(parent) = out.parent() {
std::fs::create_dir_all(parent)?;
}
dir.write(std::fs::File::create(out)?)?;
Ok(())
}
pub fn png(src: &Path, out: &Path, size: u32) -> Result<()> {
png_inner(src, out, size).map_err(|e| Error::Icons(e.to_string()))
}
fn png_inner(
src: &Path,
out: &Path,
size: u32,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let resized = image::open(src)?.resize_exact(size, size, FilterType::Lanczos3);
if let Some(parent) = out.parent() {
std::fs::create_dir_all(parent)?;
}
resized.save(out)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn write_src_png(path: &Path) {
let mut img = image::RgbaImage::new(64, 64);
for (x, y, px) in img.enumerate_pixels_mut() {
*px = image::Rgba([(x * 4) as u8, (y * 4) as u8, 128, 255]);
}
img.save(path).unwrap();
}
#[test]
fn favicon_and_png() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("logo.png");
write_src_png(&src);
let ico = dir.path().join("favicon.ico");
favicon(&src, &ico, &[16, 32]).unwrap();
let bytes = std::fs::read(&ico).unwrap();
assert!(
bytes.len() > 4 && bytes[0..4] == [0, 0, 1, 0],
"ICO magic header"
);
let touch = dir.path().join("apple-touch-icon.png");
png(&src, &touch, 180).unwrap();
assert!(touch.metadata().unwrap().len() > 0);
}
}