image.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::{
  5. embedded_assets::{EmbeddedAssetsError, EmbeddedAssetsResult},
  6. Cached,
  7. };
  8. use proc_macro2::TokenStream;
  9. use quote::{quote, ToTokens, TokenStreamExt};
  10. use std::{ffi::OsStr, io::Cursor, path::Path};
  11. /// The format the Icon is consumed as.
  12. pub(crate) enum IconFormat {
  13. /// The image, completely unmodified.
  14. Raw,
  15. /// RGBA raw data, meant to be consumed by [`tauri::image::Image`].
  16. Image { width: u32, height: u32 },
  17. }
  18. pub struct CachedIcon {
  19. cache: Cached,
  20. format: IconFormat,
  21. root: TokenStream,
  22. }
  23. impl CachedIcon {
  24. pub fn new(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
  25. match icon.extension().map(OsStr::to_string_lossy).as_deref() {
  26. Some("png") => Self::new_png(root, icon),
  27. Some("ico") => Self::new_ico(root, icon),
  28. unknown => Err(EmbeddedAssetsError::InvalidImageExtension {
  29. extension: unknown.unwrap_or_default().into(),
  30. path: icon.to_path_buf(),
  31. }),
  32. }
  33. }
  34. /// Cache the icon without any manipulation.
  35. pub fn new_raw(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
  36. let buf = Self::open(icon);
  37. Cached::try_from(buf).map(|cache| Self {
  38. cache,
  39. root: root.clone(),
  40. format: IconFormat::Raw,
  41. })
  42. }
  43. /// Cache an ICO icon as RGBA data, see [`ImageFormat::Image`].
  44. pub fn new_ico(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
  45. let buf = Self::open(icon);
  46. let icon_dir = ico::IconDir::read(Cursor::new(&buf))
  47. .unwrap_or_else(|e| panic!("failed to parse icon {}: {}", icon.display(), e));
  48. let entry = &icon_dir.entries()[0];
  49. let rgba = entry
  50. .decode()
  51. .unwrap_or_else(|e| panic!("failed to decode icon {}: {}", icon.display(), e))
  52. .rgba_data()
  53. .to_vec();
  54. Cached::try_from(rgba).map(|cache| Self {
  55. cache,
  56. root: root.clone(),
  57. format: IconFormat::Image {
  58. width: entry.width(),
  59. height: entry.height(),
  60. },
  61. })
  62. }
  63. /// Cache a PNG icon as RGBA data, see [`ImageFormat::Image`].
  64. pub fn new_png(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
  65. let buf = Self::open(icon);
  66. let decoder = png::Decoder::new(Cursor::new(&buf));
  67. let mut reader = decoder
  68. .read_info()
  69. .unwrap_or_else(|e| panic!("failed to read icon {}: {}", icon.display(), e));
  70. if reader.output_color_type().0 != png::ColorType::Rgba {
  71. panic!("icon {} is not RGBA", icon.display());
  72. }
  73. let mut rgba = Vec::with_capacity(reader.output_buffer_size());
  74. while let Ok(Some(row)) = reader.next_row() {
  75. rgba.extend(row.data());
  76. }
  77. Cached::try_from(rgba).map(|cache| Self {
  78. cache,
  79. root: root.clone(),
  80. format: IconFormat::Image {
  81. width: reader.info().width,
  82. height: reader.info().height,
  83. },
  84. })
  85. }
  86. fn open(path: &Path) -> Vec<u8> {
  87. std::fs::read(path).unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e))
  88. }
  89. }
  90. impl ToTokens for CachedIcon {
  91. fn to_tokens(&self, tokens: &mut TokenStream) {
  92. let root = &self.root;
  93. let cache = &self.cache;
  94. let raw = quote!(::std::include_bytes!(#cache));
  95. tokens.append_all(match self.format {
  96. IconFormat::Raw => raw,
  97. IconFormat::Image { width, height } => {
  98. quote!(#root::image::Image::new(#raw, #width, #height))
  99. }
  100. })
  101. }
  102. }