mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
pixels: Extend Image to allow for multiple frames (#36058)
Signed-off-by: rayguo17 <rayguo17@gmail.com>
This commit is contained in:
parent
584b37a1f3
commit
ec20d9a3d7
6 changed files with 119 additions and 28 deletions
|
@ -30,7 +30,7 @@ use fnv::FnvHashMap;
|
||||||
use ipc_channel::ipc::{self, IpcSharedMemory};
|
use ipc_channel::ipc::{self, IpcSharedMemory};
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use log::{debug, info, trace, warn};
|
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::{self as profile_time, ProfilerCategory};
|
||||||
use profile_traits::time_profile;
|
use profile_traits::time_profile;
|
||||||
use script_traits::{
|
use script_traits::{
|
||||||
|
@ -1454,7 +1454,12 @@ impl IOCompositor {
|
||||||
width: image.width(),
|
width: image.width(),
|
||||||
height: image.height(),
|
height: image.height(),
|
||||||
format: PixelFormat::RGBA8,
|
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,
|
id: None,
|
||||||
cors_status: CorsStatus::Safe,
|
cors_status: CorsStatus::Safe,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -55,14 +55,15 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
|
let frame_bytes = image.bytes();
|
||||||
let is_opaque = match image.format {
|
let is_opaque = match image.format {
|
||||||
PixelFormat::BGRA8 => {
|
PixelFormat::BGRA8 => {
|
||||||
bytes.extend_from_slice(&image.bytes);
|
bytes.extend_from_slice(&frame_bytes);
|
||||||
pixels::rgba8_premultiply_inplace(bytes.as_mut_slice())
|
pixels::rgba8_premultiply_inplace(bytes.as_mut_slice())
|
||||||
},
|
},
|
||||||
PixelFormat::RGB8 => {
|
PixelFormat::RGB8 => {
|
||||||
bytes.reserve(image.bytes.len() / 3 * 4);
|
bytes.reserve(frame_bytes.len() / 3 * 4);
|
||||||
for bgr in image.bytes.chunks(3) {
|
for bgr in frame_bytes.chunks(3) {
|
||||||
bytes.extend_from_slice(&[bgr[2], bgr[1], bgr[0], 0xff]);
|
bytes.extend_from_slice(&[bgr[2], bgr[1], bgr[0], 0xff]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::borrow::Cow;
|
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 euclid::default::{Point2D, Rect, Size2D};
|
||||||
use image::ImageFormat;
|
use image::codecs::gif::GifDecoder;
|
||||||
|
use image::{AnimationDecoder as _, ImageFormat};
|
||||||
use ipc_channel::ipc::IpcSharedMemory;
|
use ipc_channel::ipc::IpcSharedMemory;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
|
@ -120,11 +123,33 @@ pub struct Image {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub format: PixelFormat,
|
pub format: PixelFormat,
|
||||||
#[ignore_malloc_size_of = "Defined in ipc-channel"]
|
|
||||||
pub bytes: IpcSharedMemory,
|
|
||||||
#[ignore_malloc_size_of = "Defined in webrender_api"]
|
#[ignore_malloc_size_of = "Defined in webrender_api"]
|
||||||
pub id: Option<ImageKey>,
|
pub id: Option<ImageKey>,
|
||||||
pub cors_status: CorsStatus,
|
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 {
|
impl fmt::Debug for Image {
|
||||||
|
@ -157,22 +182,31 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image>
|
||||||
debug!("{}", msg);
|
debug!("{}", msg);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
Ok(_) => match image::load_from_memory(buffer) {
|
Ok(format) => match format {
|
||||||
Ok(image) => {
|
ImageFormat::Gif => decode_gif(buffer, cors_status),
|
||||||
let mut rgba = image.into_rgba8();
|
_ => match image::load_from_memory(buffer) {
|
||||||
rgba8_byte_swap_colors_inplace(&mut rgba);
|
Ok(image) => {
|
||||||
Some(Image {
|
let mut rgba = image.into_rgba8();
|
||||||
width: rgba.width(),
|
rgba8_byte_swap_colors_inplace(&mut rgba);
|
||||||
height: rgba.height(),
|
let frame = ImageFrame {
|
||||||
format: PixelFormat::BGRA8,
|
delay: None,
|
||||||
bytes: IpcSharedMemory::from_bytes(&rgba),
|
bytes: IpcSharedMemory::from_bytes(&rgba),
|
||||||
id: None,
|
width: rgba.width(),
|
||||||
cors_status,
|
height: rgba.height(),
|
||||||
})
|
};
|
||||||
},
|
Some(Image {
|
||||||
Err(e) => {
|
width: rgba.width(),
|
||||||
debug!("Image decoding error: {:?}", e);
|
height: rgba.height(),
|
||||||
None
|
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"
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::detect_image_format;
|
use super::detect_image_format;
|
||||||
|
|
|
@ -292,7 +292,7 @@ impl CanvasState {
|
||||||
|
|
||||||
let image_size = Size2D::new(img.width, img.height);
|
let image_size = Size2D::new(img.width, img.height);
|
||||||
let image_data = match img.format {
|
let image_data = match img.format {
|
||||||
PixelFormat::BGRA8 => img.bytes.clone(),
|
PixelFormat::BGRA8 => img.bytes(),
|
||||||
pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format),
|
pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -619,7 +619,7 @@ impl WebGLRenderingContext {
|
||||||
|
|
||||||
let size = Size2D::new(img.width, img.height);
|
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,
|
// TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D,
|
||||||
// but we need to refactor it moving it to `HTMLCanvasElement` and support
|
// but we need to refactor it moving it to `HTMLCanvasElement` and support
|
||||||
|
|
|
@ -1677,7 +1677,7 @@ impl Handler {
|
||||||
"Unexpected screenshot pixel format"
|
"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());
|
let mut png_data = Cursor::new(Vec::new());
|
||||||
DynamicImage::ImageRgba8(rgb)
|
DynamicImage::ImageRgba8(rgb)
|
||||||
.write_to(&mut png_data, ImageFormat::Png)
|
.write_to(&mut png_data, ImageFormat::Png)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue