Skip to content

Commit

Permalink
refactor(core): use the image crate (#9132)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Mar 11, 2024
1 parent 26f0f71 commit db0a24a
Show file tree
Hide file tree
Showing 12 changed files with 39 additions and 169 deletions.
5 changes: 5 additions & 0 deletions .changes/image-crate.md
@@ -0,0 +1,5 @@
---
"tauri": patch:breaking
---

Use the image crate for `tauri::image::Image` and remove the `from_png_bytes` and `from_ico_bytes` APIs.
5 changes: 5 additions & 0 deletions .changes/remove-from-format-image.md
@@ -0,0 +1,5 @@
---
"@tauri-apps/api": patch:breaking
---

Remove the `Image.fromPngBytes` and `Image.fromIcoBytes` APIs. Use `Image.fromBytes` instead.
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions core/tauri/Cargo.toml
Expand Up @@ -68,9 +68,7 @@ urlpattern = "0.2"
mime = "0.3"
data-url = { version = "0.3", optional = true }
serialize-to-javascript = "=0.1.1"
infer = { version = "0.15", optional = true }
png = { version = "0.17", optional = true }
ico = { version = "0.3.0", optional = true }
image = { version = "0.24", default-features = false, optional = true }
http-range = { version = "0.1.5", optional = true }
tracing = { version = "0.1", optional = true }
heck = "0.4"
Expand Down Expand Up @@ -154,8 +152,8 @@ webview-data-url = [ "data-url" ]
protocol-asset = [ "http-range" ]
config-json5 = [ "tauri-macros/config-json5" ]
config-toml = [ "tauri-macros/config-toml" ]
image-ico = [ "ico", "infer" ]
image-png = [ "png", "infer" ]
image-ico = [ "image/ico" ]
image-png = [ "image/png" ]
macos-proxy = [ "tauri-runtime-wry/macos-proxy" ]

[[example]]
Expand Down
2 changes: 0 additions & 2 deletions core/tauri/build.rs
Expand Up @@ -142,8 +142,6 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
&[
("new", true),
("from_bytes", true),
("from_png_bytes", true),
("from_ico_bytes", true),
("from_path", true),
("rgba", true),
("width", true),
Expand Down
4 changes: 0 additions & 4 deletions core/tauri/permissions/image/autogenerated/reference.md
Expand Up @@ -2,12 +2,8 @@
|------|-----|
|`allow-from-bytes`|Enables the from_bytes command without any pre-configured scope.|
|`deny-from-bytes`|Denies the from_bytes command without any pre-configured scope.|
|`allow-from-ico-bytes`|Enables the from_ico_bytes command without any pre-configured scope.|
|`deny-from-ico-bytes`|Denies the from_ico_bytes command without any pre-configured scope.|
|`allow-from-path`|Enables the from_path command without any pre-configured scope.|
|`deny-from-path`|Denies the from_path command without any pre-configured scope.|
|`allow-from-png-bytes`|Enables the from_png_bytes command without any pre-configured scope.|
|`deny-from-png-bytes`|Denies the from_png_bytes command without any pre-configured scope.|
|`allow-height`|Enables the height command without any pre-configured scope.|
|`deny-height`|Denies the height command without any pre-configured scope.|
|`allow-new`|Enables the new command without any pre-configured scope.|
Expand Down
2 changes: 1 addition & 1 deletion core/tauri/scripts/bundle.global.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions core/tauri/src/error.rs
Expand Up @@ -81,10 +81,10 @@ pub enum Error {
/// Invalid glob pattern.
#[error("invalid glob pattern: {0}")]
GlobPattern(#[from] glob::PatternError),
/// Error decoding PNG image.
#[cfg(feature = "image-png")]
#[error("failed to decode PNG: {0}")]
PngDecode(#[from] png::DecodingError),
/// Image error.
#[cfg(any(feature = "image-png", feature = "image-ico"))]
#[error("failed to process image: {0}")]
Image(#[from] image::error::ImageError),
/// The Window's raw handle is invalid for the platform.
#[error("Unexpected `raw_window_handle` for the current platform")]
InvalidWindowHandle,
Expand Down
89 changes: 16 additions & 73 deletions core/tauri/src/image/mod.rs
Expand Up @@ -7,7 +7,6 @@
pub(crate) mod plugin;

use std::borrow::Cow;
use std::io::{Error, ErrorKind};
use std::sync::Arc;

use crate::{Manager, Resource, ResourceId, Runtime};
Expand Down Expand Up @@ -45,88 +44,32 @@ impl<'a> Image<'a> {
}
}

/// Creates a new image using the provided png bytes.
#[cfg(feature = "image-png")]
#[cfg_attr(docsrs, doc(cfg(feature = "image-png")))]
pub fn from_png_bytes(bytes: &[u8]) -> std::io::Result<Self> {
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
let mut reader = decoder.read_info()?;
let mut buffer = Vec::new();
while let Ok(Some(row)) = reader.next_row() {
buffer.extend(row.data());
}
Ok(Self {
rgba: Cow::Owned(buffer),
width: reader.info().width,
height: reader.info().height,
})
}

/// Creates a new image using the provided ico bytes.
#[cfg(feature = "image-ico")]
#[cfg_attr(docsrs, doc(cfg(feature = "image-ico")))]
pub fn from_ico_bytes(bytes: &[u8]) -> std::io::Result<Self> {
let icon_dir = ico::IconDir::read(std::io::Cursor::new(&bytes))?;
let first = icon_dir.entries().first().ok_or_else(|| {
Error::new(
ErrorKind::NotFound,
"Couldn't find any icons inside provided ico bytes",
)
})?;

let rgba = first.decode()?.rgba_data().to_vec();

Ok(Self {
rgba: Cow::Owned(rgba),
width: first.width(),
height: first.height(),
})
}

/// Creates a new image using the provided bytes.
///
/// Only `ico` and `png` are supported (based on activated feature flag).
#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "image-ico", feature = "image-png"))))]
pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
let extension = infer::get(bytes)
.expect("could not determine icon extension")
.extension();

match extension {
#[cfg(feature = "image-ico")]
"ico" => Self::from_ico_bytes(bytes),
#[cfg(feature = "image-png")]
"png" => Self::from_png_bytes(bytes),
_ => {
let supported = [
#[cfg(feature = "image-png")]
"'png'",
#[cfg(feature = "image-ico")]
"'ico'",
];

Err(Error::new(
ErrorKind::InvalidInput,
format!(
"Unexpected image format, expected {}, found '{extension}'. Please check the `image-*` Cargo features on the tauri crate to see if Tauri has optional support for this format.",
if supported.is_empty() {
"''".to_string()
} else {
supported.join(" or ")
}
),
))
}
}
pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
use image::GenericImageView;

let img = image::load_from_memory(bytes)?;
let pixels = img
.pixels()
.flat_map(|(_, _, pixel)| pixel.0)
.collect::<Vec<_>>();
Ok(Self {
rgba: Cow::Owned(pixels),
width: img.width(),
height: img.height(),
})
}

/// Creates a new image using the provided path.
///
/// Only `ico` and `png` are supported (based on activated feature flag).
#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "image-ico", feature = "image-png"))))]
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> crate::Result<Self> {
let bytes = std::fs::read(path)?;
Self::from_bytes(&bytes)
}
Expand Down Expand Up @@ -242,8 +185,8 @@ impl JsImage {

#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
_ => Err(
Error::new(
ErrorKind::InvalidInput,
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"expected RGBA image data, found {}",
match self {
Expand Down
39 changes: 1 addition & 38 deletions core/tauri/src/image/plugin.rs
Expand Up @@ -33,36 +33,6 @@ fn from_bytes() -> std::result::Result<(), &'static str> {
Err("from_bytes is only supported if the `image-ico` or `image-png` Cargo features are enabled")
}

#[cfg(feature = "image-ico")]
#[command(root = "crate")]
fn from_ico_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
let image = Image::from_ico_bytes(&bytes)?.to_owned();
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}

#[cfg(not(feature = "image-ico"))]
#[command(root = "crate")]
fn from_ico_bytes() -> std::result::Result<(), &'static str> {
Err("from_ico_bytes is only supported if the `image-ico` Cargo feature is enabled")
}

#[cfg(feature = "image-png")]
#[command(root = "crate")]
fn from_png_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
let image = Image::from_png_bytes(&bytes)?.to_owned();
let mut resources_table = app.resources_table();
let rid = resources_table.add(image);
Ok(rid)
}

#[cfg(not(feature = "image-png"))]
#[command(root = "crate")]
fn from_png_bytes() -> std::result::Result<(), &'static str> {
Err("from_png_bytes is only supported if the `image-ico` Cargo feature is enabled")
}

#[cfg(any(feature = "image-ico", feature = "image-png"))]
#[command(root = "crate")]
fn from_path<R: Runtime>(app: AppHandle<R>, path: std::path::PathBuf) -> crate::Result<ResourceId> {
Expand Down Expand Up @@ -103,14 +73,7 @@ fn height<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<u32>
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("image")
.invoke_handler(crate::generate_handler![
new,
from_bytes,
from_ico_bytes,
from_png_bytes,
from_path,
rgba,
width,
height
new, from_bytes, from_path, rgba, width, height
])
.build()
}
5 changes: 2 additions & 3 deletions examples/api/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 0 additions & 36 deletions tooling/api/src/image.ts
Expand Up @@ -44,42 +44,6 @@ export class Image extends Resource {
}).then((rid) => new Image(rid))
}

/**
* Creates a new image using the provided png bytes.
*
* Note that you need the `image-png` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-png"] }
* ```
*/
static async fromPngBytes(
bytes: number[] | Uint8Array | ArrayBuffer
): Promise<Image> {
return invoke<number>('plugin:image|from_png_bytes', {
bytes: transformImage(bytes)
}).then((rid) => new Image(rid))
}

/**
* Creates a new image using the provided ico bytes.
*
* Note that you need the `image-ico` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "image-ico"] }
* ```
*/
static async fromIcoBytes(
bytes: number[] | Uint8Array | ArrayBuffer
): Promise<Image> {
return invoke<number>('plugin:image|from_ico_bytes', {
bytes: transformImage(bytes)
}).then((rid) => new Image(rid))
}

/**
* Creates a new image using the provided path.
*
Expand Down

0 comments on commit db0a24a

Please sign in to comment.