refactor(core): use the image crate (#9132)

This commit is contained in:
Lucas Fernandes Nogueira 2024-03-11 11:46:34 -03:00 committed by GitHub
parent 26f0f71a40
commit db0a24a973
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 38 additions and 168 deletions

5
.changes/image-crate.md Normal file
View File

@ -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.

View File

@ -0,0 +1,5 @@
---
"@tauri-apps/api": patch:breaking
---
Remove the `Image.fromPngBytes` and `Image.fromIcoBytes` APIs. Use `Image.fromBytes` instead.

5
Cargo.lock generated
View File

@ -1591,6 +1591,7 @@ dependencies = [
"byteorder", "byteorder",
"color_quant", "color_quant",
"num-traits", "num-traits",
"png",
] ]
[[package]] [[package]]
@ -3610,8 +3611,7 @@ dependencies = [
"heck", "heck",
"http", "http",
"http-range", "http-range",
"ico", "image",
"infer",
"jni", "jni",
"libc", "libc",
"log", "log",
@ -3619,7 +3619,6 @@ dependencies = [
"muda", "muda",
"objc", "objc",
"percent-encoding", "percent-encoding",
"png",
"proptest", "proptest",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",

View File

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

View File

@ -142,8 +142,6 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
&[ &[
("new", true), ("new", true),
("from_bytes", true), ("from_bytes", true),
("from_png_bytes", true),
("from_ico_bytes", true),
("from_path", true), ("from_path", true),
("rgba", true), ("rgba", true),
("width", true), ("width", true),

View File

@ -2,12 +2,8 @@
|------|-----| |------|-----|
|`allow-from-bytes`|Enables the from_bytes command without any pre-configured scope.| |`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.| |`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.| |`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.| |`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.| |`allow-height`|Enables the height command without any pre-configured scope.|
|`deny-height`|Denies 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.| |`allow-new`|Enables the new command without any pre-configured scope.|

File diff suppressed because one or more lines are too long

View File

@ -81,10 +81,10 @@ pub enum Error {
/// Invalid glob pattern. /// Invalid glob pattern.
#[error("invalid glob pattern: {0}")] #[error("invalid glob pattern: {0}")]
GlobPattern(#[from] glob::PatternError), GlobPattern(#[from] glob::PatternError),
/// Error decoding PNG image. /// Image error.
#[cfg(feature = "image-png")] #[cfg(any(feature = "image-png", feature = "image-ico"))]
#[error("failed to decode PNG: {0}")] #[error("failed to process image: {0}")]
PngDecode(#[from] png::DecodingError), Image(#[from] image::error::ImageError),
/// The Window's raw handle is invalid for the platform. /// The Window's raw handle is invalid for the platform.
#[error("Unexpected `raw_window_handle` for the current platform")] #[error("Unexpected `raw_window_handle` for the current platform")]
InvalidWindowHandle, InvalidWindowHandle,

View File

@ -7,7 +7,6 @@
pub(crate) mod plugin; pub(crate) mod plugin;
use std::borrow::Cow; use std::borrow::Cow;
use std::io::{Error, ErrorKind};
use std::sync::Arc; use std::sync::Arc;
use crate::{Manager, Resource, ResourceId, Runtime}; use crate::{Manager, Resource, ResourceId, Runtime};
@ -45,80 +44,24 @@ 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. /// Creates a new image using the provided bytes.
/// ///
/// Only `ico` and `png` are supported (based on activated feature flag). /// Only `ico` and `png` are supported (based on activated feature flag).
#[cfg(any(feature = "image-ico", feature = "image-png"))] #[cfg(any(feature = "image-ico", feature = "image-png"))]
#[cfg_attr(docsrs, doc(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> { pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
let extension = infer::get(bytes) use image::GenericImageView;
.expect("could not determine icon extension")
.extension();
match extension { let img = image::load_from_memory(bytes)?;
#[cfg(feature = "image-ico")] let pixels = img
"ico" => Self::from_ico_bytes(bytes), .pixels()
#[cfg(feature = "image-png")] .flat_map(|(_, _, pixel)| pixel.0)
"png" => Self::from_png_bytes(bytes), .collect::<Vec<_>>();
_ => { Ok(Self {
let supported = [ rgba: Cow::Owned(pixels),
#[cfg(feature = "image-png")] width: img.width(),
"'png'", height: img.height(),
#[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 ")
}
),
))
}
}
} }
/// Creates a new image using the provided path. /// Creates a new image using the provided path.
@ -126,7 +69,7 @@ impl<'a> Image<'a> {
/// Only `ico` and `png` are supported (based on activated feature flag). /// Only `ico` and `png` are supported (based on activated feature flag).
#[cfg(any(feature = "image-ico", feature = "image-png"))] #[cfg(any(feature = "image-ico", feature = "image-png"))]
#[cfg_attr(docsrs, doc(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)?; let bytes = std::fs::read(path)?;
Self::from_bytes(&bytes) Self::from_bytes(&bytes)
} }
@ -242,8 +185,8 @@ impl JsImage {
#[cfg(not(any(feature = "image-ico", feature = "image-png")))] #[cfg(not(any(feature = "image-ico", feature = "image-png")))]
_ => Err( _ => Err(
Error::new( std::io::Error::new(
ErrorKind::InvalidInput, std::io::ErrorKind::InvalidInput,
format!( format!(
"expected RGBA image data, found {}", "expected RGBA image data, found {}",
match self { match self {

View File

@ -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") 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"))] #[cfg(any(feature = "image-ico", feature = "image-png"))]
#[command(root = "crate")] #[command(root = "crate")]
fn from_path<R: Runtime>(app: AppHandle<R>, path: std::path::PathBuf) -> crate::Result<ResourceId> { fn from_path<R: Runtime>(app: AppHandle<R>, path: std::path::PathBuf) -> crate::Result<ResourceId> {
@ -103,14 +73,7 @@ fn height<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<u32>
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("image") Builder::new("image")
.invoke_handler(crate::generate_handler![ .invoke_handler(crate::generate_handler![
new, new, from_bytes, from_path, rgba, width, height
from_bytes,
from_ico_bytes,
from_png_bytes,
from_path,
rgba,
width,
height
]) ])
.build() .build()
} }

View File

@ -1516,6 +1516,7 @@ dependencies = [
"byteorder", "byteorder",
"color_quant", "color_quant",
"num-traits", "num-traits",
"png",
] ]
[[package]] [[package]]
@ -3165,8 +3166,7 @@ dependencies = [
"heck", "heck",
"http", "http",
"http-range", "http-range",
"ico", "image",
"infer",
"jni", "jni",
"libc", "libc",
"log", "log",
@ -3174,7 +3174,6 @@ dependencies = [
"muda", "muda",
"objc", "objc",
"percent-encoding", "percent-encoding",
"png",
"raw-window-handle 0.6.0", "raw-window-handle 0.6.0",
"reqwest", "reqwest",
"serde", "serde",

View File

@ -44,42 +44,6 @@ export class Image extends Resource {
}).then((rid) => new Image(rid)) }).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. * Creates a new image using the provided path.
* *