pixels/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5mod 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    /// No image interpolation (Nearest-neighbor)
31    None,
32    /// Low-quality image interpolation (Bilinear)
33    Low,
34    /// Medium-quality image interpolation (CatmullRom, Mitchell)
35    Medium,
36    /// High-quality image interpolation (Lanczos)
37    High,
38}
39
40#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
41pub enum PixelFormat {
42    /// Luminance channel only
43    K8,
44    /// Luminance + alpha
45    KA8,
46    /// RGB, 8 bits per channel
47    RGB8,
48    /// RGB + alpha, 8 bits per channel
49    RGBA8,
50    /// BGR + alpha, 8 bits per channel
51    BGRA8,
52}
53
54/// Computes image byte length, returning None if overflow occurred or the total length exceeds
55/// the maximum image allocation size.
56pub fn compute_rgba8_byte_length_if_within_limit(width: usize, height: usize) -> Option<usize> {
57    // Maximum allowed image allocation size (2^31-1 ~ 2GB).
58    const MAX_IMAGE_BYTE_LENGTH: usize = 2147483647;
59
60    // The color components of each pixel must be stored in four sequential
61    // elements in the order of red, green, blue, and then alpha.
62    4usize
63        .checked_mul(width)
64        .and_then(|v| v.checked_mul(height))
65        .filter(|v| *v <= MAX_IMAGE_BYTE_LENGTH)
66}
67
68/// Copies the rectangle of the source image to the destination image.
69pub 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
112/// Scales the source image to the required size, performing sampling filter algorithm.
113pub 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
135/// Flips the source image vertically in place.
136pub 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
174// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
175pub 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
192/// Returns true if the pixels were found to be completely opaque.
193pub 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/// Returns a*b/255, rounding any fractional bits to nearest integer
206/// to reduce the loss of precision after multiple consequence alpha
207/// (un)premultiply operations.
208#[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    // From: https://html.spec.whatwg.org/multipage/#serialising-bitmaps-to-a-file
242    // User agents must support PNG ("image/png"). User agents may support other
243    // types. If the user agent does not support the requested type, then it
244    // must create the file using the PNG format.
245    // Anything different than image/jpeg or image/webp is thus treated as PNG.
246    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/// Whether this response passed any CORS checks, and is thus safe to read from
270/// in cross-origin environments.
271#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
272pub enum CorsStatus {
273    /// The response is either same-origin or cross-origin but passed CORS checks.
274    Safe,
275    /// The response is cross-origin and did not pass CORS checks. It is unsafe
276    /// to expose pixel data to the requesting environment.
277    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    // Very small timeout values are problematic for two reasons: we don't want
292    // to burn energy redrawing animated images extremely fast, and broken tools
293    // generate these values when they actually want a "default" value, so such
294    // images won't play back right without normalization.
295    // https://searchfox.org/firefox-main/rev/c79acad610ddbb31bd92e837e056b53716f5ccf2/image/FrameTimeout.h#35
296    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    /// References a range of the `bytes` field from the image that this
307    /// frame belongs to.
308    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
319/// A non-owning reference to the data of an [ImageFrame]
320pub 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
373// FIXME: Images must not be copied every frame. Instead we should atomically
374// reference count them.
375
376pub 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
426// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
427pub 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, // 1 premultiply, 2 unmultiply
500    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    // https://developers.google.com/speed/webp/docs/riff_container
556    // First four bytes: `RIFF`, header size 12 bytes
557    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    // Bytes 4..8 are a little endian u32 indicating
562    // > The size of the file in bytes, starting at offset 8.
563    // > The maximum value of this field is 2^32 minus 10 bytes and thus the size
564    // > of the whole file is at most 4 GiB minus 2 bytes.
565    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    // This uses `map_while`, because the first non-decodable frame seems to
641    // send the frame iterator into an infinite loop. See
642    // <https://github.com/image-rs/image/issues/2442>.
643    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            // The image size should be at least as large as the largest frame.
660            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    // Coalesce the frame data into one single shared memory region.
684    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}