use std::fs;
use std::io::Cursor;
use std::path::Path;
use image::codecs::ico::{IcoEncoder, IcoFrame};
use image::codecs::png::PngEncoder;
use image::{ColorType, ImageEncoder};
use resvg::usvg::{Options, Tree};
use tiny_skia::{Pixmap, Transform};
#[derive(Debug)]
pub enum IconError {
Io(std::io::Error),
Svg(String),
Render(String),
Encode(String),
}
impl std::fmt::Display for IconError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IconError::Io(e) => write!(f, "icon I/O error: {e}"),
IconError::Svg(s) => write!(f, "SVG error: {s}"),
IconError::Render(s) => write!(f, "render error: {s}"),
IconError::Encode(s) => write!(f, "encode error: {s}"),
}
}
}
impl std::error::Error for IconError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
IconError::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for IconError {
fn from(e: std::io::Error) -> Self {
IconError::Io(e)
}
}
pub type Result<T> = std::result::Result<T, IconError>;
fn parse_tree(svg: &[u8]) -> Result<Tree> {
Tree::from_data(svg, &Options::default())
.map_err(|e| IconError::Svg(e.to_string()))
}
fn render_to_pixmap(tree: &Tree, size: u32) -> Result<Pixmap> {
let mut pixmap = Pixmap::new(size, size)
.ok_or_else(|| IconError::Render(format!("cannot create {size}×{size} pixmap")))?;
let svg_size = tree.size();
let sx = size as f32 / svg_size.width();
let sy = size as f32 / svg_size.height();
let transform = Transform::from_scale(sx, sy);
resvg::render(tree, transform, &mut pixmap.as_mut());
Ok(pixmap)
}
fn pixmap_to_png_bytes(pixmap: &Pixmap) -> Result<Vec<u8>> {
let mut out = Vec::new();
let encoder = PngEncoder::new(Cursor::new(&mut out));
encoder
.write_image(
pixmap.data(),
pixmap.width(),
pixmap.height(),
ColorType::Rgba8.into(),
)
.map_err(|e| IconError::Encode(e.to_string()))?;
Ok(out)
}
pub fn svg_to_png(
svg_path: impl AsRef<Path>,
out_path: impl AsRef<Path>,
size: u32,
) -> Result<()> {
let svg = fs::read(svg_path)?;
let png = svg_bytes_to_png_bytes(&svg, size)?;
fs::write(out_path, png)?;
Ok(())
}
pub fn svg_to_ico(
svg_path: impl AsRef<Path>,
out_path: impl AsRef<Path>,
sizes: &[u32],
) -> Result<()> {
let svg = fs::read(svg_path)?;
let ico = svg_bytes_to_ico_bytes(&svg, sizes)?;
fs::write(out_path, ico)?;
Ok(())
}
pub fn svg_bytes_to_png_bytes(svg: &[u8], size: u32) -> Result<Vec<u8>> {
let tree = parse_tree(svg)?;
let pixmap = render_to_pixmap(&tree, size)?;
pixmap_to_png_bytes(&pixmap)
}
pub fn svg_bytes_to_rgba(svg: &[u8], size: u32) -> Result<Vec<u8>> {
let tree = parse_tree(svg)?;
let pixmap = render_to_pixmap(&tree, size)?;
Ok(pixmap.data().to_vec())
}
pub fn svg_bytes_to_ico_bytes(svg: &[u8], sizes: &[u32]) -> Result<Vec<u8>> {
let tree = parse_tree(svg)?;
let mut png_frames: Vec<Vec<u8>> = Vec::with_capacity(sizes.len());
for &sz in sizes {
let pixmap = render_to_pixmap(&tree, sz)?;
let png = pixmap_to_png_bytes(&pixmap)?;
png_frames.push(png);
}
let frames: std::result::Result<Vec<IcoFrame<'_>>, _> = png_frames
.iter()
.zip(sizes.iter())
.map(|(png, &sz)| IcoFrame::as_png(png, sz, sz, ColorType::Rgba8.into()))
.collect();
let frames = frames.map_err(|e| IconError::Encode(e.to_string()))?;
let mut out = Vec::new();
let encoder = IcoEncoder::new(Cursor::new(&mut out));
encoder
.encode_images(&frames)
.map_err(|e| IconError::Encode(e.to_string()))?;
Ok(out)
}