use crate::prelude::common::*;
use crate::io::*;
use crate::meta::*;
use crate::meta::attribute::*;
use crate::error::{Result, UnitResult, Error};
use crate::math::*;
use std::io::{Seek, BufReader, BufWriter};
use crate::image::{OnWriteProgress, OnReadProgress, WriteOptions, ReadOptions};
use crate::block::lines::{LineRef, LineRefMut};
use crate::meta::header::Header;
use std::convert::TryFrom;
#[derive(Clone, PartialEq, Debug)]
pub struct Image {
pub layers: Layers,
pub attributes: ImageAttributes,
}
pub type Layers = SmallVec<[Layer; 3]>;
#[derive(Clone, PartialEq, Debug)]
pub struct Layer {
pub channels: Channels,
pub attributes: LayerAttributes,
pub size: Vec2<usize>,
pub line_order: LineOrder,
pub compression: Compression,
pub tile_size: Option<Vec2<usize>>,
}
pub type Channels = SmallVec<[Channel; 5]>;
#[derive(Clone, Debug, PartialEq)]
pub struct Channel {
pub name: Text,
pub samples: Samples,
pub quantize_linearly: bool,
pub sampling: Vec2<usize>,
}
#[derive(Clone, PartialEq)]
pub enum Samples {
F16(Vec<f16>),
F32(Vec<f32>),
U32(Vec<u32>),
}
impl Image {
pub fn new_from_single_layer(layer: Layer) -> Self {
Self {
attributes: ImageAttributes::new(layer.size),
layers: smallvec![ layer ],
}
}
pub fn new_from_layers(layers: Layers, display_window: IntegerBounds) -> Self {
Self { layers, attributes: ImageAttributes::default().with_display_window(display_window) }
}
#[must_use]
pub fn read_from_file(path: impl AsRef<std::path::Path>, options: ReadOptions<impl OnReadProgress>) -> 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<impl OnReadProgress>) -> Result<Self> { Self::read_from_buffered(BufReader::new(unbuffered), options)
}
#[must_use]
pub fn read_from_buffered(read: impl Read + Send + Seek, options: ReadOptions<impl OnReadProgress>) -> Result<Self> { let mut image: Image = crate::block::lines::read_filtered_lines_from_buffered(
read,
Image::allocate,
|_image, (_, header), (_, tile_index)| {
!header.deep && tile_index.location.is_largest_resolution_level()
},
|image, _meta, line| Image::insert_line(image, line),
options
)?;
{ 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<impl OnWriteProgress>) -> UnitResult {
crate::io::attempt_delete_file_on_write_error(path.as_ref(), |write|
self.write_to_unbuffered(write, options)
)
}
#[must_use]
pub fn write_to_unbuffered(&self, unbuffered: impl Write + Seek, options: WriteOptions<impl OnWriteProgress>) -> UnitResult {
self.write_to_buffered(BufWriter::new(unbuffered), options)
}
#[must_use]
pub fn write_to_buffered(&self, write: impl Write + Seek, options: WriteOptions<impl OnWriteProgress>) -> UnitResult {
crate::block::lines::write_all_lines_to_buffered(
write, self.infer_meta_data(),
|_meta, line_mut| self.extract_line(line_mut),
options
)
}
pub fn contains_nan_pixels(&self) -> bool {
self.layers.iter().flat_map(|layer: &Layer| &layer.channels)
.any(|channel: &Channel| channel.samples.contains_nan())
}
pub fn remove_excess(&mut self) {
let layers = std::mem::take(&mut self.layers);
let layers = layers.into_iter().flat_map(|layer| layer.without_excess()).collect();
self.layers = layers;
}
}
impl Layer {
pub fn new(name: Text, data_size: impl Into<Vec2<usize>>, mut channels: Channels) -> Self {
let data_size: Vec2<usize> = data_size.into();
assert!(!channels.is_empty(), "at least one channel is required");
assert!(
channels.iter().all(|chan|
chan.samples.len() / chan.sampling.area() == data_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,
size: data_size,
compression: Compression::Uncompressed,
tile_size: None,
line_order: LineOrder::Unspecified,
attributes: LayerAttributes::new(name),
}
}
pub fn with_block_format(self, tiles: Option<Vec2<usize>>, line_order: LineOrder) -> Self {
Self { tile_size: tiles, line_order, .. self }
}
pub fn with_compression(self, compression: Compression) -> Self {
Self { compression, .. self }
}
pub fn data_window(&self) -> IntegerBounds {
IntegerBounds::new(self.attributes.layer_position, self.size)
}
pub fn without_excess(mut self) -> Option<Self> {
let content = self.find_content_bounds()?;
self.crop(content);
Some(self)
}
pub fn crop(&mut self, absolute_bounds: IntegerBounds) {
let bounds = absolute_bounds.with_origin(-self.attributes.layer_position);
assert!(self.data_window().contains(absolute_bounds), "bounds not valid for layer dimensions");
assert!(bounds.size.area() > 0, "the cropped image would be empty");
let start_x = usize::try_from(bounds.position.x()).unwrap();
let start_y = usize::try_from(bounds.position.y()).unwrap();
if bounds.size != self.size {
fn crop_samples<T: Copy>(samples: &[T], old_width: usize, new_height: usize, x_range: std::ops::Range<usize>, y_start: usize) -> Vec<T> {
let kept_old_lines = samples.chunks_exact(old_width).skip(y_start).take(new_height);
let trimmed_lines = kept_old_lines.map(|line| &line[x_range.clone()]);
trimmed_lines.flatten().map(|x| *x).collect() }
for channel in &mut self.channels {
let samples: &mut Samples = &mut channel.samples;
let x_range = start_x .. start_x + bounds.size.width();
match samples {
Samples::F16(samples) => *samples = crop_samples(samples, self.size.width(), bounds.size.height(), x_range.clone(), start_y),
Samples::F32(samples) => *samples = crop_samples(samples, self.size.width(), bounds.size.height(), x_range.clone(), start_y),
Samples::U32(samples) => *samples = crop_samples(samples, self.size.width(), bounds.size.height(), x_range.clone(), start_y),
}
}
self.size = bounds.size;
self.attributes.layer_position = absolute_bounds.position;
}
}
pub fn find_content_bounds(&mut self) -> Option<IntegerBounds> {
type Bounds = (Vec2<usize>, Vec2<usize>);
fn extend_bounds(bounds: Option<Bounds>, element: Option<Bounds>) -> Option<Bounds> {
if let Some((min, max)) = element {
if let Some((min0, max0)) = bounds { Some((min0.min(min), max0.max(max))) }
else { Some((min, max)) }
}
else { bounds }
}
fn crop_line<T>(samples: &[T]) -> Option<(usize, usize)> where T: PartialEq + Default {
let discard = |value: &T| *value != T::default();
let end = samples.iter().rposition(discard)? + 1; let start = samples[..end].iter().position(discard);
Some((start.unwrap_or(end), end))
}
fn crop_lines<T: Sync + PartialEq + Default>(samples: &[T], resolution: Vec2<usize>) -> Option<Bounds> {
use rayon::prelude::*;
samples
.par_chunks(resolution.width())
.enumerate()
.map(|(y, line)|
crop_line(line).map(|(start_x, end_x)| (Vec2(start_x, y), Vec2(end_x, y + 1)))
)
.reduce(|| None, extend_bounds)
}
let original_bounds = (Vec2(0,0), self.size);
let new_layer_bounds = {
if self.channels.iter().any(|channel| matches!(channel.samples, Samples::U32(_))) {
Some(original_bounds)
}
else {
self.channels.iter()
.map(|channel| {
match &channel.samples { Samples::F16(samples) => crop_lines(samples, self.size),
Samples::F32(samples) => crop_lines(samples, self.size),
Samples::U32(_) => unreachable!("do not crop id pixels"),
}
})
.fold(None, extend_bounds)
}
};
new_layer_bounds.map(|(min, max)| IntegerBounds::new(
self.attributes.layer_position + min.to_i32(),
max - min
))
}
}
impl Channel {
pub fn color_data(name: Text, samples: Samples) -> Self {
Self { name, samples, quantize_linearly: false, sampling: Vec2(1, 1) }
}
pub fn non_color_data(name: Text, samples: Samples) -> Self {
Self { name, samples, quantize_linearly: true, sampling: Vec2(1, 1) }
}
}
impl Samples {
pub fn len(&self) -> usize {
match self {
Samples::F16(vec) => vec.len(),
Samples::F32(vec) => vec.len(),
Samples::U32(vec) => vec.len(),
}
}
pub fn contains_nan(&self) -> bool {
match self {
Samples::F16(ref values) => values.iter().any(|sample| sample.is_nan()),
Samples::F32(ref values) => values.iter().any(|sample| sample.is_nan()),
Samples::U32(_) => false,
}
}
}
impl Image {
pub fn allocate(headers: &[Header]) -> Result<Self> {
let shared_attributes = &headers.iter()
.max_by_key(|header| header.shared_attributes.other.len())
.expect("no headers found").shared_attributes;
let headers : Result<_> = headers.iter()
.map(Layer::allocate).collect();
Ok(Image {
layers: headers?,
attributes: shared_attributes.clone(),
})
}
pub fn insert_line(&mut self, line: LineRef<'_>) -> UnitResult {
debug_assert_ne!(line.location.sample_count, 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, line: LineRefMut<'_>) {
debug_assert_ne!(line.location.sample_count, 0, "line width calculation bug");
let layer = self.layers.get(line.location.layer)
.expect("invalid part index");
layer.extract_line(line)
}
pub fn infer_meta_data(&self) -> Headers {
self.layers.iter()
.map(|layer| layer.infer_header(&self.attributes))
.collect()
}
}
impl Layer {
pub fn allocate(header: &Header) -> Result<Self> {
Ok(Layer {
size: header.layer_size,
attributes: header.own_attributes.clone(),
channels: header.channels.list.iter().map(|channel| Channel::allocate(header, channel)).collect(),
compression: header.compression,
line_order: header.line_order,
tile_size: match header.blocks {
Blocks::ScanLines => None,
Blocks::Tiles(tiles) => Some(tiles.tile_size),
}
})
}
pub fn insert_line(&mut self, line: LineRef<'_>) -> UnitResult {
debug_assert!(line.location.position.x() + line.location.sample_count <= self.size.width(), "line index calculation bug");
debug_assert!(line.location.position.y() < self.size.height(), "line index calculation bug");
self.channels.get_mut(line.location.channel)
.expect("invalid channel index")
.insert_line(line, self.size)
}
pub fn extract_line(&self, line: LineRefMut<'_>) {
debug_assert!(line.location.position.x() + line.location.sample_count <= self.size.width(), "line index calculation bug");
debug_assert!(line.location.position.y() < self.size.height(), "line index calculation bug");
self.channels.get(line.location.channel)
.expect("invalid channel index")
.extract_line(line, self.size)
}
pub fn infer_header(&self, shared_attributes: &ImageAttributes) -> Header {
let blocks = match self.tile_size {
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.size, blocks
);
Header {
chunk_count,
layer_size: self.size,
compression: self.compression,
channels: ChannelList::new(channels),
line_order: self.line_order,
own_attributes: self.attributes.clone(), shared_attributes: shared_attributes.clone(),
blocks,
deep_data_version: None,
max_samples_per_pixel: None,
deep: false,
}
}
}
impl Channel {
pub fn allocate(header: &Header, channel: &crate::meta::attribute::ChannelInfo) -> Self {
let size = if header.deep { Vec2(0, 0) } else {
header.layer_size / channel.sampling
};
Channel {
name: channel.name.clone(), quantize_linearly: channel.quantize_linearly, sampling: channel.sampling,
samples: Samples::allocate(size, channel.sample_type)
}
}
pub fn insert_line(&mut self, line: LineRef<'_>, resolution: Vec2<usize>) -> UnitResult {
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, line: LineRefMut<'_>, resolution: Vec2<usize>) {
debug_assert_eq!(line.location.level, Vec2(0,0), "line index calculation bug");
self.samples.extract_line(line, resolution / self.sampling)
}
pub fn infer_channel_attribute(&self) -> attribute::ChannelInfo {
attribute::ChannelInfo {
sample_type: match self.samples {
Samples::F16(_) => SampleType::F16,
Samples::F32(_) => SampleType::F32,
Samples::U32(_) => SampleType::U32,
},
name: self.name.clone(),
quantize_linearly: self.quantize_linearly,
sampling: self.sampling,
}
}
}
impl Samples {
pub fn allocate(resolution: Vec2<usize>, sample_type: SampleType) -> Self {
let count = resolution.area();
debug_assert!(count < 1920*20 * 1920*20, "suspiciously large image: {} mega pixels", count / 1_000_000);
match sample_type {
SampleType::F16 => Samples::F16(vec![f16::ZERO; count ] ),
SampleType::F32 => Samples::F32(vec![0.0; count ] ),
SampleType::U32 => Samples::U32(vec![0; count ] ),
}
}
pub fn insert_line(&mut self, resolution: Vec2<usize>, line: LineRef<'_>) -> UnitResult {
if line.location.position.x() + line.location.sample_count > resolution.width() {
return Err(Error::invalid("data block x coordinate"))
}
if line.location.position.y() > resolution.height() {
return Err(Error::invalid("data block y coordinate"))
}
let start_index = line.location.position.y() * resolution.width() + line.location.position.x();
let end_index = start_index + line.location.sample_count;
match self {
Samples::F16(samples) => line.read_samples_into_slice(&mut samples[start_index .. end_index]),
Samples::F32(samples) => line.read_samples_into_slice(&mut samples[start_index .. end_index]),
Samples::U32(samples) => line.read_samples_into_slice(&mut samples[start_index .. end_index]),
}
}
pub fn extract_line(&self, line: LineRefMut<'_>, resolution: Vec2<usize>) {
let index = line.location;
debug_assert!(index.position.x() + index.sample_count <= resolution.width(), "line index calculation bug");
debug_assert!(index.position.y() < resolution.height(), "line index calculation bug");
debug_assert_ne!(resolution.0, 0, "sample size but");
let start_index = index.position.y() * resolution.width() + index.position.x();
let end_index = start_index + index.sample_count;
match &self {
Samples::F16(samples) =>
line.write_samples_from_slice(&samples[start_index .. end_index])
.expect("writing line bytes failed"),
Samples::F32(samples) =>
line.write_samples_from_slice(&samples[start_index .. end_index])
.expect("writing line bytes failed"),
Samples::U32(samples) =>
line.write_samples_from_slice(&samples[start_index .. end_index])
.expect("writing line bytes failed"),
}
}
}
impl std::fmt::Debug for Samples {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.len() < 32 {
match self {
Samples::F16(vec) => vec.fmt(formatter),
Samples::F32(vec) => vec.fmt(formatter),
Samples::U32(vec) => vec.fmt(formatter),
}
}
else {
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()),
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_crop(){
let channel = Channel::color_data("".try_into().unwrap(), Samples::F32(vec![
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 3.0, 0.0,
0.0, 0.4, 0.3, 0.0,
0.0, 0.0, 0.0, 0.0,
]));
let expected_channel = Channel::color_data("".try_into().unwrap(), Samples::F32(vec![
0.0, 3.0,
0.4, 0.3,
]));
let image = Image::new_from_single_layer(Layer::new(
"".try_into().unwrap(), Vec2(4, 4), smallvec![ channel ]
));
let mut cropped = image.clone();
cropped.remove_excess();
assert_ne!(image, cropped);
assert_eq!(cropped.layers[0].channels[0], expected_channel);
assert_eq!(cropped.layers[0].attributes.layer_position, Vec2(1,1));
}
#[test]
pub fn test_crop_size(){
let channel_a = Channel::color_data("".try_into().unwrap(), Samples::F32(vec![
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 3.3, 0.0,
0.0, 0.0, 0.0, 0.0,
]));
let channel_b = Channel::color_data("".try_into().unwrap(), Samples::F32(vec![
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
3.3, 0.0, 0.0, 0.0,
]));
let image = Image::new_from_single_layer(Layer::new(
"".try_into().unwrap(), Vec2(4, 4), smallvec![ channel_a, channel_b ]
));
let mut cropped = image.clone();
cropped.remove_excess();
assert_ne!(image, cropped);
assert_eq!(cropped.layers[0].attributes.layer_position, Vec2(0,2));
assert_eq!(cropped.layers[0].size, Vec2(3,2));
}
}