Skip to content
Snippets Groups Projects
color.rs 11.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • //! Color manipulation functions and constants.
    
    
    use boytacean_common::util::copy_fast;
    
    
    pub const RGB_SIZE: usize = 3;
    pub const RGBA_SIZE: usize = 4;
    pub const RGB888_SIZE: usize = 3;
    pub const XRGB8888_SIZE: usize = 4;
    pub const RGB1555_SIZE: usize = 2;
    pub const RGB565_SIZE: usize = 2;
    
    /// Defines the Game Boy pixel type as a buffer
    /// with the size of RGB (3 bytes).
    pub type Pixel = [u8; RGB_SIZE];
    
    /// Defines a transparent Game Boy pixel type as a buffer
    /// with the size of RGBA (4 bytes).
    pub type PixelAlpha = [u8; RGBA_SIZE];
    
    /// Defines a pixel with 5 bits per channel plus a padding
    /// bit at the beginning.
    pub type PixelRgb1555 = [u8; RGB1555_SIZE];
    
    /// Defines a pixel with 5 bits per channel except for the
    /// green channel which uses 6 bits.
    pub type PixelRgb565 = [u8; RGB565_SIZE];
    
    pub fn rgb555_to_rgb888(first: u8, second: u8) -> Pixel {
        let r = (first & 0x1f) << 3;
        let g = (((first & 0xe0) >> 5) | ((second & 0x03) << 3)) << 3;
        let b = ((second & 0x7c) >> 2) << 3;
        [r, g, b]
    }
    
    pub fn rgb888_to_rgb1555(first: u8, second: u8, third: u8) -> PixelRgb1555 {
        let pixel = rgb888_to_rgb1555_u16(first, second, third);
        [pixel as u8, (pixel >> 8) as u8]
    }
    
    pub fn rgb888_to_rgb1555_u16(first: u8, second: u8, third: u8) -> u16 {
        let r = (first as u16 >> 3) & 0x1f;
        let g = (second as u16 >> 3) & 0x1f;
        let b = (third as u16 >> 3) & 0x1f;
        let a = 1;
        (a << 15) | (r << 10) | (g << 5) | b
    }
    
    pub fn rgb888_to_rgb565(first: u8, second: u8, third: u8) -> PixelRgb565 {
        let pixel = rgb888_to_rgb565_u16(first, second, third);
        [pixel as u8, (pixel >> 8) as u8]
    }
    
    pub fn rgb888_to_rgb565_u16(first: u8, second: u8, third: u8) -> u16 {
        let r = (first as u16 >> 3) & 0x1f;
        let g = (second as u16 >> 2) & 0x3f;
        let b = (third as u16 >> 3) & 0x1f;
        (r << 11) | (g << 5) | b
    }
    
    pub fn rgb888_to_rgb1555_array(rgb888_pixels: &[u8], rgb1555_pixels: &mut [u8]) {
        #[cfg(feature = "simd")]
        {
            rgb888_to_rgb1555_simd(rgb888_pixels, rgb1555_pixels);
        }
        #[cfg(not(feature = "simd"))]
        {
            rgb888_to_rgb1555_scalar(rgb888_pixels, rgb1555_pixels);
        }
    }
    
    /// Converts an array of RGB888 pixels to RGB565 format using a scalar implementation.
    ///
    /// This method should provide the same results as the SIMD implementation.
    pub fn rgb888_to_rgb1555_scalar(rgb888_pixels: &[u8], rgb1555_pixels: &mut [u8]) {
        assert!(
            rgb888_pixels.len() % 3 == 0,
            "Length of rgb888_pixels must be a multiple of 3"
        );
        assert!(
            rgb1555_pixels.len() % 2 == 0,
            "Length of rgb1555_pixels must be a multiple of 2"
        );
        assert!(
            rgb888_pixels.len() / 3 == rgb1555_pixels.len() / 2,
            "Length of rgb1555_pixels must be two thirds the length of rgb888_pixels"
        );
        for index in 0..rgb888_pixels.len() / RGB_SIZE {
            let (r, g, b) = (
                rgb888_pixels[index * RGB_SIZE],
                rgb888_pixels[index * RGB_SIZE + 1],
                rgb888_pixels[index * RGB_SIZE + 2],
            );
            let rgb1555 = rgb888_to_rgb1555(r, g, b);
            let output_offset = index * RGB1555_SIZE;
            copy_fast(
                &rgb1555,
                &mut rgb1555_pixels[output_offset..output_offset + RGB1555_SIZE],
                RGB1555_SIZE,
            )
        }
    }
    
    /// Converts an array of RGB888 pixels to RGB1555 format using SIMD.
    ///
    /// This method is only available when the `simd` feature is enabled.
    ///
    /// Note: The length of `rgb888_pixels` must be a multiple of 3, and
    /// `rgb1555_pixels` must be a multiple of 2.
    #[cfg(feature = "simd")]
    pub fn rgb888_to_rgb1555_simd(rgb888_pixels: &[u8], rgb1555_pixels: &mut [u8]) {
    
    João Magalhães's avatar
    João Magalhães committed
        use boytacean_common::util::interleave_arrays;
    
        use std::simd::u8x16;
    
        const SIMD_WIDTH: usize = 16;
    
        assert!(
            rgb888_pixels.len() % 3 == 0,
            "Length of rgb888_pixels must be a multiple of 3"
        );
        assert!(
            rgb1555_pixels.len() % 2 == 0,
            "Length of rgb1555_pixels must be a multiple of 2"
        );
        assert!(
            rgb888_pixels.len() / 3 == rgb1555_pixels.len() / 2,
            "Length of rgb1555_pixels must be two thirds the length of rgb888_pixels"
        );
    
        let num_pixels = rgb888_pixels.len() / 3;
        let simd_chunks = num_pixels / SIMD_WIDTH;
    
        for index in 0..simd_chunks {
            let offset = index * SIMD_WIDTH * 3;
            let r = u8x16::from_slice(&[
                rgb888_pixels[offset],
                rgb888_pixels[offset + 3],
                rgb888_pixels[offset + 6],
                rgb888_pixels[offset + 9],
                rgb888_pixels[offset + 12],
                rgb888_pixels[offset + 15],
                rgb888_pixels[offset + 18],
                rgb888_pixels[offset + 21],
                rgb888_pixels[offset + 24],
                rgb888_pixels[offset + 27],
                rgb888_pixels[offset + 30],
                rgb888_pixels[offset + 33],
                rgb888_pixels[offset + 36],
                rgb888_pixels[offset + 39],
                rgb888_pixels[offset + 42],
                rgb888_pixels[offset + 45],
            ]);
            let g = u8x16::from_slice(&[
                rgb888_pixels[offset + 1],
                rgb888_pixels[offset + 4],
                rgb888_pixels[offset + 7],
                rgb888_pixels[offset + 10],
                rgb888_pixels[offset + 13],
                rgb888_pixels[offset + 16],
                rgb888_pixels[offset + 19],
                rgb888_pixels[offset + 22],
                rgb888_pixels[offset + 25],
                rgb888_pixels[offset + 28],
                rgb888_pixels[offset + 31],
                rgb888_pixels[offset + 34],
                rgb888_pixels[offset + 37],
                rgb888_pixels[offset + 40],
                rgb888_pixels[offset + 43],
                rgb888_pixels[offset + 46],
            ]);
            let b = u8x16::from_slice(&[
                rgb888_pixels[offset + 2],
                rgb888_pixels[offset + 5],
                rgb888_pixels[offset + 8],
                rgb888_pixels[offset + 11],
                rgb888_pixels[offset + 14],
                rgb888_pixels[offset + 17],
                rgb888_pixels[offset + 20],
                rgb888_pixels[offset + 23],
                rgb888_pixels[offset + 26],
                rgb888_pixels[offset + 29],
                rgb888_pixels[offset + 32],
                rgb888_pixels[offset + 35],
                rgb888_pixels[offset + 38],
                rgb888_pixels[offset + 41],
                rgb888_pixels[offset + 44],
                rgb888_pixels[offset + 47],
            ]);
    
            let r_shifted_high = (r >> 1) & u8x16::splat(0x7c);
            let g_shifted_high = (g >> 6) & u8x16::splat(0x03);
            let g_shifted_low = (g << 2) & u8x16::splat(0xe0);
            let b_shifted = (b >> 3) & u8x16::splat(0x3f);
    
            let high_byte = u8x16::splat(0x80) | r_shifted_high | g_shifted_high;
            let low_byte = g_shifted_low | b_shifted;
    
            let output_offset = index * SIMD_WIDTH * RGB1555_SIZE;
            interleave_arrays(
                low_byte.as_array(),
                high_byte.as_array(),
                &mut rgb1555_pixels[output_offset..output_offset + SIMD_WIDTH * RGB1555_SIZE],
            );
        }
    
        let remainder = num_pixels % SIMD_WIDTH;
        let offset = simd_chunks * SIMD_WIDTH * RGB_SIZE;
        let offset_rgb1555 = simd_chunks * SIMD_WIDTH * RGB1555_SIZE;
    
        for index in 0..remainder {
            let current_offset = offset + index * RGB_SIZE;
            let (r, g, b) = (
                rgb888_pixels[current_offset],
                rgb888_pixels[current_offset + 1],
                rgb888_pixels[current_offset + 2],
            );
            let rgb1555 = rgb888_to_rgb1555(r, g, b);
            let output_offset = offset_rgb1555 + index * RGB1555_SIZE;
            copy_fast(
                &rgb1555,
                &mut rgb1555_pixels[output_offset..output_offset + RGB1555_SIZE],
                RGB1555_SIZE,
            );
        }
    }
    
    #[cfg(test)]
    mod tests {
    
        #![allow(clippy::zero_prefixed_literal)]
    
    
        use super::{rgb888_to_rgb1555, rgb888_to_rgb1555_scalar};
    
        #[test]
        fn test_rgb888_to_rgb1555() {
            let result = rgb888_to_rgb1555(255, 0, 0);
            assert_eq!(result, [0b00000000, 0b11111100]);
    
            let result = rgb888_to_rgb1555(0, 255, 0);
            assert_eq!(result, [0b11100000, 0b10000011]);
    
            let result = rgb888_to_rgb1555(0, 0, 255);
            assert_eq!(result, [0b00011111, 0b10000000]);
    
            let result = rgb888_to_rgb1555(255, 255, 0);
            assert_eq!(result, [0b11100000, 0b11111111]);
        }
    
        #[test]
        fn test_rgb888_to_rgb1555_scalar() {
            let rgb888_pixels: Vec<u8> = vec![
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                020, 020, 200, // Blueish
            ];
            let mut rgb1555_pixels: Vec<u8> = vec![0; 36];
    
            rgb888_to_rgb1555_scalar(&rgb888_pixels, &mut rgb1555_pixels);
    
            let expected_rgb1555: Vec<u8> = vec![
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b01011001, 0b10001000, // Blueish
            ];
    
            assert_eq!(rgb1555_pixels, expected_rgb1555);
        }
    
        #[test]
        #[cfg(feature = "simd")]
        fn test_rgb888_to_rgb1555_simd() {
            use super::rgb888_to_rgb1555_simd;
    
            let rgb888_pixels: Vec<u8> = vec![
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                000, 255, 000, // Green
                000, 000, 255, // Blue
                255, 255, 000, // Yellow
                255, 000, 000, // Red
                020, 020, 200, // Blueish
            ];
            let mut rgb1555_pixels: Vec<u8> = vec![0; 36];
    
            rgb888_to_rgb1555_simd(&rgb888_pixels, &mut rgb1555_pixels);
    
            let expected_rgb1555: Vec<u8> = vec![
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b11100000, 0b10000011, // Green
                0b00011111, 0b10000000, // Blue
                0b11100000, 0b11111111, // Yellow
                0b00000000, 0b11111100, // Red
                0b01011001, 0b10001000, // Blueish
            ];
    
            assert_eq!(rgb1555_pixels, expected_rgb1555);
        }
    }