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