use std::io::{self, Write};
use crate::{ColorSpace, DynamicImageOwned, DynamicImageRef, ImageProps, PixelType};
fn sub_container_bits(pt: PixelType) -> Option<u8> {
matches!(pt, PixelType::U10 | PixelType::U12 | PixelType::U14).then_some(pt.bit_depth())
}
pub(super) struct ImageView<'a> {
pub w: usize,
pub h: usize,
pub ch: usize,
pub cspace: ColorSpace,
pub adc_bits: Option<u8>,
pixels: Pixels<'a>,
}
enum Pixels<'a> {
U8(&'a [u8]),
U16(&'a [u16]),
F32(&'a [f32]),
}
pub(super) enum Tile {
I16(Vec<i16>),
I8(Vec<i8>),
F32(Vec<f32>),
}
impl Tile {
pub(super) fn to_be_bytes(&self) -> Vec<u8> {
match self {
Tile::I8(v) => v.iter().map(|&x| x as u8).collect(),
Tile::I16(v) => v.iter().flat_map(|x| x.to_be_bytes()).collect(),
Tile::F32(v) => v.iter().flat_map(|x| x.to_be_bytes()).collect(),
}
}
}
impl<'a> ImageView<'a> {
pub(super) fn from_ref(img: &'a DynamicImageRef<'_>) -> Self {
let (w, h, ch) = (img.width(), img.height(), img.channels() as usize);
let cspace = img.color_space();
let adc_bits = sub_container_bits(img.pixel_type());
let pixels = match img {
DynamicImageRef::U8(r) => Pixels::U8(r.as_slice()),
DynamicImageRef::U16(r) => Pixels::U16(r.as_slice()),
DynamicImageRef::F32(r) => Pixels::F32(r.as_slice()),
};
Self {
w,
h,
ch,
cspace,
adc_bits,
pixels,
}
}
pub(super) fn from_owned(img: &'a DynamicImageOwned) -> Self {
let (w, h, ch) = (img.width(), img.height(), img.channels() as usize);
let cspace = img.color_space();
let adc_bits = sub_container_bits(img.pixel_type());
let pixels = match img {
DynamicImageOwned::U8(r) => Pixels::U8(r.as_slice()),
DynamicImageOwned::U16(r) => Pixels::U16(r.as_slice()),
DynamicImageOwned::F32(r) => Pixels::F32(r.as_slice()),
};
Self {
w,
h,
ch,
cspace,
adc_bits,
pixels,
}
}
pub(super) fn bitpix(&self) -> i64 {
match self.pixels {
Pixels::U8(_) => 8,
Pixels::U16(_) => 16,
Pixels::F32(_) => -32,
}
}
pub(super) fn bzero(&self) -> Option<i64> {
matches!(self.pixels, Pixels::U16(_)).then_some(32768)
}
pub(super) fn is_float(&self) -> bool {
matches!(self.pixels, Pixels::F32(_))
}
pub(super) fn axes(&self) -> Vec<usize> {
if self.ch > 1 {
vec![self.w, self.h, self.ch]
} else {
vec![self.w, self.h]
}
}
pub(super) fn bytepix(&self) -> usize {
match self.pixels {
Pixels::U8(_) => 1,
Pixels::U16(_) => 2,
Pixels::F32(_) => 4,
}
}
fn interleaved_index(&self, plane: usize, row: usize, col: usize) -> usize {
(row * self.w + col) * self.ch + plane
}
fn rect_indices(
&self,
plane: usize,
x0: usize,
y0: usize,
tw: usize,
th: usize,
) -> impl Iterator<Item = usize> + '_ {
(0..th).flat_map(move |ry| {
(0..tw).map(move |rx| self.interleaved_index(plane, y0 + ry, x0 + rx))
})
}
pub(super) fn rect_tile(
&self,
plane: usize,
x0: usize,
y0: usize,
tw: usize,
th: usize,
) -> Tile {
let idx = self.rect_indices(plane, x0, y0, tw, th);
match self.pixels {
Pixels::U8(p) => Tile::I8(idx.map(|i| p[i] as i8).collect()),
Pixels::U16(p) => Tile::I16(idx.map(|i| (p[i] as i32 - 32768) as i16).collect()),
Pixels::F32(p) => Tile::F32(idx.map(|i| p[i]).collect()),
}
}
pub(super) fn rect_i32(
&self,
plane: usize,
x0: usize,
y0: usize,
tw: usize,
th: usize,
) -> Vec<i32> {
self.rect_indices(plane, x0, y0, tw, th)
.map(|i| match self.pixels {
Pixels::U8(p) => p[i] as i32,
Pixels::U16(p) => p[i] as i32 - 32768,
Pixels::F32(_) => unreachable!("f32 tiles are quantized before HCOMPRESS"),
})
.collect()
}
pub(super) fn rect_f32(
&self,
plane: usize,
x0: usize,
y0: usize,
tw: usize,
th: usize,
) -> Vec<f32> {
match self.pixels {
Pixels::F32(p) => self
.rect_indices(plane, x0, y0, tw, th)
.map(|i| p[i])
.collect(),
_ => unreachable!("rect_f32 on a non-float image"),
}
}
pub(super) fn planar_f32(&self) -> Vec<f32> {
let mut out = Vec::with_capacity(self.w * self.h * self.ch);
for plane in 0..self.ch {
out.extend(self.rect_f32(plane, 0, 0, self.w, self.h));
}
out
}
pub(super) fn native_be_len(&self) -> usize {
self.w * self.h * self.ch * self.bytepix()
}
pub(super) fn write_native_be<W: Write>(&self, w: &mut W) -> io::Result<()> {
let mut row = Vec::with_capacity(self.w * self.bytepix());
for plane in 0..self.ch {
for y in 0..self.h {
row.clear();
for x in 0..self.w {
let i = self.interleaved_index(plane, y, x);
match self.pixels {
Pixels::U8(p) => row.push(p[i]),
Pixels::U16(p) => {
let v = (p[i] as i32 - 32768) as i16;
row.extend_from_slice(&v.to_be_bytes());
}
Pixels::F32(p) => row.extend_from_slice(&p[i].to_be_bytes()),
}
}
w.write_all(&row)?;
}
}
Ok(())
}
}
pub(super) fn colorspace_str(cs: &ColorSpace) -> String {
use crate::BayerPattern::*;
match cs {
ColorSpace::Gray => "GRAY".into(),
ColorSpace::Rgb => "RGB".into(),
ColorSpace::Bayer(Bggr) => "BGGR".into(),
ColorSpace::Bayer(Gbrg) => "GBRG".into(),
ColorSpace::Bayer(Grbg) => "GRBG".into(),
ColorSpace::Bayer(Rggb) => "RGGB".into(),
ColorSpace::Custom(n, name) => format!("C({n},{name})"),
}
}
pub(super) fn bayer_pattern(cs: &ColorSpace) -> Option<&'static str> {
use crate::BayerPattern::*;
match cs {
ColorSpace::Bayer(Bggr) => Some("BGGR"),
ColorSpace::Bayer(Gbrg) => Some("GBRG"),
ColorSpace::Bayer(Grbg) => Some("GRBG"),
ColorSpace::Bayer(Rggb) => Some("RGGB"),
_ => None,
}
}