use smallvec::SmallVec;
use half::f16;
use crate::io::*;
use crate::meta::*;
use crate::meta::attributes::*;
use crate::error::{Result, PassiveResult, Error};
use crate::math::*;
use std::io::{Seek, BufReader, BufWriter};
use crate::image::{Line, LineIndex};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct WriteOptions {
pub parallel_compression: bool,
pub pedantic: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ReadOptions {
pub parallel_decompression: bool,
}
#[derive(Clone, PartialEq, Debug)]
pub struct Image {
pub layers: Layers,
pub display_window: IntRect,
pub pixel_aspect: f32,
}
pub type Layers = SmallVec<[Layer; 3]>;
#[derive(Clone, PartialEq, Debug)]
pub struct Layer {
pub name: Option<Text>,
pub attributes: Attributes,
pub data_window: IntRect,
pub screen_window_center: Vec2<f32>,
pub screen_window_width: f32,
pub line_order: LineOrder,
pub compression: Compression,
pub tiles: Option<Vec2<usize>>,
pub channels: Channels,
}
pub type Channels = SmallVec<[Channel; 5]>;
#[derive(Clone, Debug, PartialEq)]
pub struct Channel {
pub name: Text,
pub samples: Samples,
pub is_linear: bool,
pub sampling: Vec2<usize>,
}
#[derive(Clone, PartialEq)]
pub enum Samples {
F16(Vec<f16>),
F32(Vec<f32>),
U32(Vec<u32>),
}
impl Default for WriteOptions {
fn default() -> Self { Self::high() }
}
impl Default for ReadOptions {
fn default() -> Self { Self::high() }
}
impl WriteOptions {
pub fn higher() -> Self { WriteOptions { parallel_compression: true, pedantic: false } }
pub fn high() -> Self { WriteOptions { parallel_compression: true, pedantic: true } }
pub fn low() -> Self { WriteOptions { parallel_compression: false, pedantic: true } }
}
impl ReadOptions {
pub fn high() -> Self { ReadOptions { parallel_decompression: true } }
pub fn low() -> Self { ReadOptions { parallel_decompression: false } }
}
impl Image {
pub fn new_from_single_layer(layer: Layer) -> Self {
Self {
pixel_aspect: 1.0,
display_window: layer.data_window,
layers: smallvec![ layer ],
}
}
pub fn new_from_layers(layers: Layers, display_window: IntRect) -> Self {
Self { layers, display_window, pixel_aspect: 1.0, }
}
#[must_use]
pub fn read_from_file(path: impl AsRef<std::path::Path>, options: ReadOptions) -> Result<Self> {
Self::read_from_unbuffered(std::fs::File::open(path)?, options)
}
#[must_use]
pub fn read_from_unbuffered(unbuffered: impl Read + Send + Seek, options: ReadOptions) -> Result<Self> { Self::read_from_buffered(BufReader::new(unbuffered), options)
}
#[must_use]
pub fn read_from_buffered(read: impl Read + Send + Seek, options: ReadOptions) -> Result<Self> { let mut image: Image = crate::image::read_filtered_lines_from_buffered(
read, options.parallel_decompression,
|header, tile_index| {
!header.deep && tile_index.location.level_index == Vec2(0,0)
},
Image::allocate, Image::insert_line
)?;
{ for layer in &mut image.layers {
layer.channels.retain(|channel| channel.samples.len() > 0);
}
image.layers.retain(|layer| layer.channels.len() > 0);
}
Ok(image)
}
#[must_use]
pub fn write_to_file(&self, path: impl AsRef<std::path::Path>, options: WriteOptions) -> PassiveResult {
self.write_to_unbuffered(std::fs::File::create(path)?, options)
}
#[must_use]
pub fn write_to_unbuffered(&self, unbuffered: impl Write + Seek, options: WriteOptions) -> PassiveResult {
self.write_to_buffered(BufWriter::new(unbuffered), options)
}
#[must_use]
pub fn write_to_buffered(&self, write: impl Write + Seek, options: WriteOptions) -> PassiveResult {
crate::image::write_all_lines_to_buffered(
write, options.parallel_compression, options.pedantic, self.infer_meta_data(),
|location, write| {
self.extract_line(location, write);
}
)
}
}
impl Layer {
pub fn new(name: Text, data_window: IntRect, mut channels: Channels) -> Self {
assert!(!channels.is_empty(), "at least one channel is required");
assert!(
channels.iter().all(|chan|
chan.samples.len() / (chan.sampling.0 * chan.sampling.1) == data_window.size.area()
),
"channel data size must conform to data window size (scaled by channel sampling)"
);
channels.sort_by_key(|chan| chan.name.clone());
Layer {
channels,
data_window,
name: Some(name),
attributes: Vec::new(),
compression: Compression::Uncompressed,
tiles: None,
line_order: LineOrder::Unspecified, screen_window_center: Vec2(0.0, 0.0),
screen_window_width: 1.0,
}
}
pub fn with_block_format(self, tiles: Option<Vec2<usize>>, line_order: LineOrder) -> Self {
Self { tiles, line_order, .. self }
}
pub fn with_compression(self, compression: Compression) -> Self {
Self { compression, .. self }
}
}
impl Channel {
pub fn new(name: Text, is_linear: bool, samples: Samples) -> Self {
Self { name, samples, is_linear, sampling: Vec2(1, 1) }
}
pub fn new_linear(name: Text, samples: Samples) -> Self {
Self::new(name, true, samples)
}
}
impl Samples {
pub fn len(&self) -> usize {
match self {
Samples::F16(vec) => vec.len(),
Samples::F32(vec) => vec.len(),
Samples::U32(vec) => vec.len(),
}
}
}
impl Image {
pub fn allocate(headers: &[Header]) -> Result<Self> {
let display_window = headers.iter()
.map(|header| header.display_window)
.next().unwrap_or(IntRect::zero());
let pixel_aspect = headers.iter()
.map(|header| header.pixel_aspect)
.next().unwrap_or(1.0);
let headers : Result<_> = headers.iter().map(Layer::allocate).collect();
Ok(Image {
layers: headers?,
display_window,
pixel_aspect
})
}
pub fn insert_line(&mut self, line: Line<'_>) -> PassiveResult {
debug_assert_ne!(line.location.width, 0, "line width calculation bug");
let layer = self.layers.get_mut(line.location.layer)
.ok_or(Error::invalid("chunk part index"))?;
layer.insert_line(line)
}
pub fn extract_line(&self, index: LineIndex, write: &mut impl Write) {
debug_assert_ne!(index.width, 0, "line width calculation bug");
let layer = self.layers.get(index.layer)
.expect("invalid part index");
layer.extract_line(index, write)
}
pub fn infer_meta_data(&self) -> MetaData {
let headers: Headers = self.layers.iter()
.map(|layer| layer.infer_header(self.display_window, self.pixel_aspect))
.collect();
MetaData::new(headers)
}
}
impl Layer {
pub fn allocate(header: &Header) -> Result<Self> {
Ok(Layer {
data_window: header.data_window,
screen_window_center: header.screen_window_center,
screen_window_width: header.screen_window_width,
name: header.name.clone(),
attributes: header.custom_attributes.clone(),
channels: header.channels.list.iter().map(|channel| Channel::allocate(header, channel)).collect(),
compression: header.compression,
line_order: header.line_order,
tiles: match header.blocks {
Blocks::ScanLines => None,
Blocks::Tiles(tiles) => Some(tiles.tile_size),
}
})
}
pub fn insert_line(&mut self, line: Line<'_>) -> PassiveResult {
debug_assert!(line.location.position.0 + line.location.width <= self.data_window.size.0, "line index calculation bug");
debug_assert!(line.location.position.1 < self.data_window.size.1, "line index calculation bug");
self.channels.get_mut(line.location.channel)
.expect("invalid channel index")
.insert_line(line, self.data_window.size)
}
pub fn extract_line(&self, index: LineIndex, write: &mut impl Write) {
debug_assert!(index.position.0 + index.width <= self.data_window.size.0, "line index calculation bug");
debug_assert!(index.position.1 < self.data_window.size.1, "line index calculation bug");
self.channels.get(index.channel)
.expect("invalid channel index")
.extract_line(index, self.data_window.size, write)
}
pub fn infer_header(&self, display_window: IntRect, pixel_aspect: f32) -> Header {
let blocks = match self.tiles {
Some(tiles) => Blocks::Tiles(TileDescription {
tile_size: tiles,
level_mode: LevelMode::Singular,
rounding_mode: RoundingMode::Down
}),
None => Blocks::ScanLines,
};
let channels = self.channels.iter()
.map(Channel::infer_channel_attribute).collect();
let chunk_count = compute_chunk_count(
self.compression, self.data_window, blocks
);
Header {
chunk_count,
name: self.name.clone(),
data_window: self.data_window,
screen_window_center: self.screen_window_center,
screen_window_width: self.screen_window_width,
compression: self.compression,
channels: ChannelList::new(channels),
line_order: self.line_order,
custom_attributes: self.attributes.clone(),
display_window, pixel_aspect,
blocks,
deep_data_version: None,
max_samples_per_pixel: None,
deep: false
}
}
}
impl Channel {
pub fn allocate(header: &Header, channel: &crate::meta::attributes::Channel) -> Self {
let size = if header.deep { Vec2(0, 0) } else {
header.data_window.size / channel.sampling
};
Channel {
name: channel.name.clone(), is_linear: channel.is_linear, sampling: channel.sampling,
samples: Samples::allocate(size, channel.pixel_type)
}
}
pub fn insert_line(&mut self, line: Line<'_>, resolution: Vec2<usize>) -> PassiveResult {
assert_eq!(line.location.level, Vec2(0,0), "line index calculation bug");
self.samples.insert_line(resolution / self.sampling, line)
}
pub fn extract_line(&self, index: LineIndex, resolution: Vec2<usize>, write: &mut impl Write) {
debug_assert_eq!(index.level, Vec2(0,0), "line index calculation bug");
self.samples.extract_line(index, resolution / self.sampling, write)
}
pub fn infer_channel_attribute(&self) -> attributes::Channel {
attributes::Channel {
pixel_type: match self.samples {
Samples::F16(_) => PixelType::F16,
Samples::F32(_) => PixelType::F32,
Samples::U32(_) => PixelType::U32,
},
name: self.name.clone(),
is_linear: self.is_linear,
sampling: self.sampling,
}
}
}
impl Samples {
pub fn allocate(resolution: Vec2<usize>, pixel_type: PixelType) -> Self {
let count = resolution.area();
match pixel_type {
PixelType::F16 => Samples::F16(vec![ f16::ZERO; count ] ),
PixelType::F32 => Samples::F32(vec![ 0.0; count ] ),
PixelType::U32 => Samples::U32(vec![ 0; count ] ),
}
}
pub fn insert_line(&mut self, resolution: Vec2<usize>, line: Line<'_>) -> PassiveResult {
debug_assert_ne!(line.location.width, 0, "line index calculation bug");
if line.location.position.0 + line.location.width > resolution.0 {
return Err(Error::invalid("data block x coordinate"))
}
if line.location.position.1 > resolution.1 {
return Err(Error::invalid("data block y coordinate"))
}
debug_assert_ne!(resolution.0, 0, "sample size bug");
debug_assert_ne!(line.location.width, 0, "line index calculation bug");
let start_index = line.location.position.1 * resolution.0 + line.location.position.0;
let end_index = start_index + line.location.width;
match self {
Samples::F16(samples) => line.read_samples(&mut samples[start_index .. end_index]),
Samples::F32(samples) => line.read_samples(&mut samples[start_index .. end_index]),
Samples::U32(samples) => line.read_samples(&mut samples[start_index .. end_index]),
}
}
pub fn extract_line(&self, index: LineIndex, resolution: Vec2<usize>, write: &mut impl Write) {
debug_assert!(index.position.0 + index.width <= resolution.0, "line index calculation bug");
debug_assert!(index.position.1 < resolution.1, "line index calculation bug");
debug_assert_ne!(index.width, 0, "line index bug");
debug_assert_ne!(resolution.0, 0, "sample size but");
debug_assert_ne!(index.width, 0, "line index bug");
let start_index = index.position.1 * resolution.0 + index.position.0;
let end_index = start_index + index.width;
match &self {
Samples::F16(samples) =>
LineIndex::write_samples(&samples[start_index .. end_index], write)
.expect("writing line bytes failed"),
Samples::F32(samples) =>
LineIndex::write_samples(&samples[start_index .. end_index], write)
.expect("writing line bytes failed"),
Samples::U32(samples) =>
LineIndex::write_samples(&samples[start_index .. end_index], write)
.expect("writing line bytes failed"),
}
}
}
impl std::fmt::Debug for Samples {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Samples::F16(vec) => write!(formatter, "[f16; {}]", vec.len()),
Samples::F32(vec) => write!(formatter, "[f32; {}]", vec.len()),
Samples::U32(vec) => write!(formatter, "[u32; {}]", vec.len()),
}
}
}