Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changes/image-from-resource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"tauri": minor:feat
"tauri-build": minor:feat
"tauri-codegen": minor:feat
"tauri-macros": minor:feat
---

Added `Image::from_app_icon_resource` and `Image::from_icon_resource` on Windows for loading images from resources embedded in the executable, and the default `default_window_icon` from `tauri::generate_context` macro is now loaded using `from_app_icon_resource`
16 changes: 12 additions & 4 deletions crates/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ impl WindowsAttributes {
/// Creates the default attribute set.
pub fn new() -> Self {
Self {
window_icon_path: Default::default(),
app_manifest: Some(include_str!("windows-app-manifest.xml").into()),
window_icon_path: None,
append_rc_content: Vec::new(),
}
}
Expand All @@ -267,13 +267,15 @@ impl WindowsAttributes {
pub fn new_without_app_manifest() -> Self {
Self {
app_manifest: None,
window_icon_path: Default::default(),
window_icon_path: None,
append_rc_content: Vec::new(),
}
}

/// Sets the icon to use on the window. Currently only used on Windows.
/// It must be in `ico` format. Defaults to `icons/icon.ico`.
/// Sets the icon to use as the application icon and default window icon.
/// It must be in `ico` format.
///
/// If not set, we will search for a `.ico` from the `bundle > icon` in your tauri config file, then `icons/icon.ico`.
#[must_use]
pub fn window_icon_path<P: AsRef<Path>>(mut self, window_icon_path: P) -> Self {
self
Expand Down Expand Up @@ -666,6 +668,12 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
}

if window_icon_path.exists() {
// Keep this `name_id` in sync with the one in `tauri::image::Image::from_app_icon_resource`
//
// `32512` has no special meaning here,
// it was used because we misunderstood `IDI_APPLICATION` (`MAKEINTRESOURCE(32512)`)
// should be used for the application icon, which is not true.
// See https://devblogs.microsoft.com/oldnewthing/20250423-00/?p=111106
res.set_icon_with_id(&window_icon_path.display().to_string(), "32512");
} else {
return Err(anyhow!(format!(
Expand Down
20 changes: 1 addition & 19 deletions crates/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,7 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
let default_window_icon = {
if target == Target::Windows {
// handle default window icons for Windows targets
let icon_path = find_icon(
&config,
&config_parent,
|i| i.ends_with(".ico"),
"icons/icon.ico",
);
if icon_path.exists() {
let icon = CachedIcon::new(&root, &icon_path)?;
quote!(::std::option::Option::Some(#icon))
} else {
let icon_path = find_icon(
&config,
&config_parent,
|i| i.ends_with(".png"),
"icons/icon.png",
);
let icon = CachedIcon::new(&root, &icon_path)?;
quote!(::std::option::Option::Some(#icon))
}
quote!(#root::image::Image::from_app_icon_resource(64).ok())
} else {
// handle default window icons for Unix targets
let icon_path = find_icon(
Expand Down
2 changes: 2 additions & 0 deletions crates/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ windows = { version = "0.61", features = [
"Win32_Foundation",
"Win32_UI",
"Win32_UI_WindowsAndMessaging",
"Win32_System_LibraryLoader",
"Win32_Graphics_Gdi",
] }

# mobile
Expand Down
4 changes: 4 additions & 0 deletions crates/tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ pub enum Error {
/// tokio oneshot channel failed to receive message
#[error(transparent)]
TokioOneshotRecv(#[from] tokio::sync::oneshot::error::RecvError),
#[cfg(windows)]
/// [`crate::image::Image::from_icon_resource`] failed
#[error("Can not load Image from icon resources: {0}")]
ImageFromResource(windows::core::Error),
}

impl From<getrandom::Error> for Error {
Expand Down
110 changes: 110 additions & 0 deletions crates/tauri/src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ pub(crate) mod plugin;
use std::borrow::Cow;
use std::sync::Arc;

#[cfg(windows)]
use windows::{
core::{Owned, PCWSTR},
Win32::{
Foundation::GetLastError,
Graphics::Gdi::{
CreateCompatibleDC, DeleteDC, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS,
},
System::LibraryLoader::GetModuleHandleW,
UI::WindowsAndMessaging::{
GetIconInfo, LoadImageW, HICON, ICONINFO, IMAGE_ICON, LR_DEFAULTCOLOR,
},
},
};

use crate::{Resource, ResourceId, ResourceTable};

/// An RGBA Image in row-major order from top to bottom.
Expand Down Expand Up @@ -98,6 +113,101 @@ impl<'a> Image<'a> {
Self::from_bytes(&bytes)
}

/// Creates a new image from the application icon embedded in this executable or library.
///
/// The application icon is currently the icon with `nameID 32512` we embedded through `tauri-build`,
/// this could change in the future.
#[cfg(windows)]
pub fn from_app_icon_resource(size: u32) -> crate::Result<Self> {
// Make sure we keep this `resource_id` in sync with the one in `tauri-build`
Image::from_icon_resource(PCWSTR(32512 as _), size, size)
}

/// Create a new image from an icon resource embedded in this executable or library.
///
/// **Note**: This might take ~2ms for [`LoadImageW`] to load the image for the first time.
///
/// ## Examples
///
/// The `resource_id` can be an `u16` wrapped as `PCWSTR(1 as _)` or a wide string like `w!("icon")`
///
/// ```
/// # use tauri::image::Image;
/// # use windows::core::{w, PCWSTR};
/// Image::from_icon_resource(PCWSTR(1 as _), 32, 32);
/// Image::from_icon_resource(w!("icon"), 32, 32);
/// ```
#[cfg(windows)]
pub fn from_icon_resource(resource_id: PCWSTR, width: u32, height: u32) -> crate::Result<Self> {
let width_i32 = width as i32;
let height_i32 = height as i32;
let color_depth_bytes = 4;

let hicon = unsafe {
Owned::new(HICON(
LoadImageW(
Some(
GetModuleHandleW(PCWSTR::null())
.map_err(crate::Error::ImageFromResource)?
.into(),
),
resource_id,
IMAGE_ICON,
width_i32,
height_i32,
LR_DEFAULTCOLOR,
)
.map_err(crate::Error::ImageFromResource)?
.0,
))
};

let mut icon_info = ICONINFO::default();
unsafe { GetIconInfo(*hicon, &mut icon_info).map_err(crate::Error::ImageFromResource)? };
let _hbm_mask = unsafe { Owned::new(icon_info.hbmMask) };
let hbm_color = unsafe { Owned::new(icon_info.hbmColor) };

let image_bytes = (width_i32 * height_i32 * color_depth_bytes as i32) as usize;
let mut bgra: Vec<u8> = Vec::with_capacity(image_bytes);

let mut bitmap_info = BITMAPINFO::default();
bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as _;
bitmap_info.bmiHeader.biWidth = width_i32;
// nagative value for top-down
bitmap_info.bmiHeader.biHeight = -height_i32;
bitmap_info.bmiHeader.biBitCount = color_depth_bytes * 8;
bitmap_info.bmiHeader.biPlanes = 1;
bitmap_info.bmiHeader.biCompression = BI_RGB.0;

unsafe {
let hdc = CreateCompatibleDC(None);
let result = GetDIBits(
hdc,
*hbm_color,
0,
height,
Some(bgra.as_mut_ptr() as _),
&mut bitmap_info,
DIB_RGB_COLORS,
);
let _ = DeleteDC(hdc);
if result == 0 {
return Err(crate::Error::ImageFromResource(GetLastError().into()));
}
bgra.set_len(image_bytes);
}

let rgba = {
for px in bgra.chunks_exact_mut(color_depth_bytes as usize) {
// Swap Blue and Red channels
px.swap(0, 2);
}
bgra
};

Ok(Image::new_owned(rgba, width, height))
}

/// Returns the RGBA data for this image, in row-major order from top to bottom.
pub fn rgba(&'a self) -> &'a [u8] {
&self.rgba
Expand Down
Loading