use crate::color::ColorSpace;
#[inline]
pub fn white_level_from_bit_depth(bit_depth: u8) -> u16 {
if bit_depth >= 16 {
u16::MAX
} else if bit_depth == 0 {
0
} else {
(1u16 << bit_depth) - 1
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Size {
pub width: u32,
pub height: u32,
}
impl Size {
pub fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
pub fn is_valid(&self) -> bool {
self.width > 0 && self.height > 0
}
pub fn pixel_count(&self) -> u64 {
self.width as u64 * self.height as u64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Point {
pub x: u32,
pub y: u32,
}
impl Point {
pub fn new(x: u32, y: u32) -> Self {
Self { x, y }
}
pub const ORIGIN: Point = Point { x: 0, y: 0 };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rect {
pub origin: Point,
pub size: Size,
}
impl Rect {
pub fn new(origin: Point, size: Size) -> Self {
Self { origin, size }
}
pub fn from_coords(x: u32, y: u32, width: u32, height: u32) -> Self {
Self {
origin: Point::new(x, y),
size: Size::new(width, height),
}
}
pub fn right(&self) -> u32 {
self.origin.x.saturating_add(self.size.width)
}
pub fn bottom(&self) -> u32 {
self.origin.y.saturating_add(self.size.height)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CfaPattern {
Rggb,
Grbg,
Bggr,
Gbrg,
}
impl CfaPattern {
pub fn from_array(pattern: [u8; 4]) -> Option<Self> {
match pattern {
[0, 1, 1, 2] => Some(CfaPattern::Rggb),
[1, 0, 2, 1] => Some(CfaPattern::Grbg),
[2, 1, 1, 0] => Some(CfaPattern::Bggr),
[1, 2, 0, 1] => Some(CfaPattern::Gbrg),
_ => None,
}
}
pub fn to_array(self) -> [u8; 4] {
match self {
CfaPattern::Rggb => [0, 1, 1, 2],
CfaPattern::Grbg => [1, 0, 2, 1],
CfaPattern::Bggr => [2, 1, 1, 0],
CfaPattern::Gbrg => [1, 2, 0, 1],
}
}
pub fn name(&self) -> &'static str {
match self {
CfaPattern::Rggb => "RGGB",
CfaPattern::Grbg => "GRBG",
CfaPattern::Bggr => "BGGR",
CfaPattern::Gbrg => "GBRG",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XTransPattern {
pub cells: [[u8; 6]; 6],
}
impl XTransPattern {
pub fn standard() -> Self {
Self {
cells: [
[1, 2, 1, 1, 0, 1],
[0, 1, 0, 2, 1, 2],
[1, 2, 1, 1, 0, 1],
[1, 0, 1, 2, 1, 2],
[1, 2, 1, 1, 0, 1],
[0, 1, 0, 2, 1, 2],
],
}
}
#[inline]
pub fn color_at(&self, x: usize, y: usize) -> u8 {
self.cells[y % 6][x % 6]
}
}
#[derive(Debug, Clone)]
pub struct RawImage {
size: Size,
active_area: Rect,
bit_depth: u8,
cfa_pattern: CfaPattern,
xtrans_pattern: Option<XTransPattern>,
black_levels: [u16; 4],
white_level: u16,
pub data: Vec<u16>,
baseline_exposure: Option<f32>,
default_crop: Option<Rect>,
}
impl RawImage {
pub fn new(size: Size, active_area: Rect, bit_depth: u8, cfa_pattern: CfaPattern) -> Self {
let pixel_count = size.pixel_count() as usize;
Self {
size,
active_area,
bit_depth,
cfa_pattern,
xtrans_pattern: None,
black_levels: [0; 4],
white_level: white_level_from_bit_depth(bit_depth),
data: vec![0u16; pixel_count],
baseline_exposure: None,
default_crop: None,
}
}
pub fn builder(
size: Size,
active_area: Rect,
bit_depth: u8,
cfa_pattern: CfaPattern,
) -> RawImageBuilder {
RawImageBuilder {
size,
active_area,
bit_depth,
cfa_pattern,
xtrans_pattern: None,
black_levels: [0; 4],
white_level: white_level_from_bit_depth(bit_depth),
data: None,
baseline_exposure: None,
default_crop: None,
}
}
pub fn size(&self) -> Size {
self.size
}
pub fn width(&self) -> u32 {
self.size.width
}
pub fn height(&self) -> u32 {
self.size.height
}
pub fn active_area(&self) -> Rect {
self.active_area
}
pub fn bit_depth(&self) -> u8 {
self.bit_depth
}
pub fn cfa_pattern(&self) -> CfaPattern {
self.cfa_pattern
}
pub fn xtrans_pattern(&self) -> Option<&XTransPattern> {
self.xtrans_pattern.as_ref()
}
pub fn black_levels(&self) -> &[u16; 4] {
&self.black_levels
}
pub fn white_level(&self) -> u16 {
self.white_level
}
pub fn baseline_exposure(&self) -> Option<f32> {
self.baseline_exposure
}
pub fn default_crop(&self) -> Option<Rect> {
self.default_crop
}
pub fn set_black_levels(&mut self, levels: [u16; 4]) {
self.black_levels = levels;
}
pub fn set_white_level(&mut self, level: u16) {
self.white_level = level;
}
pub fn set_baseline_exposure(&mut self, ev: Option<f32>) {
self.baseline_exposure = ev;
}
pub fn set_default_crop(&mut self, crop: Option<Rect>) {
self.default_crop = crop;
}
pub fn set_xtrans_pattern(&mut self, pattern: Option<XTransPattern>) {
self.xtrans_pattern = pattern;
}
pub fn set_bit_depth(&mut self, bit_depth: u8) {
self.bit_depth = bit_depth;
}
pub fn get_pixel(&self, x: u32, y: u32) -> Option<u16> {
if x < self.size.width && y < self.size.height {
let idx = (y as usize) * (self.size.width as usize) + (x as usize);
Some(self.data[idx])
} else {
None
}
}
pub fn set_pixel(&mut self, x: u32, y: u32, value: u16) {
if x < self.size.width && y < self.size.height {
let idx = (y as usize) * (self.size.width as usize) + (x as usize);
self.data[idx] = value;
}
}
}
pub struct RawImageBuilder {
size: Size,
active_area: Rect,
bit_depth: u8,
cfa_pattern: CfaPattern,
xtrans_pattern: Option<XTransPattern>,
black_levels: [u16; 4],
white_level: u16,
data: Option<Vec<u16>>,
baseline_exposure: Option<f32>,
default_crop: Option<Rect>,
}
impl RawImageBuilder {
pub fn black_levels(mut self, levels: [u16; 4]) -> Self {
self.black_levels = levels;
self
}
pub fn white_level(mut self, level: u16) -> Self {
self.white_level = level;
self
}
pub fn xtrans_pattern(mut self, pattern: XTransPattern) -> Self {
self.xtrans_pattern = Some(pattern);
self
}
pub fn baseline_exposure(mut self, ev: f32) -> Self {
self.baseline_exposure = Some(ev);
self
}
pub fn default_crop(mut self, crop: Rect) -> Self {
self.default_crop = Some(crop);
self
}
pub fn data(mut self, data: Vec<u16>) -> Self {
self.data = Some(data);
self
}
pub fn build(self) -> RawImage {
let data = self
.data
.unwrap_or_else(|| vec![0u16; self.size.pixel_count() as usize]);
RawImage {
size: self.size,
active_area: self.active_area,
bit_depth: self.bit_depth,
cfa_pattern: self.cfa_pattern,
xtrans_pattern: self.xtrans_pattern,
black_levels: self.black_levels,
white_level: self.white_level,
data,
baseline_exposure: self.baseline_exposure,
default_crop: self.default_crop,
}
}
}
#[derive(Debug, Clone)]
pub struct RgbImage {
size: Size,
pub data: Vec<u16>,
baseline_exposure: Option<f32>,
default_crop: Option<Rect>,
color_space: ColorSpace,
}
impl RgbImage {
pub fn new(width: u32, height: u32, data: Vec<u16>) -> Self {
Self {
size: Size::new(width, height),
data,
baseline_exposure: None,
default_crop: None,
color_space: ColorSpace::Unknown,
}
}
pub fn with_color_space(
width: u32,
height: u32,
data: Vec<u16>,
color_space: ColorSpace,
) -> Self {
Self {
size: Size::new(width, height),
data,
baseline_exposure: None,
default_crop: None,
color_space,
}
}
pub fn size(&self) -> Size {
self.size
}
pub fn width(&self) -> u32 {
self.size.width
}
pub fn height(&self) -> u32 {
self.size.height
}
pub fn baseline_exposure(&self) -> Option<f32> {
self.baseline_exposure
}
pub fn default_crop(&self) -> Option<Rect> {
self.default_crop
}
pub fn color_space(&self) -> ColorSpace {
self.color_space
}
pub fn set_baseline_exposure(&mut self, ev: Option<f32>) {
self.baseline_exposure = ev;
}
pub fn set_color_space(&mut self, color_space: ColorSpace) {
self.color_space = color_space;
}
pub fn set_default_crop(&mut self, crop: Option<Rect>) {
self.default_crop = crop;
}
pub fn set_size(&mut self, size: Size) {
self.size = size;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_white_level_from_bit_depth() {
assert_eq!(white_level_from_bit_depth(0), 0);
assert_eq!(white_level_from_bit_depth(1), 1);
assert_eq!(white_level_from_bit_depth(8), 255);
assert_eq!(white_level_from_bit_depth(12), 4095);
assert_eq!(white_level_from_bit_depth(14), 16383);
assert_eq!(white_level_from_bit_depth(15), 32767);
assert_eq!(white_level_from_bit_depth(16), u16::MAX);
assert_eq!(white_level_from_bit_depth(32), u16::MAX);
assert_eq!(white_level_from_bit_depth(255), u16::MAX);
}
#[test]
fn test_size() {
let size = Size::new(100, 200);
assert_eq!(size.pixel_count(), 20000);
assert!(size.is_valid());
let empty = Size::new(0, 100);
assert!(!empty.is_valid());
}
#[test]
fn test_cfa_pattern() {
assert_eq!(CfaPattern::from_array([0, 1, 1, 2]), Some(CfaPattern::Rggb));
assert_eq!(CfaPattern::Rggb.to_array(), [0, 1, 1, 2]);
assert_eq!(CfaPattern::Rggb.name(), "RGGB");
}
#[test]
fn test_raw_image() {
let size = Size::new(10, 10);
let active = Rect::from_coords(0, 0, 10, 10);
let mut img = RawImage::new(size, active, 14, CfaPattern::Rggb);
img.set_pixel(5, 5, 1000);
assert_eq!(img.get_pixel(5, 5), Some(1000));
assert_eq!(img.get_pixel(100, 100), None);
}
#[test]
fn test_raw_image_pixel_access() {
let size = Size::new(4, 4);
let active = Rect::from_coords(0, 0, 4, 4);
let mut img = RawImage::new(size, active, 14, CfaPattern::Rggb);
img.set_pixel(0, 0, 100);
img.set_pixel(3, 0, 200);
img.set_pixel(0, 3, 300);
img.set_pixel(3, 3, 400);
img.set_pixel(2, 1, 500);
assert_eq!(img.get_pixel(0, 0), Some(100));
assert_eq!(img.get_pixel(3, 0), Some(200));
assert_eq!(img.get_pixel(0, 3), Some(300));
assert_eq!(img.get_pixel(3, 3), Some(400));
assert_eq!(img.get_pixel(2, 1), Some(500));
assert_eq!(img.get_pixel(4, 0), None);
assert_eq!(img.get_pixel(0, 4), None);
assert_eq!(img.get_pixel(u32::MAX, u32::MAX), None);
}
#[test]
fn test_rgb_image_indexing() {
let data = vec![
100u16, 200, 300, 400, 500, 600, ];
let img = RgbImage::new(2, 1, data.clone());
assert_eq!(img.data[0], 100, "pixel 0 R");
assert_eq!(img.data[1], 200, "pixel 0 G");
assert_eq!(img.data[2], 300, "pixel 0 B");
assert_eq!(img.data[3], 400, "pixel 1 R");
assert_eq!(img.data[4], 500, "pixel 1 G");
assert_eq!(img.data[5], 600, "pixel 1 B");
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 1);
assert_eq!(img.data.len(), 6);
}
#[test]
fn test_size_pixel_count() {
let s = Size::new(1920, 1080);
assert_eq!(s.pixel_count(), 1920 * 1080);
let s = Size::new(0, 100);
assert_eq!(s.pixel_count(), 0);
let s = Size::new(10000, 10000);
assert_eq!(s.pixel_count(), 100_000_000u64);
}
#[test]
fn test_rect_dimensions() {
let r = Rect::from_coords(10, 20, 100, 200);
assert_eq!(r.origin.x, 10);
assert_eq!(r.origin.y, 20);
assert_eq!(r.size.width, 100);
assert_eq!(r.size.height, 200);
assert_eq!(r.right(), 110);
assert_eq!(r.bottom(), 220);
}
#[test]
fn test_raw_image_builder() {
let size = Size::new(10, 10);
let active = Rect::from_coords(0, 0, 10, 10);
let img = RawImage::builder(size, active, 14, CfaPattern::Rggb)
.black_levels([100, 100, 100, 100])
.white_level(16383)
.build();
assert_eq!(img.size(), size);
assert_eq!(img.active_area(), active);
assert_eq!(img.bit_depth(), 14);
assert_eq!(img.cfa_pattern(), CfaPattern::Rggb);
assert_eq!(*img.black_levels(), [100, 100, 100, 100]);
assert_eq!(img.white_level(), 16383);
assert_eq!(img.data.len(), 100);
}
#[test]
fn test_raw_image_builder_with_data() {
let size = Size::new(2, 2);
let active = Rect::from_coords(0, 0, 2, 2);
let img = RawImage::builder(size, active, 14, CfaPattern::Rggb)
.data(vec![1000, 2000, 3000, 4000])
.build();
assert_eq!(img.data, vec![1000, 2000, 3000, 4000]);
}
#[test]
fn test_rgb_image_accessors() {
let img = RgbImage::new(100, 200, vec![0u16; 100 * 200 * 3]);
assert_eq!(img.width(), 100);
assert_eq!(img.height(), 200);
assert_eq!(img.size(), Size::new(100, 200));
assert_eq!(img.baseline_exposure(), None);
assert_eq!(img.default_crop(), None);
}
#[test]
fn test_raw_image_setters() {
let size = Size::new(4, 4);
let active = Rect::from_coords(0, 0, 4, 4);
let mut img = RawImage::new(size, active, 14, CfaPattern::Rggb);
img.set_black_levels([100, 100, 100, 100]);
assert_eq!(*img.black_levels(), [100, 100, 100, 100]);
img.set_white_level(4095);
assert_eq!(img.white_level(), 4095);
img.set_baseline_exposure(Some(-0.8));
assert_eq!(img.baseline_exposure(), Some(-0.8));
let crop = Rect::from_coords(1, 1, 2, 2);
img.set_default_crop(Some(crop));
assert_eq!(img.default_crop(), Some(crop));
img.set_xtrans_pattern(Some(XTransPattern::standard()));
assert!(img.xtrans_pattern().is_some());
}
}