1mod snapshot;
6
7use std::borrow::Cow;
8use std::io::Cursor;
9use std::ops::Range;
10use std::time::Duration;
11use std::{cmp, fmt, vec};
12
13use euclid::default::{Point2D, Rect, Size2D};
14use image::codecs::{bmp, gif, ico, jpeg, png, webp};
15use image::error::ImageFormatHint;
16use image::imageops::{self, FilterType};
17use image::{
18 AnimationDecoder, DynamicImage, ImageBuffer, ImageDecoder, ImageError, ImageFormat,
19 ImageResult, Limits, Rgba,
20};
21use ipc_channel::ipc::IpcSharedMemory;
22use log::debug;
23use malloc_size_of_derive::MallocSizeOf;
24use serde::{Deserialize, Serialize};
25pub use snapshot::*;
26use webrender_api::ImageKey;
27
28#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
29pub enum FilterQuality {
30 None,
32 Low,
34 Medium,
36 High,
38}
39
40#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
41pub enum PixelFormat {
42 K8,
44 KA8,
46 RGB8,
48 RGBA8,
50 BGRA8,
52}
53
54pub fn compute_rgba8_byte_length_if_within_limit(width: usize, height: usize) -> Option<usize> {
57 const MAX_IMAGE_BYTE_LENGTH: usize = 2147483647;
59
60 4usize
63 .checked_mul(width)
64 .and_then(|v| v.checked_mul(height))
65 .filter(|v| *v <= MAX_IMAGE_BYTE_LENGTH)
66}
67
68pub fn copy_rgba8_image(
70 src_size: Size2D<u32>,
71 src_rect: Rect<u32>,
72 src_pixels: &[u8],
73 dest_size: Size2D<u32>,
74 dest_rect: Rect<u32>,
75 dest_pixels: &mut [u8],
76) {
77 assert!(!src_rect.is_empty());
78 assert!(!dest_rect.is_empty());
79 assert!(Rect::from_size(src_size).contains_rect(&src_rect));
80 assert!(Rect::from_size(dest_size).contains_rect(&dest_rect));
81 assert!(src_rect.size == dest_rect.size);
82 assert_eq!(src_pixels.len() % 4, 0);
83 assert_eq!(dest_pixels.len() % 4, 0);
84
85 if src_size == dest_size && src_rect == dest_rect {
86 dest_pixels.copy_from_slice(src_pixels);
87 return;
88 }
89
90 let src_first_column_start = src_rect.origin.x as usize * 4;
91 let src_row_length = src_size.width as usize * 4;
92 let src_first_row_start = src_rect.origin.y as usize * src_row_length;
93
94 let dest_first_column_start = dest_rect.origin.x as usize * 4;
95 let dest_row_length = dest_size.width as usize * 4;
96 let dest_first_row_start = dest_rect.origin.y as usize * dest_row_length;
97
98 let (chunk_length, chunk_count) = (
99 src_rect.size.width as usize * 4,
100 src_rect.size.height as usize,
101 );
102
103 for i in 0..chunk_count {
104 let src = &src_pixels[src_first_row_start + i * src_row_length..][src_first_column_start..]
105 [..chunk_length];
106 let dest = &mut dest_pixels[dest_first_row_start + i * dest_row_length..]
107 [dest_first_column_start..][..chunk_length];
108 dest.copy_from_slice(src);
109 }
110}
111
112pub fn scale_rgba8_image(
114 size: Size2D<u32>,
115 pixels: &[u8],
116 required_size: Size2D<u32>,
117 quality: FilterQuality,
118) -> Option<Vec<u8>> {
119 let filter = match quality {
120 FilterQuality::None => FilterType::Nearest,
121 FilterQuality::Low => FilterType::Triangle,
122 FilterQuality::Medium => FilterType::CatmullRom,
123 FilterQuality::High => FilterType::Lanczos3,
124 };
125
126 let buffer: ImageBuffer<Rgba<u8>, &[u8]> =
127 ImageBuffer::from_raw(size.width, size.height, pixels)?;
128
129 let scaled_buffer =
130 imageops::resize(&buffer, required_size.width, required_size.height, filter);
131
132 Some(scaled_buffer.into_vec())
133}
134
135pub fn flip_y_rgba8_image_inplace(size: Size2D<u32>, pixels: &mut [u8]) {
137 assert_eq!(pixels.len() % 4, 0);
138
139 let row_length = size.width as usize * 4;
140 let half_height = (size.height / 2) as usize;
141
142 let (left, right) = pixels.split_at_mut(pixels.len() - row_length * half_height);
143
144 for i in 0..half_height {
145 let top = &mut left[i * row_length..][..row_length];
146 let bottom = &mut right[(half_height - i - 1) * row_length..][..row_length];
147 top.swap_with_slice(bottom);
148 }
149}
150
151pub fn rgba8_get_rect(pixels: &[u8], size: Size2D<u32>, rect: Rect<u32>) -> Cow<'_, [u8]> {
152 assert!(!rect.is_empty());
153 assert!(Rect::from_size(size).contains_rect(&rect));
154 assert_eq!(pixels.len() % 4, 0);
155 assert_eq!(size.area() as usize, pixels.len() / 4);
156 let area = rect.size.area() as usize;
157 let first_column_start = rect.origin.x as usize * 4;
158 let row_length = size.width as usize * 4;
159 let first_row_start = rect.origin.y as usize * row_length;
160 if rect.origin.x == 0 && rect.size.width == size.width || rect.size.height == 1 {
161 let start = first_column_start + first_row_start;
162 return Cow::Borrowed(&pixels[start..start + area * 4]);
163 }
164 let mut data = Vec::with_capacity(area * 4);
165 for row in pixels[first_row_start..]
166 .chunks(row_length)
167 .take(rect.size.height as usize)
168 {
169 data.extend_from_slice(&row[first_column_start..][..rect.size.width as usize * 4]);
170 }
171 data.into()
172}
173
174pub fn rgba8_byte_swap_colors_inplace(pixels: &mut [u8]) {
176 assert!(pixels.len() % 4 == 0);
177 for rgba in pixels.chunks_mut(4) {
178 rgba.swap(0, 2);
179 }
180}
181
182pub fn rgba8_byte_swap_and_premultiply_inplace(pixels: &mut [u8]) {
183 assert!(pixels.len() % 4 == 0);
184 for rgba in pixels.chunks_mut(4) {
185 let b = rgba[0];
186 rgba[0] = multiply_u8_color(rgba[2], rgba[3]);
187 rgba[1] = multiply_u8_color(rgba[1], rgba[3]);
188 rgba[2] = multiply_u8_color(b, rgba[3]);
189 }
190}
191
192pub fn rgba8_premultiply_inplace(pixels: &mut [u8]) -> bool {
194 assert!(pixels.len() % 4 == 0);
195 let mut is_opaque = true;
196 for rgba in pixels.chunks_mut(4) {
197 rgba[0] = multiply_u8_color(rgba[0], rgba[3]);
198 rgba[1] = multiply_u8_color(rgba[1], rgba[3]);
199 rgba[2] = multiply_u8_color(rgba[2], rgba[3]);
200 is_opaque = is_opaque && rgba[3] == 255;
201 }
202 is_opaque
203}
204
205#[inline(always)]
209pub fn multiply_u8_color(a: u8, b: u8) -> u8 {
210 let c = a as u32 * b as u32 + 128;
211 ((c + (c >> 8)) >> 8) as u8
212}
213
214pub fn clip(
215 mut origin: Point2D<i32>,
216 mut size: Size2D<u32>,
217 surface: Size2D<u32>,
218) -> Option<Rect<u32>> {
219 if origin.x < 0 {
220 size.width = size.width.saturating_sub(-origin.x as u32);
221 origin.x = 0;
222 }
223 if origin.y < 0 {
224 size.height = size.height.saturating_sub(-origin.y as u32);
225 origin.y = 0;
226 }
227 let origin = Point2D::new(origin.x as u32, origin.y as u32);
228 Rect::new(origin, size)
229 .intersection(&Rect::from_size(surface))
230 .filter(|rect| !rect.is_empty())
231}
232
233#[derive(PartialEq)]
234pub enum EncodedImageType {
235 Png,
236 Jpeg,
237 Webp,
238}
239
240impl From<String> for EncodedImageType {
241 fn from(mime_type: String) -> Self {
247 let mime = mime_type.to_lowercase();
248 if mime == "image/jpeg" {
249 Self::Jpeg
250 } else if mime == "image/webp" {
251 Self::Webp
252 } else {
253 Self::Png
254 }
255 }
256}
257
258impl EncodedImageType {
259 pub fn as_mime_type(&self) -> String {
260 match self {
261 Self::Png => "image/png",
262 Self::Jpeg => "image/jpeg",
263 Self::Webp => "image/webp",
264 }
265 .to_owned()
266 }
267}
268
269#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
272pub enum CorsStatus {
273 Safe,
275 Unsafe,
278}
279
280#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
281pub struct RasterImage {
282 pub metadata: ImageMetadata,
283 pub format: PixelFormat,
284 pub id: Option<ImageKey>,
285 pub cors_status: CorsStatus,
286 pub bytes: IpcSharedMemory,
287 pub frames: Vec<ImageFrame>,
288}
289
290fn sensible_delay(delay: Duration) -> Duration {
291 if delay <= Duration::from_millis(10) {
297 Duration::from_millis(100)
298 } else {
299 delay
300 }
301}
302
303#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
304pub struct ImageFrame {
305 pub delay: Option<Duration>,
306 pub byte_range: Range<usize>,
309 pub width: u32,
310 pub height: u32,
311}
312
313impl ImageFrame {
314 pub fn delay(&self) -> Option<Duration> {
315 self.delay.map(sensible_delay)
316 }
317}
318
319pub struct ImageFrameView<'a> {
321 pub delay: Option<Duration>,
322 pub bytes: &'a [u8],
323 pub width: u32,
324 pub height: u32,
325}
326
327impl ImageFrameView<'_> {
328 pub fn delay(&self) -> Option<Duration> {
329 self.delay.map(sensible_delay)
330 }
331}
332
333impl RasterImage {
334 pub fn should_animate(&self) -> bool {
335 self.frames.len() > 1
336 }
337
338 fn frame_view<'image>(&'image self, frame: &ImageFrame) -> ImageFrameView<'image> {
339 ImageFrameView {
340 delay: frame.delay,
341 bytes: self.bytes.get(frame.byte_range.clone()).unwrap(),
342 width: frame.width,
343 height: frame.height,
344 }
345 }
346
347 pub fn frame(&self, index: usize) -> Option<ImageFrameView<'_>> {
348 self.frames.get(index).map(|frame| self.frame_view(frame))
349 }
350
351 pub fn first_frame(&self) -> ImageFrameView<'_> {
352 self.frame(0)
353 .expect("All images should have at least one frame")
354 }
355}
356
357impl fmt::Debug for RasterImage {
358 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359 write!(
360 f,
361 "Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
362 self.metadata.width, self.metadata.height, self.format, self.id
363 )
364 }
365}
366
367#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
368pub struct ImageMetadata {
369 pub width: u32,
370 pub height: u32,
371}
372
373pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
377 if buffer.is_empty() {
378 return None;
379 }
380
381 let image_fmt_result = detect_image_format(buffer);
382 match image_fmt_result {
383 Err(msg) => {
384 debug!("{}", msg);
385 None
386 },
387 Ok(format) => {
388 let Ok(image_decoder) = make_decoder(format, buffer) else {
389 return None;
390 };
391 match image_decoder {
392 GenericImageDecoder::Png(png_decoder) => {
393 if png_decoder.is_apng().unwrap_or_default() {
394 let Ok(apng_decoder) = png_decoder.apng() else {
395 return None;
396 };
397 decode_animated_image(cors_status, apng_decoder)
398 } else {
399 decode_static_image(cors_status, *png_decoder)
400 }
401 },
402 GenericImageDecoder::Gif(animation_decoder) => {
403 decode_animated_image(cors_status, *animation_decoder)
404 },
405 GenericImageDecoder::Webp(webp_decoder) => {
406 if webp_decoder.has_animation() {
407 decode_animated_image(cors_status, *webp_decoder)
408 } else {
409 decode_static_image(cors_status, *webp_decoder)
410 }
411 },
412 GenericImageDecoder::Bmp(image_decoder) => {
413 decode_static_image(cors_status, *image_decoder)
414 },
415 GenericImageDecoder::Jpeg(image_decoder) => {
416 decode_static_image(cors_status, *image_decoder)
417 },
418 GenericImageDecoder::Ico(image_decoder) => {
419 decode_static_image(cors_status, *image_decoder)
420 },
421 }
422 },
423 }
424}
425
426pub fn detect_image_format(buffer: &[u8]) -> Result<ImageFormat, &str> {
428 if is_gif(buffer) {
429 Ok(ImageFormat::Gif)
430 } else if is_jpeg(buffer) {
431 Ok(ImageFormat::Jpeg)
432 } else if is_png(buffer) {
433 Ok(ImageFormat::Png)
434 } else if is_webp(buffer) {
435 Ok(ImageFormat::WebP)
436 } else if is_bmp(buffer) {
437 Ok(ImageFormat::Bmp)
438 } else if is_ico(buffer) {
439 Ok(ImageFormat::Ico)
440 } else {
441 Err("Image Format Not Supported")
442 }
443}
444
445pub fn unmultiply_inplace<const SWAP_RB: bool>(pixels: &mut [u8]) {
446 for rgba in pixels.chunks_mut(4) {
447 let a = rgba[3] as u32;
448 let mut b = rgba[2] as u32;
449 let mut g = rgba[1] as u32;
450 let mut r = rgba[0] as u32;
451
452 if a > 0 {
453 r = r * 255 / a;
454 g = g * 255 / a;
455 b = b * 255 / a;
456
457 if SWAP_RB {
458 rgba[2] = r as u8;
459 rgba[1] = g as u8;
460 rgba[0] = b as u8;
461 } else {
462 rgba[2] = b as u8;
463 rgba[1] = g as u8;
464 rgba[0] = r as u8;
465 }
466 }
467 }
468}
469
470#[repr(u8)]
471pub enum Multiply {
472 None = 0,
473 PreMultiply = 1,
474 UnMultiply = 2,
475}
476
477pub fn transform_inplace(pixels: &mut [u8], multiply: Multiply, swap_rb: bool, clear_alpha: bool) {
478 match (multiply, swap_rb, clear_alpha) {
479 (Multiply::None, true, true) => generic_transform_inplace::<0, true, true>(pixels),
480 (Multiply::None, true, false) => generic_transform_inplace::<0, true, false>(pixels),
481 (Multiply::None, false, true) => generic_transform_inplace::<0, false, true>(pixels),
482 (Multiply::None, false, false) => generic_transform_inplace::<0, false, false>(pixels),
483 (Multiply::PreMultiply, true, true) => generic_transform_inplace::<1, true, true>(pixels),
484 (Multiply::PreMultiply, true, false) => generic_transform_inplace::<1, true, false>(pixels),
485 (Multiply::PreMultiply, false, true) => generic_transform_inplace::<1, false, true>(pixels),
486 (Multiply::PreMultiply, false, false) => {
487 generic_transform_inplace::<1, false, false>(pixels)
488 },
489 (Multiply::UnMultiply, true, true) => generic_transform_inplace::<2, true, true>(pixels),
490 (Multiply::UnMultiply, true, false) => generic_transform_inplace::<2, true, false>(pixels),
491 (Multiply::UnMultiply, false, true) => generic_transform_inplace::<2, false, true>(pixels),
492 (Multiply::UnMultiply, false, false) => {
493 generic_transform_inplace::<2, false, false>(pixels)
494 },
495 }
496}
497
498pub fn generic_transform_inplace<
499 const MULTIPLY: u8, const SWAP_RB: bool,
501 const CLEAR_ALPHA: bool,
502>(
503 pixels: &mut [u8],
504) {
505 for rgba in pixels.chunks_mut(4) {
506 match MULTIPLY {
507 1 => {
508 let a = rgba[3];
509
510 rgba[0] = multiply_u8_color(rgba[0], a);
511 rgba[1] = multiply_u8_color(rgba[1], a);
512 rgba[2] = multiply_u8_color(rgba[2], a);
513 },
514 2 => {
515 let a = rgba[3] as u32;
516
517 if a > 0 {
518 rgba[0] = (rgba[0] as u32 * 255 / a) as u8;
519 rgba[1] = (rgba[1] as u32 * 255 / a) as u8;
520 rgba[2] = (rgba[2] as u32 * 255 / a) as u8;
521 }
522 },
523 _ => {},
524 }
525 if SWAP_RB {
526 rgba.swap(0, 2);
527 }
528 if CLEAR_ALPHA {
529 rgba[3] = u8::MAX;
530 }
531 }
532}
533
534fn is_gif(buffer: &[u8]) -> bool {
535 buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a")
536}
537
538fn is_jpeg(buffer: &[u8]) -> bool {
539 buffer.starts_with(&[0xff, 0xd8, 0xff])
540}
541
542fn is_png(buffer: &[u8]) -> bool {
543 buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
544}
545
546fn is_bmp(buffer: &[u8]) -> bool {
547 buffer.starts_with(&[0x42, 0x4D])
548}
549
550fn is_ico(buffer: &[u8]) -> bool {
551 buffer.starts_with(&[0x00, 0x00, 0x01, 0x00])
552}
553
554fn is_webp(buffer: &[u8]) -> bool {
555 if !buffer.starts_with(b"RIFF") || buffer.len() < 12 {
558 return false;
559 }
560 let size: [u8; 4] = [buffer[4], buffer[5], buffer[6], buffer[7]];
561 let len: usize = u32::from_le_bytes(size) as usize;
566 buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
567}
568
569enum GenericImageDecoder<R: std::io::BufRead + std::io::Seek> {
570 Png(Box<png::PngDecoder<R>>),
571 Gif(Box<gif::GifDecoder<R>>),
572 Webp(Box<webp::WebPDecoder<R>>),
573 Jpeg(Box<jpeg::JpegDecoder<R>>),
574 Bmp(Box<bmp::BmpDecoder<R>>),
575 Ico(Box<ico::IcoDecoder<R>>),
576}
577
578fn make_decoder(
579 format: ImageFormat,
580 buffer: &[u8],
581) -> ImageResult<GenericImageDecoder<Cursor<&[u8]>>> {
582 let limits = Limits::default();
583 let reader = Cursor::new(buffer);
584 Ok(match format {
585 ImageFormat::Png => {
586 GenericImageDecoder::Png(Box::new(png::PngDecoder::with_limits(reader, limits)?))
587 },
588 ImageFormat::Gif => GenericImageDecoder::Gif(Box::new(gif::GifDecoder::new(reader)?)),
589 ImageFormat::WebP => GenericImageDecoder::Webp(Box::new(webp::WebPDecoder::new(reader)?)),
590 ImageFormat::Jpeg => GenericImageDecoder::Jpeg(Box::new(jpeg::JpegDecoder::new(reader)?)),
591 ImageFormat::Bmp => GenericImageDecoder::Bmp(Box::new(bmp::BmpDecoder::new(reader)?)),
592 ImageFormat::Ico => GenericImageDecoder::Ico(Box::new(ico::IcoDecoder::new(reader)?)),
593 _ => {
594 return Err(ImageError::Unsupported(
595 ImageFormatHint::Exact(format).into(),
596 ));
597 },
598 })
599}
600
601fn decode_static_image(
602 cors_status: CorsStatus,
603 image_decoder: impl ImageDecoder,
604) -> Option<RasterImage> {
605 let Ok(dynamic_image) = DynamicImage::from_decoder(image_decoder) else {
606 debug!("Image decoding error");
607 return None;
608 };
609 let mut rgba = dynamic_image.into_rgba8();
610 rgba8_byte_swap_colors_inplace(&mut rgba);
611 let frame = ImageFrame {
612 delay: None,
613 byte_range: 0..rgba.len(),
614 width: rgba.width(),
615 height: rgba.height(),
616 };
617 Some(RasterImage {
618 metadata: ImageMetadata {
619 width: rgba.width(),
620 height: rgba.height(),
621 },
622 format: PixelFormat::BGRA8,
623 frames: vec![frame],
624 bytes: IpcSharedMemory::from_bytes(&rgba),
625 id: None,
626 cors_status,
627 })
628}
629
630fn decode_animated_image<'a, T>(
631 cors_status: CorsStatus,
632 animated_image_decoder: T,
633) -> Option<RasterImage>
634where
635 T: AnimationDecoder<'a>,
636{
637 let mut width = 0;
638 let mut height = 0;
639
640 let mut frame_data = vec![];
644 let mut total_number_of_bytes = 0;
645 let frames: Vec<ImageFrame> = animated_image_decoder
646 .into_frames()
647 .map_while(|decoded_frame| {
648 let mut animated_frame = match decoded_frame {
649 Ok(decoded_frame) => decoded_frame,
650 Err(error) => {
651 debug!("decode Animated frame error: {error}");
652 return None;
653 },
654 };
655 rgba8_byte_swap_colors_inplace(animated_frame.buffer_mut());
656 let frame_start = total_number_of_bytes;
657 total_number_of_bytes += animated_frame.buffer().len();
658
659 let frame_width = animated_frame.buffer().width();
661 let frame_height = animated_frame.buffer().height();
662 width = cmp::max(width, frame_width);
663 height = cmp::max(height, frame_height);
664
665 let frame = ImageFrame {
666 byte_range: frame_start..total_number_of_bytes,
667 delay: Some(Duration::from(animated_frame.delay())),
668 width: frame_width,
669 height: frame_height,
670 };
671
672 frame_data.push(animated_frame);
673
674 Some(frame)
675 })
676 .collect();
677
678 if frames.is_empty() {
679 debug!("Animated Image decoding error");
680 return None;
681 }
682
683 let mut bytes = Vec::with_capacity(total_number_of_bytes);
685 for frame in frame_data {
686 bytes.extend_from_slice(frame.buffer());
687 }
688
689 Some(RasterImage {
690 metadata: ImageMetadata { width, height },
691 cors_status,
692 frames,
693 id: None,
694 format: PixelFormat::BGRA8,
695 bytes: IpcSharedMemory::from_bytes(&bytes),
696 })
697}
698
699#[cfg(test)]
700mod test {
701 use super::detect_image_format;
702
703 #[test]
704 fn test_supported_images() {
705 let gif1 = [b'G', b'I', b'F', b'8', b'7', b'a'];
706 let gif2 = [b'G', b'I', b'F', b'8', b'9', b'a'];
707 let jpeg = [0xff, 0xd8, 0xff];
708 let png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
709 let webp = [
710 b'R', b'I', b'F', b'F', 0x04, 0x00, 0x00, 0x00, b'W', b'E', b'B', b'P',
711 ];
712 let bmp = [0x42, 0x4D];
713 let ico = [0x00, 0x00, 0x01, 0x00];
714 let junk_format = [0x01, 0x02, 0x03, 0x04, 0x05];
715
716 assert!(detect_image_format(&gif1).is_ok());
717 assert!(detect_image_format(&gif2).is_ok());
718 assert!(detect_image_format(&jpeg).is_ok());
719 assert!(detect_image_format(&png).is_ok());
720 assert!(detect_image_format(&webp).is_ok());
721 assert!(detect_image_format(&bmp).is_ok());
722 assert!(detect_image_format(&ico).is_ok());
723 assert!(detect_image_format(&junk_format).is_err());
724 }
725}