1mod decoding;
6mod snapshot;
7
8use std::borrow::Cow;
9use std::fmt;
10use std::num::NonZeroU32;
11use std::ops::Range;
12use std::sync::Arc;
13use std::time::Duration;
14
15use euclid::default::{Point2D, Rect, Size2D};
16use image::imageops::{self, FilterType};
17use image::{ImageBuffer, ImageFormat, Rgba};
18use log::{debug, error};
19use malloc_size_of_derive::MallocSizeOf;
20use serde::{Deserialize, Serialize};
21use servo_base::cross_process_instant::CrossProcessInstant;
22use servo_base::generic_channel::GenericSharedMemory;
23pub use snapshot::*;
24use webrender_api::units::DeviceIntSize;
25use webrender_api::{
26 ImageDescriptor, ImageDescriptorFlags, ImageFormat as WebRenderImageFormat, ImageKey,
27};
28
29use crate::decoding::ServoImageDecoder;
30
31#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
32pub enum FilterQuality {
33 None,
35 Low,
37 Medium,
39 High,
41}
42
43#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
44pub enum PixelFormat {
45 K8,
47 KA8,
49 RGB8,
51 RGBA8,
53 BGRA8,
55}
56
57pub fn compute_rgba8_byte_length_if_within_limit(width: usize, height: usize) -> Option<usize> {
60 const MAX_IMAGE_BYTE_LENGTH: usize = 2147483647;
62
63 4usize
66 .checked_mul(width)
67 .and_then(|v| v.checked_mul(height))
68 .filter(|v| *v <= MAX_IMAGE_BYTE_LENGTH)
69}
70
71pub fn copy_rgba8_image(
73 src_size: Size2D<u32>,
74 src_rect: Rect<u32>,
75 src_pixels: &[u8],
76 dest_size: Size2D<u32>,
77 dest_rect: Rect<u32>,
78 dest_pixels: &mut [u8],
79) {
80 assert!(!src_rect.is_empty());
81 assert!(!dest_rect.is_empty());
82 assert!(Rect::from_size(src_size).contains_rect(&src_rect));
83 assert!(Rect::from_size(dest_size).contains_rect(&dest_rect));
84 assert!(src_rect.size == dest_rect.size);
85 assert_eq!(src_pixels.len() % 4, 0);
86 assert_eq!(dest_pixels.len() % 4, 0);
87
88 if src_size == dest_size && src_rect == dest_rect {
89 dest_pixels.copy_from_slice(src_pixels);
90 return;
91 }
92
93 let src_first_column_start = src_rect.origin.x as usize * 4;
94 let src_row_length = src_size.width as usize * 4;
95 let src_first_row_start = src_rect.origin.y as usize * src_row_length;
96
97 let dest_first_column_start = dest_rect.origin.x as usize * 4;
98 let dest_row_length = dest_size.width as usize * 4;
99 let dest_first_row_start = dest_rect.origin.y as usize * dest_row_length;
100
101 let (chunk_length, chunk_count) = (
102 src_rect.size.width as usize * 4,
103 src_rect.size.height as usize,
104 );
105
106 for i in 0..chunk_count {
107 let src = &src_pixels[src_first_row_start + i * src_row_length..][src_first_column_start..]
108 [..chunk_length];
109 let dest = &mut dest_pixels[dest_first_row_start + i * dest_row_length..]
110 [dest_first_column_start..][..chunk_length];
111 dest.copy_from_slice(src);
112 }
113}
114
115pub fn scale_rgba8_image(
117 size: Size2D<u32>,
118 pixels: &[u8],
119 required_size: Size2D<u32>,
120 quality: FilterQuality,
121) -> Option<Vec<u8>> {
122 let filter = match quality {
123 FilterQuality::None => FilterType::Nearest,
124 FilterQuality::Low => FilterType::Triangle,
125 FilterQuality::Medium => FilterType::CatmullRom,
126 FilterQuality::High => FilterType::Lanczos3,
127 };
128
129 let buffer: ImageBuffer<Rgba<u8>, &[u8]> =
130 ImageBuffer::from_raw(size.width, size.height, pixels)?;
131
132 let scaled_buffer =
133 imageops::resize(&buffer, required_size.width, required_size.height, filter);
134
135 Some(scaled_buffer.into_vec())
136}
137
138pub fn flip_y_rgba8_image_inplace(size: Size2D<u32>, pixels: &mut [u8]) {
140 assert_eq!(pixels.len() % 4, 0);
141
142 let row_length = size.width as usize * 4;
143 let half_height = (size.height / 2) as usize;
144
145 let (left, right) = pixels.split_at_mut(pixels.len() - row_length * half_height);
146
147 for i in 0..half_height {
148 let top = &mut left[i * row_length..][..row_length];
149 let bottom = &mut right[(half_height - i - 1) * row_length..][..row_length];
150 top.swap_with_slice(bottom);
151 }
152}
153
154pub fn rgba8_get_rect(pixels: &[u8], size: Size2D<u32>, rect: Rect<u32>) -> Cow<'_, [u8]> {
155 assert!(!rect.is_empty());
156 assert!(Rect::from_size(size).contains_rect(&rect));
157 assert_eq!(pixels.len() % 4, 0);
158 assert_eq!(size.area() as usize, pixels.len() / 4);
159 let area = rect.size.area() as usize;
160 let first_column_start = rect.origin.x as usize * 4;
161 let row_length = size.width as usize * 4;
162 let first_row_start = rect.origin.y as usize * row_length;
163 if rect.origin.x == 0 && rect.size.width == size.width || rect.size.height == 1 {
164 let start = first_column_start + first_row_start;
165 return Cow::Borrowed(&pixels[start..start + area * 4]);
166 }
167 let mut data = Vec::with_capacity(area * 4);
168 for row in pixels[first_row_start..]
169 .chunks(row_length)
170 .take(rect.size.height as usize)
171 {
172 data.extend_from_slice(&row[first_column_start..][..rect.size.width as usize * 4]);
173 }
174 data.into()
175}
176
177pub fn rgba8_byte_swap_colors_inplace(pixels: &mut [u8]) {
179 assert!(pixels.len().is_multiple_of(4));
180 for rgba in pixels.chunks_mut(4) {
181 rgba.swap(0, 2);
182 }
183}
184
185pub fn rgba8_byte_swap_and_premultiply_inplace(pixels: &mut [u8]) {
186 assert!(pixels.len().is_multiple_of(4));
187 for rgba in pixels.chunks_mut(4) {
188 let b = rgba[0];
189 rgba[0] = multiply_u8_color(rgba[2], rgba[3]);
190 rgba[1] = multiply_u8_color(rgba[1], rgba[3]);
191 rgba[2] = multiply_u8_color(b, rgba[3]);
192 }
193}
194
195pub fn rgba8_premultiply_inplace(pixels: &mut [u8]) -> bool {
197 assert!(pixels.len().is_multiple_of(4));
198 let mut is_opaque = true;
199 for rgba in pixels.chunks_mut(4) {
200 rgba[0] = multiply_u8_color(rgba[0], rgba[3]);
201 rgba[1] = multiply_u8_color(rgba[1], rgba[3]);
202 rgba[2] = multiply_u8_color(rgba[2], rgba[3]);
203 is_opaque = is_opaque && rgba[3] == 255;
204 }
205 is_opaque
206}
207
208#[inline(always)]
212pub fn multiply_u8_color(a: u8, b: u8) -> u8 {
213 let c = a as u32 * b as u32 + 128;
214 ((c + (c >> 8)) >> 8) as u8
215}
216
217pub fn clip(
218 mut origin: Point2D<i32>,
219 mut size: Size2D<u32>,
220 surface: Size2D<u32>,
221) -> Option<Rect<u32>> {
222 if origin.x < 0 {
223 size.width = size.width.saturating_sub(-origin.x as u32);
224 origin.x = 0;
225 }
226 if origin.y < 0 {
227 size.height = size.height.saturating_sub(-origin.y as u32);
228 origin.y = 0;
229 }
230 let origin = Point2D::new(origin.x as u32, origin.y as u32);
231 Rect::new(origin, size)
232 .intersection(&Rect::from_size(surface))
233 .filter(|rect| !rect.is_empty())
234}
235
236#[derive(PartialEq)]
237pub enum EncodedImageType {
238 Png,
239 Jpeg,
240 Webp,
241}
242
243impl From<&str> for EncodedImageType {
244 fn from(mime_string: &str) -> Self {
250 if mime_string.eq_ignore_ascii_case("image/jpeg") {
251 Self::Jpeg
252 } else if mime_string.eq_ignore_ascii_case("image/webp") {
253 Self::Webp
254 } else {
255 Self::Png
256 }
257 }
258}
259
260impl EncodedImageType {
261 pub fn as_mime_type(&self) -> &'static str {
262 match self {
263 Self::Png => "image/png",
264 Self::Jpeg => "image/jpeg",
265 Self::Webp => "image/webp",
266 }
267 }
268}
269
270#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
273pub enum CorsStatus {
274 Safe,
276 Unsafe,
279}
280
281#[derive(Clone, MallocSizeOf, PartialEq)]
282pub enum Repeat {
283 Infinite,
284 Finite(NonZeroU32),
285}
286#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
288pub struct SharedRasterImage {
289 pub metadata: ImageMetadata,
290 pub format: PixelFormat,
291 pub id: Option<ImageKey>,
292 pub cors_status: CorsStatus,
293 #[conditional_malloc_size_of]
294 pub bytes: Arc<GenericSharedMemory>,
295 pub frames: Vec<ImageFrame>,
296 pub is_opaque: bool,
298}
299
300#[derive(Clone, MallocSizeOf)]
301pub struct RasterImage {
302 pub metadata: ImageMetadata,
303 pub format: PixelFormat,
304 pub id: Option<ImageKey>,
305 pub cors_status: CorsStatus,
306 pub load_time: Option<CrossProcessInstant>,
307 #[conditional_malloc_size_of]
308 pub bytes: Arc<Vec<u8>>,
309 pub frames: Vec<ImageFrame>,
310 pub is_opaque: bool,
312 pub loop_count: Option<Repeat>,
316}
317
318fn sensible_delay(delay: Duration) -> Duration {
319 if delay <= Duration::from_millis(10) {
325 Duration::from_millis(100)
326 } else {
327 delay
328 }
329}
330
331#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
332pub struct ImageFrame {
333 pub delay: Option<Duration>,
334 pub byte_range: Range<usize>,
337 pub width: u32,
338 pub height: u32,
339}
340
341impl ImageFrame {
342 pub fn delay(&self) -> Option<Duration> {
343 self.delay.map(sensible_delay)
344 }
345}
346
347pub struct ImageFrameView<'a> {
349 pub delay: Option<Duration>,
350 pub bytes: &'a [u8],
351 pub width: u32,
352 pub height: u32,
353}
354
355impl ImageFrameView<'_> {
356 pub fn delay(&self) -> Option<Duration> {
357 self.delay.map(sensible_delay)
358 }
359}
360
361impl RasterImage {
362 pub fn should_animate(&self) -> bool {
363 self.frames.len() > 1
364 }
365
366 fn frame_view<'image>(&'image self, frame: &ImageFrame) -> ImageFrameView<'image> {
367 ImageFrameView {
368 delay: frame.delay,
369 bytes: self.bytes.get(frame.byte_range.clone()).unwrap(),
370 width: frame.width,
371 height: frame.height,
372 }
373 }
374
375 pub fn frame(&self, index: usize) -> Option<ImageFrameView<'_>> {
376 self.frames.get(index).map(|frame| self.frame_view(frame))
377 }
378
379 pub fn first_frame(&self) -> ImageFrameView<'_> {
380 self.frame(0)
381 .expect("All images should have at least one frame")
382 }
383
384 pub fn as_snapshot(&self) -> Snapshot {
385 let size = Size2D::new(self.metadata.width, self.metadata.height);
386 let format = match self.format {
387 PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
388 PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
389 pixel_format => {
390 unimplemented!("unsupported pixel format ({pixel_format:?})");
391 },
392 };
393
394 let alpha_mode = SnapshotAlphaMode::Transparent {
395 premultiplied: true,
396 };
397
398 Snapshot::from_arc_vec(
399 size.cast(),
400 format,
401 alpha_mode,
402 self.bytes.clone(),
403 self.frames[0].byte_range.clone(),
404 )
405 }
406
407 pub fn frame_data(&self, index: usize) -> Option<&ImageFrame> {
408 self.frames.get(index)
409 }
410
411 pub fn webrender_image_descriptor_and_data_for_frame(
416 &self,
417 frame_index: usize,
418 ) -> (ImageDescriptor, GenericSharedMemory, bool) {
419 let frame = self
420 .frames
421 .get(frame_index)
422 .unwrap_or_else(|| panic!("Asked for a frame that did not exist: {frame_index:?}"));
423
424 let (format, data, should_animate) = match self.format {
425 PixelFormat::BGRA8 => (
426 WebRenderImageFormat::BGRA8,
427 GenericSharedMemory::from_arc_vec(self.bytes.clone()),
428 self.should_animate(),
429 ),
430 PixelFormat::RGBA8 => (
431 WebRenderImageFormat::RGBA8,
432 GenericSharedMemory::from_arc_vec(self.bytes.clone()),
433 self.should_animate(),
434 ),
435 PixelFormat::RGB8 => {
436 let frame_bytes = &self.bytes[frame.byte_range.clone()];
437 let mut bytes = Vec::with_capacity(frame_bytes.len() / 3 * 4);
438 for rgb in frame_bytes.chunks(3) {
439 bytes.extend_from_slice(&[rgb[2], rgb[1], rgb[0], 0xff]);
440 }
441 (
442 WebRenderImageFormat::BGRA8,
443 GenericSharedMemory::from_vec(bytes),
444 false,
448 )
449 },
450 PixelFormat::K8 | PixelFormat::KA8 => {
451 panic!("Not support by webrender yet");
452 },
453 };
454 let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
455 flags.set(ImageDescriptorFlags::IS_OPAQUE, self.is_opaque);
456
457 let size = DeviceIntSize::new(self.metadata.width as i32, self.metadata.height as i32);
458 let descriptor = ImageDescriptor {
459 size,
460 stride: None,
461 format,
462 offset: frame.byte_range.start as i32,
463 flags,
464 };
465 (descriptor, data, should_animate)
466 }
467
468 pub fn webrender_image_descriptor_and_offset_for_frame(&self) -> Option<ImageDescriptor> {
471 if self.format == PixelFormat::RGB8 ||
472 self.format == PixelFormat::K8 ||
473 self.format == PixelFormat::KA8
474 {
475 return None;
476 }
477 let format = match self.format {
478 PixelFormat::BGRA8 => WebRenderImageFormat::BGRA8,
479 PixelFormat::RGBA8 => WebRenderImageFormat::RGBA8,
480 PixelFormat::RGB8 => WebRenderImageFormat::BGRA8,
481 PixelFormat::KA8 | PixelFormat::K8 => {
482 error!("Pixel format currently not supported");
483 return None;
484 },
485 };
486 let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
487 flags.set(ImageDescriptorFlags::IS_OPAQUE, self.is_opaque);
488
489 let size = DeviceIntSize::new(self.metadata.width as i32, self.metadata.height as i32);
490 let descriptor = ImageDescriptor {
491 size,
492 stride: None,
493 format,
494 offset: 0,
495 flags,
496 };
497 Some(descriptor)
498 }
499
500 pub fn to_shared(&self) -> Arc<SharedRasterImage> {
501 Arc::new(SharedRasterImage {
502 metadata: self.metadata,
503 format: self.format,
504 id: self.id,
505 cors_status: self.cors_status,
506 bytes: Arc::new(GenericSharedMemory::from_arc_vec(self.bytes.clone())),
507 frames: self.frames.clone(),
508 is_opaque: self.is_opaque,
509 })
510 }
511}
512
513impl fmt::Debug for RasterImage {
514 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
515 write!(
516 f,
517 "Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
518 self.metadata.width, self.metadata.height, self.format, self.id
519 )
520 }
521}
522
523#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
524pub struct ImageMetadata {
525 pub width: u32,
526 pub height: u32,
527}
528
529pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
533 if buffer.is_empty() {
534 return None;
535 }
536
537 let image_fmt_result = detect_image_format(buffer);
538 match image_fmt_result {
539 Err(msg) => {
540 debug!("{}", msg);
541 None
542 },
543 Ok(format) => {
544 let Ok(image_decoder) = decoding::DefaultImageDecoder::make_decoder(format, buffer)
545 else {
546 return None;
547 };
548
549 if image_decoder.is_animated() {
550 decoding::decode_animated_image(cors_status, image_decoder.animated_decoder())
551 } else {
552 decoding::decode_static_image(cors_status, image_decoder.decoder())
553 }
554 },
555 }
556}
557
558pub fn detect_image_format(buffer: &[u8]) -> Result<ImageFormat, &str> {
560 if is_gif(buffer) {
561 Ok(ImageFormat::Gif)
562 } else if is_jpeg(buffer) {
563 Ok(ImageFormat::Jpeg)
564 } else if is_png(buffer) {
565 Ok(ImageFormat::Png)
566 } else if is_webp(buffer) {
567 Ok(ImageFormat::WebP)
568 } else if is_bmp(buffer) {
569 Ok(ImageFormat::Bmp)
570 } else if is_ico(buffer) {
571 Ok(ImageFormat::Ico)
572 } else {
573 Err("Image Format Not Supported")
574 }
575}
576
577#[expect(
578 clippy::manual_checked_ops,
579 reason = "This code becomes less readable by applying the lint"
580)]
581pub fn unmultiply_inplace<const SWAP_RB: bool>(pixels: &mut [u8]) {
582 for rgba in pixels.chunks_mut(4) {
583 let a = rgba[3] as u32;
584 let mut b = rgba[2] as u32;
585 let mut g = rgba[1] as u32;
586 let mut r = rgba[0] as u32;
587
588 if a > 0 {
589 r = r * 255 / a;
590 g = g * 255 / a;
591 b = b * 255 / a;
592
593 if SWAP_RB {
594 rgba[2] = r as u8;
595 rgba[1] = g as u8;
596 rgba[0] = b as u8;
597 } else {
598 rgba[2] = b as u8;
599 rgba[1] = g as u8;
600 rgba[0] = r as u8;
601 }
602 }
603 }
604}
605
606#[repr(u8)]
607pub enum Multiply {
608 None = 0,
609 PreMultiply = 1,
610 UnMultiply = 2,
611}
612
613pub fn transform_inplace(pixels: &mut [u8], multiply: Multiply, swap_rb: bool, clear_alpha: bool) {
614 match (multiply, swap_rb, clear_alpha) {
615 (Multiply::None, true, true) => generic_transform_inplace::<0, true, true>(pixels),
616 (Multiply::None, true, false) => generic_transform_inplace::<0, true, false>(pixels),
617 (Multiply::None, false, true) => generic_transform_inplace::<0, false, true>(pixels),
618 (Multiply::None, false, false) => generic_transform_inplace::<0, false, false>(pixels),
619 (Multiply::PreMultiply, true, true) => generic_transform_inplace::<1, true, true>(pixels),
620 (Multiply::PreMultiply, true, false) => generic_transform_inplace::<1, true, false>(pixels),
621 (Multiply::PreMultiply, false, true) => generic_transform_inplace::<1, false, true>(pixels),
622 (Multiply::PreMultiply, false, false) => {
623 generic_transform_inplace::<1, false, false>(pixels)
624 },
625 (Multiply::UnMultiply, true, true) => generic_transform_inplace::<2, true, true>(pixels),
626 (Multiply::UnMultiply, true, false) => generic_transform_inplace::<2, true, false>(pixels),
627 (Multiply::UnMultiply, false, true) => generic_transform_inplace::<2, false, true>(pixels),
628 (Multiply::UnMultiply, false, false) => {
629 generic_transform_inplace::<2, false, false>(pixels)
630 },
631 }
632}
633
634#[expect(
635 clippy::manual_checked_ops,
636 reason = "This code becomes less readable by applying the lint"
637)]
638pub fn generic_transform_inplace<
639 const MULTIPLY: u8, const SWAP_RB: bool,
641 const CLEAR_ALPHA: bool,
642>(
643 pixels: &mut [u8],
644) {
645 for rgba in pixels.chunks_mut(4) {
646 match MULTIPLY {
647 1 => {
648 let a = rgba[3];
649
650 rgba[0] = multiply_u8_color(rgba[0], a);
651 rgba[1] = multiply_u8_color(rgba[1], a);
652 rgba[2] = multiply_u8_color(rgba[2], a);
653 },
654 2 => {
655 let a = rgba[3] as u32;
656
657 if a > 0 {
658 rgba[0] = (rgba[0] as u32 * 255 / a) as u8;
659 rgba[1] = (rgba[1] as u32 * 255 / a) as u8;
660 rgba[2] = (rgba[2] as u32 * 255 / a) as u8;
661 }
662 },
663 _ => {},
664 }
665 if SWAP_RB {
666 rgba.swap(0, 2);
667 }
668 if CLEAR_ALPHA {
669 rgba[3] = u8::MAX;
670 }
671 }
672}
673
674fn is_gif(buffer: &[u8]) -> bool {
675 buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a")
676}
677
678fn is_jpeg(buffer: &[u8]) -> bool {
679 buffer.starts_with(&[0xff, 0xd8, 0xff])
680}
681
682fn is_png(buffer: &[u8]) -> bool {
683 buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
684}
685
686fn is_bmp(buffer: &[u8]) -> bool {
687 buffer.starts_with(&[0x42, 0x4D])
688}
689
690fn is_ico(buffer: &[u8]) -> bool {
691 buffer.starts_with(&[0x00, 0x00, 0x01, 0x00])
692}
693
694fn is_webp(buffer: &[u8]) -> bool {
695 if !buffer.starts_with(b"RIFF") || buffer.len() < 12 {
698 return false;
699 }
700 let size: [u8; 4] = [buffer[4], buffer[5], buffer[6], buffer[7]];
701 let len: usize = u32::from_le_bytes(size) as usize;
706 buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
707}
708
709#[cfg(test)]
710mod test {
711 use super::detect_image_format;
712
713 #[test]
714 fn test_supported_images() {
715 let gif1 = [b'G', b'I', b'F', b'8', b'7', b'a'];
716 let gif2 = [b'G', b'I', b'F', b'8', b'9', b'a'];
717 let jpeg = [0xff, 0xd8, 0xff];
718 let png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
719 let webp = [
720 b'R', b'I', b'F', b'F', 0x04, 0x00, 0x00, 0x00, b'W', b'E', b'B', b'P',
721 ];
722 let bmp = [0x42, 0x4D];
723 let ico = [0x00, 0x00, 0x01, 0x00];
724 let junk_format = [0x01, 0x02, 0x03, 0x04, 0x05];
725
726 assert!(detect_image_format(&gif1).is_ok());
727 assert!(detect_image_format(&gif2).is_ok());
728 assert!(detect_image_format(&jpeg).is_ok());
729 assert!(detect_image_format(&png).is_ok());
730 assert!(detect_image_format(&webp).is_ok());
731 assert!(detect_image_format(&bmp).is_ok());
732 assert!(detect_image_format(&ico).is_ok());
733 assert!(detect_image_format(&junk_format).is_err());
734 }
735}