use fovea::image::ImageView;
use fovea::pixel::{
Bgr8, Bgra8, Mono, Mono8, Mono16, Mono32, Mono64, MonoF32, MonoF64, Rgb8, RgbF32, Rgba8,
RgbaF32, Srgb8, SrgbMono8, SrgbMonoA8, Srgba8,
};
use fovea::transform::{ConvertPixel, SrgbGamma};
use crate::pixel::DisplayPixel;
pub trait DisplayStrategy<P: Copy> {
fn to_display(&self, pixel: &P) -> Srgba8;
}
pub struct Identity;
impl DisplayStrategy<Srgba8> for Identity {
#[inline]
fn to_display(&self, pixel: &Srgba8) -> Srgba8 {
*pixel
}
}
impl DisplayStrategy<Srgb8> for Identity {
#[inline]
fn to_display(&self, pixel: &Srgb8) -> Srgba8 {
Srgba8::new(pixel.r.0, pixel.g.0, pixel.b.0, 255)
}
}
impl DisplayStrategy<SrgbMono8> for Identity {
#[inline]
fn to_display(&self, pixel: &SrgbMono8) -> Srgba8 {
let v = pixel.0.0;
Srgba8::new(v, v, v, 255)
}
}
impl DisplayStrategy<SrgbMonoA8> for Identity {
#[inline]
fn to_display(&self, pixel: &SrgbMonoA8) -> Srgba8 {
let v = pixel.v.0;
Srgba8::new(v, v, v, pixel.a.0)
}
}
pub struct LinearToDisplay;
impl DisplayStrategy<Rgb8> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &Rgb8) -> Srgba8 {
let linear = RgbF32::new(
pixel.r.0 as f32 / 255.0,
pixel.g.0 as f32 / 255.0,
pixel.b.0 as f32 / 255.0,
);
let srgb: Srgb8 = SrgbGamma.convert(&linear);
Srgba8::new(srgb.r.0, srgb.g.0, srgb.b.0, 255)
}
}
impl DisplayStrategy<Rgba8> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &Rgba8) -> Srgba8 {
let linear = RgbaF32::new(
pixel.r.0 as f32 / 255.0,
pixel.g.0 as f32 / 255.0,
pixel.b.0 as f32 / 255.0,
pixel.a.0 as f32 / 255.0,
);
SrgbGamma.convert(&linear)
}
}
impl DisplayStrategy<RgbF32> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &RgbF32) -> Srgba8 {
let srgb: Srgb8 = SrgbGamma.convert(pixel);
Srgba8::new(srgb.r.0, srgb.g.0, srgb.b.0, 255)
}
}
impl DisplayStrategy<RgbaF32> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &RgbaF32) -> Srgba8 {
SrgbGamma.convert(pixel)
}
}
impl DisplayStrategy<Bgr8> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &Bgr8) -> Srgba8 {
let linear = RgbF32::new(
pixel.r.0 as f32 / 255.0,
pixel.g.0 as f32 / 255.0,
pixel.b.0 as f32 / 255.0,
);
let srgb: Srgb8 = SrgbGamma.convert(&linear);
Srgba8::new(srgb.r.0, srgb.g.0, srgb.b.0, 255)
}
}
impl DisplayStrategy<Bgra8> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &Bgra8) -> Srgba8 {
let linear = RgbaF32::new(
pixel.r.0 as f32 / 255.0,
pixel.g.0 as f32 / 255.0,
pixel.b.0 as f32 / 255.0,
pixel.a.0 as f32 / 255.0,
);
SrgbGamma.convert(&linear)
}
}
impl DisplayStrategy<Mono8> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &Mono8) -> Srgba8 {
let linear = MonoF32::new(pixel.value() as f32 / 255.0);
let srgb: SrgbMono8 = SrgbGamma.convert(&linear);
let v = srgb.0.0;
Srgba8::new(v, v, v, 255)
}
}
impl DisplayStrategy<f32> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &f32) -> Srgba8 {
let clamped = MonoF32::new(pixel.clamp(0.0, 1.0));
let srgb: SrgbMono8 = SrgbGamma.convert(&clamped);
let v = srgb.0.0;
Srgba8::new(v, v, v, 255)
}
}
impl DisplayStrategy<MonoF32> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &MonoF32) -> Srgba8 {
<Self as DisplayStrategy<f32>>::to_display(self, &pixel.0)
}
}
impl DisplayStrategy<MonoF64> for LinearToDisplay {
#[inline]
fn to_display(&self, pixel: &MonoF64) -> Srgba8 {
let clamped = MonoF32::new(pixel.0.clamp(0.0, 1.0) as f32);
let srgb: SrgbMono8 = SrgbGamma.convert(&clamped);
let v = srgb.0.0;
Srgba8::new(v, v, v, 255)
}
}
#[derive(Clone, Copy)]
struct RangeMap {
min: f64,
scale: f64, }
impl RangeMap {
fn new(min: f64, max: f64) -> Self {
let range = max - min;
let scale = if range.abs() < f64::EPSILON {
0.0
} else {
1.0 / range
};
RangeMap { min, scale }
}
#[inline]
fn map_to_srgba8(&self, value: f64) -> Srgba8 {
if self.scale == 0.0 {
return Srgba8::new(128, 128, 128, 255);
}
let t = MonoF32::new(((value - self.min) * self.scale).clamp(0.0, 1.0) as f32);
let srgb: SrgbMono8 = SrgbGamma.convert(&t);
let v = srgb.0.0;
Srgba8::new(v, v, v, 255)
}
}
#[derive(Clone, Copy)]
pub struct AutoContrast {
range: RangeMap,
}
impl AutoContrast {
#[must_use]
pub fn new(min: f64, max: f64) -> Self {
AutoContrast {
range: RangeMap::new(min, max),
}
}
pub fn scan<V>(image: &V) -> Self
where
V: ImageView,
V::Pixel: Copy + Into<f64>,
{
Self::scan_with(image, |p| (*p).into())
}
pub fn scan_with<V, F>(image: &V, to_scalar: F) -> Self
where
V: ImageView,
V::Pixel: Copy,
F: Fn(&V::Pixel) -> f64,
{
let w = image.width();
let h = image.height();
if w == 0 || h == 0 {
return AutoContrast::new(0.0, 0.0);
}
let first = to_scalar(&image.pixel_at(0, 0));
let mut min = first;
let mut max = first;
for y in 0..h {
for x in 0..w {
let v = to_scalar(&image.pixel_at(x, y));
if v < min {
min = v;
}
if v > max {
max = v;
}
}
}
AutoContrast::new(min, max)
}
}
#[inline(always)]
fn mono8_to_f64(pixel: &Mono8) -> f64 {
pixel.value() as f64
}
#[inline(always)]
fn mono16_to_f64(pixel: &Mono16) -> f64 {
pixel.value() as f64
}
#[inline(always)]
fn mono32_to_f64(pixel: &Mono32) -> f64 {
pixel.value() as f64
}
#[inline(always)]
fn mono64_to_f64(pixel: &Mono64) -> f64 {
pixel.value() as f64
}
impl DisplayStrategy<Mono8> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &Mono8) -> Srgba8 {
self.range.map_to_srgba8(mono8_to_f64(pixel))
}
}
impl DisplayStrategy<Mono16> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &Mono16) -> Srgba8 {
self.range.map_to_srgba8(mono16_to_f64(pixel))
}
}
impl DisplayStrategy<Mono32> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &Mono32) -> Srgba8 {
self.range.map_to_srgba8(mono32_to_f64(pixel))
}
}
impl DisplayStrategy<Mono64> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &Mono64) -> Srgba8 {
self.range.map_to_srgba8(mono64_to_f64(pixel))
}
}
impl DisplayStrategy<f32> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &f32) -> Srgba8 {
self.range.map_to_srgba8(*pixel as f64)
}
}
impl DisplayStrategy<f64> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &f64) -> Srgba8 {
self.range.map_to_srgba8(*pixel)
}
}
impl DisplayStrategy<MonoF32> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &MonoF32) -> Srgba8 {
self.range.map_to_srgba8(pixel.0 as f64)
}
}
impl DisplayStrategy<MonoF64> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &MonoF64) -> Srgba8 {
self.range.map_to_srgba8(pixel.0)
}
}
impl DisplayStrategy<u8> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &u8) -> Srgba8 {
self.range.map_to_srgba8(*pixel as f64)
}
}
impl DisplayStrategy<u16> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &u16) -> Srgba8 {
self.range.map_to_srgba8(*pixel as f64)
}
}
impl<const BITS: usize> DisplayStrategy<Mono<BITS>> for AutoContrast {
#[inline]
fn to_display(&self, pixel: &Mono<BITS>) -> Srgba8 {
self.range.map_to_srgba8(pixel.value() as f64)
}
}
#[derive(Clone, Copy)]
pub struct FixedRange {
range: RangeMap,
}
impl FixedRange {
#[must_use]
pub fn new(min: f64, max: f64) -> Self {
FixedRange {
range: RangeMap::new(min, max),
}
}
}
impl DisplayStrategy<Mono8> for FixedRange {
#[inline]
fn to_display(&self, pixel: &Mono8) -> Srgba8 {
self.range.map_to_srgba8(mono8_to_f64(pixel))
}
}
impl DisplayStrategy<Mono16> for FixedRange {
#[inline]
fn to_display(&self, pixel: &Mono16) -> Srgba8 {
self.range.map_to_srgba8(mono16_to_f64(pixel))
}
}
impl DisplayStrategy<Mono32> for FixedRange {
#[inline]
fn to_display(&self, pixel: &Mono32) -> Srgba8 {
self.range.map_to_srgba8(mono32_to_f64(pixel))
}
}
impl DisplayStrategy<Mono64> for FixedRange {
#[inline]
fn to_display(&self, pixel: &Mono64) -> Srgba8 {
self.range.map_to_srgba8(mono64_to_f64(pixel))
}
}
impl DisplayStrategy<f32> for FixedRange {
#[inline]
fn to_display(&self, pixel: &f32) -> Srgba8 {
self.range.map_to_srgba8(*pixel as f64)
}
}
impl DisplayStrategy<f64> for FixedRange {
#[inline]
fn to_display(&self, pixel: &f64) -> Srgba8 {
self.range.map_to_srgba8(*pixel)
}
}
impl DisplayStrategy<u8> for FixedRange {
#[inline]
fn to_display(&self, pixel: &u8) -> Srgba8 {
self.range.map_to_srgba8(*pixel as f64)
}
}
impl DisplayStrategy<u16> for FixedRange {
#[inline]
fn to_display(&self, pixel: &u16) -> Srgba8 {
self.range.map_to_srgba8(*pixel as f64)
}
}
impl<const BITS: usize> DisplayStrategy<Mono<BITS>> for FixedRange {
#[inline]
fn to_display(&self, pixel: &Mono<BITS>) -> Srgba8 {
self.range.map_to_srgba8(pixel.value() as f64)
}
}
pub(crate) struct Framebuffer {
pub width: u32,
pub height: u32,
pub data: Vec<u32>,
}
impl Framebuffer {
pub(crate) fn from_image<V, S>(image: &V, strategy: S) -> Self
where
V: ImageView,
V::Pixel: Copy,
S: DisplayStrategy<V::Pixel>,
{
let w = image.width();
let h = image.height();
let len = w * h;
let mut data = Vec::with_capacity(len);
for y in 0..h {
for x in 0..w {
let pixel = image.pixel_at(x, y);
let display = strategy.to_display(&pixel);
data.push(display.to_framebuffer_u32());
}
}
Framebuffer {
width: w as u32,
height: h as u32,
data,
}
}
#[allow(dead_code)]
pub(crate) fn from_raw(width: u32, height: u32, data: Vec<u32>) -> Self {
assert_eq!(
data.len(),
(width as usize) * (height as usize),
"Framebuffer::from_raw: data length ({}) does not match dimensions ({}×{}={})",
data.len(),
width,
height,
(width as usize) * (height as usize),
);
Framebuffer {
width,
height,
data,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fovea::image::{Image, ImageViewMut, SubView};
use fovea::pixel::*;
#[test]
fn identity_srgba8_passthrough() {
let px = Srgba8::new(42, 100, 200, 128);
assert_eq!(Identity.to_display(&px), px);
}
#[test]
fn identity_srgba8_all_values_preserved() {
for r in [0u8, 1, 127, 128, 254, 255] {
for g in [0u8, 128, 255] {
for b in [0u8, 128, 255] {
for a in [0u8, 128, 255] {
let px = Srgba8::new(r, g, b, a);
assert_eq!(Identity.to_display(&px), px);
}
}
}
}
}
#[test]
fn identity_srgb8_adds_alpha() {
let px = Srgb8::new(128, 64, 200);
assert_eq!(Identity.to_display(&px), Srgba8::new(128, 64, 200, 255));
}
#[test]
fn identity_srgb8_black() {
let px = Srgb8::new(0, 0, 0);
assert_eq!(Identity.to_display(&px), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn identity_srgb8_white() {
let px = Srgb8::new(255, 255, 255);
assert_eq!(Identity.to_display(&px), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn identity_srgb_mono8_broadcast() {
let px = SrgbMono8::new(128);
assert_eq!(Identity.to_display(&px), Srgba8::new(128, 128, 128, 255));
}
#[test]
fn identity_srgb_mono8_black() {
let px = SrgbMono8::new(0);
assert_eq!(Identity.to_display(&px), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn identity_srgb_mono8_white() {
let px = SrgbMono8::new(255);
assert_eq!(Identity.to_display(&px), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn identity_srgb_mono_a8_broadcast_with_alpha() {
let px = SrgbMonoA8::new(128, 64);
assert_eq!(Identity.to_display(&px), Srgba8::new(128, 128, 128, 64));
}
#[test]
fn identity_srgb_mono_a8_black_transparent() {
let px = SrgbMonoA8::new(0, 0);
assert_eq!(Identity.to_display(&px), Srgba8::new(0, 0, 0, 0));
}
#[test]
fn identity_srgb_mono_a8_white_opaque() {
let px = SrgbMonoA8::new(255, 255);
assert_eq!(Identity.to_display(&px), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn linear_rgb8_black() {
let px = Rgb8::new(0, 0, 0);
assert_eq!(LinearToDisplay.to_display(&px), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn linear_rgb8_white() {
let px = Rgb8::new(255, 255, 255);
assert_eq!(
LinearToDisplay.to_display(&px),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn linear_rgbf32_mid_gray() {
let px = RgbF32::new(0.5, 0.5, 0.5);
let result = LinearToDisplay.to_display(&px);
assert_eq!(result.r.0, 188);
assert_eq!(result.g.0, 188);
assert_eq!(result.b.0, 188);
assert_eq!(result.a.0, 255);
}
#[test]
fn linear_mono8_correct_srgb_gray() {
let px = Mono8::new(128);
let result = LinearToDisplay.to_display(&px);
assert_eq!(result.r.0, result.g.0);
assert_eq!(result.g.0, result.b.0);
assert_eq!(result.a.0, 255);
assert!(result.r.0 >= 187 && result.r.0 <= 189);
}
#[test]
fn linear_rgba_f32_alpha_preserved() {
let px = RgbaF32::new(0.5, 0.5, 0.5, 0.5);
let result = LinearToDisplay.to_display(&px);
assert_eq!(result.a.0, 128);
assert_eq!(result.r.0, 188);
}
#[test]
fn linear_bgr8_channel_order() {
let px = Bgr8::new(0, 0, 255); let result = LinearToDisplay.to_display(&px);
assert_eq!(result.r.0, 255); assert_eq!(result.g.0, 0);
assert_eq!(result.b.0, 0);
}
#[test]
fn linear_bgra8_channel_order_with_alpha() {
let px = Bgra8::new(0, 0, 255, 128); let result = LinearToDisplay.to_display(&px);
assert_eq!(result.r.0, 255);
assert_eq!(result.g.0, 0);
assert_eq!(result.b.0, 0);
assert_eq!(result.a.0, 128);
}
#[test]
fn linear_f32_clamps_below_zero() {
let px: f32 = -0.5;
let result = LinearToDisplay.to_display(&px);
assert_eq!(result, Srgba8::new(0, 0, 0, 255));
}
#[test]
fn linear_f32_clamps_above_one() {
let px: f32 = 1.5;
let result = LinearToDisplay.to_display(&px);
assert_eq!(result, Srgba8::new(255, 255, 255, 255));
}
#[test]
fn range_map_zero_to_one_black() {
let rm = RangeMap::new(0.0, 1.0);
assert_eq!(rm.map_to_srgba8(0.0), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn range_map_zero_to_one_white() {
let rm = RangeMap::new(0.0, 1.0);
assert_eq!(rm.map_to_srgba8(1.0), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn range_map_zero_to_one_mid_gray() {
let rm = RangeMap::new(0.0, 1.0);
let result = rm.map_to_srgba8(0.5);
assert_eq!(result.r.0, 188);
assert_eq!(result.g.0, 188);
assert_eq!(result.b.0, 188);
assert_eq!(result.a.0, 255);
}
#[test]
fn range_map_custom_range_black() {
let rm = RangeMap::new(100.0, 200.0);
assert_eq!(rm.map_to_srgba8(100.0), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn range_map_custom_range_white() {
let rm = RangeMap::new(100.0, 200.0);
assert_eq!(rm.map_to_srgba8(200.0), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn range_map_degenerate_mid_gray() {
let rm = RangeMap::new(5.0, 5.0);
assert_eq!(rm.map_to_srgba8(5.0), Srgba8::new(128, 128, 128, 255));
}
#[test]
fn range_map_clamp_below() {
let rm = RangeMap::new(100.0, 200.0);
assert_eq!(rm.map_to_srgba8(50.0), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn range_map_clamp_above() {
let rm = RangeMap::new(100.0, 200.0);
assert_eq!(rm.map_to_srgba8(300.0), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn auto_contrast_mono16_full_range() {
let ac = AutoContrast::new(0.0, 65535.0);
assert_eq!(ac.to_display(&Mono16::new(0)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
ac.to_display(&Mono16::new(65535)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn auto_contrast_custom_range() {
let ac = AutoContrast::new(100.0, 200.0);
assert_eq!(ac.to_display(&Mono16::new(100)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
ac.to_display(&Mono16::new(200)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn auto_contrast_f32_mid_gray() {
let ac = AutoContrast::new(0.0, 1.0);
let result = ac.to_display(&0.5f32);
assert_eq!(result.r.0, 188);
}
#[test]
fn auto_contrast_scan_f32() {
let mut img = Image::<MonoF32>::fill(4, 4, MonoF32::new(0.5));
*img.get_mut(0, 0).unwrap() = MonoF32::new(0.0);
*img.get_mut(3, 3).unwrap() = MonoF32::new(1.0);
let ac = AutoContrast::scan(&img);
assert_eq!(ac.to_display(&MonoF32::new(0.0)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
ac.to_display(&MonoF32::new(1.0)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn auto_contrast_scan_constant_image_degenerate() {
let img = Image::<MonoF32>::fill(4, 4, MonoF32::new(0.5));
let ac = AutoContrast::scan(&img);
assert_eq!(
ac.to_display(&MonoF32::new(0.5)),
Srgba8::new(128, 128, 128, 255)
);
}
#[test]
fn auto_contrast_scan_single_pixel_degenerate() {
let img = Image::<MonoF64>::fill(1, 1, MonoF64::new(42.0));
let ac = AutoContrast::scan(&img);
assert_eq!(
ac.to_display(&MonoF64::new(42.0)),
Srgba8::new(128, 128, 128, 255)
);
}
#[test]
fn auto_contrast_scan_empty_image() {
let img = Image::<MonoF32>::fill(0, 0, MonoF32::new(0.0));
let ac = AutoContrast::scan(&img);
assert_eq!(
ac.to_display(&MonoF32::new(0.0)),
Srgba8::new(128, 128, 128, 255)
);
}
#[test]
fn auto_contrast_scan_with_mono16() {
let mut img = Image::<Mono16>::fill(4, 4, Mono16::new(100));
*img.get_mut(0, 0).unwrap() = Mono16::new(50);
*img.get_mut(3, 3).unwrap() = Mono16::new(200);
let ac = AutoContrast::scan_with(&img, |p| mono16_to_f64(p));
assert_eq!(ac.to_display(&Mono16::new(50)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
ac.to_display(&Mono16::new(200)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn auto_contrast_mono32() {
let ac = AutoContrast::new(0.0, u32::MAX as f64);
assert_eq!(ac.to_display(&Mono32::new(0)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
ac.to_display(&Mono32::new(u32::MAX)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn auto_contrast_mono64() {
let ac = AutoContrast::new(0.0, u64::MAX as f64);
assert_eq!(ac.to_display(&Mono64::new(0)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
ac.to_display(&Mono64::new(u64::MAX)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn auto_contrast_u8() {
let ac = AutoContrast::new(0.0, 255.0);
assert_eq!(ac.to_display(&0u8), Srgba8::new(0, 0, 0, 255));
assert_eq!(ac.to_display(&255u8), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn auto_contrast_u16() {
let ac = AutoContrast::new(0.0, 65535.0);
assert_eq!(ac.to_display(&0u16), Srgba8::new(0, 0, 0, 255));
assert_eq!(ac.to_display(&65535u16), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn auto_contrast_f64() {
let ac = AutoContrast::new(0.0, 1.0);
assert_eq!(ac.to_display(&0.0f64), Srgba8::new(0, 0, 0, 255));
assert_eq!(ac.to_display(&1.0f64), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn auto_contrast_mono10() {
let ac = AutoContrast::new(0.0, 1023.0);
let px = Mono10::new(0);
assert_eq!(ac.to_display(&px), Srgba8::new(0, 0, 0, 255));
let px = Mono10::new(1023);
assert_eq!(ac.to_display(&px), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn auto_contrast_mono12() {
let ac = AutoContrast::new(0.0, 4095.0);
let px = Mono12::new(0);
assert_eq!(ac.to_display(&px), Srgba8::new(0, 0, 0, 255));
let px = Mono12::new(4095);
assert_eq!(ac.to_display(&px), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn auto_contrast_mono14() {
let ac = AutoContrast::new(0.0, 16383.0);
let px = Mono14::new(0);
assert_eq!(ac.to_display(&px), Srgba8::new(0, 0, 0, 255));
let px = Mono14::new(16383);
assert_eq!(ac.to_display(&px), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn fixed_range_mono16_boundaries() {
let fr = FixedRange::new(100.0, 200.0);
assert_eq!(fr.to_display(&Mono16::new(100)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
fr.to_display(&Mono16::new(200)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn fixed_range_clamping_below() {
let fr = FixedRange::new(100.0, 200.0);
assert_eq!(fr.to_display(&Mono16::new(0)), Srgba8::new(0, 0, 0, 255));
}
#[test]
fn fixed_range_clamping_above() {
let fr = FixedRange::new(100.0, 200.0);
assert_eq!(
fr.to_display(&Mono16::new(65535)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn fixed_range_degenerate() {
let fr = FixedRange::new(42.0, 42.0);
assert_eq!(
fr.to_display(&Mono16::new(42)),
Srgba8::new(128, 128, 128, 255)
);
}
#[test]
fn fixed_range_f32() {
let fr = FixedRange::new(0.0, 1.0);
assert_eq!(fr.to_display(&0.0f32), Srgba8::new(0, 0, 0, 255));
assert_eq!(fr.to_display(&1.0f32), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn fixed_range_f64() {
let fr = FixedRange::new(-1.0, 1.0);
assert_eq!(fr.to_display(&-1.0f64), Srgba8::new(0, 0, 0, 255));
assert_eq!(fr.to_display(&1.0f64), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn fixed_range_u8() {
let fr = FixedRange::new(0.0, 255.0);
assert_eq!(fr.to_display(&0u8), Srgba8::new(0, 0, 0, 255));
assert_eq!(fr.to_display(&255u8), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn fixed_range_u16() {
let fr = FixedRange::new(0.0, 65535.0);
assert_eq!(fr.to_display(&0u16), Srgba8::new(0, 0, 0, 255));
assert_eq!(fr.to_display(&65535u16), Srgba8::new(255, 255, 255, 255));
}
#[test]
fn fixed_range_mono10() {
let fr = FixedRange::new(0.0, 1023.0);
assert_eq!(fr.to_display(&Mono10::new(0)), Srgba8::new(0, 0, 0, 255));
assert_eq!(
fr.to_display(&Mono10::new(1023)),
Srgba8::new(255, 255, 255, 255)
);
}
#[test]
fn framebuffer_from_image_2x2_srgba8() {
let mut img = Image::fill(2, 2, Srgba8::new(0, 0, 0, 255));
*img.get_mut(0, 0).unwrap() = Srgba8::new(255, 0, 0, 255);
*img.get_mut(1, 0).unwrap() = Srgba8::new(0, 255, 0, 255);
*img.get_mut(0, 1).unwrap() = Srgba8::new(0, 0, 255, 255);
*img.get_mut(1, 1).unwrap() = Srgba8::new(255, 255, 255, 255);
let fb = Framebuffer::from_image(&img, Identity);
assert_eq!(fb.width, 2);
assert_eq!(fb.height, 2);
assert_eq!(fb.data.len(), 4);
assert_eq!(fb.data[0], 0x00FF0000); assert_eq!(fb.data[1], 0x0000FF00); assert_eq!(fb.data[2], 0x000000FF); assert_eq!(fb.data[3], 0x00FFFFFF); }
#[test]
fn framebuffer_from_image_zero_size() {
let img = Image::<Srgba8>::fill(0, 0, Srgba8::new(0, 0, 0, 0));
let fb = Framebuffer::from_image(&img, Identity);
assert_eq!(fb.width, 0);
assert_eq!(fb.height, 0);
assert!(fb.data.is_empty());
}
#[test]
fn framebuffer_from_image_1x1() {
let img = Image::fill(1, 1, Srgba8::new(0xAA, 0xBB, 0xCC, 0xFF));
let fb = Framebuffer::from_image(&img, Identity);
assert_eq!(fb.width, 1);
assert_eq!(fb.height, 1);
assert_eq!(fb.data.len(), 1);
assert_eq!(fb.data[0], 0x00AABBCC);
}
#[test]
fn framebuffer_from_image_with_strategy() {
let img = Image::fill(2, 1, Mono8::new(0));
let fb = Framebuffer::from_image(&img, LinearToDisplay);
assert_eq!(fb.width, 2);
assert_eq!(fb.height, 1);
assert_eq!(fb.data.len(), 2);
assert_eq!(fb.data[0], 0x00000000);
assert_eq!(fb.data[1], 0x00000000);
}
#[test]
fn framebuffer_from_image_roi() {
let mut img = Image::fill(4, 4, Srgba8::new(0, 0, 0, 255));
*img.get_mut(1, 1).unwrap() = Srgba8::new(255, 0, 0, 255);
*img.get_mut(2, 1).unwrap() = Srgba8::new(0, 255, 0, 255);
*img.get_mut(1, 2).unwrap() = Srgba8::new(0, 0, 255, 255);
*img.get_mut(2, 2).unwrap() = Srgba8::new(255, 255, 255, 255);
let roi = img
.roi(fovea::Rectangle::new((1usize, 1usize), (2usize, 2usize)))
.unwrap();
let fb = Framebuffer::from_image(&roi, Identity);
assert_eq!(fb.width, 2);
assert_eq!(fb.height, 2);
assert_eq!(fb.data[0], 0x00FF0000); assert_eq!(fb.data[1], 0x0000FF00); assert_eq!(fb.data[2], 0x000000FF); assert_eq!(fb.data[3], 0x00FFFFFF); }
#[test]
fn framebuffer_from_raw_valid() {
let fb = Framebuffer::from_raw(2, 2, vec![0, 1, 2, 3]);
assert_eq!(fb.width, 2);
assert_eq!(fb.height, 2);
assert_eq!(fb.data, vec![0, 1, 2, 3]);
}
#[test]
fn framebuffer_from_raw_empty() {
let fb = Framebuffer::from_raw(0, 0, vec![]);
assert_eq!(fb.width, 0);
assert_eq!(fb.height, 0);
assert!(fb.data.is_empty());
}
#[test]
#[should_panic(expected = "does not match dimensions")]
fn framebuffer_from_raw_wrong_size() {
let _ = Framebuffer::from_raw(2, 2, vec![0, 1, 2]);
}
}