aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTIN TUN AUNG <62133983+rayguo17@users.noreply.github.com>2025-03-21 18:32:14 +0800
committerGitHub <noreply@github.com>2025-03-21 10:32:14 +0000
commitec20d9a3d7633ef1a4b25c991ff00bb0bb1cd295 (patch)
tree31bd0ac5236e5ce696b3f70b8e14f7f669059467
parent584b37a1f3ff3a60f88bae537b3db657f3572d6f (diff)
downloadservo-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.rs9
-rw-r--r--components/net/image_cache.rs7
-rw-r--r--components/pixels/lib.rs125
-rw-r--r--components/script/canvas_state.rs2
-rw-r--r--components/script/dom/webglrenderingcontext.rs2
-rw-r--r--components/webdriver_server/lib.rs2
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)