#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Rotation {
Deg0,
Deg90,
Deg180,
Deg270,
}
impl Rotation {
#[inline]
pub const fn swaps_axes(self) -> bool {
matches!(self, Rotation::Deg90 | Rotation::Deg270)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ColorFormat {
Argb8888,
Rgb888,
Rgb565,
Rgb444,
L8,
Mono,
}
impl ColorFormat {
pub const fn quantize(&self, r: u8, g: u8, b: u8) -> (u8, u8, u8) {
match self {
ColorFormat::Argb8888 | ColorFormat::Rgb888 => (r, g, b),
ColorFormat::Rgb565 => {
let r5 = r & 0xF8;
let g6 = g & 0xFC;
let b5 = b & 0xF8;
(r5 | (r5 >> 5), g6 | (g6 >> 6), b5 | (b5 >> 5))
}
ColorFormat::Rgb444 => {
let r4 = r & 0xF0;
let g4 = g & 0xF0;
let b4 = b & 0xF0;
(r4 | (r4 >> 4), g4 | (g4 >> 4), b4 | (b4 >> 4))
}
ColorFormat::L8 => {
let l = ((r as u32 * 299 + g as u32 * 587 + b as u32 * 114) / 1000) as u8;
(l, l, l)
}
ColorFormat::Mono => {
let l = (r as u16 + g as u16 + b as u16) / 3;
let v = if l >= 128 { 255 } else { 0 };
(v, v, v)
}
}
}
}
pub const DEFAULT_FRAME_HZ: u32 = 60;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Screen {
pub width: u32,
pub height: u32,
pub rotation: Rotation,
pub color_format: ColorFormat,
pub frame_hz: u32,
}
impl Screen {
#[inline]
pub const fn new(width: u32, height: u32, rotation: Rotation) -> Self {
Self {
width,
height,
rotation,
color_format: ColorFormat::Argb8888,
frame_hz: DEFAULT_FRAME_HZ,
}
}
#[inline]
pub const fn landscape(width: u32, height: u32) -> Self {
Self::new(width, height, Rotation::Deg0)
}
#[inline]
pub const fn with_color_format(self, color_format: ColorFormat) -> Self {
Self {
color_format,
..self
}
}
#[inline]
pub const fn with_frame_hz(self, frame_hz: u32) -> Self {
let frame_hz = if frame_hz == 0 { 1 } else { frame_hz };
Self { frame_hz, ..self }
}
#[inline]
pub const fn logical_size(&self) -> (u32, u32) {
(self.width, self.height)
}
#[inline]
pub const fn physical_size(&self) -> (u32, u32) {
if self.rotation.swaps_axes() {
(self.height, self.width)
} else {
(self.width, self.height)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn landscape_is_deg0() {
let s = Screen::landscape(800, 480);
assert_eq!(s.rotation, Rotation::Deg0);
assert_eq!(s.logical_size(), (800, 480));
assert_eq!(s.physical_size(), (800, 480));
}
#[test]
fn deg90_swaps_physical_axes() {
let s = Screen::new(800, 480, Rotation::Deg90);
assert_eq!(s.logical_size(), (800, 480));
assert_eq!(s.physical_size(), (480, 800));
}
#[test]
fn deg180_preserves_axes() {
let s = Screen::new(800, 480, Rotation::Deg180);
assert_eq!(s.logical_size(), s.physical_size());
}
#[test]
fn deg270_swaps_physical_axes() {
let s = Screen::new(320, 240, Rotation::Deg270);
assert_eq!(s.physical_size(), (240, 320));
}
#[test]
fn swaps_axes_matches_rotation() {
assert!(!Rotation::Deg0.swaps_axes());
assert!(Rotation::Deg90.swaps_axes());
assert!(!Rotation::Deg180.swaps_axes());
assert!(Rotation::Deg270.swaps_axes());
}
#[test]
fn default_color_format_is_argb8888() {
let s = Screen::landscape(800, 480);
assert_eq!(s.color_format, ColorFormat::Argb8888);
}
#[test]
fn with_color_format_overrides_only_color() {
let s = Screen::new(480, 320, Rotation::Deg90).with_color_format(ColorFormat::Rgb565);
assert_eq!(s.color_format, ColorFormat::Rgb565);
assert_eq!(s.rotation, Rotation::Deg90);
assert_eq!(s.logical_size(), (480, 320));
}
#[test]
fn argb8888_quantize_is_passthrough() {
let (r, g, b) = ColorFormat::Argb8888.quantize(0x12, 0x34, 0x56);
assert_eq!((r, g, b), (0x12, 0x34, 0x56));
}
#[test]
fn rgb565_quantize_drops_low_bits_then_replicates() {
let (r, g, b) = ColorFormat::Rgb565.quantize(0x12, 0x34, 0x56);
assert_eq!(r & 0x07, r >> 5, "low 3 bits should mirror top 3");
assert_eq!(g & 0x03, g >> 6, "low 2 bits should mirror top 2");
assert_eq!(b & 0x07, b >> 5, "low 3 bits should mirror top 3");
assert_eq!(ColorFormat::Rgb565.quantize(255, 255, 255), (255, 255, 255));
assert_eq!(ColorFormat::Rgb565.quantize(0, 0, 0), (0, 0, 0));
}
#[test]
fn rgb444_quantize_uses_4_bits_per_channel() {
assert_eq!(
ColorFormat::Rgb444.quantize(0xFF, 0xFF, 0xFF),
(0xFF, 0xFF, 0xFF)
);
assert_eq!(
ColorFormat::Rgb444.quantize(0x00, 0x00, 0x00),
(0x00, 0x00, 0x00)
);
let (r, _, _) = ColorFormat::Rgb444.quantize(0x12, 0, 0);
assert_eq!(r, 0x11);
}
#[test]
fn l8_quantize_collapses_to_grey() {
let (r, g, b) = ColorFormat::L8.quantize(255, 0, 0);
assert_eq!(r, g);
assert_eq!(g, b);
assert!((75..=77).contains(&r));
}
#[test]
fn mono_quantize_thresholds_at_half() {
assert_eq!(ColorFormat::Mono.quantize(255, 255, 255), (255, 255, 255));
assert_eq!(ColorFormat::Mono.quantize(0, 0, 0), (0, 0, 0));
assert_eq!(ColorFormat::Mono.quantize(120, 120, 120), (0, 0, 0));
assert_eq!(ColorFormat::Mono.quantize(140, 140, 140), (255, 255, 255));
}
}