1use std::fs;
15use std::io::Cursor;
16use std::path::Path;
17
18use image::codecs::ico::{IcoEncoder, IcoFrame};
19use image::codecs::png::PngEncoder;
20use image::{ColorType, ImageEncoder};
21use resvg::usvg::{Options, Tree};
22use tiny_skia::{Pixmap, Transform};
23
24#[derive(Debug)]
28pub enum IconError {
29 Io(std::io::Error),
31 Svg(String),
33 Render(String),
35 Encode(String),
37}
38
39impl std::fmt::Display for IconError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 IconError::Io(e) => write!(f, "icon I/O error: {e}"),
43 IconError::Svg(s) => write!(f, "SVG error: {s}"),
44 IconError::Render(s) => write!(f, "render error: {s}"),
45 IconError::Encode(s) => write!(f, "encode error: {s}"),
46 }
47 }
48}
49
50impl std::error::Error for IconError {
51 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
52 match self {
53 IconError::Io(e) => Some(e),
54 _ => None,
55 }
56 }
57}
58
59impl From<std::io::Error> for IconError {
60 fn from(e: std::io::Error) -> Self {
61 IconError::Io(e)
62 }
63}
64
65pub type Result<T> = std::result::Result<T, IconError>;
67
68fn parse_tree(svg: &[u8]) -> Result<Tree> {
72 Tree::from_data(svg, &Options::default())
73 .map_err(|e| IconError::Svg(e.to_string()))
74}
75
76fn render_to_pixmap(tree: &Tree, size: u32) -> Result<Pixmap> {
78 let mut pixmap = Pixmap::new(size, size)
79 .ok_or_else(|| IconError::Render(format!("cannot create {size}×{size} pixmap")))?;
80
81 let svg_size = tree.size();
82 let sx = size as f32 / svg_size.width();
83 let sy = size as f32 / svg_size.height();
84 let transform = Transform::from_scale(sx, sy);
85
86 resvg::render(tree, transform, &mut pixmap.as_mut());
87 Ok(pixmap)
88}
89
90fn pixmap_to_png_bytes(pixmap: &Pixmap) -> Result<Vec<u8>> {
92 let mut out = Vec::new();
93 let encoder = PngEncoder::new(Cursor::new(&mut out));
94 encoder
95 .write_image(
96 pixmap.data(),
97 pixmap.width(),
98 pixmap.height(),
99 ColorType::Rgba8.into(),
100 )
101 .map_err(|e| IconError::Encode(e.to_string()))?;
102 Ok(out)
103}
104
105pub fn svg_to_png(
120 svg_path: impl AsRef<Path>,
121 out_path: impl AsRef<Path>,
122 size: u32,
123) -> Result<()> {
124 let svg = fs::read(svg_path)?;
125 let png = svg_bytes_to_png_bytes(&svg, size)?;
126 fs::write(out_path, png)?;
127 Ok(())
128}
129
130pub fn svg_to_ico(
142 svg_path: impl AsRef<Path>,
143 out_path: impl AsRef<Path>,
144 sizes: &[u32],
145) -> Result<()> {
146 let svg = fs::read(svg_path)?;
147 let ico = svg_bytes_to_ico_bytes(&svg, sizes)?;
148 fs::write(out_path, ico)?;
149 Ok(())
150}
151
152pub fn svg_bytes_to_png_bytes(svg: &[u8], size: u32) -> Result<Vec<u8>> {
156 let tree = parse_tree(svg)?;
157 let pixmap = render_to_pixmap(&tree, size)?;
158 pixmap_to_png_bytes(&pixmap)
159}
160
161pub fn svg_bytes_to_rgba(svg: &[u8], size: u32) -> Result<Vec<u8>> {
166 let tree = parse_tree(svg)?;
167 let pixmap = render_to_pixmap(&tree, size)?;
168 Ok(pixmap.data().to_vec())
169}
170
171pub fn svg_bytes_to_ico_bytes(svg: &[u8], sizes: &[u32]) -> Result<Vec<u8>> {
175 let tree = parse_tree(svg)?;
176
177 let mut png_frames: Vec<Vec<u8>> = Vec::with_capacity(sizes.len());
179 for &sz in sizes {
180 let pixmap = render_to_pixmap(&tree, sz)?;
181 let png = pixmap_to_png_bytes(&pixmap)?;
182 png_frames.push(png);
183 }
184
185 let frames: std::result::Result<Vec<IcoFrame<'_>>, _> = png_frames
187 .iter()
188 .zip(sizes.iter())
189 .map(|(png, &sz)| IcoFrame::as_png(png, sz, sz, ColorType::Rgba8.into()))
190 .collect();
191 let frames = frames.map_err(|e| IconError::Encode(e.to_string()))?;
192
193 let mut out = Vec::new();
194 let encoder = IcoEncoder::new(Cursor::new(&mut out));
195 encoder
196 .encode_images(&frames)
197 .map_err(|e| IconError::Encode(e.to_string()))?;
198
199 Ok(out)
200}