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