diff options
author | TIN TUN AUNG <62133983+rayguo17@users.noreply.github.com> | 2025-03-21 18:32:14 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-21 10:32:14 +0000 |
commit | ec20d9a3d7633ef1a4b25c991ff00bb0bb1cd295 (patch) | |
tree | 31bd0ac5236e5ce696b3f70b8e14f7f669059467 | |
parent | 584b37a1f3ff3a60f88bae537b3db657f3572d6f (diff) | |
download | servo-ec20d9a3d7633ef1a4b25c991ff00bb0bb1cd295.tar.gz servo-ec20d9a3d7633ef1a4b25c991ff00bb0bb1cd295.zip |
pixels: Extend Image to allow for multiple frames (#36058)
Signed-off-by: rayguo17 <rayguo17@gmail.com>
-rw-r--r-- | components/compositing/compositor.rs | 9 | ||||
-rw-r--r-- | components/net/image_cache.rs | 7 | ||||
-rw-r--r-- | components/pixels/lib.rs | 125 | ||||
-rw-r--r-- | components/script/canvas_state.rs | 2 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 2 | ||||
-rw-r--r-- | components/webdriver_server/lib.rs | 2 |
6 files changed, 119 insertions, 28 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 8de9a74427d..4c8034aa883 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -30,7 +30,7 @@ use fnv::FnvHashMap; use ipc_channel::ipc::{self, IpcSharedMemory}; use libc::c_void; use log::{debug, info, trace, warn}; -use pixels::{CorsStatus, Image, PixelFormat}; +use pixels::{CorsStatus, Image, ImageFrame, PixelFormat}; use profile_traits::time::{self as profile_time, ProfilerCategory}; use profile_traits::time_profile; use script_traits::{ @@ -1454,7 +1454,12 @@ impl IOCompositor { width: image.width(), height: image.height(), format: PixelFormat::RGBA8, - bytes: ipc::IpcSharedMemory::from_bytes(&image), + frames: vec![ImageFrame { + delay: None, + bytes: ipc::IpcSharedMemory::from_bytes(&image), + width: image.width(), + height: image.height(), + }], id: None, cors_status: CorsStatus::Safe, })) diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index 97999bc6d4d..dea123054ad 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -55,14 +55,15 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m return; } let mut bytes = Vec::new(); + let frame_bytes = image.bytes(); let is_opaque = match image.format { PixelFormat::BGRA8 => { - bytes.extend_from_slice(&image.bytes); + bytes.extend_from_slice(&frame_bytes); pixels::rgba8_premultiply_inplace(bytes.as_mut_slice()) }, PixelFormat::RGB8 => { - bytes.reserve(image.bytes.len() / 3 * 4); - for bgr in image.bytes.chunks(3) { + bytes.reserve(frame_bytes.len() / 3 * 4); + for bgr in frame_bytes.chunks(3) { bytes.extend_from_slice(&[bgr[2], bgr[1], bgr[0], 0xff]); } diff --git a/components/pixels/lib.rs b/components/pixels/lib.rs index b7ac325fccd..b327d401262 100644 --- a/components/pixels/lib.rs +++ b/components/pixels/lib.rs @@ -3,10 +3,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::Cow; -use std::fmt; +use std::io::Cursor; +use std::time::Duration; +use std::{cmp, fmt, vec}; use euclid::default::{Point2D, Rect, Size2D}; -use image::ImageFormat; +use image::codecs::gif::GifDecoder; +use image::{AnimationDecoder as _, ImageFormat}; use ipc_channel::ipc::IpcSharedMemory; use log::debug; use malloc_size_of_derive::MallocSizeOf; @@ -120,11 +123,33 @@ pub struct Image { pub width: u32, pub height: u32, pub format: PixelFormat, - #[ignore_malloc_size_of = "Defined in ipc-channel"] - pub bytes: IpcSharedMemory, #[ignore_malloc_size_of = "Defined in webrender_api"] pub id: Option<ImageKey>, pub cors_status: CorsStatus, + pub frames: Vec<ImageFrame>, +} + +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] +pub struct ImageFrame { + pub delay: Option<Duration>, + #[ignore_malloc_size_of = "Defined in ipc-channel"] + pub bytes: IpcSharedMemory, + pub width: u32, + pub height: u32, +} + +impl Image { + pub fn should_animate(&self) -> bool { + self.frames.len() > 1 + } + + pub fn bytes(&self) -> IpcSharedMemory { + self.frames + .first() + .expect("Should have at least one frame") + .bytes + .clone() + } } impl fmt::Debug for Image { @@ -157,22 +182,31 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> debug!("{}", msg); None }, - Ok(_) => match image::load_from_memory(buffer) { - Ok(image) => { - let mut rgba = image.into_rgba8(); - rgba8_byte_swap_colors_inplace(&mut rgba); - Some(Image { - width: rgba.width(), - height: rgba.height(), - format: PixelFormat::BGRA8, - bytes: IpcSharedMemory::from_bytes(&rgba), - id: None, - cors_status, - }) - }, - Err(e) => { - debug!("Image decoding error: {:?}", e); - None + Ok(format) => match format { + ImageFormat::Gif => decode_gif(buffer, cors_status), + _ => match image::load_from_memory(buffer) { + Ok(image) => { + let mut rgba = image.into_rgba8(); + rgba8_byte_swap_colors_inplace(&mut rgba); + let frame = ImageFrame { + delay: None, + bytes: IpcSharedMemory::from_bytes(&rgba), + width: rgba.width(), + height: rgba.height(), + }; + Some(Image { + width: rgba.width(), + height: rgba.height(), + format: PixelFormat::BGRA8, + frames: vec![frame], + id: None, + cors_status, + }) + }, + Err(e) => { + debug!("Image decoding error: {:?}", e); + None + }, }, }, } @@ -257,6 +291,57 @@ fn is_webp(buffer: &[u8]) -> bool { buffer[8..].len() >= len && &buffer[8..12] == b"WEBP" } +fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> { + let Ok(decoded_gif) = GifDecoder::new(Cursor::new(buffer)) else { + return None; + }; + let mut width = 0; + let mut height = 0; + + // This uses `map_while`, because the first non-decodable frame seems to + // send the frame iterator into an infinite loop. See + // <https://github.com/image-rs/image/issues/2442>. + let frames: Vec<ImageFrame> = decoded_gif + .into_frames() + .map_while(|decoded_frame| { + let mut frame = match decoded_frame { + Ok(decoded_frame) => decoded_frame, + Err(error) => { + debug!("decode GIF frame error: {error}"); + return None; + }, + }; + rgba8_byte_swap_colors_inplace(frame.buffer_mut()); + + let frame = ImageFrame { + bytes: IpcSharedMemory::from_bytes(frame.buffer()), + delay: Some(Duration::from(frame.delay())), + width: frame.buffer().width(), + height: frame.buffer().height(), + }; + + // The image size should be at least as large as the largest frame. + width = cmp::max(width, frame.width); + height = cmp::max(height, frame.height); + Some(frame) + }) + .collect(); + + if frames.is_empty() { + debug!("Animated Image decoding error"); + None + } else { + Some(Image { + width, + height, + cors_status, + frames, + id: None, + format: PixelFormat::BGRA8, + }) + } +} + #[cfg(test)] mod test { use super::detect_image_format; diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 9394e3785a2..e2434b45006 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -292,7 +292,7 @@ impl CanvasState { let image_size = Size2D::new(img.width, img.height); let image_data = match img.format { - PixelFormat::BGRA8 => img.bytes.clone(), + PixelFormat::BGRA8 => img.bytes(), pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format), }; diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 98908854f28..54478a810cd 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -619,7 +619,7 @@ impl WebGLRenderingContext { let size = Size2D::new(img.width, img.height); - TexPixels::new(img.bytes.clone(), size, img.format, false) + TexPixels::new(img.bytes(), size, img.format, false) }, // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, // but we need to refactor it moving it to `HTMLCanvasElement` and support diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 701b5c56251..a1163595585 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -1677,7 +1677,7 @@ impl Handler { "Unexpected screenshot pixel format" ); - let rgb = RgbaImage::from_raw(img.width, img.height, img.bytes.to_vec()).unwrap(); + let rgb = RgbaImage::from_raw(img.width, img.height, img.bytes().to_vec()).unwrap(); let mut png_data = Cursor::new(Vec::new()); DynamicImage::ImageRgba8(rgb) .write_to(&mut png_data, ImageFormat::Png) |