From 1d85b6e887761ae885bec3f0b5405bf902ef8b64 Mon Sep 17 00:00:00 2001 From: Liam Murphy Date: Sun, 21 Mar 2021 14:36:06 +1100 Subject: feat(web): Support in-memory image data I had to create two methods which basically do the same thing, `from_memory` and `from_slice`, but `from_memory` takes ownership of the bytes to be compatible with `iced_native`. Also, `Data` is incompatible, because if I stored the bytes in `Data` and created a new object URL every render, it would have caused a memory leak because bumpalo doesn't call destructors and there'd be no way to call URL.revokeObjectUrl on it. It's also more efficient this way. --- web/src/widget/image.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'web/src/widget') diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index 05c89ea5..545e7e28 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -2,11 +2,15 @@ use crate::{Bus, Css, Element, Hasher, Length, Widget}; use dodrio::bumpalo; +use js_sys::Array; +use js_sys::Uint8Array; use std::{ hash::{Hash, Hasher as _}, path::PathBuf, sync::Arc, }; +use web_sys::Blob; +use web_sys::Url; /// A frame that displays an image while keeping aspect ratio. /// @@ -75,6 +79,7 @@ impl Widget for Image { let src = String::from_str_in( match self.handle.data.as_ref() { Data::Path(path) => path.to_str().unwrap_or(""), + Data::ObjectUrl(url) => &url, }, bump, ) @@ -122,6 +127,27 @@ impl Handle { Self::from_data(Data::Path(path.into())) } + /// Creates an image [`Handle`] containing the image data directly. + /// + /// NOTE: this unnecessarily takes ownership of the data to be compaticle with `iced_native`. + /// If you're only using `iced_web`, you should use `from_slice` instead. + pub fn from_memory(bytes: Vec) -> Handle { + Handle::from_slice(bytes.as_slice()) + } + + /// Creates an image [`Handle`] containing the image data directly. + pub fn from_slice(bytes: &[u8]) -> Handle { + // This copies the memory twice (once in Uint8Array::from and once in Blob::new), + // but unsafe code is needed not to do that and #[forbid(unsafe_code)] is on. + let blob = Blob::new_with_u8_array_sequence(&Array::of1( + &Uint8Array::from(bytes), + )) + .unwrap(); + Self::from_data(Data::ObjectUrl( + Url::create_object_url_with_blob(&blob).unwrap(), + )) + } + fn from_data(data: Data) -> Handle { let mut hasher = Hasher::default(); data.hash(&mut hasher); @@ -160,12 +186,25 @@ impl From<&str> for Handle { pub enum Data { /// A remote image Path(PathBuf), + + /// An Object URL pointing to some image data. + ObjectUrl(String), } impl std::fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Data::Path(path) => write!(f, "Path({:?})", path), + Data::ObjectUrl(url) => write!(f, "ObjectUrl({:?})", url), + } + } +} + +impl Drop for Data { + fn drop(&mut self) { + match self { + Data::ObjectUrl(url) => Url::revoke_object_url(&url).unwrap(), + _ => {} } } } -- cgit From 99c56b9bff891d55794dccc20f460192ff06f0b8 Mon Sep 17 00:00:00 2001 From: Liam Murphy Date: Sun, 21 Mar 2021 15:34:20 +1100 Subject: fix: Don't rely on image handle not being dropped It now causes a memory leak, though. :/ --- web/src/widget/image.rs | 57 ++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) (limited to 'web/src/widget') diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index 545e7e28..f38f5cfb 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -76,14 +76,21 @@ impl Widget for Image { use dodrio::builder::*; use dodrio::bumpalo::collections::String; - let src = String::from_str_in( - match self.handle.data.as_ref() { - Data::Path(path) => path.to_str().unwrap_or(""), - Data::ObjectUrl(url) => &url, - }, - bump, - ) - .into_bump_str(); + let src = match self.handle.data.as_ref() { + Data::Path(path) => { + String::from_str_in(path.to_str().unwrap_or(""), bump) + .into_bump_str() + } + Data::Bytes(bytes) => bump.alloc( + Url::create_object_url_with_blob( + &Blob::new_with_u8_array_sequence(&Array::of1( + &Uint8Array::from(bytes.as_slice()), + )) + .unwrap(), + ) + .unwrap(), + ), + }; let alt = String::from_str_in(&self.alt, bump).into_bump_str(); @@ -129,23 +136,10 @@ impl Handle { /// Creates an image [`Handle`] containing the image data directly. /// - /// NOTE: this unnecessarily takes ownership of the data to be compaticle with `iced_native`. - /// If you're only using `iced_web`, you should use `from_slice` instead. + /// This is useful if you already have your image loaded in-memory, maybe + /// because you downloaded or generated it procedurally. pub fn from_memory(bytes: Vec) -> Handle { - Handle::from_slice(bytes.as_slice()) - } - - /// Creates an image [`Handle`] containing the image data directly. - pub fn from_slice(bytes: &[u8]) -> Handle { - // This copies the memory twice (once in Uint8Array::from and once in Blob::new), - // but unsafe code is needed not to do that and #[forbid(unsafe_code)] is on. - let blob = Blob::new_with_u8_array_sequence(&Array::of1( - &Uint8Array::from(bytes), - )) - .unwrap(); - Self::from_data(Data::ObjectUrl( - Url::create_object_url_with_blob(&blob).unwrap(), - )) + Self::from_data(Data::Bytes(bytes)) } fn from_data(data: Data) -> Handle { @@ -187,24 +181,15 @@ pub enum Data { /// A remote image Path(PathBuf), - /// An Object URL pointing to some image data. - ObjectUrl(String), + /// In-memory data + Bytes(Vec), } impl std::fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Data::Path(path) => write!(f, "Path({:?})", path), - Data::ObjectUrl(url) => write!(f, "ObjectUrl({:?})", url), - } - } -} - -impl Drop for Data { - fn drop(&mut self) { - match self { - Data::ObjectUrl(url) => Url::revoke_object_url(&url).unwrap(), - _ => {} + Data::Bytes(_) => write!(f, "Bytes(...)"), } } } -- cgit From 11f29bca86e299ed3e2c0d3db0d8058efe4be7ef Mon Sep 17 00:00:00 2001 From: Liam Murphy Date: Sat, 10 Apr 2021 17:18:04 +1000 Subject: Use data urls instead of blob URLs I didn't do this originally because I was half doing it in the first place to mess with Blob URLs, and it feels kinda wrong to be encoding it as base64 when that option is available. But not having memory leaks is more important. --- web/src/widget/image.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'web/src/widget') diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index f38f5cfb..28435f4f 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -2,15 +2,11 @@ use crate::{Bus, Css, Element, Hasher, Length, Widget}; use dodrio::bumpalo; -use js_sys::Array; -use js_sys::Uint8Array; use std::{ hash::{Hash, Hasher as _}, path::PathBuf, sync::Arc, }; -use web_sys::Blob; -use web_sys::Url; /// A frame that displays an image while keeping aspect ratio. /// @@ -79,18 +75,13 @@ impl Widget for Image { let src = match self.handle.data.as_ref() { Data::Path(path) => { String::from_str_in(path.to_str().unwrap_or(""), bump) - .into_bump_str() } - Data::Bytes(bytes) => bump.alloc( - Url::create_object_url_with_blob( - &Blob::new_with_u8_array_sequence(&Array::of1( - &Uint8Array::from(bytes.as_slice()), - )) - .unwrap(), - ) - .unwrap(), - ), - }; + Data::Bytes(bytes) => { + // The web is able to infer the kind of image, so we don't have to add a dependency on image-rs to guess the mime type. + bumpalo::format!(in bump, "data:;base64,{}", base64::encode(bytes)) + }, + } + .into_bump_str(); let alt = String::from_str_in(&self.alt, bump).into_bump_str(); -- cgit