From 86e0b04a469793cbd39d77d5c77aec08b7bc8d61 Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Fri, 15 Nov 2019 09:02:00 -0500 Subject: [PATCH] Add some code for dealing with colours. --- src/colour.rs | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 218 insertions(+) create mode 100644 src/colour.rs diff --git a/src/colour.rs b/src/colour.rs new file mode 100644 index 0000000..2788c81 --- /dev/null +++ b/src/colour.rs @@ -0,0 +1,217 @@ +use nalgebra::{clamp, RealField, Vector3}; + +pub struct ColourRGB { + values: Vector3, +} + +pub trait NormalizedAsByte { + fn normalized_to_byte(self) -> u8; + fn byte_to_normalized(byte: u8) -> Self; +} + +impl ColourRGB { + pub fn new(red: T, green: T, blue: T) -> ColourRGB { + ColourRGB { + values: Vector3::new(red, green, blue), + } + } + + pub fn red(&self) -> T { + self.values[0] + } + + pub fn green(&self) -> T { + self.values[1] + } + + pub fn blue(&self) -> T { + self.values[2] + } + + pub fn as_vector3(&self) -> &Vector3 { + &self.values + } +} + +pub struct ColourRGB24 { + pub values: [u8; 3], +} + +pub trait ToneMapper { + fn apply_tone_mapping(&self, colour_in: &ColourRGB) -> ColourRGB24; +} + +pub struct ClampingToneMapper {} + +impl ClampingToneMapper { + pub fn new() -> ClampingToneMapper { + ClampingToneMapper {} + } + + fn clamp(v: &T) -> u8 { + clamp(v, &T::zero(), &T::one()).normalized_to_byte() + } +} + +impl ToneMapper for ClampingToneMapper { + fn apply_tone_mapping(&self, colour_in: &ColourRGB) -> ColourRGB24 { + ColourRGB24 { + values: [ + Self::clamp(&colour_in.values[0]), + Self::clamp(&colour_in.values[1]), + Self::clamp(&colour_in.values[2]), + ], + } + } +} + +impl NormalizedAsByte for f32 { + fn normalized_to_byte(self) -> u8 { + (self * (std::u8::MAX as f32)) as u8 + } + + fn byte_to_normalized(byte: u8) -> f32 { + (byte as f32) / (std::u8::MAX as f32) + } +} + +impl NormalizedAsByte for f64 { + fn normalized_to_byte(self) -> u8 { + (self * (std::u8::MAX as f64)) as u8 + } + + fn byte_to_normalized(byte: u8) -> f64 { + (byte as f64) / (std::u8::MAX as f64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod colour_rgb { + use super::*; + + #[test] + fn constructor_sets_correct_red_green_and_blue() { + let target = ColourRGB::new(1.0, 2.0, 3.0); + assert!(target.red() == 1.0); + assert!(target.green() == 2.0); + assert!(target.blue() == 3.0); + } + + #[test] + fn as_vector3_returns_expected_vector() { + let target = ColourRGB::new(1.0, 2.0, 3.0); + let result = target.as_vector3(); + assert!(result.x == 1.0); + } + } + mod clamping_tone_mapper { + use super::*; + + #[test] + fn black_colourrgb_becomes_black_colourrgb24() { + let target = ClampingToneMapper {}; + let colourf = ColourRGB::new(0.0, 0.0, 0.0); + let colouru = target.apply_tone_mapping(&colourf); + assert!(colouru.values == [0, 0, 0]); + } + + #[test] + fn white_colourrgb_becomes_white_colourrgb24() { + let target = ClampingToneMapper {}; + let colourf = ColourRGB::new(1.0, 1.0, 1.0); + let colouru = target.apply_tone_mapping(&colourf); + assert!(colouru.values == [0xff, 0xff, 0xff]); + } + + #[test] + fn supersaturated_white_colourrgb_becomes_white_colourrgb24() { + let target = ClampingToneMapper {}; + let colourf = ColourRGB::new(2.0, 2.0, 2.0); + let colouru = target.apply_tone_mapping(&colourf); + assert!(colouru.values == [0xff, 0xff, 0xff]); + } + + #[test] + fn supersaturated_green_colourrgb_becomes_green_colourrgb24() { + let target = ClampingToneMapper {}; + let colourf = ColourRGB::new(0.0, 2.0, 0.0); + let colouru = target.apply_tone_mapping(&colourf); + assert!(colouru.values == [0x0, 0xff, 0x0]); + } + + #[test] + fn dark_red_colourrgb_becomes_dark_red_colourrgb24() { + let target = ClampingToneMapper {}; + let colourf = ColourRGB::new(0.5, 0.0, 0.0); + let colouru = target.apply_tone_mapping(&colourf); + assert!(colouru.values == [0x7f, 0x0, 0x0]); + } + } + + mod normalized_as_byte { + use super::*; + + #[test] + fn normalized_to_byte_converts_1_to_255_for_f32() { + assert!((1.0f32).normalized_to_byte() == 0xff); + } + + #[test] + fn byte_to_normalized_converts_255_to_1_for_f32() { + assert!(f32::byte_to_normalized(0xff) == 1.0); + } + + #[test] + fn normalized_to_byte_converts_1_to_255_for_f64() { + assert!((1.0f64).normalized_to_byte() == 255); + } + + #[test] + fn byte_to_normalized_converts_255_to_1_for_f64() { + assert!(f64::byte_to_normalized(0xff) == 1.0); + } + + #[test] + fn normalized_to_byte_converts_0_to_0_for_f32() { + assert!((0.0f32).normalized_to_byte() == 0); + } + + #[test] + fn byte_to_normalized_converts_0_to_0_for_f32() { + assert!(f32::byte_to_normalized(0) == 0.0); + } + + #[test] + fn normalized_to_byte_converts_0_to_0_for_f64() { + assert!((0.0f64).normalized_to_byte() == 0); + } + + #[test] + fn byte_to_normalized_converts_0_to_0_for_f64() { + assert!(f64::byte_to_normalized(0) == 0.0); + } + + #[test] + fn normalized_to_byte_converts_half_to_127_for_f32() { + assert!((0.5f32).normalized_to_byte() == 0x7f); + } + + #[test] + fn byte_to_normalized_converts_127_to_half_for_f32() { + assert!((f32::byte_to_normalized(0x7f) - 0.5).abs() < 1.0 / 256.0); + } + + #[test] + fn normalized_to_byte_converts_half_to_127_for_f64() { + assert!((0.5f64).normalized_to_byte() == 0x7f); + } + + #[test] + fn byte_to_normalized_converts_127_to_half_for_f64() { + assert!((f64::byte_to_normalized(0x7f) - 0.5).abs() < 1.0 / 256.0); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 650ce81..9942a89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod camera; +pub mod colour; pub mod image; pub mod integrators; pub mod raycasting;