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