integrate gifs

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-02-20 15:08:13 -05:00
parent d1c7a5a239
commit 490dedfaf1
8 changed files with 318 additions and 98 deletions

View File

@@ -1,6 +1,7 @@
use crate::urls::{UrlCache, UrlMimes};
use crate::Result;
use egui::TextureHandle;
use image::{Delay, Frame};
use poll_promise::Promise;
use egui::ColorImage;
@@ -80,31 +81,8 @@ impl MediaCache {
}
}
/*
pub fn fetch(image: &str) -> Result<Image> {
let m_cached_promise = img_cache.map().get(image);
if m_cached_promise.is_none() {
let res = crate::images::fetch_img(
img_cache,
ui.ctx(),
&image,
ImageType::Content(width.round() as u32, height.round() as u32),
);
img_cache.map_mut().insert(image.to_owned(), res);
}
}
*/
pub fn write(cache_dir: &path::Path, url: &str, data: ColorImage) -> Result<()> {
let file_path = cache_dir.join(Self::key(url));
if let Some(p) = file_path.parent() {
create_dir_all(p)?;
}
let file = File::options()
.write(true)
.create(true)
.truncate(true)
.open(file_path)?;
let file = Self::create_file(cache_dir, url)?;
let encoder = image::codecs::webp::WebPEncoder::new_lossless(file);
encoder.encode(
@@ -117,6 +95,33 @@ impl MediaCache {
Ok(())
}
fn create_file(cache_dir: &path::Path, url: &str) -> Result<File> {
let file_path = cache_dir.join(Self::key(url));
if let Some(p) = file_path.parent() {
create_dir_all(p)?;
}
Ok(File::options()
.write(true)
.create(true)
.truncate(true)
.open(file_path)?)
}
pub fn write_gif(cache_dir: &path::Path, url: &str, data: Vec<ImageFrame>) -> Result<()> {
let file = Self::create_file(cache_dir, url)?;
let mut encoder = image::codecs::gif::GifEncoder::new(file);
for img in data {
let buf = color_image_to_rgba(img.image);
let frame = Frame::from_parts(buf, 0, 0, Delay::from_saturating_duration(img.delay));
if let Err(e) = encoder.encode_frame(frame) {
tracing::error!("problem encoding frame: {e}");
}
}
Ok(())
}
pub fn key(url: &str) -> String {
let k: String = sha2::Sha256::digest(url.as_bytes()).encode_hex();
PathBuf::from(&k[0..2])
@@ -174,12 +179,18 @@ impl MediaCache {
}
}
// TODO: temporary...
pub fn get_texture(textured_image: &TexturedImage) -> &TextureHandle {
match textured_image {
TexturedImage::Static(texture_handle) => texture_handle,
TexturedImage::Animated(_animation) => todo!(), // Temporary...
}
fn color_image_to_rgba(color_image: ColorImage) -> image::RgbaImage {
let width = color_image.width() as u32;
let height = color_image.height() as u32;
let rgba_pixels: Vec<u8> = color_image
.pixels
.iter()
.flat_map(|color| color.to_array()) // Convert Color32 to `[u8; 4]`
.collect();
image::RgbaImage::from_raw(width, height, rgba_pixels)
.expect("Failed to create RgbaImage from ColorImage")
}
pub struct Images {

View File

@@ -32,7 +32,8 @@ pub use error::{Error, FilterError};
pub use filter::{FilterState, FilterStates, UnifiedSubscription};
pub use fonts::NamedFontFamily;
pub use imgcache::{
get_texture, GifState, GifStateMap, Images, MediaCache, MediaCacheType, TexturedImage,
Animation, GifState, GifStateMap, ImageFrame, Images, MediaCache, MediaCacheType,
MediaCacheValue, TextureFrame, TexturedImage,
};
pub use muted::{MuteFun, Muted};
pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf};

View File

@@ -11,7 +11,7 @@ use egui::TextBuffer;
use poll_promise::Promise;
use url::Url;
use crate::Error;
use crate::{Error, MediaCacheType};
const FILE_NAME: &str = "urls.bin";
const SAVE_INTERVAL: Duration = Duration::from_secs(60);
@@ -230,10 +230,26 @@ impl SupportedMimeType {
}
}
pub fn from_mime(mime: mime_guess::mime::Mime) -> Result<Self, Error> {
if is_mime_supported(&mime) {
Ok(Self { mime })
} else {
Err(Error::Generic("Unsupported mime type".to_owned()))
}
}
#[allow(unused)]
pub fn to_mime(&self) -> &str {
self.mime.essence_str()
}
pub fn to_cache_type(&self) -> MediaCacheType {
if self.mime == mime_guess::mime::IMAGE_GIF {
MediaCacheType::Gif
} else {
MediaCacheType::Image
}
}
}
fn is_mime_supported(mime: &mime_guess::Mime) -> bool {
@@ -248,8 +264,8 @@ fn url_has_supported_mime(url: &str) -> MimeHostedAtUrl {
.extension()
.and_then(|ext| ext.to_str())
{
if SupportedMimeType::from_extension(ext).is_ok() {
return MimeHostedAtUrl::Yes;
if let Ok(supported) = SupportedMimeType::from_extension(ext) {
return MimeHostedAtUrl::Yes(supported.to_cache_type());
} else {
return MimeHostedAtUrl::No;
}
@@ -260,21 +276,23 @@ fn url_has_supported_mime(url: &str) -> MimeHostedAtUrl {
MimeHostedAtUrl::Maybe
}
pub fn supported_mime_hosted_at_url(urls: &mut UrlMimes, url: &str) -> bool {
pub fn supported_mime_hosted_at_url(urls: &mut UrlMimes, url: &str) -> Option<MediaCacheType> {
match url_has_supported_mime(url) {
MimeHostedAtUrl::Yes => true,
MimeHostedAtUrl::Yes(cache_type) => Some(cache_type),
MimeHostedAtUrl::Maybe => urls
.get(url)
.and_then(|s| s.parse::<mime_guess::mime::Mime>().ok())
.map_or(false, |mime: mime_guess::mime::Mime| {
is_mime_supported(&mime)
.and_then(|mime: mime_guess::mime::Mime| {
SupportedMimeType::from_mime(mime)
.ok()
.map(|s| s.to_cache_type())
}),
MimeHostedAtUrl::No => false,
MimeHostedAtUrl::No => None,
}
}
enum MimeHostedAtUrl {
Yes,
Yes(MediaCacheType),
Maybe,
No,
}