spectrusty-core 0.4.0

Core components of the SPECTRUSTY library.
Documentation
/*
    Copyright (C) 2020-2022  Rafal Michalski

    This file is part of SPECTRUSTY, a Rust library for building emulators.

    For the full copyright notice, see the lib.rs file.
*/
//! Building blocks for rendering pixel surfaces.
use core::slice::IterMut;

/// A trait for providing a way for placing pixels into byte buffers.
pub trait PixelBuffer<'a> {
    /// Specifies the type used for pixels.
    type Pixel: Copy;
    /// Should return a new instance of `PixelBuffer` implementation from the mutable slice of bytes representing
    /// a single line of pixels of the target buffer.
    fn from_line(line_buffer: &'a mut [u8]) -> Self;
    /// Puts the next `pixel` into the line buffer and increases an internal cursor position by a single pixel.
    ///
    /// If the internal buffer boundaries would be overflown, this method must not panic, but instead, it
    /// should just return without writing anything to the underlying buffer.
    fn put_pixel(&mut self, pixel: Self::Pixel);
    /// Puts `count` number of `pixel` copies into the line buffer and increases an internal cursor position
    /// accordingly.
    ///
    /// If the internal buffer boundaries would be overflown, this method must not panic, but instead, it
    /// should just return without writing anything to the underlying buffer.
    #[inline]
    fn put_pixels(&mut self, pixel: Self::Pixel, count: usize) {
        for _ in 0..count {
            self.put_pixel(pixel);
        }
    }
    /// Returns the stride of a single pixel in bytes.
    #[inline]
    fn pixel_stride() -> usize {
        core::mem::size_of::<Self::Pixel>()
    }
}

/// A trait used for obtaining pixel colors.
pub trait Palette {
    /// Specifies the type used for pixels.
    type Pixel: Copy;
    /// Should return one of ZX Spectrum pixel colors:
    /// ```text
    /// index color   index color
    ///   0 - black     8 - bright black (same as black)
    ///   1 - blue      9 - bright blue
    ///   2 - red      10 - bright red
    ///   3 - magenta  11 - bright magenta
    ///   4 - green    12 - bright green
    ///   5 - cyan     13 - bright cyan
    ///   6 - yellow   14 - bright yellow
    ///   7 - white    15 - bright white
    /// ```
    #[inline]
    fn get_pixel(index: u8) -> Self::Pixel {
        Self::get_pixel_grb8(index_to_grb(index))
    }
    /// Should return a grayscale pixel with the intensity of one of the ZX Spectrum colors: [0, 15].
    ///
    /// See [Palette::get_pixel] for color values.
    fn get_pixel_gray(index: u8) -> Self::Pixel;
    /// Should return one of [ULAplus](https://sinclair.wiki.zxnet.co.uk/wiki/ULAplus#GRB_palette_entries) colors.
    fn get_pixel_grb8(g3r3b2: u8) -> Self::Pixel;
    /// Should return a grayscale pixel (0 - black, 255 - full intensity white).
    fn get_pixel_gray8(value: u8) -> Self::Pixel;
}

/// A [PixelBuffer] tool for placing pixels into byte buffers using 3 `u8` element arrays of color channels
/// (3 bytes per pixel).
pub struct PixelBufA24<'a> {
    iter: IterMut<'a, [u8;3]>
}

/// A [PixelBuffer] tool for placing pixels into byte buffers using 4 `u8` element arrays of color channels
/// (4 bytes per pixel).
pub struct PixelBufA32<'a> {
    iter: IterMut<'a, [u8;4]>
}

/// A [PixelBuffer] tool for placing pixels into byte buffers using `u32` packed color channels.
pub struct PixelBufP32<'a> {
    iter: IterMut<'a, u32>
}

/// A [PixelBuffer] tool for placing pixels into byte buffers using `u16` packed color channels.
pub struct PixelBufP16<'a> {
    iter: IterMut<'a, u16>
}

/// A [PixelBuffer] tool for placing pixels into byte buffers using `u8` packed color channels.
pub struct PixelBufP8<'a> {
    iter: IterMut<'a, u8>
}

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufA24].
pub struct SpectrumPalRGB24;

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufA32].
pub struct SpectrumPalRGBA32;

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufA32].
pub struct SpectrumPalARGB32;

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufP32].
pub struct SpectrumPalA8R8G8B8;

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufP32].
pub struct SpectrumPalR8G8B8A8;

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufP16].
pub struct SpectrumPalR5G6B5;

/// A color ZX Spectrum [Palette] implementation to be used with [PixelBufP8].
pub struct SpectrumPalR3G3B2;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufA24].
pub struct GrayscalePalRGB24;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufA32].
pub struct GrayscalePalRGBA32;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufA32].
pub struct GrayscalePalARGB32;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufP32].
pub struct GrayscalePalA8R8G8B8;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufP32].
pub struct GrayscalePalR8G8B8A8;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufP16].
pub struct GrayscalePalR5G6B5;

/// A grayscale ZX Spectrum [Palette] implementation to be used with [PixelBufP8].
pub struct GrayscalePalR3G3B2;

#[allow(clippy::unusual_byte_groupings)]
#[inline]
fn index_to_grb(index: u8) -> u8 {
    #[allow(clippy::inconsistent_digit_grouping)]
    match index & 15 {
         0 => 0b000_000_00,
         1 => 0b000_000_10,
         2 => 0b000_101_00,
         3 => 0b000_101_10,
         4 => 0b101_000_00,
         5 => 0b101_000_10,
         6 => 0b101_101_00,
         7 => 0b101_101_10,
         8 => 0b000_000_00,
         9 => 0b000_000_11,
        10 => 0b000_111_00,
        11 => 0b000_111_11,
        12 => 0b111_000_00,
        13 => 0b111_000_11,
        14 => 0b111_111_00,
        15 => 0b111_111_11,
        _ => unsafe { core::hint::unreachable_unchecked() }
    }
}

macro_rules! impl_pixel_buffer {
    ($pixel_buf:ty, $pixel:ty) => {
        impl<'a> PixelBuffer<'a> for $pixel_buf {
            type Pixel = $pixel;

            fn from_line(line_buffer: &'a mut [u8]) -> Self {
                let (_, pixels, _) = unsafe { line_buffer.align_to_mut::<Self::Pixel>() };
                let iter = pixels.iter_mut();
                Self { iter }
            }

            #[inline]
            fn put_pixel(&mut self, pixel: Self::Pixel) {
                if let Some(dest) = self.iter.next() {
                    *dest = pixel;
                }
            }

            #[inline]
            fn put_pixels(&mut self, pixel: Self::Pixel, count: usize) {
                for dest in self.iter.by_ref().take(count) {
                    *dest = pixel;
                }
            }
        }
    };
}

impl_pixel_buffer!(PixelBufA24<'a>, [u8;3]);
impl_pixel_buffer!(PixelBufA32<'a>, [u8;4]);
impl_pixel_buffer!(PixelBufP32<'a>, u32);
impl_pixel_buffer!(PixelBufP16<'a>, u16);
impl_pixel_buffer!(PixelBufP8<'a>,  u8);

macro_rules! impl_palette {
    ($palette:ty, $pixel:ty) => {
        impl Palette for $palette {
            type Pixel = $pixel;

            #[inline(always)]
            fn get_pixel_grb8(g3r3b2: u8) -> Self::Pixel {
                Self::pixel_grb(g3r3b2)
            }
            #[inline(always)]
            fn get_pixel_gray(index: u8) -> Self::Pixel {
                Self::pixel_gray_grb(index_to_grb(index))
            }
            #[inline(always)]
            fn get_pixel_gray8(value: u8) -> Self::Pixel {
                Self::pixel_gray_intensity(value)
            }
        }        
    };
}

macro_rules! impl_pixel_grb_const {
    ($palette:ty, $pixel:ty, $grayscale:ident) => {
        impl $palette {
            #[inline(always)]
            fn pixel_grb(g3r3b2: u8) -> $pixel {
                Self::COLORS_GRB[g3r3b2 as usize]
            }
            #[inline(always)]
            fn pixel_gray_grb(i: u8) -> $pixel {
                Self::pixel_gray_intensity($grayscale[i as usize])
            }
        }
    };
}

macro_rules! impl_pixel_grb_gray {
    ($palette:ty, $pixel:ty, $grayscale:ident) => {
        impl $palette {
            #[inline(always)]
            fn pixel_grb(g3r3b2: u8) -> $pixel {
                Self::pixel_gray_grb(g3r3b2)
            }
            #[inline(always)]
            fn pixel_gray_grb(i: u8) -> $pixel {
                Self::pixel_gray_intensity($grayscale[i as usize])
            }
        }
    };
}

const ALPHA_MAX: u8 = u8::max_value();

const fn grb_2r(grb: u8) -> u8 {
    ((grb >> 3) & 3) | (grb & 0b11100) | ((grb & 0b11100) << 3)
}

const fn grb_2g(grb: u8) -> u8 {
    ((grb >> 6) & 3) | ((grb >> 3) & 0b11100) | (grb & 0b11100000)
}

const fn grb_2b(grb: u8) -> u8 {
    (grb & 3) | ((grb << 3) & 0b11000) | (((grb << 2) | (grb << 1)) & 0b100) |
    ((grb << 6) & 0b11000000) | (((grb << 5) | (grb << 4)) & 0b100000)
}

#[inline(always)]
const fn pack_8888(a: u8, b: u8, c: u8, d: u8) -> u32 {
    ((a as u32) << 24) | ((b as u32) << 16) | ((c as u32) << 8) | (d as u32)
}

#[inline(always)]
const fn pack_565(a: u8, b: u8, c: u8) -> u16 {
    (((a as u16) >> 3) << 11) | (((b as u16) >> 2) << 5) | ((c as u16) >> 3)
}

#[inline(always)]
const fn pack_332(a: u8, b: u8, c: u8) -> u8 {
    ((a >> 5) << 5) | ((b >> 5) << 2) | (c >> 6)
}

const fn grayscale(r: u8, g: u8, b: u8) -> u8 {
    ((13933 * r as u32 + 46871 * g as u32 + 4732 * b as u32) >> 16) as u8
}

macro_rules! impl_palette_plus_formats {
    ([$($grb:expr),*]) => {
        const GRAYSCALE: [u8; 256] = [$(grayscale(grb_2r($grb), grb_2g($grb), grb_2b($grb))),*];

        impl SpectrumPalRGB24 {
            const COLORS_GRB: [[u8;3]; 256] = [
                $([grb_2r($grb), grb_2g($grb), grb_2b($grb)]),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> [u8;3] { [v, v, v] }
        }
        impl_pixel_grb_const!(SpectrumPalRGB24, [u8;3], GRAYSCALE);

        impl SpectrumPalRGBA32 {
            const COLORS_GRB: [[u8;4]; 256] = [
                $([grb_2r($grb), grb_2g($grb), grb_2b($grb), ALPHA_MAX]),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> [u8;4] { [v, v, v, ALPHA_MAX] }
        }
        impl_pixel_grb_const!(SpectrumPalRGBA32, [u8;4], GRAYSCALE);

        impl SpectrumPalARGB32 {
            const COLORS_GRB: [[u8;4]; 256] = [
                $([ALPHA_MAX, grb_2r($grb), grb_2g($grb), grb_2b($grb)]),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> [u8;4] { [ALPHA_MAX, v, v, v] }
        }
        impl_pixel_grb_const!(SpectrumPalARGB32, [u8;4], GRAYSCALE);

        impl SpectrumPalA8R8G8B8 {
            const COLORS_GRB: [u32; 256] = [
                $(pack_8888(ALPHA_MAX, grb_2r($grb), grb_2g($grb), grb_2b($grb))),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u32 { pack_8888(ALPHA_MAX, v, v, v) }
        }
        impl_pixel_grb_const!(SpectrumPalA8R8G8B8, u32, GRAYSCALE);

        impl SpectrumPalR8G8B8A8 {
            const COLORS_GRB: [u32; 256] = [
                $(pack_8888(grb_2r($grb), grb_2g($grb), grb_2b($grb), ALPHA_MAX)),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u32 { pack_8888(v, v, v, ALPHA_MAX) }
        }
        impl_pixel_grb_const!(SpectrumPalR8G8B8A8, u32, GRAYSCALE);

        impl SpectrumPalR5G6B5 {
            const COLORS_GRB: [u16; 256] = [
                $(pack_565(grb_2r($grb), grb_2g($grb), grb_2b($grb))),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u16 { pack_565(v, v, v) }
        }
        impl_pixel_grb_const!(SpectrumPalR5G6B5, u16, GRAYSCALE);

        impl SpectrumPalR3G3B2 {
            const COLORS_GRB: [u8; 256] = [
                $(pack_332(grb_2r($grb), grb_2g($grb), grb_2b($grb))),*
            ];
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u8 { pack_332(v, v, v) }
        }
        impl_pixel_grb_const!(SpectrumPalR3G3B2, u8, GRAYSCALE);

        impl GrayscalePalRGB24 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> [u8;3] { [v, v, v] }
        }
        impl_pixel_grb_gray!(GrayscalePalRGB24, [u8;3], GRAYSCALE);

        impl GrayscalePalRGBA32 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> [u8;4] { [v, v, v, ALPHA_MAX] }
        }
        impl_pixel_grb_gray!(GrayscalePalRGBA32, [u8;4], GRAYSCALE);

        impl GrayscalePalARGB32 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> [u8;4] { [ALPHA_MAX, v, v, v] }
        }
        impl_pixel_grb_gray!(GrayscalePalARGB32, [u8;4], GRAYSCALE);

        impl GrayscalePalA8R8G8B8 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u32 { pack_8888(ALPHA_MAX, v, v, v) }
        }
        impl_pixel_grb_gray!(GrayscalePalA8R8G8B8, u32, GRAYSCALE);

        impl GrayscalePalR8G8B8A8 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u32 { pack_8888(v, v, v, ALPHA_MAX) }
        }
        impl_pixel_grb_gray!(GrayscalePalR8G8B8A8, u32, GRAYSCALE);

        impl GrayscalePalR5G6B5 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u16 { pack_565(v, v, v) }
        }
        impl_pixel_grb_gray!(GrayscalePalR5G6B5, u16, GRAYSCALE);

        impl GrayscalePalR3G3B2 {
            #[inline(always)]
            fn pixel_gray_intensity(v: u8) -> u8 { pack_332(v, v, v) }
        }
        impl_pixel_grb_gray!(GrayscalePalR3G3B2, u8, GRAYSCALE);
    };
}

// let colors = [
//     (  0,  0,  0),
//     ( 21, 21,201),
//     (202, 33, 33),
//     (203, 38,203),
//     ( 44,203, 44),
//     ( 47,204,204),
//     (205,205, 53),
//     (205,205,205),
//     (  0,  0,  0),
//     ( 27, 27,251),
//     (252, 41, 41),
//     (252, 47,252),
//     ( 55,253, 55),
//     ( 59,254,254),
//     (255,255, 65),
//     (255,255,255)
// ];

// constant eval iterators where are you...
impl_palette_plus_formats!([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255]);
impl_palette!(SpectrumPalRGB24,     [u8;3]);
impl_palette!(SpectrumPalRGBA32,    [u8;4]);
impl_palette!(SpectrumPalARGB32,    [u8;4]);
impl_palette!(SpectrumPalA8R8G8B8,  u32);
impl_palette!(SpectrumPalR8G8B8A8,  u32);
impl_palette!(SpectrumPalR5G6B5,    u16);
impl_palette!(SpectrumPalR3G3B2,    u8);
impl_palette!(GrayscalePalRGB24,    [u8;3]);
impl_palette!(GrayscalePalRGBA32,   [u8;4]);
impl_palette!(GrayscalePalARGB32,   [u8;4]);
impl_palette!(GrayscalePalA8R8G8B8, u32);
impl_palette!(GrayscalePalR8G8B8A8, u32);
impl_palette!(GrayscalePalR5G6B5,   u16);
impl_palette!(GrayscalePalR3G3B2,   u8);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pixel_palette_works() {
        for i in (0..=255).step_by(16) {
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 0),  [         0,          0,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 1),  [         0,          0, 0b10110110]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 2),  [0b10110110,          0,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 3),  [0b10110110,          0, 0b10110110]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 4),  [         0, 0b10110110,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 5),  [         0, 0b10110110, 0b10110110]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 6),  [0b10110110, 0b10110110,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 7),  [0b10110110, 0b10110110, 0b10110110]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 8),  [         0,          0,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 9),  [         0,          0, 0b11111111]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 10), [0b11111111,          0,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 11), [0b11111111,          0, 0b11111111]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 12), [         0, 0b11111111,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 13), [         0, 0b11111111, 0b11111111]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 14), [0b11111111, 0b11111111,          0]);
            assert_eq!(SpectrumPalRGB24::get_pixel(i + 15), [0b11111111, 0b11111111, 0b11111111]);

            assert_eq!(GrayscalePalRGB24::get_pixel(i + 0),  [  0,  0,  0]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 1),  [ 13, 13, 13]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 2),  [ 38, 38, 38]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 3),  [ 51, 51, 51]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 4),  [130,130,130]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 5),  [143,143,143]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 6),  [168,168,168]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 7),  [182,182,182]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 8),  [  0,  0,  0]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 9),  [ 18, 18, 18]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 10), [ 54, 54, 54]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 11), [ 72, 72, 72]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 12), [182,182,182]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 13), [200,200,200]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 14), [236,236,236]);
            assert_eq!(GrayscalePalRGB24::get_pixel(i + 15), [255,255,255]);
        }
    }

    #[test]
    fn pixel_palette_grb_works() {
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(0), [0, 0, 0]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(0b000_001_00), [0b00100100, 0, 0]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(0b001_010_00), [0b01001001, 0b00100100, 0]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(1), [0, 0, 0b01101101]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(0b000_101_01), [0b10110110, 0, 0b01101101]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(0b110_111_01), [0b11111111, 0b11011011, 0b01101101]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(2), [0, 0, 0b10110110]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(0b011_011_10), [0b01101101, 0b01101101, 0b10110110]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(3), [0, 0, 0b11111111]);
        assert_eq!(SpectrumPalRGB24::get_pixel_grb8(255), [255, 255, 255]);
        assert_eq!(SpectrumPalRGBA32::get_pixel_grb8(0), [0, 0, 0, 255]);
        assert_eq!(SpectrumPalRGBA32::get_pixel_grb8(0b111_011_01), [0b01101101, 0b11111111, 0b01101101, 255]);
        assert_eq!(SpectrumPalRGBA32::get_pixel_grb8(255), [255, 255, 255, 255]);
        assert_eq!(SpectrumPalARGB32::get_pixel_grb8(0), [255, 0, 0, 0]);
        assert_eq!(SpectrumPalARGB32::get_pixel_grb8(0b111_011_01), [255, 0b01101101, 0b11111111, 0b01101101]);
        assert_eq!(SpectrumPalARGB32::get_pixel_grb8(255), [255, 255, 255, 255]);
        assert_eq!(SpectrumPalR8G8B8A8::get_pixel_grb8(0), 255);
        assert_eq!(SpectrumPalR8G8B8A8::get_pixel_grb8(0b111_011_01), 0b01101101_11111111_01101101_11111111);
        assert_eq!(SpectrumPalR8G8B8A8::get_pixel_grb8(255), 0xffff_ffff);
        assert_eq!(SpectrumPalA8R8G8B8::get_pixel_grb8(0), 0xff00_0000);
        assert_eq!(SpectrumPalA8R8G8B8::get_pixel_grb8(0b111_011_01), 0b11111111_01101101_11111111_01101101);
        assert_eq!(SpectrumPalA8R8G8B8::get_pixel_grb8(255), 0xffff_ffff);
        assert_eq!(SpectrumPalR5G6B5::get_pixel_grb8(0), 0);
        assert_eq!(SpectrumPalR5G6B5::get_pixel_grb8(0b111_011_01), 0b01101_111111_01101);
        assert_eq!(SpectrumPalR5G6B5::get_pixel_grb8(255), 0xffff);
        assert_eq!(SpectrumPalR3G3B2::get_pixel_grb8(0), 0);
        assert_eq!(SpectrumPalR3G3B2::get_pixel_grb8(0b111_011_01), 0b011_111_01);
        assert_eq!(SpectrumPalR3G3B2::get_pixel_grb8(255), 0xff);
    }

    #[test]
    fn pixel_palette_gray_works() {
        let grayscale_u16 = |v| (((v as u16) >> 3) << 11)|(((v as u16) >> 2) << 5)|((v as u16) >> 3);
        let grayscale_u8 = |v| ((v >> 5) << 5)|((v >> 5) << 2)|(v >> 6);
        for i in 0..=255u8 {
            let v = GRAYSCALE[i as usize];
            assert_eq!(SpectrumPalRGB24::get_pixel_gray8(i), [i, i, i]);
            assert_eq!(GrayscalePalRGB24::get_pixel_gray8(i), [i, i, i]);
            assert_eq!(GrayscalePalRGB24::get_pixel_grb8(i), [v, v, v]);
            assert_eq!(SpectrumPalRGBA32::get_pixel_gray8(i), [i, i, i, 255]);
            assert_eq!(GrayscalePalRGBA32::get_pixel_gray8(i), [i, i, i, 255]);
            assert_eq!(GrayscalePalRGBA32::get_pixel_grb8(i), [v, v, v, 255]);
            assert_eq!(SpectrumPalARGB32::get_pixel_gray8(i), [255, i, i, i]);
            assert_eq!(GrayscalePalARGB32::get_pixel_gray8(i), [255, i, i, i]);
            assert_eq!(GrayscalePalARGB32::get_pixel_grb8(i), [255, v, v, v]);
            assert_eq!(SpectrumPalA8R8G8B8::get_pixel_gray8(i), u32::from_be_bytes([255, i, i, i]));
            assert_eq!(GrayscalePalA8R8G8B8::get_pixel_gray8(i), u32::from_be_bytes([255, i, i, i]));
            assert_eq!(GrayscalePalA8R8G8B8::get_pixel_grb8(i), u32::from_be_bytes([255, v, v, v]));
            assert_eq!(SpectrumPalR8G8B8A8::get_pixel_gray8(i), u32::from_be_bytes([i, i, i, 255]));
            assert_eq!(GrayscalePalR8G8B8A8::get_pixel_gray8(i), u32::from_be_bytes([i, i, i, 255]));
            assert_eq!(GrayscalePalR8G8B8A8::get_pixel_grb8(i), u32::from_be_bytes([v, v, v, 255]));
            assert_eq!(SpectrumPalR5G6B5::get_pixel_gray8(i), grayscale_u16(i));
            assert_eq!(GrayscalePalR5G6B5::get_pixel_gray8(i), grayscale_u16(i));
            assert_eq!(GrayscalePalR5G6B5::get_pixel_grb8(i), grayscale_u16(v));
            assert_eq!(SpectrumPalR3G3B2::get_pixel_gray8(i), grayscale_u8(i));
            assert_eq!(GrayscalePalR3G3B2::get_pixel_gray8(i), grayscale_u8(i));
            assert_eq!(GrayscalePalR3G3B2::get_pixel_grb8(i), grayscale_u8(v));
        }
    }
}