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) -> String {
261        match self {
262            Self::Png => "image/png",
263            Self::Jpeg => "image/jpeg",
264            Self::Webp => "image/webp",
265        }
266        .to_owned()
267    }
268}
269
270/// Whether this response passed any CORS checks, and is thus safe to read from
271/// in cross-origin environments.
272#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
273pub enum CorsStatus {
274    /// The response is either same-origin or cross-origin but passed CORS checks.
275    Safe,
276    /// The response is cross-origin and did not pass CORS checks. It is unsafe
277    /// to expose pixel data to the requesting environment.
278    Unsafe,
279}
280
281#[derive(Clone, MallocSizeOf, PartialEq)]
282pub enum Repeat {
283    Infinite,
284    Finite(NonZeroU32),
285}
286/// A version of [`RasterImage`] that can be sent across IPC channels.
287#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
288pub struct SharedRasterImage {
289    pub metadata: ImageMetadata,
290    pub format: PixelFormat,
291    pub id: Option<ImageKey>,
292    pub cors_status: CorsStatus,
293    #[conditional_malloc_size_of]
294    pub bytes: Arc<GenericSharedMemory>,
295    pub frames: Vec<ImageFrame>,
296    /// Whether or not all of the frames of this image are opaque.
297    pub is_opaque: bool,
298}
299
300#[derive(Clone, MallocSizeOf)]
301pub struct RasterImage {
302    pub metadata: ImageMetadata,
303    pub format: PixelFormat,
304    pub id: Option<ImageKey>,
305    pub cors_status: CorsStatus,
306    #[conditional_malloc_size_of]
307    pub bytes: Arc<Vec<u8>>,
308    pub frames: Vec<ImageFrame>,
309    /// Whether or not all of the frames of this image are opaque.
310    pub is_opaque: bool,
311    /// The loop count for this image's animation. For animated images, this
312    /// has a default value of `Repeat::Infinite` (if no loop count is specified in
313    /// the image).  For images that do not animate, this will be `None`.
314    pub loop_count: Option<Repeat>,
315}
316
317fn sensible_delay(delay: Duration) -> Duration {
318    // Very small timeout values are problematic for two reasons: we don't want
319    // to burn energy redrawing animated images extremely fast, and broken tools
320    // generate these values when they actually want a "default" value, so such
321    // images won't play back right without normalization.
322    // https://searchfox.org/firefox-main/rev/c79acad610ddbb31bd92e837e056b53716f5ccf2/image/FrameTimeout.h#35
323    if delay <= Duration::from_millis(10) {
324        Duration::from_millis(100)
325    } else {
326        delay
327    }
328}
329
330#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
331pub struct ImageFrame {
332    pub delay: Option<Duration>,
333    /// References a range of the `bytes` field from the image that this
334    /// frame belongs to.
335    pub byte_range: Range<usize>,
336    pub width: u32,
337    pub height: u32,
338}
339
340impl ImageFrame {
341    pub fn delay(&self) -> Option<Duration> {
342        self.delay.map(sensible_delay)
343    }
344}
345
346/// A non-owning reference to the data of an [ImageFrame]
347pub struct ImageFrameView<'a> {
348    pub delay: Option<Duration>,
349    pub bytes: &'a [u8],
350    pub width: u32,
351    pub height: u32,
352}
353
354impl ImageFrameView<'_> {
355    pub fn delay(&self) -> Option<Duration> {
356        self.delay.map(sensible_delay)
357    }
358}
359
360impl RasterImage {
361    pub fn should_animate(&self) -> bool {
362        self.frames.len() > 1
363    }
364
365    fn frame_view<'image>(&'image self, frame: &ImageFrame) -> ImageFrameView<'image> {
366        ImageFrameView {
367            delay: frame.delay,
368            bytes: self.bytes.get(frame.byte_range.clone()).unwrap(),
369            width: frame.width,
370            height: frame.height,
371        }
372    }
373
374    pub fn frame(&self, index: usize) -> Option<ImageFrameView<'_>> {
375        self.frames.get(index).map(|frame| self.frame_view(frame))
376    }
377
378    pub fn first_frame(&self) -> ImageFrameView<'_> {
379        self.frame(0)
380            .expect("All images should have at least one frame")
381    }
382
383    pub fn as_snapshot(&self) -> Snapshot {
384        let size = Size2D::new(self.metadata.width, self.metadata.height);
385        let format = match self.format {
386            PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
387            PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
388            pixel_format => {
389                unimplemented!("unsupported pixel format ({pixel_format:?})");
390            },
391        };
392
393        let alpha_mode = SnapshotAlphaMode::Transparent {
394            premultiplied: true,
395        };
396
397        Snapshot::from_arc_vec(
398            size.cast(),
399            format,
400            alpha_mode,
401            self.bytes.clone(),
402            self.frames[0].byte_range.clone(),
403        )
404    }
405
406    pub fn frame_data(&self, index: usize) -> Option<&ImageFrame> {
407        self.frames.get(index)
408    }
409
410    /// Returns tuple containing three items:
411    ///   - An [`ImageDescriptor`] descriptor used to describe this image to WebRender
412    ///   - A [`GenericSharedMemory`] containing the image data
413    ///  - Whether or not this image should be cached in the `Painter` animating image cache.
414    pub fn webrender_image_descriptor_and_data_for_frame(
415        &self,
416        frame_index: usize,
417    ) -> (ImageDescriptor, GenericSharedMemory, bool) {
418        let frame = self
419            .frames
420            .get(frame_index)
421            .unwrap_or_else(|| panic!("Asked for a frame that did not exist: {frame_index:?}"));
422
423        let (format, data, should_animate) = match self.format {
424            PixelFormat::BGRA8 => (
425                WebRenderImageFormat::BGRA8,
426                GenericSharedMemory::from_arc_vec(self.bytes.clone()),
427                self.should_animate(),
428            ),
429            PixelFormat::RGBA8 => (
430                WebRenderImageFormat::RGBA8,
431                GenericSharedMemory::from_arc_vec(self.bytes.clone()),
432                self.should_animate(),
433            ),
434            PixelFormat::RGB8 => {
435                let frame_bytes = &self.bytes[frame.byte_range.clone()];
436                let mut bytes = Vec::with_capacity(frame_bytes.len() / 3 * 4);
437                for rgb in frame_bytes.chunks(3) {
438                    bytes.extend_from_slice(&[rgb[2], rgb[1], rgb[0], 0xff]);
439                }
440                (
441                    WebRenderImageFormat::BGRA8,
442                    GenericSharedMemory::from_vec(bytes),
443                    // As we are transforming each frame individually we cache all frames
444                    // in the painter and adjust the offset, therefore this image should not
445                    // be added to the Painter's image cache.
446                    false,
447                )
448            },
449            PixelFormat::K8 | PixelFormat::KA8 => {
450                panic!("Not support by webrender yet");
451            },
452        };
453        let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
454        flags.set(ImageDescriptorFlags::IS_OPAQUE, self.is_opaque);
455
456        let size = DeviceIntSize::new(self.metadata.width as i32, self.metadata.height as i32);
457        let descriptor = ImageDescriptor {
458            size,
459            stride: None,
460            format,
461            offset: frame.byte_range.start as i32,
462            flags,
463        };
464        (descriptor, data, should_animate)
465    }
466
467    /// For animations the image already exists in a cache in 'Painter'. We just send the description.
468    /// Currently we do not support 'PixelFormat::RGB8'
469    pub fn webrender_image_descriptor_and_offset_for_frame(&self) -> Option<ImageDescriptor> {
470        if self.format == PixelFormat::RGB8 ||
471            self.format == PixelFormat::K8 ||
472            self.format == PixelFormat::KA8
473        {
474            return None;
475        }
476        let format = match self.format {
477            PixelFormat::BGRA8 => WebRenderImageFormat::BGRA8,
478            PixelFormat::RGBA8 => WebRenderImageFormat::RGBA8,
479            PixelFormat::RGB8 => WebRenderImageFormat::BGRA8,
480            PixelFormat::KA8 | PixelFormat::K8 => {
481                error!("Pixel format currently not supported");
482                return None;
483            },
484        };
485        let mut flags = ImageDescriptorFlags::ALLOW_MIPMAPS;
486        flags.set(ImageDescriptorFlags::IS_OPAQUE, self.is_opaque);
487
488        let size = DeviceIntSize::new(self.metadata.width as i32, self.metadata.height as i32);
489        let descriptor = ImageDescriptor {
490            size,
491            stride: None,
492            format,
493            offset: 0,
494            flags,
495        };
496        Some(descriptor)
497    }
498
499    pub fn to_shared(&self) -> Arc<SharedRasterImage> {
500        Arc::new(SharedRasterImage {
501            metadata: self.metadata,
502            format: self.format,
503            id: self.id,
504            cors_status: self.cors_status,
505            bytes: Arc::new(GenericSharedMemory::from_arc_vec(self.bytes.clone())),
506            frames: self.frames.clone(),
507            is_opaque: self.is_opaque,
508        })
509    }
510}
511
512impl fmt::Debug for RasterImage {
513    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
514        write!(
515            f,
516            "Image {{ width: {}, height: {}, format: {:?}, ..., id: {:?} }}",
517            self.metadata.width, self.metadata.height, self.format, self.id
518        )
519    }
520}
521
522#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
523pub struct ImageMetadata {
524    pub width: u32,
525    pub height: u32,
526}
527
528// FIXME: Images must not be copied every frame. Instead we should atomically
529// reference count them.
530
531pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<RasterImage> {
532    if buffer.is_empty() {
533        return None;
534    }
535
536    let image_fmt_result = detect_image_format(buffer);
537    match image_fmt_result {
538        Err(msg) => {
539            debug!("{}", msg);
540            None
541        },
542        Ok(format) => {
543            let Ok(image_decoder) = decoding::DefaultImageDecoder::make_decoder(format, buffer)
544            else {
545                return None;
546            };
547
548            if image_decoder.is_animated() {
549                decoding::decode_animated_image(cors_status, image_decoder.animated_decoder())
550            } else {
551                decoding::decode_static_image(cors_status, image_decoder.decoder())
552            }
553        },
554    }
555}
556
557// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
558pub fn detect_image_format(buffer: &[u8]) -> Result<ImageFormat, &str> {
559    if is_gif(buffer) {
560        Ok(ImageFormat::Gif)
561    } else if is_jpeg(buffer) {
562        Ok(ImageFormat::Jpeg)
563    } else if is_png(buffer) {
564        Ok(ImageFormat::Png)
565    } else if is_webp(buffer) {
566        Ok(ImageFormat::WebP)
567    } else if is_bmp(buffer) {
568        Ok(ImageFormat::Bmp)
569    } else if is_ico(buffer) {
570        Ok(ImageFormat::Ico)
571    } else {
572        Err("Image Format Not Supported")
573    }
574}
575
576#[expect(
577    clippy::manual_checked_ops,
578    reason = "This code becomes less readable by applying the lint"
579)]
580pub fn unmultiply_inplace<const SWAP_RB: bool>(pixels: &mut [u8]) {
581    for rgba in pixels.chunks_mut(4) {
582        let a = rgba[3] as u32;
583        let mut b = rgba[2] as u32;
584        let mut g = rgba[1] as u32;
585        let mut r = rgba[0] as u32;
586
587        if a > 0 {
588            r = r * 255 / a;
589            g = g * 255 / a;
590            b = b * 255 / a;
591
592            if SWAP_RB {
593                rgba[2] = r as u8;
594                rgba[1] = g as u8;
595                rgba[0] = b as u8;
596            } else {
597                rgba[2] = b as u8;
598                rgba[1] = g as u8;
599                rgba[0] = r as u8;
600            }
601        }
602    }
603}
604
605#[repr(u8)]
606pub enum Multiply {
607    None = 0,
608    PreMultiply = 1,
609    UnMultiply = 2,
610}
611
612pub fn transform_inplace(pixels: &mut [u8], multiply: Multiply, swap_rb: bool, clear_alpha: bool) {
613    match (multiply, swap_rb, clear_alpha) {
614        (Multiply::None, true, true) => generic_transform_inplace::<0, true, true>(pixels),
615        (Multiply::None, true, false) => generic_transform_inplace::<0, true, false>(pixels),
616        (Multiply::None, false, true) => generic_transform_inplace::<0, false, true>(pixels),
617        (Multiply::None, false, false) => generic_transform_inplace::<0, false, false>(pixels),
618        (Multiply::PreMultiply, true, true) => generic_transform_inplace::<1, true, true>(pixels),
619        (Multiply::PreMultiply, true, false) => generic_transform_inplace::<1, true, false>(pixels),
620        (Multiply::PreMultiply, false, true) => generic_transform_inplace::<1, false, true>(pixels),
621        (Multiply::PreMultiply, false, false) => {
622            generic_transform_inplace::<1, false, false>(pixels)
623        },
624        (Multiply::UnMultiply, true, true) => generic_transform_inplace::<2, true, true>(pixels),
625        (Multiply::UnMultiply, true, false) => generic_transform_inplace::<2, true, false>(pixels),
626        (Multiply::UnMultiply, false, true) => generic_transform_inplace::<2, false, true>(pixels),
627        (Multiply::UnMultiply, false, false) => {
628            generic_transform_inplace::<2, false, false>(pixels)
629        },
630    }
631}
632
633#[expect(
634    clippy::manual_checked_ops,
635    reason = "This code becomes less readable by applying the lint"
636)]
637pub fn generic_transform_inplace<
638    const MULTIPLY: u8, // 1 premultiply, 2 unmultiply
639    const SWAP_RB: bool,
640    const CLEAR_ALPHA: bool,
641>(
642    pixels: &mut [u8],
643) {
644    for rgba in pixels.chunks_mut(4) {
645        match MULTIPLY {
646            1 => {
647                let a = rgba[3];
648
649                rgba[0] = multiply_u8_color(rgba[0], a);
650                rgba[1] = multiply_u8_color(rgba[1], a);
651                rgba[2] = multiply_u8_color(rgba[2], a);
652            },
653            2 => {
654                let a = rgba[3] as u32;
655
656                if a > 0 {
657                    rgba[0] = (rgba[0] as u32 * 255 / a) as u8;
658                    rgba[1] = (rgba[1] as u32 * 255 / a) as u8;
659                    rgba[2] = (rgba[2] as u32 * 255 / a) as u8;
660                }
661            },
662            _ => {},
663        }
664        if SWAP_RB {
665            rgba.swap(0, 2);
666        }
667        if CLEAR_ALPHA {
668            rgba[3] = u8::MAX;
669        }
670    }
671}
672
673fn is_gif(buffer: &[u8]) -> bool {
674    buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a")
675}
676
677fn is_jpeg(buffer: &[u8]) -> bool {
678    buffer.starts_with(&[0xff, 0xd8, 0xff])
679}
680
681fn is_png(buffer: &[u8]) -> bool {
682    buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
683}
684
685fn is_bmp(buffer: &[u8]) -> bool {
686    buffer.starts_with(&[0x42, 0x4D])
687}
688
689fn is_ico(buffer: &[u8]) -> bool {
690    buffer.starts_with(&[0x00, 0x00, 0x01, 0x00])
691}
692
693fn is_webp(buffer: &[u8]) -> bool {
694    // https://developers.google.com/speed/webp/docs/riff_container
695    // First four bytes: `RIFF`, header size 12 bytes
696    if !buffer.starts_with(b"RIFF") || buffer.len() < 12 {
697        return false;
698    }
699    let size: [u8; 4] = [buffer[4], buffer[5], buffer[6], buffer[7]];
700    // Bytes 4..8 are a little endian u32 indicating
701    // > The size of the file in bytes, starting at offset 8.
702    // > The maximum value of this field is 2^32 minus 10 bytes and thus the size
703    // > of the whole file is at most 4 GiB minus 2 bytes.
704    let len: usize = u32::from_le_bytes(size) as usize;
705    buffer[8..].len() >= len && &buffer[8..12] == b"WEBP"
706}
707
708#[cfg(test)]
709mod test {
710    use super::detect_image_format;
711
712    #[test]
713    fn test_supported_images() {
714        let gif1 = [b'G', b'I', b'F', b'8', b'7', b'a'];
715        let gif2 = [b'G', b'I', b'F', b'8', b'9', b'a'];
716        let jpeg = [0xff, 0xd8, 0xff];
717        let png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
718        let webp = [
719            b'R', b'I', b'F', b'F', 0x04, 0x00, 0x00, 0x00, b'W', b'E', b'B', b'P',
720        ];
721        let bmp = [0x42, 0x4D];
722        let ico = [0x00, 0x00, 0x01, 0x00];
723        let junk_format = [0x01, 0x02, 0x03, 0x04, 0x05];
724
725        assert!(detect_image_format(&gif1).is_ok());
726        assert!(detect_image_format(&gif2).is_ok());
727        assert!(detect_image_format(&jpeg).is_ok());
728        assert!(detect_image_format(&png).is_ok());
729        assert!(detect_image_format(&webp).is_ok());
730        assert!(detect_image_format(&bmp).is_ok());
731        assert!(detect_image_format(&ico).is_ok());
732        assert!(detect_image_format(&junk_format).is_err());
733    }
734}