Forráskód Böngészése

[feat (issue #6389)] make tauri icon support SVGs (#6444)

Co-authored-by: Fetzer <fetz@fetzverse.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Co-authored-by: FabianLars <fabianlars@fabianlars.de>
fetzsav 1 éve
szülő
commit
50f7ccbbf3
4 módosított fájl, 409 hozzáadás és 25 törlés
  1. 6 0
      .changes/icon-svg.md
  2. 307 0
      tooling/cli/Cargo.lock
  3. 1 0
      tooling/cli/Cargo.toml
  4. 95 25
      tooling/cli/src/icon.rs

+ 6 - 0
.changes/icon-svg.md

@@ -0,0 +1,6 @@
+---
+"tauri-cli": patch:feat
+"@tauri-apps/cli": patch:feat
+---
+
+Add suport to SVG input image for the `tauri icon` command.

+ 307 - 0
tooling/cli/Cargo.lock

@@ -155,6 +155,18 @@ version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69"
 
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
 [[package]]
 name = "async-lock"
 version = "2.8.0"
@@ -850,6 +862,12 @@ version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
 
+[[package]]
+name = "data-url"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f"
+
 [[package]]
 name = "deranged"
 version = "0.3.9"
@@ -1183,6 +1201,12 @@ dependencies = [
  "miniz_oxide",
 ]
 
+[[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+
 [[package]]
 name = "flume"
 version = "0.11.0"
@@ -1198,6 +1222,29 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "fontconfig-parser"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4"
+dependencies = [
+ "roxmltree",
+]
+
+[[package]]
+name = "fontdb"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38"
+dependencies = [
+ "fontconfig-parser",
+ "log",
+ "memmap2",
+ "slotmap",
+ "tinyvec",
+ "ttf-parser",
+]
+
 [[package]]
 name = "foreign-types"
 version = "0.3.2"
@@ -1687,6 +1734,12 @@ dependencies = [
  "tiff",
 ]
 
+[[package]]
+name = "imagesize"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
+
 [[package]]
 name = "include_dir"
 version = "0.7.3"
@@ -2042,6 +2095,15 @@ dependencies = [
  "selectors",
 ]
 
+[[package]]
+name = "kurbo"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
+dependencies = [
+ "arrayvec",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -2257,6 +2319,15 @@ version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
+[[package]]
+name = "memmap2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "memoffset"
 version = "0.9.0"
@@ -2945,6 +3016,12 @@ dependencies = [
  "siphasher",
 ]
 
+[[package]]
+name = "pico-args"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
+
 [[package]]
 name = "pin-project"
 version = "1.1.3"
@@ -3183,6 +3260,12 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "rctree"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f"
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
@@ -3285,6 +3368,32 @@ dependencies = [
  "winreg 0.50.0",
 ]
 
+[[package]]
+name = "resvg"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7980f653f9a7db31acff916a262c3b78c562919263edea29bf41a056e20497"
+dependencies = [
+ "gif",
+ "jpeg-decoder",
+ "log",
+ "pico-args",
+ "png",
+ "rgb",
+ "svgtypes",
+ "tiny-skia",
+ "usvg",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8"
+dependencies = [
+ "bytemuck",
+]
+
 [[package]]
 name = "ring"
 version = "0.16.20"
@@ -3312,6 +3421,15 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
 
+[[package]]
+name = "roxmltree"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302"
+dependencies = [
+ "xmlparser",
+]
+
 [[package]]
 name = "rpassword"
 version = "7.2.0"
@@ -3395,6 +3513,22 @@ version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
 
+[[package]]
+name = "rustybuzz"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71cd15fef9112a1f94ac64b58d1e4628192631ad6af4dc69997f995459c874e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "bytemuck",
+ "smallvec",
+ "ttf-parser",
+ "unicode-bidi-mirroring",
+ "unicode-ccc",
+ "unicode-properties",
+ "unicode-script",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.15"
@@ -3751,6 +3885,15 @@ version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
 
+[[package]]
+name = "simplecss"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
+dependencies = [
+ "log",
+]
+
 [[package]]
 name = "siphasher"
 version = "0.3.11"
@@ -3766,6 +3909,15 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "slotmap"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.11.1"
@@ -3851,6 +4003,15 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+dependencies = [
+ "float-cmp",
+]
+
 [[package]]
 name = "string_cache"
 version = "0.8.7"
@@ -3963,6 +4124,16 @@ dependencies = [
  "sval_fmt",
 ]
 
+[[package]]
+name = "svgtypes"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71499ff2d42f59d26edb21369a308ede691421f79ebc0f001e2b1fd3a7c9e52"
+dependencies = [
+ "kurbo",
+ "siphasher",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.109"
@@ -4119,6 +4290,7 @@ dependencies = [
  "os_info",
  "os_pipe",
  "regex",
+ "resvg",
  "semver",
  "serde",
  "serde-value",
@@ -4353,6 +4525,32 @@ dependencies = [
  "time-core",
 ]
 
+[[package]]
+name = "tiny-skia"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b72a92a05db376db09fe6d50b7948d106011761c05a6a45e23e17ee9b556222"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "png",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac3865b9708fc7e1961a65c3a4fa55e984272f33092d3c859929f887fceb647"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.6.0"
@@ -4553,6 +4751,12 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
+[[package]]
+name = "ttf-parser"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
+
 [[package]]
 name = "tungstenite"
 version = "0.20.1"
@@ -4599,6 +4803,18 @@ version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
 
+[[package]]
+name = "unicode-bidi-mirroring"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694"
+
+[[package]]
+name = "unicode-ccc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1"
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
@@ -4614,12 +4830,30 @@ dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "unicode-properties"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0"
+
+[[package]]
+name = "unicode-script"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc"
+
 [[package]]
 name = "unicode-segmentation"
 version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
 
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
 [[package]]
 name = "unicode-width"
 version = "0.1.11"
@@ -4671,6 +4905,67 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "usvg"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c51daa774fe9ee5efcf7b4fec13019b8119cda764d9a8b5b06df02bb1445c656"
+dependencies = [
+ "base64 0.21.4",
+ "log",
+ "pico-args",
+ "usvg-parser",
+ "usvg-text-layout",
+ "usvg-tree",
+ "xmlwriter",
+]
+
+[[package]]
+name = "usvg-parser"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45c88a5ffaa338f0e978ecf3d4e00d8f9f493e29bed0752e1a808a1db16afc40"
+dependencies = [
+ "data-url",
+ "flate2",
+ "imagesize",
+ "kurbo",
+ "log",
+ "roxmltree",
+ "simplecss",
+ "siphasher",
+ "svgtypes",
+ "usvg-tree",
+]
+
+[[package]]
+name = "usvg-text-layout"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d2374378cb7a3fb8f33894e0fdb8625e1bbc4f25312db8d91f862130b541593"
+dependencies = [
+ "fontdb",
+ "kurbo",
+ "log",
+ "rustybuzz",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "usvg-tree",
+]
+
+[[package]]
+name = "usvg-tree"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cacb0c5edeaf3e80e5afcf5b0d4004cc1d36318befc9a7c6606507e5d0f4062"
+dependencies = [
+ "rctree",
+ "strict-num",
+ "svgtypes",
+ "tiny-skia-path",
+]
+
 [[package]]
 name = "utf-8"
 version = "0.7.6"
@@ -5205,6 +5500,18 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "xmlparser"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
+
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
 [[package]]
 name = "zeroize"
 version = "1.6.0"

+ 1 - 0
tooling/cli/Cargo.toml

@@ -94,6 +94,7 @@ serde-value = "0.7.0"
 itertools = "0.11"
 local-ip-address = "0.5"
 css-color = "0.2"
+resvg = "0.36.0"
 
 [target."cfg(windows)".dependencies]
 winapi = { version = "0.3", features = [ "handleapi", "processenv", "winbase", "wincon", "winnt" ] }

+ 95 - 25
tooling/cli/src/icon.rs

@@ -23,6 +23,8 @@ use image::{
   imageops::FilterType,
   open, ColorType, DynamicImage, ImageBuffer, ImageEncoder, Rgba,
 };
+use resvg::usvg::{fontdb, TreeParsing, TreeTextToPath};
+use resvg::{tiny_skia, usvg};
 use serde::Deserialize;
 
 #[derive(Debug, Deserialize)]
@@ -41,7 +43,7 @@ struct PngEntry {
 #[derive(Debug, Parser)]
 #[clap(about = "Generate various icons for all major platforms")]
 pub struct Options {
-  /// Path to the source icon (png, 1024x1024px with transparency).
+  /// Path to the source icon (squared PNG or SVG file with transparency).
   #[clap(default_value = "./app-icon.png")]
   input: PathBuf,
   /// Output directory.
@@ -58,6 +60,43 @@ pub struct Options {
   ios_color: String,
 }
 
+enum Source {
+  Svg(resvg::Tree),
+  DynamicImage(DynamicImage),
+}
+
+impl Source {
+  fn width(&self) -> u32 {
+    match self {
+      Self::Svg(svg) => svg.size.width() as u32,
+      Self::DynamicImage(i) => i.width(),
+    }
+  }
+
+  fn height(&self) -> u32 {
+    match self {
+      Self::Svg(svg) => svg.size.height() as u32,
+      Self::DynamicImage(i) => i.height(),
+    }
+  }
+
+  fn resize_exact(&self, size: u32) -> Result<DynamicImage> {
+    match self {
+      Self::Svg(svg) => {
+        let mut pixmap = tiny_skia::Pixmap::new(size, size).unwrap();
+        let scale = size as f32 / svg.size.height();
+        svg.render(
+          tiny_skia::Transform::from_scale(scale, scale),
+          &mut pixmap.as_mut(),
+        );
+        let img_buffer = ImageBuffer::from_raw(size, size, pixmap.take()).unwrap();
+        Ok(DynamicImage::ImageRgba8(img_buffer))
+      }
+      Self::DynamicImage(i) => Ok(i.resize_exact(size, size, FilterType::Lanczos3)),
+    }
+  }
+}
+
 pub fn command(options: Options) -> Result<()> {
   let input = options.input;
   let out_dir = options.output.unwrap_or_else(|| tauri_dir().join("icons"));
@@ -75,11 +114,37 @@ pub fn command(options: Options) -> Result<()> {
 
   create_dir_all(&out_dir).context("Can't create output directory")?;
 
-  let source = open(input)
-    .context("Can't read and decode source image")?
-    .into_rgba8();
+  let source = if let Some(extension) = input.extension() {
+    if extension == "svg" {
+      let rtree = {
+        let opt = usvg::Options {
+          // Get file's absolute directory.
+          resources_dir: std::fs::canonicalize(&input)
+            .ok()
+            .and_then(|p| p.parent().map(|p| p.to_path_buf())),
+          ..Default::default()
+        };
+
+        let mut fontdb = fontdb::Database::new();
+        fontdb.load_system_fonts();
+
+        let svg_data = std::fs::read(&input).unwrap();
+        let mut tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();
+        tree.convert_text(&fontdb);
+        resvg::Tree::from_usvg(&tree)
+      };
 
-  let source = DynamicImage::ImageRgba8(source);
+      Source::Svg(rtree)
+    } else {
+      Source::DynamicImage(DynamicImage::ImageRgba8(
+        open(&input)
+          .context("Can't read and decode source image")?
+          .into_rgba8(),
+      ))
+    }
+  } else {
+    panic!("Error loading image");
+  };
 
   if source.height() != source.width() {
     panic!("Source image must be square");
@@ -106,29 +171,29 @@ pub fn command(options: Options) -> Result<()> {
       .collect::<Vec<PngEntry>>()
     {
       log::info!(action = "PNG"; "Creating {}", target.name);
-      resize_and_save_png(&source, target.size, &target.out_path)?;
+      resize_and_save_png(&source, target.size, &target.out_path, None)?;
     }
   }
 
   Ok(())
 }
 
-fn appx(source: &DynamicImage, out_dir: &Path) -> Result<()> {
+fn appx(source: &Source, out_dir: &Path) -> Result<()> {
   log::info!(action = "Appx"; "Creating StoreLogo.png");
-  resize_and_save_png(source, 50, &out_dir.join("StoreLogo.png"))?;
+  resize_and_save_png(source, 50, &out_dir.join("StoreLogo.png"), None)?;
 
   for size in [30, 44, 71, 89, 107, 142, 150, 284, 310] {
     let file_name = format!("Square{size}x{size}Logo.png");
     log::info!(action = "Appx"; "Creating {}", file_name);
 
-    resize_and_save_png(source, size, &out_dir.join(&file_name))?;
+    resize_and_save_png(source, size, &out_dir.join(&file_name), None)?;
   }
 
   Ok(())
 }
 
 // Main target: macOS
-fn icns(source: &DynamicImage, out_dir: &Path) -> Result<()> {
+fn icns(source: &Source, out_dir: &Path) -> Result<()> {
   log::info!(action = "ICNS"; "Creating icon.icns");
   let entries: HashMap<String, IcnsEntry> =
     serde_json::from_slice(include_bytes!("helpers/icns.json")).unwrap();
@@ -139,7 +204,7 @@ fn icns(source: &DynamicImage, out_dir: &Path) -> Result<()> {
     let size = entry.size;
     let mut buf = Vec::new();
 
-    let image = source.resize_exact(size, size, FilterType::Lanczos3);
+    let image = source.resize_exact(size)?;
 
     write_png(image.as_bytes(), &mut buf, size)?;
 
@@ -162,12 +227,12 @@ fn icns(source: &DynamicImage, out_dir: &Path) -> Result<()> {
 
 // Generate .ico file with layers for the most common sizes.
 // Main target: Windows
-fn ico(source: &DynamicImage, out_dir: &Path) -> Result<()> {
+fn ico(source: &Source, out_dir: &Path) -> Result<()> {
   log::info!(action = "ICO"; "Creating icon.ico");
   let mut frames = Vec::new();
 
   for size in [32, 16, 24, 48, 64, 256] {
-    let image = source.resize_exact(size, size, FilterType::Lanczos3);
+    let image = source.resize_exact(size)?;
 
     // Only the 256px layer can be compressed according to the ico specs.
     if size == 256 {
@@ -196,7 +261,7 @@ fn ico(source: &DynamicImage, out_dir: &Path) -> Result<()> {
 
 // Generate .png files in 32x32, 128x128, 256x256, 512x512 (icon.png)
 // Main target: Linux
-fn png(source: &DynamicImage, out_dir: &Path, ios_color: Rgba<u8>) -> Result<()> {
+fn png(source: &Source, out_dir: &Path, ios_color: Rgba<u8>) -> Result<()> {
   fn desktop_entries(out_dir: &Path) -> Vec<PngEntry> {
     let mut entries = Vec::new();
 
@@ -383,27 +448,32 @@ fn png(source: &DynamicImage, out_dir: &Path, ios_color: Rgba<u8>) -> Result<()>
 
   for entry in entries {
     log::info!(action = "PNG"; "Creating {}", entry.name);
-    resize_and_save_png(source, entry.size, &entry.out_path)?;
+    resize_and_save_png(source, entry.size, &entry.out_path, None)?;
   }
 
-  let source_rgba8 = source.as_rgba8().expect("unexpected image type");
-  let mut img = ImageBuffer::from_fn(source_rgba8.width(), source_rgba8.height(), |_, _| {
-    ios_color
-  });
-  image::imageops::overlay(&mut img, source_rgba8, 0, 0);
-  let image = DynamicImage::ImageRgba8(img);
-
   for entry in ios_entries(&out)? {
     log::info!(action = "iOS"; "Creating {}", entry.name);
-    resize_and_save_png(&image, entry.size, &entry.out_path)?;
+    resize_and_save_png(source, entry.size, &entry.out_path, Some(ios_color))?;
   }
 
   Ok(())
 }
 
 // Resize image and save it to disk.
-fn resize_and_save_png(source: &DynamicImage, size: u32, file_path: &Path) -> Result<()> {
-  let image = source.resize_exact(size, size, FilterType::Lanczos3);
+fn resize_and_save_png(
+  source: &Source,
+  size: u32,
+  file_path: &Path,
+  bg_color: Option<Rgba<u8>>,
+) -> Result<()> {
+  let mut image = source.resize_exact(size)?;
+
+  if let Some(bg_color) = bg_color {
+    let mut bg_img = ImageBuffer::from_fn(size, size, |_, _| bg_color);
+    image::imageops::overlay(&mut bg_img, &image, 0, 0);
+    image = bg_img.into();
+  }
+
   let mut out_file = BufWriter::new(File::create(file_path)?);
   write_png(image.as_bytes(), &mut out_file, size)?;
   Ok(out_file.flush()?)