Skip to main content

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::sync::Arc;
11use std::time::Duration;
12use std::{cmp, fmt, vec};
13
14use euclid::default::{Point2D, Rect, Size2D};
15use image::codecs::{bmp, gif, ico, jpeg, png, webp};
16use image::error::ImageFormatHint;
17use image::imageops::{self, FilterType};
18use image::{
19    AnimationDecoder, DynamicImage, ImageBuffer, ImageDecoder, ImageError, ImageFormat,
20    ImageResult, Limits, Rgba,
21};
22use log::{debug, error};
23use malloc_size_of_derive::MallocSizeOf;
24use serde::{Deserialize, Serialize};
25use servo_base::generic_channel::GenericSharedMemory;
26pub use snapshot::*;
27use webrender_api::units::DeviceIntSize;
28use webrender_api::{
29    ImageDescriptor, ImageDescriptorFlags, ImageFormat as WebRenderImageFormat, ImageKey,
30};
31
32#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
33pub enum FilterQuality {
34    /// No image interpolation (Nearest-neighbor)
35    None,
36    /// Low-quality image interpolation (Bilinear)
37    Low,
38    /// Medium-quality image interpolation (CatmullRom, Mitchell)
39    Medium,
40    /// High-quality image interpolation (Lanczos)
41    High,
42}
43
44#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
45pub enum PixelFormat {
46    /// Luminance channel only
47    K8,
48    /// Luminance + alpha
49    KA8,
50    /// RGB, 8 bits per channel
51    RGB8,
52    /// RGB + alpha, 8 bits per channel
53    RGBA8,
54    /// BGR + alpha, 8 bits per channel
55    BGRA8,
56}
57
58/// Computes image byte length, returning None if overflow occurred or the total length exceeds
59/// the maximum image allocation size.
60pub fn compute_rgba8_byte_length_if_within_limit(width: usize, height: usize) -> Option<usize> {
61    // Maximum allowed image allocation size (2^31-1 ~ 2GB).
62    const MAX_IMAGE_BYTE_LENGTH: usize = 2147483647;
63
64    // The color components of each pixel must be stored in four sequential
65    // elements in the order of red, green, blue, and then alpha.
66    4usize
67        .checked_mul(width)
68        .and_then(|v| v.checked_mul(height))
69        .filter(|v| *v <= MAX_IMAGE_BYTE_LENGTH)
70}
71
72/// Copies the rectangle of the source image to the destination image.
73pub fn copy_rgba8_image(
74    src_size: Size2D<u32>,
75    src_rect: Rect<u32>,
76    src_pixels: &[u8],
77    dest_size: Size2D<u32>,
78    dest_rect: Rect<u32>,
79    dest_pixels: &mut [u8],
80) {
81    assert!(!src_rect.is_empty());
82    assert!(!dest_rect.is_empty());
83    assert!(Rect::from_size(src_size).contains_rect(&src_rect));
84    assert!(Rect::from_size(dest_size).contains_rect(&dest_rect));
85    assert!(src_rect.size == dest_rect.size);
86    assert_eq!(src_pixels.len() % 4, 0);
87    assert_eq!(dest_pixels.len() % 4, 0);
88
89    if src_size == dest_size && src_rect == dest_rect {
90        dest_pixels.copy_from_slice(src_pixels);
91        return;
92    }
93
94    let src_first_column_start = src_rect.origin.x as usize * 4;
95    let src_row_length = src_size.width as usize * 4;
96    let src_first_row_start = src_rect.origin.y as usize * src_row_length;
97
98    let dest_first_column_start = dest_rect.origin.x as usize * 4;
99    let dest_row_length = dest_size.width as usize * 4;
100    let dest_first_row_start = dest_rect.origin.y as usize * dest_row_length;
101
102    let (chunk_length, chunk_count) = (
103        src_rect.size.width as usize * 4,
104        src_rect.size.height as usize,
105    );
106
107    for i in 0..chunk_count {
108        let src = &src_pixels[src_first_row_start + i * src_row_length..][src_first_column_start..]
109            [..chunk_length];
110        let dest = &mut dest_pixels[dest_first_row_start + i * dest_row_length..]
111            [dest_first_column_start..][..chunk_length];
112        dest.copy_from_slice(src);
113    }
114}
115
116/// Scales the source image to the required size, performing sampling filter algorithm.
117pub fn scale_rgba8_image(
118    size: Size2D<u32>,
119    pixels: &[u8],
120    required_size: Size2D<u32>,
121    quality: FilterQuality,
122) -> Option<Vec<u8>> {
123    let filter = match quality {
124        FilterQuality::None => FilterType::Nearest,
125        FilterQuality::Low => FilterType::Triangle,
126        FilterQuality::Medium => FilterType::CatmullRom,
127        FilterQuality::High => FilterType::Lanczos3,
128    };
129
130    let buffer: ImageBuffer<Rgba<u8>, &[u8]> =
131        ImageBuffer::from_raw(size.width, size.height, pixels)?;
132
133    let scaled_buffer =
134        imageops::resize(&buffer, required_size.width, required_size.height, filter);
135
136    Some(scaled_buffer.into_vec())
137}
138
139/// Flips the source image vertically in place.
140pub fn flip_y_rgba8_image_inplace(size: Size2D<u32>, pixels: &mut [u8]) {
141    assert_eq!(pixels.len() % 4, 0);
142
143    let row_length = size.width as usize * 4;
144    let half_height = (size.height / 2) as usize;
145
146    let (left, right) = pixels.split_at_mut(pixels.len() - row_length * half_height);
147
148    for i in 0..half_height {
149        let top = &mut left[i * row_length..][..row_length];
150        let bottom = &mut right[(half_height - i - 1) * row_length..][..row_length];
151        top.swap_with_slice(bottom);
152    }
153}
154
155pub fn rgba8_get_rect(pixels: &[u8], size: Size2D<u32>, rect: Rect<u32>) -> Cow<'_, [u8]> {
156    assert!(!rect.is_empty());
157    assert!(Rect::from_size(size).contains_rect(&rect));
158    assert_eq!(pixels.len() % 4, 0);
159    assert_eq!(size.area() as usize, pixels.len() / 4);
160    let area = rect.size.area() as usize;
161    let first_column_start = rect.origin.x as usize * 4;
162    let row_length = size.width as usize * 4;
163    let first_row_start = rect.origin.y as usize * row_length;
164    if rect.origin.x == 0 && rect.size.width == size.width || rect.size.height == 1 {
165        let start = first_column_start + first_row_start;
166        return Cow::Borrowed(&pixels[start..start + area * 4]);
167    }
168    let mut data = Vec::with_capacity(area * 4);
169    for row in pixels[first_row_start..]
170        .chunks(row_length)
171        .take(rect.size.height as usize)
172    {
173        data.extend_from_slice(&row[first_column_start..][..rect.size.width as usize * 4]);
174    }
175    data.into()
176}
177
178// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
179pub fn rgba8_byte_swap_colors_inplace(pixels: &mut [u8]) {
180    assert!(pixels.len().is_multiple_of(4));
181    for rgba in pixels.chunks_mut(4) {
182        rgba.swap(0, 2);
183    }
184}
185
186pub fn rgba8_byte_swap_and_premultiply_inplace(pixels: &mut [u8]) {
187    assert!(pixels.len().is_multiple_of(4));
188    for rgba in pixels.chunks_mut(4) {
189        let b = rgba[0];
190        rgba[0] = multiply_u8_color(rgba[2], rgba[3]);
191        rgba[1] = multiply_u8_color(rgba[1], rgba[3]);
192        rgba[2] = multiply_u8_color(b, rgba[3]);
193    }
194}
195
196/// Returns true if the pixels were found to be completely opaque.
197pub fn rgba8_premultiply_inplace(pixels: &mut [u8]) -> bool {
198    assert!(pixels.len().is_multiple_of(4));
199    let mut is_opaque = true;
200    for rgba in pixels.chunks_mut(4) {
201        rgba[0] = multiply_u8_color(rgba[0], rgba[3]);
202        rgba[1] = multiply_u8_color(rgba[1], rgba[3]);
203        rgba[2] = multiply_u8_color(rgba[2], rgba[3]);
204        is_opaque = is_opaque && rgba[3] == 255;
205    }
206    is_opaque
207}
208
209/// Returns a*b/255, rounding any fractional bits to nearest integer
210/// to reduce the loss of precision after multiple consequence alpha
211/// (un)premultiply operations.
212#[inline(always)]
213pub fn multiply_u8_color(a: u8, b: u8) -> u8 {
214    let c = a as u32 * b as u32 + 128;
215    ((c + (c >> 8)) >> 8) as u8
216}
217
218pub fn clip(
219    mut origin: Point2D<i32>,
220    mut size: Size2D<u32>,
221    surface: Size2D<u32>,
222) -> Option<Rect<u32>> {
223    if origin.x < 0 {
224        size.width = size.width.saturating_sub(-origin.x as u32);
225        origin.x = 0;
226    }
227    if origin.y < 0 {
228        size.height = size.height.saturating_sub(-origin.y as u32);
229        origin.y = 0;
230    }
231    let origin = Point2D::new(origin.x as u32, origin.y as u32);
232    Rect::new(origin, size)
233        .intersection(&Rect::from_size(surface))
234        .filter(|rect| !rect.is_empty())
235}
236
237#[derive(PartialEq)]
238pub enum EncodedImageType {
239    Png,
240    Jpeg,
241    Webp,
242}
243
244impl From<&str> for EncodedImageType {
245    // From: https://html.spec.whatwg.org/multipage/#serialising-bitmaps-to-a-file
246    // User agents must support PNG ("image/png"). User agents may support other
247    // types. If the user agent does not support the requested type, then it
248    // must create the file using the PNG format.
249    // Anything different than image/jpeg or image/webp is thus treated as PNG.
250    fn from(mime_string: &str) -> Self {
251        if mime_string.eq_ignore_ascii_case("image/jpeg") {
252            Self::Jpeg
253        } else if mime_string.eq_ignore_ascii_case("image/webp") {
254            Self::Webp
255        } else {
256            Self::Png
257        }
258    }
259}
260
261impl EncodedImageType {
262    pub fn as_mime_type(&self) -> String {
263        match self {
264            Self::Png => "image/png",
265            Self::Jpeg => "image/jpeg",
266            Self::Webp => "image/webp",
267        }
268        .to_owned()
269    }
270}
271
272/// Whether this response passed any CORS checks, and is thus safe to read from
273/// in cross-origin environments.
274#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
275pub enum CorsStatus {
276    /// The response is either same-origin or cross-origin but passed CORS checks.
277    Safe,
278    /// The response is cross-origin and did not pass CORS checks. It is unsafe
279    /// to expose pixel data to the requesting environment.
280    Unsafe,
281}
282
283/// A version of [`RasterImage`] that can be sent across IPC channels.
284#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
285pub struct SharedRasterImage {
286    pub metadata: ImageMetadata,
287    pub format: PixelFormat,
288    pub id: Option<ImageKey>,
289    pub cors_status: CorsStatus,
290    #[conditional_malloc_size_of]
291    pub bytes: Arc<GenericSharedMemory>,
292    pub frames: Vec<ImageFrame>,
293    /// Whether or not all of the frames of this image are opaque.
294    pub is_opaque: bool,
295}
296
297#[derive(Clone, MallocSizeOf)]
298pub struct RasterImage {
299    pub metadata: ImageMetadata,
300    pub format: PixelFormat,
301    pub id: Option<ImageKey>,
302    pub cors_status: CorsStatus,
303    #[conditional_malloc_size_of]
304    pub bytes: Arc<Vec<u8>>,
305    pub frames: Vec<ImageFrame>,
306    /// Whether or not all of the frames of this image are opaque.
307    pub is_opaque: bool,
308}
309
310fn sensible_delay(delay: Duration) -> Duration {
311    // Very small timeout values are problematic for two reasons: we don't want
312    // to burn energy redrawing animated images extremely fast, and broken tools
313    // generate these values when they actually want a "default" value, so such
314    // images won't play back right without normalization.
315    // https://searchfox.org/firefox-main/rev/c79acad610ddbb31bd92e837e056b53716f5ccf2/image/FrameTimeout.h#35
316    if delay <= Duration::from_millis(10) {
317        Duration::from_millis(100)
318    } else {
319        delay
320    }
321}
322
323#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
324pub struct ImageFrame {
325    pub delay: Option<Duration>,
326    /// References a range of the `bytes` field from the image that this
327    /// frame belongs to.
328    pub byte_range: Range<usize>,
329    pub width: u32,
330    pub height: u32,
331}
332
333impl ImageFrame {
334    pub fn delay(&self) -> Option<Duration> {
335        self.delay.map(sensible_delay)
336    }
337}
338
339/// A non-owning reference to the data of an [ImageFrame]
340pub struct ImageFrameView<'a> {
341    pub delay: Option<Duration>,
342    pub bytes: &'a [u8],
343    pub width: u32,
344    pub height: u32,
345}
346
347impl ImageFrameView<'_> {
348    pub fn delay(&self) -> Option<Duration> {
349        self.delay.map(sensible_delay)
350    }
351}
352
353impl RasterImage {
354    pub fn should_animate(&self) -> bool {
355        self.frames.len() > 1
356    }
357
358    fn frame_view<'image>(&'image self, frame: &ImageFrame) -> ImageFrameView<'image> {
359        ImageFrameView {
360            delay: frame.delay,
361            bytes: self.bytes.get(frame.byte_range.clone()).unwrap(),
362            width: frame.width,
363            height: frame.height,
364        }
365    }
366
367    pub fn frame(&self, index: usize) -> Option<ImageFrameView<'_>> {
368        self.frames.get(index).map(|frame| self.frame_view(frame))
369    }
370
371    pub fn first_frame(&self) -> ImageFrameView<'_> {
372        self.frame(0)
373            .expect("All images should have at least one frame")
374    }
375
376    pub fn as_snapshot(&self) -> Snapshot {
377        let size = Size2D::new(self.metadata.width, self.metadata.height);
378        let format = match self.format {
379            PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
380            PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
381            pixel_format => {
382                unimplemented!("unsupported pixel format ({pixel_format:?})");
383            },
384        };
385
386        let alpha_mode = SnapshotAlphaMode::Transparent {
387            premultiplied: true,
388        };
389
390        Snapshot::from_arc_vec(
391            size.cast(),
392            format,
393            alpha_mode,
394            self.bytes.clone(),
395            self.frames[0].byte_range.clone(),
396        )
397    }
398
399    pub fn frame_data(&self, index: usize) -> Option<&ImageFrame> {
400        self.frames.get(index)
401    }
402
403    /// Returns tuple containing three items:
404    ///   - An [`ImageDescriptor`] descriptor used to describe this image to WebRender
405    ///   - A [`GenericSharedMemory`] containing the image data
406    ///  - Whether or not this image should be cached in the `Painter` animating image cache.
407    pub fn webrender_image_descriptor_and_data_for_frame(
408        &self,
409        frame_index: usize,
410    ) -> (ImageDescriptor, GenericSharedMemory, bool) {
411        let frame = self
412            .frames
413            .get(frame_index)
414            .unwrap_or_else(|| panic!("Asked for a frame that did not exist: {frame_index:?}"));
415
416        let (format, data, should_animate) = match self.format {
417            PixelFormat::BGRA8 => (
418                WebRenderImageFormat::BGRA8,
419                GenericSharedMemory::from_arc_vec(self.bytes.clone()),
420                self.should_animate(),
421            ),
422            PixelFormat::RGBA8 => (
423                WebRenderImageFormat::RGBA8,
424                GenericSharedMemory::from_arc_vec(self.bytes.clone()),
425                self.should_animate(),
426            ),
427            PixelFormat::RGB8 => {
428                let frame_bytes = &self.bytes[frame.byte_range.clone()];
429                let mut bytes = Vec::with_capacity(frame_bytes.len() / 3 * 4);
430                for rgb in frame_bytes.chunks(3) {
431                    bytes.extend_from_slice(&[rgb[2], rgb[1], rgb[0], 0xff]);
432                }
433                (
434                    WebRenderImageFormat::BGRA8,
435                    GenericSharedMemory::from_vec(bytes),
436                    // As we are transforming each frame individually we cache all frames
437                    // in the painter and adjust the offset, therefore this image should not
438                    // be added to the Painter's image cache.
439                    false,
440                )
441            },
442            PixelFormat::K8 | PixelFormat::KA8 => {
443                panic!("Not support by webrender yet");
444            },
445        };
446        let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
447        flags.set(ImageDescriptorFlags::IS_OPAQUE, self.is_opaque);
448
449        let size = DeviceIntSize::new(self.metadata.width as i32, self.metadata.height as i32);
450        let descriptor = ImageDescriptor {
451            size,
452            stride: None,
453            format,
454            offset: frame.byte_range.start as i32,
455            flags,
456        };
457        (descriptor, data, should_animate)
458    }
459
460    /// For animations the image already exists in a cache in 'Painter'. We just send the description.
461    /// Currently we do not support 'PixelFormat::RGB8'
462    pub fn webrender_image_descriptor_and_offset_for_frame(&self) -> Option<ImageDescriptor> {
463        if self.format == PixelFormat::RGB8 ||
464            self.format == PixelFormat::K8 ||
465            self.format == PixelFormat::KA8
466        {
467            return None;
468        }
469        let format = match self.format {
470            PixelFormat::BGRA8 => WebRenderImageFormat::BGRA8,
471            PixelFormat::RGBA8 => WebRenderImageFormat::RGBA8,
472            PixelFormat::RGB8 => WebRenderImageFormat::BGRA8,
473            PixelFormat::KA8 | PixelFormat::K8 => {
474                error!("Pixel format currently not supported");
475                return None;
476            },
477        };
478        let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
479        flags.set(ImageDescriptorFlags::IS_OPAQUE, self.is_opaque);
480
481        let size = DeviceIntSize::new(self.metadata.width as i32, self.metadata.height as i32);
482        let descriptor = ImageDescriptor {
483            size,
484            stride: None,
485            format,
486            offset: 0,
487            flags,
488        };
489        Some(descriptor)
490    }
491
492    pub fn to_shared(&self) -> Arc<SharedRasterImage> {
493        Arc::new(SharedRasterImage {
494            metadata: self.metadata,
495            format: self.format,
496            id: self.id,
497            cors_status: self.cors_status,
498            bytes: Arc::new(GenericSharedMemory::from_arc_vec(self.bytes.clone())),
499            frames: self.frames.clone(),
500            is_opaque: self.is_opaque,
501        })
502    }
503}
504
505impl fmt::Debug for RasterImage {
506    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
507        write!(
508            f,
509            "Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
510            self.metadata.width, self.metadata.height, self.format, self.id
511        )
512    }
513}
514
515#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
516pub struct ImageMetadata {
517    pub width: u32,
518    pub height: u32,
519}
520
521// FIXME: Images must not be copied every frame. Instead we should atomically
522// reference count them.
523
524pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
525    if buffer.is_empty() {
526        return None;
527    }
528
529    let image_fmt_result = detect_image_format(buffer);
530    match image_fmt_result {
531        Err(msg) => {
532            debug!("{}", msg);
533            None
534        },
535        Ok(format) => {
536            let Ok(image_decoder) = make_decoder(format, buffer) else {
537                return None;
538            };
539            match image_decoder {
540                GenericImageDecoder::Png(png_decoder) => {
541                    if png_decoder.is_apng().unwrap_or_default() {
542                        let Ok(apng_decoder) = png_decoder.apng() else {
543                            return None;
544                        };
545                        decode_animated_image(cors_status, apng_decoder)
546                    } else {
547                        decode_static_image(cors_status, *png_decoder)
548                    }
549                },
550                GenericImageDecoder::Gif(animation_decoder) => {
551                    decode_animated_image(cors_status, *animation_decoder)
552                },
553                GenericImageDecoder::Webp(webp_decoder) => {
554                    if webp_decoder.has_animation() {
555                        decode_animated_image(cors_status, *webp_decoder)
556                    } else {
557                        decode_static_image(cors_status, *webp_decoder)
558                    }
559                },
560                GenericImageDecoder::Bmp(image_decoder) => {
561                    decode_static_image(cors_status, *image_decoder)
562                },
563                GenericImageDecoder::Jpeg(image_decoder) => {
564                    decode_static_image(cors_status, *image_decoder)
565                },
566                GenericImageDecoder::Ico(image_decoder) => {
567                    decode_static_image(cors_status, *image_decoder)
568                },
569            }
570        },
571    }
572}
573
574// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
575pub fn detect_image_format(buffer: &[u8]) -> Result<ImageFormat, &str> {
576    if is_gif(buffer) {
577        Ok(ImageFormat::Gif)
578    } else if is_jpeg(buffer) {
579        Ok(ImageFormat::Jpeg)
580    } else if is_png(buffer) {
581        Ok(ImageFormat::Png)
582    } else if is_webp(buffer) {
583        Ok(ImageFormat::WebP)
584    } else if is_bmp(buffer) {
585        Ok(ImageFormat::Bmp)
586    } else if is_ico(buffer) {
587        Ok(ImageFormat::Ico)
588    } else {
589        Err("Image Format Not Supported")
590    }
591}
592
593#[expect(
594    clippy::manual_checked_ops,
595    reason = "This code becomes less readable by applying the lint"
596)]
597pub fn unmultiply_inplace<const SWAP_RB: bool>(pixels: &mut [u8]) {
598    for rgba in pixels.chunks_mut(4) {
599        let a = rgba[3] as u32;
600        let mut b = rgba[2] as u32;
601        let mut g = rgba[1] as u32;
602        let mut r = rgba[0] as u32;
603
604        if a > 0 {
605            r = r * 255 / a;
606            g = g * 255 / a;
607            b = b * 255 / a;
608
609            if SWAP_RB {
610                rgba[2] = r as u8;
611                rgba[1] = g as u8;
612                rgba[0] = b as u8;
613            } else {
614                rgba[2] = b as u8;
615                rgba[1] = g as u8;
616                rgba[0] = r as u8;
617            }
618        }
619    }
620}
621
622#[repr(u8)]
623pub enum Multiply {
624    None = 0,
625    PreMultiply = 1,
626    UnMultiply = 2,
627}
628
629pub fn transform_inplace(pixels: &mut [u8], multiply: Multiply, swap_rb: bool, clear_alpha: bool) {
630    match (multiply, swap_rb, clear_alpha) {
631        (Multiply::None, true, true) => generic_transform_inplace::<0, true, true>(pixels),
632        (Multiply::None, true, false) => generic_transform_inplace::<0, true, false>(pixels),
633        (Multiply::None, false, true) => generic_transform_inplace::<0, false, true>(pixels),
634        (Multiply::None, false, false) => generic_transform_inplace::<0, false, false>(pixels),
635        (Multiply::PreMultiply, true, true) => generic_transform_inplace::<1, true, true>(pixels),
636        (Multiply::PreMultiply, true, false) => generic_transform_inplace::<1, true, false>(pixels),
637        (Multiply::PreMultiply, false, true) => generic_transform_inplace::<1, false, true>(pixels),
638        (Multiply::PreMultiply, false, false) => {
639            generic_transform_inplace::<1, false, false>(pixels)
640        },
641        (Multiply::UnMultiply, true, true) => generic_transform_inplace::<2, true, true>(pixels),
642        (Multiply::UnMultiply, true, false) => generic_transform_inplace::<2, true, false>(pixels),
643        (Multiply::UnMultiply, false, true) => generic_transform_inplace::<2, false, true>(pixels),
644        (Multiply::UnMultiply, false, false) => {
645            generic_transform_inplace::<2, false, false>(pixels)
646        },
647    }
648}
649
650#[expect(
651    clippy::manual_checked_ops,
652    reason = "This code becomes less readable by applying the lint"
653)]
654pub fn generic_transform_inplace<
655    const MULTIPLY: u8, // 1 premultiply, 2 unmultiply
656    const SWAP_RB: bool,
657    const CLEAR_ALPHA: bool,
658>(
659    pixels: &mut [u8],
660) {
661    for rgba in pixels.chunks_mut(4) {
662        match MULTIPLY {
663            1 => {
664                let a = rgba[3];
665
666                rgba[0] = multiply_u8_color(rgba[0], a);
667                rgba[1] = multiply_u8_color(rgba[1], a);
668                rgba[2] = multiply_u8_color(rgba[2], a);
669            },
670            2 => {
671                let a = rgba[3] as u32;
672
673                if a > 0 {
674                    rgba[0] = (rgba[0] as u32 * 255 / a) as u8;
675                    rgba[1] = (rgba[1] as u32 * 255 / a) as u8;
676                    rgba[2] = (rgba[2] as u32 * 255 / a) as u8;
677                }
678            },
679            _ => {},
680        }
681        if SWAP_RB {
682            rgba.swap(0, 2);
683        }
684        if CLEAR_ALPHA {
685            rgba[3] = u8::MAX;
686        }
687    }
688}
689
690fn is_gif(buffer: &[u8]) -> bool {
691    buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a")
692}
693
694fn is_jpeg(buffer: &[u8]) -> bool {
695    buffer.starts_with(&[0xff, 0xd8, 0xff])
696}
697
698fn is_png(buffer: &[u8]) -> bool {
699    buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
700}
701
702fn is_bmp(buffer: &[u8]) -> bool {
703    buffer.starts_with(&[0x42, 0x4D])
704}
705
706fn is_ico(buffer: &[u8]) -> bool {
707    buffer.starts_with(&[0x00, 0x00, 0x01, 0x00])
708}
709
710fn is_webp(buffer: &[u8]) -> bool {
711    // https://developers.google.com/speed/webp/docs/riff_container
712    // First four bytes: `RIFF`, header size 12 bytes
713    if !buffer.starts_with(b"RIFF") || buffer.len() < 12 {
714        return false;
715    }
716    let size: [u8; 4] = [buffer[4], buffer[5], buffer[6], buffer[7]];
717    // Bytes 4..8 are a little endian u32 indicating
718    // > The size of the file in bytes, starting at offset 8.
719    // > The maximum value of this field is 2^32 minus 10 bytes and thus the size
720    // > of the whole file is at most 4 GiB minus 2 bytes.
721    let len: usize = u32::from_le_bytes(size) as usize;
722    buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
723}
724
725enum GenericImageDecoder<R: std::io::BufRead + std::io::Seek> {
726    Png(Box<png::PngDecoder<R>>),
727    Gif(Box<gif::GifDecoder<R>>),
728    Webp(Box<webp::WebPDecoder<R>>),
729    Jpeg(Box<jpeg::JpegDecoder<R>>),
730    Bmp(Box<bmp::BmpDecoder<R>>),
731    Ico(Box<ico::IcoDecoder<R>>),
732}
733
734fn make_decoder(
735    format: ImageFormat,
736    buffer: &[u8],
737) -> ImageResult<GenericImageDecoder<Cursor<&[u8]>>> {
738    let limits = Limits::default();
739    let reader = Cursor::new(buffer);
740    Ok(match format {
741        ImageFormat::Png => {
742            GenericImageDecoder::Png(Box::new(png::PngDecoder::with_limits(reader, limits)?))
743        },
744        ImageFormat::Gif => GenericImageDecoder::Gif(Box::new(gif::GifDecoder::new(reader)?)),
745        ImageFormat::WebP => GenericImageDecoder::Webp(Box::new(webp::WebPDecoder::new(reader)?)),
746        ImageFormat::Jpeg => GenericImageDecoder::Jpeg(Box::new(jpeg::JpegDecoder::new(reader)?)),
747        ImageFormat::Bmp => GenericImageDecoder::Bmp(Box::new(bmp::BmpDecoder::new(reader)?)),
748        ImageFormat::Ico => GenericImageDecoder::Ico(Box::new(ico::IcoDecoder::new(reader)?)),
749        _ => {
750            return Err(ImageError::Unsupported(
751                ImageFormatHint::Exact(format).into(),
752            ));
753        },
754    })
755}
756
757fn decode_static_image(
758    cors_status: CorsStatus,
759    mut image_decoder: impl ImageDecoder,
760) -> Option<RasterImage> {
761    let orientation = image_decoder.orientation();
762
763    let Ok(mut dynamic_image) = DynamicImage::from_decoder(image_decoder) else {
764        debug!("Image decoding error");
765        return None;
766    };
767
768    if let Ok(orientation) = orientation {
769        dynamic_image.apply_orientation(orientation);
770    }
771
772    let mut rgba = dynamic_image.into_rgba8();
773
774    // Store pre-multiplied data as that prevents having to do conversions of the data at later
775    // times. This does cause an issue with some canvas APIs. See:
776    // https://github.com/servo/servo/issues/40257
777    let is_opaque = rgba8_premultiply_inplace(&mut rgba);
778
779    let frame = ImageFrame {
780        delay: None,
781        byte_range: 0..rgba.len(),
782        width: rgba.width(),
783        height: rgba.height(),
784    };
785    Some(RasterImage {
786        metadata: ImageMetadata {
787            width: rgba.width(),
788            height: rgba.height(),
789        },
790        format: PixelFormat::RGBA8,
791        frames: vec![frame],
792        bytes: Arc::new(rgba.to_vec()),
793        id: None,
794        cors_status,
795        is_opaque,
796    })
797}
798
799fn decode_animated_image<'a, T>(
800    cors_status: CorsStatus,
801    animated_image_decoder: T,
802) -> Option<RasterImage>
803where
804    T: AnimationDecoder<'a>,
805{
806    let mut width = 0;
807    let mut height = 0;
808
809    // This uses `map_while`, because the first non-decodable frame seems to
810    // send the frame iterator into an infinite loop. See
811    // <https://github.com/image-rs/image/issues/2442>.
812    let mut frame_data = vec![];
813    let mut total_number_of_bytes = 0;
814    let mut is_opaque = true;
815    let frames: Vec<ImageFrame> = animated_image_decoder
816        .into_frames()
817        .map_while(|decoded_frame| {
818            let mut animated_frame = match decoded_frame {
819                Ok(decoded_frame) => decoded_frame,
820                Err(error) => {
821                    debug!("decode Animated frame error: {error}");
822                    return None;
823                },
824            };
825
826            // Store pre-multiplied data as that prevents having to do conversions of the data at later
827            // times. This does cause an issue with some canvas APIs. See:
828            // https://github.com/servo/servo/issues/40257
829            is_opaque = rgba8_premultiply_inplace(animated_frame.buffer_mut()) && is_opaque;
830
831            let frame_start = total_number_of_bytes;
832            total_number_of_bytes += animated_frame.buffer().len();
833
834            // The image size should be at least as large as the largest frame.
835            let frame_width = animated_frame.buffer().width();
836            let frame_height = animated_frame.buffer().height();
837            width = cmp::max(width, frame_width);
838            height = cmp::max(height, frame_height);
839
840            let frame = ImageFrame {
841                byte_range: frame_start..total_number_of_bytes,
842                delay: Some(Duration::from(animated_frame.delay())),
843                width: frame_width,
844                height: frame_height,
845            };
846
847            frame_data.push(animated_frame);
848
849            Some(frame)
850        })
851        .collect();
852
853    if frames.is_empty() {
854        debug!("Animated Image decoding error");
855        return None;
856    }
857
858    // Coalesce the frame data into one single shared memory region.
859    let mut bytes = Vec::with_capacity(total_number_of_bytes);
860    for frame in frame_data {
861        bytes.extend_from_slice(frame.buffer());
862    }
863
864    Some(RasterImage {
865        metadata: ImageMetadata { width, height },
866        cors_status,
867        frames,
868        id: None,
869        format: PixelFormat::RGBA8,
870        bytes: Arc::new(bytes),
871        is_opaque,
872    })
873}
874
875#[cfg(test)]
876mod test {
877    use super::detect_image_format;
878
879    #[test]
880    fn test_supported_images() {
881        let gif1 = [b'G', b'I', b'F', b'8', b'7', b'a'];
882        let gif2 = [b'G', b'I', b'F', b'8', b'9', b'a'];
883        let jpeg = [0xff, 0xd8, 0xff];
884        let png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
885        let webp = [
886            b'R', b'I', b'F', b'F', 0x04, 0x00, 0x00, 0x00, b'W', b'E', b'B', b'P',
887        ];
888        let bmp = [0x42, 0x4D];
889        let ico = [0x00, 0x00, 0x01, 0x00];
890        let junk_format = [0x01, 0x02, 0x03, 0x04, 0x05];
891
892        assert!(detect_image_format(&gif1).is_ok());
893        assert!(detect_image_format(&gif2).is_ok());
894        assert!(detect_image_format(&jpeg).is_ok());
895        assert!(detect_image_format(&png).is_ok());
896        assert!(detect_image_format(&webp).is_ok());
897        assert!(detect_image_format(&bmp).is_ok());
898        assert!(detect_image_format(&ico).is_ok());
899        assert!(detect_image_format(&junk_format).is_err());
900    }
901}