-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GIF support #4620
Merged
Merged
GIF support #4620
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
690de99
gif image loader
JustFrederik 1394873
github action issues
JustFrederik 271f276
add doc to include_gif!
JustFrederik bd97db4
clippy
JustFrederik f0c9b91
wrap vec<durations> in arc
JustFrederik 8b3be1b
remove redundant enumerate
JustFrederik 8696074
converted gif duration id from string to tuple
JustFrederik 9dc2e68
fix copy paste mistake
JustFrederik fecfd65
gif requested changes
JustFrederik ceaa44f
clippy & remove include_gif!
JustFrederik 0039b66
cargo doc --lib error fix
JustFrederik 339a045
byte loader support
JustFrederik 8d4c57a
Update crates/egui/src/widgets/image.rs
JustFrederik db96f8a
requested changes
JustFrederik 28c8664
use image_uri instead of frame_uri to get bytes
JustFrederik ec415e0
Merge branch 'emilk:master' into master
JustFrederik 75d73f8
Update crates/egui/src/widgets/image.rs
JustFrederik c339abc
requested changes
JustFrederik d89413d
clippy
JustFrederik 95ab674
slightly better error message
emilk 7020eeb
Use `&[u8]` instead of `Bytes`
emilk 5194ac9
add gif feature to all_loaders
JustFrederik c79b277
remove redundant import
JustFrederik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use egui::{ | ||
ahash::HashMap, | ||
decode_gif_uri, has_gif_magic_header, | ||
load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, | ||
mutex::Mutex, | ||
ColorImage, GifFrameDurations, Id, | ||
}; | ||
use image::AnimationDecoder as _; | ||
use std::{io::Cursor, mem::size_of, sync::Arc, time::Duration}; | ||
|
||
/// Array of Frames and the duration for how long each frame should be shown | ||
#[derive(Debug, Clone)] | ||
pub struct AnimatedImage { | ||
frames: Vec<Arc<ColorImage>>, | ||
frame_durations: GifFrameDurations, | ||
} | ||
|
||
impl AnimatedImage { | ||
fn load_gif(data: &[u8]) -> Result<Self, String> { | ||
let decoder = image::codecs::gif::GifDecoder::new(Cursor::new(data)) | ||
.map_err(|err| format!("Failed to decode gif: {err}"))?; | ||
let mut images = vec![]; | ||
let mut durations = vec![]; | ||
for frame in decoder.into_frames() { | ||
let frame = frame.map_err(|err| format!("Failed to decode gif: {err}"))?; | ||
let img = frame.buffer(); | ||
let pixels = img.as_flat_samples(); | ||
|
||
let delay: Duration = frame.delay().into(); | ||
images.push(Arc::new(ColorImage::from_rgba_unmultiplied( | ||
[img.width() as usize, img.height() as usize], | ||
pixels.as_slice(), | ||
))); | ||
durations.push(delay); | ||
} | ||
Ok(Self { | ||
frames: images, | ||
frame_durations: GifFrameDurations(Arc::new(durations)), | ||
}) | ||
} | ||
} | ||
|
||
impl AnimatedImage { | ||
pub fn byte_len(&self) -> usize { | ||
size_of::<Self>() | ||
+ self | ||
.frames | ||
.iter() | ||
.map(|image| { | ||
image.pixels.len() * size_of::<egui::Color32>() + size_of::<Duration>() | ||
}) | ||
.sum::<usize>() | ||
} | ||
|
||
/// Gets image at index | ||
pub fn get_image(&self, index: usize) -> Arc<ColorImage> { | ||
self.frames[index % self.frames.len()].clone() | ||
} | ||
} | ||
type Entry = Result<Arc<AnimatedImage>, String>; | ||
|
||
#[derive(Default)] | ||
pub struct GifLoader { | ||
cache: Mutex<HashMap<String, Entry>>, | ||
} | ||
|
||
impl GifLoader { | ||
pub const ID: &'static str = egui::generate_loader_id!(GifLoader); | ||
} | ||
|
||
impl ImageLoader for GifLoader { | ||
fn id(&self) -> &str { | ||
Self::ID | ||
} | ||
|
||
fn load(&self, ctx: &egui::Context, frame_uri: &str, _: SizeHint) -> ImageLoadResult { | ||
let (image_uri, frame_index) = | ||
decode_gif_uri(frame_uri).map_err(|_err| LoadError::NotSupported)?; | ||
let mut cache = self.cache.lock(); | ||
if let Some(entry) = cache.get(image_uri).cloned() { | ||
match entry { | ||
Ok(image) => Ok(ImagePoll::Ready { | ||
image: image.get_image(frame_index), | ||
}), | ||
Err(err) => Err(LoadError::Loading(err)), | ||
} | ||
} else { | ||
match ctx.try_load_bytes(image_uri) { | ||
Ok(BytesPoll::Ready { bytes, .. }) => { | ||
if !has_gif_magic_header(&bytes) { | ||
return Err(LoadError::NotSupported); | ||
} | ||
log::trace!("started loading {image_uri:?}"); | ||
let result = AnimatedImage::load_gif(&bytes).map(Arc::new); | ||
if let Ok(v) = &result { | ||
ctx.data_mut(|data| { | ||
*data.get_temp_mut_or_default(Id::new(image_uri)) = | ||
v.frame_durations.clone(); | ||
}); | ||
} | ||
log::trace!("finished loading {image_uri:?}"); | ||
cache.insert(image_uri.into(), result.clone()); | ||
match result { | ||
Ok(image) => Ok(ImagePoll::Ready { | ||
image: image.get_image(frame_index), | ||
}), | ||
Err(err) => Err(LoadError::Loading(err)), | ||
} | ||
} | ||
Ok(BytesPoll::Pending { size }) => Ok(ImagePoll::Pending { size }), | ||
Err(err) => Err(err), | ||
} | ||
} | ||
} | ||
|
||
fn forget(&self, uri: &str) { | ||
let _ = self.cache.lock().remove(uri); | ||
} | ||
|
||
fn forget_all(&self) { | ||
self.cache.lock().clear(); | ||
} | ||
|
||
fn byte_size(&self) -> usize { | ||
self.cache | ||
.lock() | ||
.values() | ||
.map(|v| match v { | ||
Ok(v) => v.byte_len(), | ||
Err(e) => e.len(), | ||
}) | ||
.sum() | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's super cool that this Just Works™️