use alloc::vec::Vec;
use enough::Stop;
use whereat::{ResultAtExt, at};
use zenpixels::{ChannelLayout, ChannelType, PixelDescriptor, PixelSlice};
use crate::error::{Result, TiffError};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Compression {
#[default]
Uncompressed,
Lzw,
Deflate,
PackBits,
}
impl Compression {
#[track_caller]
fn to_tiff(self) -> Result<tiff::encoder::Compression> {
match self {
Self::Uncompressed => Ok(tiff::encoder::Compression::Uncompressed),
#[cfg(feature = "lzw")]
Self::Lzw => Ok(tiff::encoder::Compression::Lzw),
#[cfg(not(feature = "lzw"))]
Self::Lzw => Err(at!(TiffError::Unsupported(
"LZW compression requires the `lzw` feature".into(),
))),
#[cfg(feature = "deflate")]
Self::Deflate => Ok(tiff::encoder::Compression::Deflate(
tiff::encoder::DeflateLevel::Balanced,
)),
#[cfg(not(feature = "deflate"))]
Self::Deflate => Err(at!(TiffError::Unsupported(
"Deflate compression requires the `deflate` feature".into(),
))),
Self::PackBits => Ok(tiff::encoder::Compression::Packbits),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Predictor {
#[default]
None,
Horizontal,
}
impl Predictor {
fn to_tiff(self) -> tiff::encoder::Predictor {
match self {
Self::None => tiff::encoder::Predictor::None,
Self::Horizontal => tiff::encoder::Predictor::Horizontal,
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct TiffEncodeConfig {
pub compression: Compression,
pub predictor: Predictor,
pub big_tiff: bool,
}
impl TiffEncodeConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_compression(mut self, compression: Compression) -> Self {
self.compression = compression;
self
}
#[must_use]
pub fn with_predictor(mut self, predictor: Predictor) -> Self {
self.predictor = predictor;
self
}
#[must_use]
pub fn with_big_tiff(mut self, big: bool) -> Self {
self.big_tiff = big;
self
}
}
impl Default for TiffEncodeConfig {
fn default() -> Self {
Self {
compression: Compression::Lzw,
predictor: Predictor::Horizontal,
big_tiff: false,
}
}
}
#[track_caller]
pub fn encode(
pixels: &PixelSlice<'_>,
config: &TiffEncodeConfig,
cancel: &dyn Stop,
) -> Result<Vec<u8>> {
cancel.check().map_err(|e| at!(TiffError::from(e)))?;
let desc = pixels.descriptor();
let width = pixels.width();
let height = pixels.rows();
let data = pixels.contiguous_bytes();
let compression = config.compression.to_tiff()?;
let predictor = config.predictor.to_tiff();
let mut buf = std::io::Cursor::new(Vec::new());
if config.big_tiff {
let enc =
tiff::encoder::TiffEncoder::new_big(&mut buf).map_err(|e| at!(TiffError::from(e)))?;
let mut enc = enc.with_compression(compression).with_predictor(predictor);
write_image(&mut enc, width, height, &desc, &data).at()?;
} else {
let enc = tiff::encoder::TiffEncoder::new(&mut buf).map_err(|e| at!(TiffError::from(e)))?;
let mut enc = enc.with_compression(compression).with_predictor(predictor);
write_image(&mut enc, width, height, &desc, &data).at()?;
}
Ok(buf.into_inner())
}
#[track_caller]
pub fn encode_into(
pixels: &PixelSlice<'_>,
config: &TiffEncodeConfig,
cancel: &dyn Stop,
output: &mut Vec<u8>,
) -> Result<()> {
let encoded = encode(pixels, config, cancel).at()?;
output.extend_from_slice(&encoded);
Ok(())
}
#[track_caller]
fn as_u16_slice(data: &[u8]) -> Result<std::borrow::Cow<'_, [u16]>> {
use std::borrow::Cow;
match bytemuck::try_cast_slice(data) {
Ok(s) => Ok(Cow::Borrowed(s)),
Err(bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned) => Ok(Cow::Owned(
data.chunks_exact(2)
.map(|c| u16::from_ne_bytes([c[0], c[1]]))
.collect(),
)),
Err(e) => Err(at!(TiffError::InvalidInput(alloc::format!(
"cannot cast pixel data to &[u16]: {e:?}"
)))),
}
}
#[track_caller]
fn as_f32_slice(data: &[u8]) -> Result<std::borrow::Cow<'_, [f32]>> {
use std::borrow::Cow;
match bytemuck::try_cast_slice(data) {
Ok(s) => Ok(Cow::Borrowed(s)),
Err(bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned) => Ok(Cow::Owned(
data.chunks_exact(4)
.map(|c| f32::from_ne_bytes([c[0], c[1], c[2], c[3]]))
.collect(),
)),
Err(e) => Err(at!(TiffError::InvalidInput(alloc::format!(
"cannot cast pixel data to &[f32]: {e:?}"
)))),
}
}
#[track_caller]
fn write_image<W: std::io::Write + std::io::Seek, K: tiff::encoder::TiffKind>(
enc: &mut tiff::encoder::TiffEncoder<W, K>,
width: u32,
height: u32,
desc: &PixelDescriptor,
data: &[u8],
) -> Result<()> {
use tiff::encoder::colortype;
let layout = desc.layout();
let ct = desc.channel_type();
match (layout, ct) {
(ChannelLayout::Gray, ChannelType::U8) => {
enc.write_image::<colortype::Gray8>(width, height, data)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Gray, ChannelType::U16) => {
let samples = as_u16_slice(data)?;
enc.write_image::<colortype::Gray16>(width, height, &samples)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Gray, ChannelType::F32) => {
let samples = as_f32_slice(data)?;
enc.write_image::<colortype::Gray32Float>(width, height, &samples)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::GrayAlpha, ChannelType::U8) => {
let rgba = expand_graya_to_rgba_u8(data);
enc.write_image::<colortype::RGBA8>(width, height, &rgba)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::GrayAlpha, ChannelType::U16) => {
let samples = as_u16_slice(data)?;
let rgba = expand_graya_to_rgba_u16(&samples);
enc.write_image::<colortype::RGBA16>(width, height, &rgba)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::GrayAlpha, ChannelType::F32) => {
let samples = as_f32_slice(data)?;
let rgba = expand_graya_to_rgba_f32(&samples);
enc.write_image::<colortype::RGBA32Float>(width, height, &rgba)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Rgb, ChannelType::U8) => {
enc.write_image::<colortype::RGB8>(width, height, data)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Rgb, ChannelType::U16) => {
let samples = as_u16_slice(data)?;
enc.write_image::<colortype::RGB16>(width, height, &samples)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Rgb, ChannelType::F32) => {
let samples = as_f32_slice(data)?;
enc.write_image::<colortype::RGB32Float>(width, height, &samples)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Rgba, ChannelType::U8) => {
enc.write_image::<colortype::RGBA8>(width, height, data)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Rgba, ChannelType::U16) => {
let samples = as_u16_slice(data)?;
enc.write_image::<colortype::RGBA16>(width, height, &samples)
.map_err(|e| at!(TiffError::from(e)))?;
}
(ChannelLayout::Rgba, ChannelType::F32) => {
let samples = as_f32_slice(data)?;
enc.write_image::<colortype::RGBA32Float>(width, height, &samples)
.map_err(|e| at!(TiffError::from(e)))?;
}
_ => {
return Err(at!(TiffError::Unsupported(alloc::format!(
"cannot encode {layout:?}/{ct:?} to TIFF"
))));
}
}
Ok(())
}
fn expand_graya_to_rgba_u8(data: &[u8]) -> Vec<u8> {
let pixel_count = data.len() / 2;
let mut rgba = Vec::with_capacity(pixel_count * 4);
for i in 0..pixel_count {
let g = data[i * 2];
let a = data[i * 2 + 1];
rgba.push(g);
rgba.push(g);
rgba.push(g);
rgba.push(a);
}
rgba
}
fn expand_graya_to_rgba_u16(data: &[u16]) -> Vec<u16> {
let pixel_count = data.len() / 2;
let mut rgba = Vec::with_capacity(pixel_count * 4);
for i in 0..pixel_count {
let g = data[i * 2];
let a = data[i * 2 + 1];
rgba.push(g);
rgba.push(g);
rgba.push(g);
rgba.push(a);
}
rgba
}
fn expand_graya_to_rgba_f32(data: &[f32]) -> Vec<f32> {
let pixel_count = data.len() / 2;
let mut rgba = Vec::with_capacity(pixel_count * 4);
for i in 0..pixel_count {
let g = data[i * 2];
let a = data[i * 2 + 1];
rgba.push(g);
rgba.push(g);
rgba.push(g);
rgba.push(a);
}
rgba
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config() {
let config = TiffEncodeConfig::default();
assert_eq!(config.compression, Compression::Lzw);
assert_eq!(config.predictor, Predictor::Horizontal);
assert!(!config.big_tiff);
}
#[test]
fn builder_chain() {
let config = TiffEncodeConfig::new()
.with_compression(Compression::Deflate)
.with_predictor(Predictor::None)
.with_big_tiff(true);
assert_eq!(config.compression, Compression::Deflate);
assert_eq!(config.predictor, Predictor::None);
assert!(config.big_tiff);
}
#[test]
fn expand_graya_u8() {
let input = [128u8, 255, 64, 128];
let result = expand_graya_to_rgba_u8(&input);
assert_eq!(result, [128, 128, 128, 255, 64, 64, 64, 128]);
}
}