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