pub mod attributes;
use crate::io::*;
use ::smallvec::SmallVec;
use self::attributes::*;
use crate::chunks::{TileCoordinates, Block};
use crate::error::*;
use std::fs::File;
use std::io::{BufReader};
use crate::math::*;
use std::collections::{HashSet, HashMap};
#[derive(Debug, Clone, PartialEq)]
pub struct MetaData {
pub requirements: Requirements,
pub headers: Headers,
}
pub type Headers = SmallVec<[Header; 3]>;
pub type OffsetTables = SmallVec<[OffsetTable; 3]>;
pub type OffsetTable = Vec<u64>;
#[derive(Clone, Debug, PartialEq)]
pub struct Header {
pub channels: ChannelList,
pub compression: Compression,
pub blocks: Blocks,
pub line_order: LineOrder,
pub data_size: Vec2<usize>,
pub deep: bool,
pub deep_data_version: Option<i32>,
pub chunk_count: usize,
pub max_samples_per_pixel: Option<usize>,
pub shared_attributes: ImageAttributes,
pub own_attributes: LayerAttributes,
}
#[derive(Clone, PartialEq, Debug)]
pub struct ImageAttributes {
pub display_window: IntRect,
pub pixel_aspect: f32,
pub list: Vec<Attribute>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct LayerAttributes {
pub name: Option<Text>,
pub data_position: Vec2<i32>,
pub screen_window_center: Vec2<f32>,
pub screen_window_width: f32,
pub list: Vec<Attribute>,
}
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct Requirements {
file_format_version: u8,
is_single_layer_and_tiled: bool,
has_long_names: bool,
has_deep_data: bool,
has_multiple_layers: bool,
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct TileIndices {
pub location: TileCoordinates,
pub size: Vec2<usize>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Blocks {
ScanLines,
Tiles(TileDescription)
}
impl Blocks {
pub fn has_tiles(&self) -> bool {
match self {
Blocks::Tiles { .. } => true,
_ => false
}
}
}
pub mod magic_number {
use super::*;
pub const BYTES: [u8; 4] = [0x76, 0x2f, 0x31, 0x01];
pub fn write(write: &mut impl Write) -> Result<()> {
u8::write_slice(write, &self::BYTES)
}
pub fn is_exr(read: &mut impl Read) -> Result<bool> {
let mut magic_num = [0; 4];
u8::read_slice(read, &mut magic_num)?;
Ok(magic_num == self::BYTES)
}
pub fn validate_exr(read: &mut impl Read) -> UnitResult {
if self::is_exr(read)? {
Ok(())
} else {
Err(Error::invalid("file identifier missing"))
}
}
}
pub mod sequence_end {
use super::*;
pub fn byte_size() -> usize {
1
}
pub fn write<W: Write>(write: &mut W) -> UnitResult {
0_u8.write(write)
}
pub fn has_come(read: &mut PeekRead<impl Read>) -> Result<bool> {
Ok(read.skip_if_eq(0)?)
}
}
fn missing_attribute(name: &str) -> Error {
Error::invalid(format!("missing `{}` attribute", name))
}
pub fn compute_block_count(full_res: usize, tile_size: usize) -> usize {
RoundingMode::Up.divide(full_res, tile_size)
}
#[inline]
pub fn calculate_block_position_and_size(total_size: usize, block_size: usize, block_index: usize) -> Result<(usize, usize)> {
let block_position = block_size * block_index;
Ok((
block_position,
calculate_block_size(total_size, block_size, block_position)?
))
}
#[inline]
pub fn calculate_block_size(total_size: usize, block_size: usize, block_position: usize) -> Result<usize> {
if block_position >= total_size {
return Err(Error::invalid("block index"))
}
if block_position + block_size <= total_size {
Ok(block_size)
}
else {
Ok(total_size - block_position)
}
}
pub fn compute_level_count(round: RoundingMode, full_res: usize) -> usize {
round.log2(full_res) + 1
}
pub fn compute_level_size(round: RoundingMode, full_res: usize, level_index: usize) -> usize {
round.divide(full_res, 1 << level_index).max(1)
}
pub fn rip_map_levels(round: RoundingMode, max_resolution: Vec2<usize>) -> impl Iterator<Item=(Vec2<usize>, Vec2<usize>)> {
rip_map_indices(round, max_resolution).map(move |level_indices|{
let width = compute_level_size(round, max_resolution.0, level_indices.0);
let height = compute_level_size(round, max_resolution.1, level_indices.1);
(level_indices, Vec2(width, height))
})
}
pub fn mip_map_levels(round: RoundingMode, max_resolution: Vec2<usize>) -> impl Iterator<Item=(usize, Vec2<usize>)> {
mip_map_indices(round, max_resolution)
.map(move |level_index|{
let width = compute_level_size(round, max_resolution.0, level_index);
let height = compute_level_size(round, max_resolution.1, level_index);
(level_index, Vec2(width, height))
})
}
pub fn rip_map_indices(round: RoundingMode, max_resolution: Vec2<usize>) -> impl Iterator<Item=Vec2<usize>> {
let (width, height) = (
compute_level_count(round, max_resolution.0),
compute_level_count(round, max_resolution.1)
);
(0..height).flat_map(move |y_level|{
(0..width).map(move |x_level|{
Vec2(x_level, y_level)
})
})
}
pub fn mip_map_indices(round: RoundingMode, max_resolution: Vec2<usize>) -> impl Iterator<Item=usize> {
(0..compute_level_count(round, max_resolution.0.max(max_resolution.1)))
}
pub fn compute_chunk_count(compression: Compression, data_size: Vec2<usize>, blocks: Blocks) -> usize {
if let Blocks::Tiles(tiles) = blocks {
let round = tiles.rounding_mode;
let Vec2(tile_width, tile_height) = tiles.tile_size;
use crate::meta::attributes::LevelMode::*;
match tiles.level_mode {
Singular => {
let tiles_x = compute_block_count(data_size.0, tile_width);
let tiles_y = compute_block_count(data_size.1, tile_height);
tiles_x * tiles_y
}
MipMap => {
mip_map_levels(round, data_size).map(|(_, Vec2(level_width, level_height))| {
compute_block_count(level_width, tile_width) * compute_block_count(level_height, tile_height)
}).sum()
},
RipMap => {
rip_map_levels(round, data_size).map(|(_, Vec2(level_width, level_height))| {
compute_block_count(level_width, tile_width) * compute_block_count(level_height, tile_height)
}).sum()
}
}
}
else {
compute_block_count(data_size.1, compression.scan_lines_per_block())
}
}
impl MetaData {
pub fn new(headers: Headers) -> Self {
MetaData {
requirements: Requirements::infer(headers.as_slice()),
headers
}
}
#[must_use]
pub fn read_from_file(path: impl AsRef<::std::path::Path>) -> Result<Self> {
Self::read_from_unbuffered(File::open(path)?)
}
#[must_use]
pub fn read_from_unbuffered(unbuffered: impl Read) -> Result<Self> {
Self::read_from_buffered(BufReader::new(unbuffered))
}
#[must_use]
pub fn read_from_buffered(buffered: impl Read) -> Result<Self> {
let mut read = PeekRead::new(buffered);
MetaData::read_unvalidated_from_buffered_peekable(&mut read)
}
#[must_use]
pub(crate) fn read_unvalidated_from_buffered_peekable(read: &mut PeekRead<impl Read>) -> Result<Self> {
magic_number::validate_exr(read)?;
let requirements = Requirements::read(read)?;
let headers = Header::read_all(read, &requirements)?;
Ok(MetaData { requirements, headers })
}
#[must_use]
pub(crate) fn read_from_buffered_peekable(read: &mut PeekRead<impl Read>) -> Result<Self> {
let meta_data = Self::read_unvalidated_from_buffered_peekable(read)?;
meta_data.validate(false)?;
Ok(meta_data)
}
pub(crate) fn write_validating_to_buffered(&self, write: &mut impl Write, pedantic: bool) -> UnitResult {
self.validate(pedantic)?;
magic_number::write(write)?;
self.requirements.write(write)?;
Header::write_all(self.headers.as_slice(), write, self.requirements.has_multiple_layers)?;
Ok(())
}
pub fn read_offset_tables(read: &mut PeekRead<impl Read>, headers: &Headers) -> Result<OffsetTables> {
headers.iter()
.map(|header| u64::read_vec(read, header.chunk_count, std::u16::MAX as usize, None))
.collect()
}
pub fn skip_offset_tables(read: &mut PeekRead<impl Read>, headers: &Headers) -> Result<usize> {
let chunk_count: usize = headers.iter().map(|header| header.chunk_count).sum();
crate::io::skip_bytes(read, chunk_count * u64::BYTE_SIZE)?;
Ok(chunk_count)
}
pub fn validate(&self, strict: bool) -> UnitResult {
self.requirements.validate()?;
let headers = self.headers.len();
if headers == 0 {
return Err(Error::invalid("at least one layer is required"));
}
for header in &self.headers {
header.validate(&self.requirements, strict)?;
}
if strict {
let mut header_names = HashSet::with_capacity(headers);
for header in &self.headers {
if !header_names.insert(&header.own_attributes.name) {
return Err(Error::invalid(format!(
"duplicate layer name: `{}`",
header.own_attributes.name.as_ref().expect("header validation bug")
)));
}
}
}
if strict {
let must_share = self.headers.iter().flat_map(|header| header.own_attributes.list.iter())
.any(|attribute| attribute.value.to_chromaticities().is_ok() || attribute.value.to_time_code().is_ok());
if must_share {
return Err(Error::invalid("chromaticities and time code attributes must must not exist in own attributes but shared instead"));
}
}
if strict && headers > 1 {
fn get_attributes(header: &'_ Header) -> HashMap<&'_ [u8], &'_ AnyValue> {
header.shared_attributes.list.iter()
.map(|attribute| (attribute.name.bytes(), &attribute.value))
.collect()
};
let first_header = self.headers.first().expect("header count validation bug");
let first_header_attributes = get_attributes(first_header);
for header in &self.headers[1..] {
let attributes = get_attributes(header);
if attributes != first_header_attributes
|| header.shared_attributes.display_window != first_header.shared_attributes.display_window
|| header.shared_attributes.pixel_aspect != first_header.shared_attributes.pixel_aspect
{
return Err(Error::invalid("display window, pixel aspect, chromaticities, and time code attributes must be equal for all headers"))
}
}
}
if self.requirements.file_format_version == 1 || !self.requirements.has_multiple_layers {
if headers != 1 {
return Err(Error::invalid("multipart flag for header count"));
}
}
Ok(())
}
}
impl Header {
pub fn new(name: Text, data_size: Vec2<usize>, channels: SmallVec<[Channel; 5]>) -> Self {
let compression = Compression::Uncompressed;
let blocks = Blocks::ScanLines;
Self {
data_size,
compression,
blocks,
channels: ChannelList::new(channels),
line_order: LineOrder::Unspecified,
shared_attributes: ImageAttributes {
display_window: IntRect::new(Vec2(0, 0), data_size),
pixel_aspect: 1.0,
list: Vec::new(),
},
own_attributes: LayerAttributes {
name: Some(name),
data_position: Vec2(0,0),
screen_window_center: Vec2(0.0, 0.0),
screen_window_width: 1.0,
list: Vec::new()
},
chunk_count: compute_chunk_count(compression, data_size, blocks),
deep: false,
deep_data_version: None,
max_samples_per_pixel: None,
}
}
pub fn with_display_window(self, display_window: IntRect) -> Self {
let mut self1 = self;
self1.shared_attributes.display_window = display_window;
self1
}
pub fn with_position(self, position: Vec2<i32>) -> Self {
let mut self1 = self;
self1.own_attributes.data_position = position;
self1
}
pub fn with_encoding(self, compression: Compression, blocks: Blocks, line_order: LineOrder) -> Self {
Self {
chunk_count: compute_chunk_count(compression, self.data_size, blocks),
compression, blocks, line_order,
.. self
}
}
pub fn with_attributes(self, own_attributes: LayerAttributes) -> Self {
Self { own_attributes, .. self }
}
pub fn with_shared_attributes(self, shared_attributes: ImageAttributes) -> Self {
Self { shared_attributes, .. self }
}
pub fn enumerate_ordered_blocks(&self) -> impl Iterator<Item = (usize, TileIndices)> + Send {
let increasing_y = self.blocks_increasing_y_order().enumerate();
let ordered: Box<dyn Send + Iterator<Item = (usize, TileIndices)>> = {
if self.line_order == LineOrder::Decreasing {
Box::new(increasing_y.rev())
}
else {
Box::new(increasing_y)
}
};
ordered
}
pub fn blocks_increasing_y_order(&self) -> impl Iterator<Item = TileIndices> + ExactSizeIterator + DoubleEndedIterator {
fn tiles_of(image_size: Vec2<usize>, tile_size: Vec2<usize>, level_index: Vec2<usize>) -> impl Iterator<Item=TileIndices> {
fn divide_and_rest(total_size: usize, block_size: usize) -> impl Iterator<Item=(usize, usize)> {
let block_count = compute_block_count(total_size, block_size);
(0..block_count).map(move |block_index| (
block_index, calculate_block_size(total_size, block_size, block_index).expect("block size calculation bug")
))
}
divide_and_rest(image_size.1, tile_size.1).flat_map(move |(y_index, tile_height)|{
divide_and_rest(image_size.0, tile_size.0).map(move |(x_index, tile_width)|{
TileIndices {
size: Vec2(tile_width, tile_height),
location: TileCoordinates { tile_index: Vec2(x_index, y_index), level_index, },
}
})
})
}
let vec: Vec<TileIndices> = {
if let Blocks::Tiles(tiles) = self.blocks {
match tiles.level_mode {
LevelMode::Singular => {
tiles_of(self.data_size, tiles.tile_size, Vec2(0, 0)).collect()
},
LevelMode::MipMap => {
mip_map_levels(tiles.rounding_mode, self.data_size)
.flat_map(move |(level_index, level_size)|{
tiles_of(level_size, tiles.tile_size, Vec2(level_index, level_index))
})
.collect()
},
LevelMode::RipMap => {
rip_map_levels(tiles.rounding_mode, self.data_size)
.flat_map(move |(level_index, level_size)| {
tiles_of(level_size, tiles.tile_size, level_index)
})
.collect()
}
}
}
else {
let tiles = Vec2(self.data_size.0, self.compression.scan_lines_per_block());
tiles_of(self.data_size, tiles, Vec2(0,0)).collect()
}
};
vec.into_iter()
}
pub fn get_block_data_window_coordinates(&self, tile: TileCoordinates) -> Result<IntRect> {
let data = self.get_absolute_block_indices(tile)?;
Ok(data.with_origin(self.own_attributes.data_position))
}
pub fn get_absolute_block_indices(&self, tile: TileCoordinates) -> Result<IntRect> {
Ok(if let Blocks::Tiles(tiles) = self.blocks {
let Vec2(data_width, data_height) = self.data_size;
let data_width = compute_level_size(tiles.rounding_mode, data_width, tile.level_index.0);
let data_height = compute_level_size(tiles.rounding_mode, data_height, tile.level_index.1);
let absolute_tile_coordinates = tile.to_data_indices(tiles.tile_size, Vec2(data_width, data_height))?;
if absolute_tile_coordinates.position.0 as i64 >= data_width as i64 || absolute_tile_coordinates.position.1 as i64 >= data_height as i64 {
return Err(Error::invalid("data block tile index"))
}
absolute_tile_coordinates
}
else {
debug_assert_eq!(tile.tile_index.0, 0, "block index calculation bug");
let (y, height) = calculate_block_position_and_size(
self.data_size.1,
self.compression.scan_lines_per_block(),
tile.tile_index.1
)?;
IntRect {
position: Vec2(0, usize_to_i32(y)),
size: Vec2(self.data_size.0, height)
}
})
}
pub fn get_block_data_indices(&self, block: &Block) -> Result<TileCoordinates> {
Ok(match block {
Block::Tile(ref tile) => {
tile.coordinates
},
Block::ScanLine(ref block) => {
let size = self.compression.scan_lines_per_block() as i32;
let y = (block.y_coordinate - self.own_attributes.data_position.1) / size;
if y < 0 {
panic!("y index calculation bug");
}
TileCoordinates {
tile_index: Vec2(0, y as usize),
level_index: Vec2(0, 0)
}
},
_ => return Err(Error::unsupported("deep data not supported yet"))
})
}
pub fn max_block_byte_size(&self) -> usize {
self.channels.bytes_per_pixel * match self.blocks {
Blocks::Tiles(tiles) => tiles.tile_size.0 * tiles.tile_size.1,
Blocks::ScanLines => self.compression.scan_lines_per_block() * self.data_size.0
}
}
pub fn validate(&self, requirements: &Requirements, strict: bool) -> UnitResult {
debug_assert_eq!(
self.chunk_count, compute_chunk_count(self.compression, self.data_size, self.blocks),
"chunk count attribute not correctly set"
);
if strict && requirements.is_multilayer() {
if self.own_attributes.name.is_none() {
return Err(missing_attribute("layer name for multi layer file"));
}
}
if strict && self.blocks == Blocks::ScanLines && self.line_order == LineOrder::Unspecified {
return Err(Error::invalid("scan line images cannot have an unspecified line order"));
}
let allow_subsampling = !self.deep && self.blocks == Blocks::ScanLines;
self.channels.validate(allow_subsampling, strict)?;
for attribute in &self.shared_attributes.list {
attribute.validate(requirements.has_long_names, allow_subsampling, strict)?;
}
for attribute in &self.own_attributes.list {
attribute.validate(requirements.has_long_names, allow_subsampling, strict)?;
}
if strict {
let mut custom_names = HashSet::with_capacity(
self.own_attributes.list.len() + self.shared_attributes.list.len()
);
for attribute in &self.own_attributes.list {
if !custom_names.insert(attribute.name.bytes()) {
return Err(Error::invalid(format!("duplicate attribute name: `{}`", attribute.name)));
}
}
for attribute in &self.shared_attributes.list {
if !custom_names.insert(attribute.name.bytes()) {
return Err(Error::invalid(format!("duplicate attribute name: `{}`", attribute.name)));
}
}
use attributes::required_attribute_names::*;
let reserved_names = [
TILES, NAME, BLOCK_TYPE, DEEP_DATA_VERSION, CHUNKS, MAX_SAMPLES, CHANNELS, COMPRESSION,
DATA_WINDOW, DISPLAY_WINDOW, LINE_ORDER, PIXEL_ASPECT, WINDOW_CENTER, WINDOW_WIDTH
];
for &reserved in &reserved_names {
if custom_names.contains(reserved) {
return Err(Error::invalid(format!(
"attribute name `{}` is already a required attribute",
Text::from_bytes_unchecked(reserved.into())
)));
}
}
}
if self.deep {
if strict && self.own_attributes.name.is_none() {
return Err(missing_attribute("layer name for deep file"));
}
match self.deep_data_version {
Some(1) => {},
Some(_) => return Err(Error::unsupported("deep data version")),
None => return Err(missing_attribute("deep data version")),
}
if strict && self.max_samples_per_pixel.is_none() {
return Err(Error::invalid("missing max samples per pixel attribute for deepdata"));
}
if !self.compression.supports_deep_data() {
return Err(Error::invalid("compression method does not support deep data"));
}
}
Ok(())
}
pub fn read_all(read: &mut PeekRead<impl Read>, version: &Requirements) -> Result<Headers> {
if !version.is_multilayer() {
Ok(smallvec![ Header::read(read, version)? ])
}
else {
let mut headers = SmallVec::new();
while !sequence_end::has_come(read)? {
headers.push(Header::read(read, version)?);
}
Ok(headers)
}
}
pub fn write_all(headers: &[Header], write: &mut impl Write, is_multilayer: bool) -> UnitResult {
for header in headers {
header.write(write)?;
}
if is_multilayer {
sequence_end::write(write)?;
}
Ok(())
}
pub fn read(read: &mut PeekRead<impl Read>, requirements: &Requirements) -> Result<Self> {
let max_string_len = if requirements.has_long_names { 256 } else { 32 };
let mut shared_custom = Vec::new();
let mut own_custom = Vec::new();
let mut tiles = None;
let mut name = None;
let mut block_type = None;
let mut version = None;
let mut chunk_count = None;
let mut max_samples_per_pixel = None;
let mut channels = None;
let mut compression = None;
let mut data_window = None;
let mut display_window = None;
let mut line_order = None;
let mut pixel_aspect = None;
let mut screen_window_center = None;
let mut screen_window_width = None;
while !sequence_end::has_come(read)? {
let Attribute { name: attribute_name, value } = Attribute::read(read, max_string_len)?;
use crate::meta::attributes::required_attribute_names::*;
match attribute_name.bytes() {
TILES => tiles = Some(value.to_tile_description()?),
NAME => name = Some(value.into_text()?),
BLOCK_TYPE => block_type = Some(BlockType::parse(value.into_text()?)?),
CHANNELS => channels = Some(value.into_channel_list()?),
COMPRESSION => compression = Some(value.to_compression()?),
DATA_WINDOW => data_window = Some(value.to_i32_box_2()?),
DISPLAY_WINDOW => display_window = Some(value.to_i32_box_2()?),
LINE_ORDER => line_order = Some(value.to_line_order()?),
PIXEL_ASPECT => pixel_aspect = Some(value.to_f32()?),
WINDOW_CENTER => screen_window_center = Some(value.to_f32_vec_2()?),
WINDOW_WIDTH => screen_window_width = Some(value.to_f32()?),
DEEP_DATA_VERSION => version = Some(value.to_i32()?),
MAX_SAMPLES => max_samples_per_pixel = Some(
i32_to_usize(value.to_i32()?, "max sample count")?
),
CHUNKS => chunk_count = Some(
i32_to_usize(value.to_i32()?, "chunk count")?
),
_ => {
if value.to_chromaticities().is_ok() || value.to_time_code().is_ok() {
shared_custom.push(Attribute { name: attribute_name, value })
}
else {
own_custom.push(Attribute { name: attribute_name, value })
}
},
}
}
let compression = compression.ok_or(missing_attribute("compression"))?;
let data_window = data_window.ok_or(missing_attribute("data window"))?;
let data_size = data_window.size;
let blocks = match block_type {
None if requirements.is_single_layer_and_tiled => {
Blocks::Tiles(tiles.ok_or(missing_attribute("tiles"))?)
},
Some(BlockType::Tile) | Some(BlockType::DeepTile) => {
Blocks::Tiles(tiles.ok_or(missing_attribute("tiles"))?)
},
_ => Blocks::ScanLines,
};
let chunk_count = match chunk_count {
None => compute_chunk_count(compression, data_size, blocks),
Some(count) => count,
};
let header = Header {
compression,
chunk_count,
data_size,
shared_attributes: ImageAttributes {
display_window: display_window.ok_or(missing_attribute("display window"))?,
pixel_aspect: pixel_aspect.unwrap_or(1.0),
list: shared_custom
},
own_attributes: LayerAttributes {
name,
data_position: data_window.position,
screen_window_center: screen_window_center.unwrap_or(Vec2(0.0, 0.0)),
screen_window_width: screen_window_width.unwrap_or(1.0),
list: own_custom,
},
channels: channels.ok_or(missing_attribute("channels"))?,
line_order: line_order.unwrap_or(LineOrder::Unspecified),
blocks,
max_samples_per_pixel,
deep_data_version: version,
deep: block_type == Some(BlockType::DeepScanLine) || block_type == Some(BlockType::DeepTile),
};
Ok(header)
}
pub fn write(&self, write: &mut impl Write) -> UnitResult {
fn write_attr<T>(write: &mut impl Write, name: &'static [u8], value: T, variant: impl Fn(T) -> AnyValue) -> UnitResult {
Attribute::predefined(name, variant(value)).write(write)
};
fn write_opt_attr<T>(write: &mut impl Write, name: &'static [u8], attribute: Option<T>, variant: impl Fn(T) -> AnyValue) -> UnitResult {
if let Some(value) = attribute { write_attr(write, name, value, variant) }
else { Ok(()) }
};
{
use crate::meta::attributes::required_attribute_names::*;
use AnyValue::*;
let (block_type, tiles) = match self.blocks {
Blocks::ScanLines => (attributes::BlockType::ScanLine, None),
Blocks::Tiles(tiles) => (attributes::BlockType::Tile, Some(tiles))
};
write_opt_attr(write, TILES, tiles, TileDescription)?;
write_opt_attr(write, NAME, self.own_attributes.name.clone(), Text)?;
write_opt_attr(write, DEEP_DATA_VERSION, self.deep_data_version, I32)?;
write_opt_attr(write, MAX_SAMPLES, self.max_samples_per_pixel, |u| I32(u as i32))?;
write_attr(write, CHUNKS, self.chunk_count, |u| I32(u as i32))?;
write_attr(write, BLOCK_TYPE, block_type, BlockType)?;
write_attr(write, CHANNELS, self.channels.clone(), ChannelList)?;
write_attr(write, COMPRESSION, self.compression, Compression)?;
write_attr(write, LINE_ORDER, self.line_order, LineOrder)?;
write_attr(write, DATA_WINDOW, self.data_window(), IntRect)?;
write_attr(write, DISPLAY_WINDOW, self.shared_attributes.display_window, IntRect)?;
write_attr(write, PIXEL_ASPECT, self.shared_attributes.pixel_aspect, F32)?;
write_attr(write, WINDOW_WIDTH, self.own_attributes.screen_window_width, F32)?;
write_attr(write, WINDOW_CENTER, self.own_attributes.screen_window_center, FloatVec2)?;
}
for attrib in &self.shared_attributes.list {
attrib.write(write)?;
}
for attrib in &self.own_attributes.list {
attrib.write(write)?;
}
sequence_end::write(write)?;
Ok(())
}
pub fn data_window(&self) -> IntRect {
IntRect::new(self.own_attributes.data_position, self.data_size)
}
}
impl Requirements {
pub fn infer(headers: &[Header]) -> Self {
let first_header_has_tiles = headers.iter().next()
.map_or(false, |header| header.blocks.has_tiles());
let is_multilayer = headers.len() > 1;
let deep = false;
Requirements {
file_format_version: 2,
is_single_layer_and_tiled: !is_multilayer && first_header_has_tiles,
has_long_names: true,
has_multiple_layers: is_multilayer,
has_deep_data: deep,
}
}
pub fn is_multilayer(&self) -> bool {
self.has_multiple_layers
}
pub fn read<R: Read>(read: &mut R) -> Result<Self> {
use ::bit_field::BitField;
let version_and_flags = u32::read(read)?;
let version = (version_and_flags & 0x000F) as u8;
let is_single_tile = version_and_flags.get_bit(9);
let has_long_names = version_and_flags.get_bit(10);
let has_deep_data = version_and_flags.get_bit(11);
let has_multiple_layers = version_and_flags.get_bit(12);
let unknown_flags = version_and_flags >> 13;
if unknown_flags != 0 {
return Err(Error::unsupported("too new file feature flags"));
}
let version = Requirements {
file_format_version: version,
is_single_layer_and_tiled: is_single_tile, has_long_names,
has_deep_data, has_multiple_layers,
};
Ok(version)
}
pub fn write<W: Write>(self, write: &mut W) -> UnitResult {
use ::bit_field::BitField;
let mut version_and_flags = self.file_format_version as u32;
version_and_flags.set_bit(9, self.is_single_layer_and_tiled);
version_and_flags.set_bit(10, self.has_long_names);
version_and_flags.set_bit(11, self.has_deep_data);
version_and_flags.set_bit(12, self.has_multiple_layers);
version_and_flags.write(write)?;
Ok(())
}
pub fn validate(&self) -> UnitResult {
if self.has_deep_data {
return Err(Error::unsupported("deep data not supported yet"));
}
if let 1..=2 = self.file_format_version {
match (
self.is_single_layer_and_tiled, self.has_deep_data, self.has_multiple_layers,
self.file_format_version
) {
(false, false, false, 1..=2) => Ok(()),
(true, false, false, 1..=2) => Ok(()),
(false, false, true, 2) => Ok(()),
(false, true, false, 2) => Ok(()),
(false, true, true, 2) => Ok(()),
_ => Err(Error::invalid("file feature flags"))
}
}
else {
Err(Error::unsupported("file version newer than `2.0`"))
}
}
}
#[cfg(test)]
mod test {
use crate::meta::{MetaData, Requirements, Header, ImageAttributes, LayerAttributes, compute_chunk_count};
use crate::meta::attributes::{Text, ChannelList, IntRect, LineOrder, Channel, PixelType};
use crate::compression::Compression;
use crate::meta::Blocks;
use crate::math::*;
#[test]
fn round_trip_requirements() {
let requirements = Requirements {
file_format_version: 2,
is_single_layer_and_tiled: true,
has_long_names: false,
has_deep_data: true,
has_multiple_layers: false
};
let mut data: Vec<u8> = Vec::new();
requirements.write(&mut data).unwrap();
let read = Requirements::read(&mut data.as_slice()).unwrap();
assert_eq!(requirements, read);
}
#[test]
fn round_trip(){
let header = Header {
channels: ChannelList {
list: smallvec![
Channel {
name: Text::from("main").unwrap(),
pixel_type: PixelType::U32,
is_linear: false,
sampling: Vec2(1, 1)
}
],
bytes_per_pixel: 4
},
compression: Compression::Uncompressed,
line_order: LineOrder::Increasing,
deep_data_version: Some(1),
chunk_count: compute_chunk_count(Compression::Uncompressed, Vec2(2000, 333), Blocks::ScanLines),
max_samples_per_pixel: Some(4),
shared_attributes: ImageAttributes {
display_window: IntRect {
position: Vec2(2,1),
size: Vec2(11, 9)
},
pixel_aspect: -3.0,
list: vec![ ]
},
blocks: Blocks::ScanLines,
deep: false,
data_size: Vec2(2000, 333),
own_attributes: LayerAttributes {
name: Some(Text::from("test name lol").unwrap()),
data_position: Vec2(3, -5),
screen_window_center: Vec2(0.3, 99.0),
screen_window_width: -0.19,
list: vec![ ]
}
};
let meta = MetaData {
requirements: Requirements {
file_format_version: 2,
is_single_layer_and_tiled: false,
has_long_names: false,
has_deep_data: false,
has_multiple_layers: false
},
headers: smallvec![ header ],
};
let mut data: Vec<u8> = Vec::new();
meta.write_validating_to_buffered(&mut data, true).unwrap();
let meta2 = MetaData::read_from_buffered(data.as_slice()).unwrap();
meta2.validate(true).unwrap();
assert_eq!(meta, meta2);
}
}