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};
use std::convert::TryFrom;
#[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 chromaticities: Option<Chromaticities>,
pub time_code: Option<TimeCode>,
pub custom: HashMap<Text, AttributeValue>,
}
#[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 white_luminance: Option<f32>,
pub adopted_neutral: Option<Vec2<f32>>,
pub rendering_transform: Option<Text>,
pub look_modification_transform: Option<Text>,
pub x_density: Option<f32>,
pub owner: Option<Text>,
pub comments: Option<Text>,
pub capture_date: Option<Text>,
pub utc_offset: Option<f32>,
pub longitude: Option<f32>,
pub latitude: Option<f32>,
pub altitude: Option<f32>,
pub focus: Option<f32>,
pub exposure: Option<f32>,
pub aperture: Option<f32>,
pub iso_speed: Option<f32>,
pub environment_map: Option<EnvironmentMap>,
pub key_code: Option<KeyCode>,
pub wrap_modes: Option<Text>,
pub frames_per_second: Option<Rational>,
pub multi_view: Option<Vec<Text>>,
pub world_to_camera: Option<Matrix4x4>,
pub world_to_normalized_device: Option<Matrix4x4>,
pub deep_image_state: Option<Rational>,
pub original_data_window: Option<IntRect>,
pub dwa_compression_level: Option<f32>,
pub preview: Option<Preview>,
pub view: Option<Text>,
pub custom: HashMap<Text, AttributeValue>,
}
#[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
}
}
}
impl LayerAttributes {
pub fn new(layer_name: Text) -> Self {
Self {
name: Some(layer_name),
.. Self::default()
}
}
pub fn with_position(self, data_position: Vec2<i32>) -> Self {
Self { data_position, ..self }
}
}
impl ImageAttributes {
pub fn new(display_size: Vec2<usize>) -> Self {
Self {
display_window: IntRect::from_dimensions(display_size),
.. Self::default()
}
}
pub fn with_display_window(self, display_window: IntRect) -> Self {
Self { display_window, ..self }
}
}
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 {
assert!(level_index < std::mem::size_of::<usize>() * 8, "largest level size exceeds maximum integer value");
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>, max_pixel_bytes: Option<usize>) -> Result<Self> {
let meta_data = Self::read_unvalidated_from_buffered_peekable(read)?;
meta_data.validate(max_pixel_bytes, false)?;
Ok(meta_data)
}
pub(crate) fn write_validating_to_buffered(&self, write: &mut impl Write, pedantic: bool) -> UnitResult {
self.validate(None, 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, max_pixel_bytes: Option<usize>, 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 let Some(max) = max_pixel_bytes {
let byte_size: usize = self.headers.iter()
.map(|header| header.data_size.area() * header.channels.bytes_per_pixel)
.sum();
if byte_size > max {
return Err(Error::invalid("image larger than specified maximum"));
}
}
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.custom.iter())
.any(|(_, value)| value.to_chromaticities().is_ok() || 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 { let first_header = self.headers.first().expect("header count validation bug");
let first_header_attributes = &first_header.shared_attributes.custom;
for header in &self.headers[1..] {
let attributes = &header.shared_attributes.custom;
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::new(data_size),
own_attributes: LayerAttributes::new(name),
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(mut self, display_window: IntRect) -> Self {
self.shared_attributes.display_window = display_window;
self
}
pub fn with_position(mut self, position: Vec2<i32>) -> Self {
self.own_attributes.data_position = position;
self
}
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 {
return Err(Error::invalid("scan block y coordinate"));
}
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),
"incorrect chunk count value"
);
self.data_window().validate(None)?;
self.shared_attributes.display_window.validate(None)?;
if strict {
if requirements.is_multilayer() {
if self.own_attributes.name.is_none() {
return Err(missing_attribute("layer name for multi layer file"));
}
}
if self.blocks == Blocks::ScanLines && self.line_order == LineOrder::Unspecified {
return Err(Error::invalid("unspecified line order in scan line images"));
}
if self.data_size == Vec2(0,0) {
return Err(Error::invalid("empty data window"));
}
if self.shared_attributes.display_window.size == Vec2(0,0) {
return Err(Error::invalid("empty display window"));
}
if !self.shared_attributes.pixel_aspect.is_normal() || self.shared_attributes.pixel_aspect < 1.0e-6 || self.shared_attributes.pixel_aspect > 1.0e6 {
return Err(Error::invalid("pixel aspect ratio"));
}
if self.own_attributes.screen_window_width < 0.0 {
return Err(Error::invalid("screen window width"));
}
}
let allow_subsampling = !self.deep && self.blocks == Blocks::ScanLines;
self.channels.validate(allow_subsampling, self.data_window(), strict)?;
for (name, value) in &self.shared_attributes.custom {
attributes::validate(name, value, requirements.has_long_names, allow_subsampling, self.data_window(), strict)?;
}
for (name, value) in &self.own_attributes.custom {
attributes::validate(name, value, requirements.has_long_names, allow_subsampling, self.data_window(), strict)?;
}
if strict {
for (name, _) in &self.shared_attributes.custom {
if !self.own_attributes.custom.contains_key(&name) {
return Err(Error::invalid(format!("duplicate attribute name: `{}`", 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,
WHITE_LUMINANCE, ADOPTED_NEUTRAL, RENDERING_TRANSFORM, LOOK_MOD_TRANSFORM, X_DENSITY,
OWNER, COMMENTS, CAPTURE_DATE, UTC_OFFSET, LONGITUDE, LATITUDE, ALTITUDE, FOCUS,
EXPOSURE_TIME, APERTURE, ISO_SPEED, ENVIRONMENT_MAP, KEY_CODE, TIME_CODE, WRAP_MODES,
FRAMES_PER_SECOND, MULTI_VIEW, WORLD_TO_CAMERA, WORLD_TO_NDC, DEEP_IMAGE_STATE,
ORIGINAL_DATA_WINDOW, DWA_COMPRESSION_LEVEL, PREVIEW, VIEW, CHROMATICITIES
];
for &reserved in reserved_names.iter() {
let name = Text::from_bytes_unchecked(SmallVec::from_slice(reserved));
if self.own_attributes.custom.contains_key(&name) || self.shared_attributes.custom.contains_key(&name) {
return Err(Error::invalid(format!(
"attribute name `{}` is reserved and cannot be custom",
Text::from_bytes_unchecked(reserved.into())
)));
}
}
}
if self.deep {
if strict {
if self.own_attributes.name.is_none() {
return Err(missing_attribute("layer name for deep file"));
}
if self.max_samples_per_pixel.is_none() {
return Err(Error::invalid("missing max samples per pixel attribute for deepdata"));
}
}
match self.deep_data_version {
Some(1) => {},
Some(_) => return Err(Error::unsupported("deep data version")),
None => return Err(missing_attribute("deep data version")),
}
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 tiles = 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 layer_attributes = LayerAttributes::default();
let mut image_attributes = ImageAttributes::default();
while !sequence_end::has_come(read)? {
let (attribute_name, value) = attributes::read(read, max_string_len)?;
use crate::meta::attributes::required_attribute_names::*;
match attribute_name.bytes() {
TILES => tiles = Some(value.to_tile_description()?),
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()?),
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")?
),
NAME => layer_attributes.name = Some(value.into_text()?),
PIXEL_ASPECT => image_attributes.pixel_aspect = value.to_f32()?,
WINDOW_CENTER => layer_attributes.screen_window_center = value.to_f32_vec_2()?,
WINDOW_WIDTH => layer_attributes.screen_window_width = value.to_f32()?,
WHITE_LUMINANCE if value.to_f32().is_ok() => layer_attributes.white_luminance = Some(value.to_f32().unwrap()),
ADOPTED_NEUTRAL if value.to_f32_vec_2().is_ok() => layer_attributes.adopted_neutral = Some(value.to_f32_vec_2()?),
RENDERING_TRANSFORM if value.to_text().is_ok() => layer_attributes.rendering_transform = Some(value.into_text()?),
LOOK_MOD_TRANSFORM if value.to_text().is_ok() => layer_attributes.look_modification_transform = Some(value.into_text()?),
X_DENSITY if value.to_f32().is_ok() => layer_attributes.x_density = Some(value.to_f32()?),
OWNER if value.to_text().is_ok() => layer_attributes.owner = Some(value.into_text()?),
COMMENTS if value.to_text().is_ok() => layer_attributes.comments = Some(value.into_text()?),
CAPTURE_DATE if value.to_text().is_ok() => layer_attributes.capture_date = Some(value.into_text()?),
UTC_OFFSET if value.to_f32().is_ok() => layer_attributes.utc_offset = Some(value.to_f32()?),
LONGITUDE if value.to_f32().is_ok() => layer_attributes.longitude = Some(value.to_f32()?),
LATITUDE if value.to_f32().is_ok() => layer_attributes.latitude = Some(value.to_f32()?),
ALTITUDE if value.to_f32().is_ok() => layer_attributes.altitude = Some(value.to_f32()?),
FOCUS if value.to_f32().is_ok() => layer_attributes.focus = Some(value.to_f32()?),
EXPOSURE_TIME if value.to_f32().is_ok() => layer_attributes.exposure = Some(value.to_f32()?),
APERTURE if value.to_f32().is_ok() => layer_attributes.aperture = Some(value.to_f32()?),
ISO_SPEED if value.to_f32().is_ok() => layer_attributes.iso_speed = Some(value.to_f32()?),
ENVIRONMENT_MAP if value.to_environment_map().is_ok() => layer_attributes.environment_map = Some(value.to_environment_map()?),
KEY_CODE if value.to_key_code().is_ok() => layer_attributes.key_code = Some(value.to_key_code()?),
TIME_CODE if value.to_time_code().is_ok() => image_attributes.time_code = Some(value.to_time_code()?),
WRAP_MODES if value.to_text().is_ok() => layer_attributes.wrap_modes = Some(value.into_text()?),
FRAMES_PER_SECOND if value.to_rational().is_ok() => layer_attributes.frames_per_second = Some(value.to_rational()?),
MULTI_VIEW if value.to_text_vector().is_ok() => layer_attributes.multi_view = Some(value.into_text_vector()?),
WORLD_TO_CAMERA if value.to_matrix4x4().is_ok() => layer_attributes.world_to_camera = Some(value.to_matrix4x4()?),
WORLD_TO_NDC if value.to_matrix4x4().is_ok() => layer_attributes.world_to_normalized_device = Some(value.to_matrix4x4()?),
DEEP_IMAGE_STATE if value.to_rational().is_ok() => layer_attributes.deep_image_state = Some(value.to_rational()?),
ORIGINAL_DATA_WINDOW if value.to_i32_box_2().is_ok() => layer_attributes.original_data_window = Some(value.to_i32_box_2()?),
DWA_COMPRESSION_LEVEL if value.to_f32().is_ok() => layer_attributes.dwa_compression_level = Some(value.to_f32()?),
CHROMATICITIES if value.to_chromaticities().is_ok() => image_attributes.chromaticities = Some(value.to_chromaticities()?),
PREVIEW if value.to_preview().is_ok() => layer_attributes.preview = Some(value.into_preview()?),
VIEW if value.to_text().is_ok() => layer_attributes.view = Some(value.into_text()?),
_ => {
if value.to_chromaticities().is_ok() || value.to_time_code().is_ok() {
image_attributes.custom.insert(attribute_name, value);
}
else {
layer_attributes.custom.insert(attribute_name, value);
}
},
}
}
let compression = compression.ok_or(missing_attribute("compression"))?;
let data_window = data_window.ok_or(missing_attribute("data window"))?;
image_attributes.display_window = display_window.ok_or(missing_attribute("display window"))?;
layer_attributes.data_position = data_window.position;
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,
};
data_window.validate(None)?;
let computed_chunk_count = compute_chunk_count(compression, data_size, blocks);
if chunk_count.is_some() && chunk_count != Some(computed_chunk_count) {
return Err(Error::invalid("chunk count not matching data size"));
}
let header = Header {
compression,
chunk_count: computed_chunk_count,
data_size,
shared_attributes: image_attributes,
own_attributes: layer_attributes,
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 {
macro_rules! write_attributes {
( $($name: ident : $variant: ident = $value: expr),* ) => { $(
attributes::write($name, & $variant ($value .clone()), write)?; )* };
}
macro_rules! write_optional_attributes {
( $($name: ident : $variant: ident = $value: expr),* ) => { $(
if let Some(value) = $value {
attributes::write($name, & $variant (value.clone()), write)?; };
)* };
}
{
use crate::meta::attributes::required_attribute_names::*;
use AttributeValue::*;
let (block_type, tiles) = match self.blocks {
Blocks::ScanLines => (attributes::BlockType::ScanLine, None),
Blocks::Tiles(tiles) => (attributes::BlockType::Tile, Some(tiles))
};
fn usize_as_i32(value: usize) -> AttributeValue {
I32(i32::try_from(value).expect("u32 exceeds i32 range"))
}
write_optional_attributes!(
TILES: TileDescription = &tiles,
DEEP_DATA_VERSION: I32 = &self.deep_data_version,
MAX_SAMPLES: usize_as_i32 = &self.max_samples_per_pixel
);
write_attributes!(
CHUNKS: usize_as_i32 = &self.chunk_count,
BLOCK_TYPE: BlockType = &block_type,
CHANNELS: ChannelList = &self.channels,
COMPRESSION: Compression = &self.compression,
LINE_ORDER: LineOrder = &self.line_order,
DATA_WINDOW: IntRect = &self.data_window(),
DISPLAY_WINDOW: IntRect = &self.shared_attributes.display_window,
PIXEL_ASPECT: F32 = &self.shared_attributes.pixel_aspect,
WINDOW_CENTER: FloatVec2 = &self.own_attributes.screen_window_center,
WINDOW_WIDTH: F32 = &self.own_attributes.screen_window_width
);
write_optional_attributes!(
NAME: Text = &self.own_attributes.name,
WHITE_LUMINANCE: F32 = &self.own_attributes.white_luminance,
ADOPTED_NEUTRAL: FloatVec2 = &self.own_attributes.adopted_neutral,
RENDERING_TRANSFORM: Text = &self.own_attributes.rendering_transform,
LOOK_MOD_TRANSFORM: Text = &self.own_attributes.look_modification_transform,
X_DENSITY: F32 = &self.own_attributes.x_density,
OWNER: Text = &self.own_attributes.owner,
COMMENTS: Text = &self.own_attributes.comments,
CAPTURE_DATE: Text = &self.own_attributes.capture_date,
UTC_OFFSET: F32 = &self.own_attributes.utc_offset,
LONGITUDE: F32 = &self.own_attributes.longitude,
LATITUDE: F32 = &self.own_attributes.latitude,
ALTITUDE: F32 = &self.own_attributes.altitude,
FOCUS: F32 = &self.own_attributes.focus,
EXPOSURE_TIME: F32 = &self.own_attributes.exposure,
APERTURE: F32 = &self.own_attributes.aperture,
ISO_SPEED: F32 = &self.own_attributes.iso_speed,
ENVIRONMENT_MAP: EnvironmentMap = &self.own_attributes.environment_map,
KEY_CODE: KeyCode = &self.own_attributes.key_code,
TIME_CODE: TimeCode = &self.shared_attributes.time_code,
WRAP_MODES: Text = &self.own_attributes.wrap_modes,
FRAMES_PER_SECOND: Rational = &self.own_attributes.frames_per_second,
MULTI_VIEW: TextVector = &self.own_attributes.multi_view,
WORLD_TO_CAMERA: Matrix4x4 = &self.own_attributes.world_to_camera,
WORLD_TO_NDC: Matrix4x4 = &self.own_attributes.world_to_normalized_device,
DEEP_IMAGE_STATE: Rational = &self.own_attributes.deep_image_state,
ORIGINAL_DATA_WINDOW: IntRect = &self.own_attributes.original_data_window,
DWA_COMPRESSION_LEVEL: F32 = &self.own_attributes.dwa_compression_level,
CHROMATICITIES: Chromaticities = &self.shared_attributes.chromaticities,
PREVIEW: Preview = &self.own_attributes.preview,
VIEW: Text = &self.own_attributes.view
);
}
for (name, value) in &self.shared_attributes.custom {
attributes::write(name.bytes(), value, write)?;
}
for (name, value) in &self.own_attributes.custom {
attributes::write(name.bytes(), value, 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`"))
}
}
}
impl Default for LayerAttributes {
fn default() -> Self {
Self {
data_position: Vec2(0, 0),
screen_window_center: Vec2(0.0, 0.0),
screen_window_width: 1.0,
name: None,
white_luminance: None,
adopted_neutral: None,
rendering_transform: None,
look_modification_transform: None,
x_density: None,
owner: None,
comments: None,
capture_date: None,
utc_offset: None,
longitude: None,
latitude: None,
altitude: None,
focus: None,
exposure: None,
aperture: None,
iso_speed: None,
environment_map: None,
key_code: None,
wrap_modes: None,
frames_per_second: None,
multi_view: None,
world_to_camera: None,
world_to_normalized_device: None,
deep_image_state: None,
original_data_window: None,
dwa_compression_level: None,
preview: None,
view: None,
custom: Default::default()
}
}
}
impl Default for ImageAttributes {
fn default() -> Self {
Self {
pixel_aspect: 1.0,
chromaticities: None,
time_code: None,
custom: Default::default(),
display_window: Default::default(),
}
}
}
#[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,
.. Default::default()
},
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,
.. Default::default()
}
};
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(None, true).unwrap();
assert_eq!(meta, meta2);
}
}