| 1 |
|
| 2 | //! Contains collections of common attributes.
|
| 3 | //! Defines some data types that list all standard attributes.
|
| 4 |
|
| 5 | use std::collections::HashMap;
|
| 6 | use crate::meta::attribute::*; // FIXME shouldn't this need some more imports????
|
| 7 | use crate::meta::*;
|
| 8 | use crate::math::Vec2;
|
| 9 |
|
| 10 | // TODO rename header to LayerDescription!
|
| 11 |
|
| 12 | /// Describes a single layer in a file.
|
| 13 | /// A file can have any number of layers.
|
| 14 | /// The meta data contains one header per layer.
|
| 15 | #[derive (Clone, Debug, PartialEq)]
|
| 16 | pub struct Header {
|
| 17 |
|
| 18 | /// List of channels in this layer.
|
| 19 | pub channels: ChannelList,
|
| 20 |
|
| 21 | /// How the pixel data of all channels in this layer is compressed. May be `Compression::Uncompressed`.
|
| 22 | pub compression: Compression,
|
| 23 |
|
| 24 | /// Describes how the pixels of this layer are divided into smaller blocks.
|
| 25 | /// A single block can be loaded without processing all bytes of a file.
|
| 26 | ///
|
| 27 | /// Also describes whether a file contains multiple resolution levels: mip maps or rip maps.
|
| 28 | /// This allows loading not the full resolution, but the smallest sensible resolution.
|
| 29 | //
|
| 30 | // Required if file contains deep data or multiple layers.
|
| 31 | // Note: This value must agree with the version field's tile bit and deep data bit.
|
| 32 | // In this crate, this attribute will always have a value, for simplicity.
|
| 33 | pub blocks: BlockDescription,
|
| 34 |
|
| 35 | /// In what order the tiles of this header occur in the file.
|
| 36 | pub line_order: LineOrder,
|
| 37 |
|
| 38 | /// The resolution of this layer. Equivalent to the size of the `DataWindow`.
|
| 39 | pub layer_size: Vec2<usize>,
|
| 40 |
|
| 41 | /// Whether this layer contains deep data.
|
| 42 | pub deep: bool,
|
| 43 |
|
| 44 | /// This library supports only deep data version 1.
|
| 45 | pub deep_data_version: Option<i32>,
|
| 46 |
|
| 47 | /// Number of chunks, that is, scan line blocks or tiles, that this image has been divided into.
|
| 48 | /// This number is calculated once at the beginning
|
| 49 | /// of the read process or when creating a header object.
|
| 50 | ///
|
| 51 | /// This value includes all chunks of all resolution levels.
|
| 52 | ///
|
| 53 | ///
|
| 54 | /// __Warning__
|
| 55 | /// _This value is relied upon. You should probably use `Header::with_encoding`,
|
| 56 | /// which automatically updates the chunk count._
|
| 57 | pub chunk_count: usize,
|
| 58 |
|
| 59 | // Required for deep data (deepscanline and deeptile) layers.
|
| 60 | // Note: Since the value of "maxSamplesPerPixel"
|
| 61 | // maybe be unknown at the time of opening the
|
| 62 | // file, the value “ -1 ” is written to the file to
|
| 63 | // indicate an unknown value. When the file is
|
| 64 | // closed, this will be overwritten with the correct value.
|
| 65 | // If file writing does not complete
|
| 66 | // correctly due to an error, the value -1 will
|
| 67 | // remain. In this case, the value must be derived
|
| 68 | // by decoding each chunk in the layer
|
| 69 | /// Maximum number of samples in a single pixel in a deep image.
|
| 70 | pub max_samples_per_pixel: Option<usize>,
|
| 71 |
|
| 72 | /// Includes mandatory fields like pixel aspect or display window
|
| 73 | /// which must be the same for all layers.
|
| 74 | pub shared_attributes: ImageAttributes,
|
| 75 |
|
| 76 | /// Does not include the attributes required for reading the file contents.
|
| 77 | /// Excludes standard fields that must be the same for all headers.
|
| 78 | pub own_attributes: LayerAttributes,
|
| 79 | }
|
| 80 |
|
| 81 | /// Includes mandatory fields like pixel aspect or display window
|
| 82 | /// which must be the same for all layers.
|
| 83 | /// For more attributes, see struct `LayerAttributes`.
|
| 84 | #[derive (Clone, PartialEq, Debug)]
|
| 85 | pub struct ImageAttributes {
|
| 86 |
|
| 87 | /// The rectangle anywhere in the global infinite 2D space
|
| 88 | /// that clips all contents of the file.
|
| 89 | pub display_window: IntegerBounds,
|
| 90 |
|
| 91 | /// Aspect ratio of each pixel in this header.
|
| 92 | pub pixel_aspect: f32,
|
| 93 |
|
| 94 | /// The chromaticities attribute of the image. See the `Chromaticities` type.
|
| 95 | pub chromaticities: Option<Chromaticities>,
|
| 96 |
|
| 97 | /// The time code of the image.
|
| 98 | pub time_code: Option<TimeCode>,
|
| 99 |
|
| 100 | /// Contains custom attributes.
|
| 101 | /// Does not contain the attributes already present in the `ImageAttributes`.
|
| 102 | /// Contains only attributes that are standardized to be the same for all headers: chromaticities and time codes.
|
| 103 | pub other: HashMap<Text, AttributeValue>,
|
| 104 | }
|
| 105 |
|
| 106 | /// Does not include the attributes required for reading the file contents.
|
| 107 | /// Excludes standard fields that must be the same for all headers.
|
| 108 | /// For more attributes, see struct `ImageAttributes`.
|
| 109 | #[derive (Clone, PartialEq)]
|
| 110 | pub struct LayerAttributes {
|
| 111 |
|
| 112 | /// The name of this layer.
|
| 113 | /// Required if this file contains deep data or multiple layers.
|
| 114 | // As this is an attribute value, it is not restricted in length, may even be empty
|
| 115 | pub layer_name: Option<Text>,
|
| 116 |
|
| 117 | /// The top left corner of the rectangle that positions this layer
|
| 118 | /// within the global infinite 2D space of the whole file.
|
| 119 | /// This represents the position of the `DataWindow`.
|
| 120 | pub layer_position: Vec2<i32>,
|
| 121 |
|
| 122 | /// Part of the perspective projection. Default should be `(0, 0)`.
|
| 123 | // TODO same for all layers?
|
| 124 | pub screen_window_center: Vec2<f32>,
|
| 125 |
|
| 126 | // TODO same for all layers?
|
| 127 | /// Part of the perspective projection. Default should be `1`.
|
| 128 | pub screen_window_width: f32,
|
| 129 |
|
| 130 | /// The white luminance of the colors.
|
| 131 | /// Defines the luminance in candelas per square meter, Nits, of the rgb value `(1, 1, 1)`.
|
| 132 | // If the chromaticities and the whiteLuminance of an RGB image are
|
| 133 | // known, then it is possible to convert the image's pixels from RGB
|
| 134 | // to CIE XYZ tristimulus values (see function RGBtoXYZ() in header
|
| 135 | // file ImfChromaticities.h).
|
| 136 | pub white_luminance: Option<f32>,
|
| 137 |
|
| 138 | /// The adopted neutral of the colors. Specifies the CIE (x,y) frequency coordinates that should
|
| 139 | /// be considered neutral during color rendering. Pixels in the image
|
| 140 | /// whose CIE (x,y) frequency coordinates match the adopted neutral value should
|
| 141 | /// be mapped to neutral values on the given display.
|
| 142 | pub adopted_neutral: Option<Vec2<f32>>,
|
| 143 |
|
| 144 | /// Name of the color transform function that is applied for rendering the image.
|
| 145 | pub rendering_transform_name: Option<Text>,
|
| 146 |
|
| 147 | /// Name of the color transform function that computes the look modification of the image.
|
| 148 | pub look_modification_transform_name: Option<Text>,
|
| 149 |
|
| 150 | /// The horizontal density, in pixels per inch.
|
| 151 | /// The image's vertical output density can be computed using `horizontal_density * pixel_aspect_ratio`.
|
| 152 | pub horizontal_density: Option<f32>,
|
| 153 |
|
| 154 | /// Name of the owner.
|
| 155 | pub owner: Option<Text>,
|
| 156 |
|
| 157 | /// Additional textual information.
|
| 158 | pub comments: Option<Text>,
|
| 159 |
|
| 160 | /// The date of image creation, in `YYYY:MM:DD hh:mm:ss` format.
|
| 161 | // TODO parse!
|
| 162 | pub capture_date: Option<Text>,
|
| 163 |
|
| 164 | /// Time offset from UTC.
|
| 165 | pub utc_offset: Option<f32>,
|
| 166 |
|
| 167 | /// Geographical image location.
|
| 168 | pub longitude: Option<f32>,
|
| 169 |
|
| 170 | /// Geographical image location.
|
| 171 | pub latitude: Option<f32>,
|
| 172 |
|
| 173 | /// Geographical image location.
|
| 174 | pub altitude: Option<f32>,
|
| 175 |
|
| 176 | /// Camera focus in meters.
|
| 177 | pub focus: Option<f32>,
|
| 178 |
|
| 179 | /// Exposure time in seconds.
|
| 180 | pub exposure: Option<f32>,
|
| 181 |
|
| 182 | /// Camera aperture measured in f-stops. Equals the focal length
|
| 183 | /// of the lens divided by the diameter of the iris opening.
|
| 184 | pub aperture: Option<f32>,
|
| 185 |
|
| 186 | /// Iso-speed of the camera sensor.
|
| 187 | pub iso_speed: Option<f32>,
|
| 188 |
|
| 189 | /// If this is an environment map, specifies how to interpret it.
|
| 190 | pub environment_map: Option<EnvironmentMap>,
|
| 191 |
|
| 192 | /// Identifies film manufacturer, film type, film roll and frame position within the roll.
|
| 193 | pub film_key_code: Option<KeyCode>,
|
| 194 |
|
| 195 | /// Specifies how texture map images are extrapolated.
|
| 196 | /// Values can be `black`, `clamp`, `periodic`, or `mirror`.
|
| 197 | pub wrap_mode_name: Option<Text>,
|
| 198 |
|
| 199 | /// Frames per second if this is a frame in a sequence.
|
| 200 | pub frames_per_second: Option<Rational>,
|
| 201 |
|
| 202 | /// Specifies the view names for multi-view, for example stereo, image files.
|
| 203 | pub multi_view_names: Option<Vec<Text>>,
|
| 204 |
|
| 205 | /// The matrix that transforms 3D points from the world to the camera coordinate space.
|
| 206 | /// Left-handed coordinate system, y up, z forward.
|
| 207 | pub world_to_camera: Option<Matrix4x4>,
|
| 208 |
|
| 209 | /// The matrix that transforms 3D points from the world to the "Normalized Device Coordinate" space.
|
| 210 | /// Left-handed coordinate system, y up, z forward.
|
| 211 | pub world_to_normalized_device: Option<Matrix4x4>,
|
| 212 |
|
| 213 | /// Specifies whether the pixels in a deep image are sorted and non-overlapping.
|
| 214 | pub deep_image_state: Option<Rational>,
|
| 215 |
|
| 216 | /// If the image was cropped, contains the original data window.
|
| 217 | pub original_data_window: Option<IntegerBounds>,
|
| 218 |
|
| 219 | /// An 8-bit rgba image representing the rendered image.
|
| 220 | pub preview: Option<Preview>,
|
| 221 |
|
| 222 | /// Name of the view, which is typically either `"right"` or `"left"` for a stereoscopic image.
|
| 223 | pub view_name: Option<Text>,
|
| 224 |
|
| 225 | /// The name of the software that produced this image.
|
| 226 | pub software_name: Option<Text>,
|
| 227 |
|
| 228 | /// The near clip plane of the virtual camera projection.
|
| 229 | pub near_clip_plane: Option<f32>,
|
| 230 |
|
| 231 | /// The far clip plane of the virtual camera projection.
|
| 232 | pub far_clip_plane: Option<f32>,
|
| 233 |
|
| 234 | /// The field of view angle, along the horizontal axis, in degrees.
|
| 235 | pub horizontal_field_of_view: Option<f32>,
|
| 236 |
|
| 237 | /// The field of view angle, along the horizontal axis, in degrees.
|
| 238 | pub vertical_field_of_view: Option<f32>,
|
| 239 |
|
| 240 | /// Contains custom attributes.
|
| 241 | /// Does not contain the attributes already present in the `Header` or `LayerAttributes` struct.
|
| 242 | /// Does not contain attributes that are standardized to be the same for all layers: no chromaticities and no time codes.
|
| 243 | pub other: HashMap<Text, AttributeValue>,
|
| 244 | }
|
| 245 |
|
| 246 |
|
| 247 | impl LayerAttributes {
|
| 248 |
|
| 249 | /// Create default layer attributes with a data position of zero.
|
| 250 | pub fn named(layer_name: impl Into<Text>) -> Self {
|
| 251 | Self {
|
| 252 | layer_name: Some(layer_name.into()),
|
| 253 | .. Self::default()
|
| 254 | }
|
| 255 | }
|
| 256 |
|
| 257 | /// Set the data position of this layer.
|
| 258 | pub fn with_position(self, data_position: Vec2<i32>) -> Self {
|
| 259 | Self { layer_position: data_position, ..self }
|
| 260 | }
|
| 261 |
|
| 262 | /// Set all common camera projection attributes at once.
|
| 263 | pub fn with_camera_frustum(
|
| 264 | self,
|
| 265 | world_to_camera: Matrix4x4,
|
| 266 | world_to_normalized_device: Matrix4x4,
|
| 267 | field_of_view: impl Into<Vec2<f32>>,
|
| 268 | depth_clip_range: std::ops::Range<f32>,
|
| 269 | ) -> Self
|
| 270 | {
|
| 271 | let fov = field_of_view.into();
|
| 272 |
|
| 273 | Self {
|
| 274 | world_to_normalized_device: Some(world_to_normalized_device),
|
| 275 | world_to_camera: Some(world_to_camera),
|
| 276 | horizontal_field_of_view: Some(fov.x()),
|
| 277 | vertical_field_of_view: Some(fov.y()),
|
| 278 | near_clip_plane: Some(depth_clip_range.start),
|
| 279 | far_clip_plane: Some(depth_clip_range.end),
|
| 280 | ..self
|
| 281 | }
|
| 282 | }
|
| 283 | }
|
| 284 |
|
| 285 | impl ImageAttributes {
|
| 286 |
|
| 287 | /// Set the display position and size of this image.
|
| 288 | pub fn new(display_window: IntegerBounds) -> Self {
|
| 289 | Self {
|
| 290 | pixel_aspect: 1.0,
|
| 291 | chromaticities: None,
|
| 292 | time_code: None,
|
| 293 | other: Default::default(),
|
| 294 | display_window,
|
| 295 | }
|
| 296 | }
|
| 297 |
|
| 298 | /// Set the display position to zero and use the specified size for this image.
|
| 299 | pub fn with_size(size: impl Into<Vec2<usize>>) -> Self {
|
| 300 | Self::new(display_window:IntegerBounds::from_dimensions(size))
|
| 301 | }
|
| 302 | }
|
| 303 |
|
| 304 |
|
| 305 |
|
| 306 |
|
| 307 | impl Header {
|
| 308 |
|
| 309 | /// Create a new Header with the specified name, display window and channels.
|
| 310 | /// Use `Header::with_encoding` and the similar methods to add further properties to the header.
|
| 311 | ///
|
| 312 | /// The other settings are left to their default values:
|
| 313 | /// - RLE compression
|
| 314 | /// - display window equal to data window
|
| 315 | /// - tiles (64 x 64 px)
|
| 316 | /// - unspecified line order
|
| 317 | /// - no custom attributes
|
| 318 | pub fn new(name: Text, data_size: impl Into<Vec2<usize>>, channels: SmallVec<[ChannelDescription; 5]>) -> Self {
|
| 319 | let data_size: Vec2<usize> = data_size.into();
|
| 320 |
|
| 321 | let compression = Compression::RLE;
|
| 322 | let blocks = BlockDescription::Tiles(TileDescription {
|
| 323 | tile_size: Vec2(64, 64),
|
| 324 | level_mode: LevelMode::Singular,
|
| 325 | rounding_mode: RoundingMode::Down
|
| 326 | });
|
| 327 |
|
| 328 | Self {
|
| 329 | layer_size: data_size,
|
| 330 | compression,
|
| 331 | blocks,
|
| 332 |
|
| 333 | channels: ChannelList::new(channels),
|
| 334 | line_order: LineOrder::Unspecified,
|
| 335 |
|
| 336 | shared_attributes: ImageAttributes::with_size(data_size),
|
| 337 | own_attributes: LayerAttributes::named(name),
|
| 338 |
|
| 339 | chunk_count: compute_chunk_count(compression, data_size, blocks),
|
| 340 |
|
| 341 | deep: false,
|
| 342 | deep_data_version: None,
|
| 343 | max_samples_per_pixel: None,
|
| 344 | }
|
| 345 | }
|
| 346 |
|
| 347 | /// Set the display window, that is, the global clipping rectangle.
|
| 348 | /// __Must be the same for all headers of a file.__
|
| 349 | pub fn with_display_window(mut self, display_window: IntegerBounds) -> Self {
|
| 350 | self.shared_attributes.display_window = display_window;
|
| 351 | self
|
| 352 | }
|
| 353 |
|
| 354 | /// Set the offset of this layer.
|
| 355 | pub fn with_position(mut self, position: Vec2<i32>) -> Self {
|
| 356 | self.own_attributes.layer_position = position;
|
| 357 | self
|
| 358 | }
|
| 359 |
|
| 360 | /// Set compression, tiling, and line order. Automatically computes chunk count.
|
| 361 | pub fn with_encoding(self, compression: Compression, blocks: BlockDescription, line_order: LineOrder) -> Self {
|
| 362 | Self {
|
| 363 | chunk_count: compute_chunk_count(compression, self.layer_size, blocks),
|
| 364 | compression, blocks, line_order,
|
| 365 | .. self
|
| 366 | }
|
| 367 | }
|
| 368 |
|
| 369 | /// Set **all** attributes of the header that are not shared with all other headers in the image.
|
| 370 | pub fn with_attributes(self, own_attributes: LayerAttributes) -> Self {
|
| 371 | Self { own_attributes, .. self }
|
| 372 | }
|
| 373 |
|
| 374 | /// Set **all** attributes of the header that are shared with all other headers in the image.
|
| 375 | pub fn with_shared_attributes(self, shared_attributes: ImageAttributes) -> Self {
|
| 376 | Self { shared_attributes, .. self }
|
| 377 | }
|
| 378 |
|
| 379 | /// Iterate over all blocks, in the order specified by the headers line order attribute.
|
| 380 | /// Unspecified line order is treated as increasing line order.
|
| 381 | /// Also enumerates the index of each block in the header, as if it were sorted in increasing line order.
|
| 382 | pub fn enumerate_ordered_blocks(&self) -> impl Iterator<Item=(usize, TileIndices)> + Send {
|
| 383 | let increasing_y = self.blocks_increasing_y_order().enumerate();
|
| 384 |
|
| 385 | // TODO without box?
|
| 386 | let ordered: Box<dyn Send + Iterator<Item=(usize, TileIndices)>> = {
|
| 387 | if self.line_order == LineOrder::Decreasing { Box::new(increasing_y.rev()) }
|
| 388 | else { Box::new(increasing_y) }
|
| 389 | };
|
| 390 |
|
| 391 | ordered
|
| 392 | }
|
| 393 |
|
| 394 | /*/// Iterate over all blocks, in the order specified by the headers line order attribute.
|
| 395 | /// Also includes an index of the block if it were `LineOrder::Increasing`, starting at zero for this header.
|
| 396 | pub fn enumerate_ordered_blocks(&self) -> impl Iterator<Item = (usize, TileIndices)> + Send {
|
| 397 | let increasing_y = self.blocks_increasing_y_order().enumerate();
|
| 398 |
|
| 399 | let ordered: Box<dyn Send + Iterator<Item = (usize, TileIndices)>> = {
|
| 400 | if self.line_order == LineOrder::Decreasing {
|
| 401 | Box::new(increasing_y.rev()) // TODO without box?
|
| 402 | }
|
| 403 | else {
|
| 404 | Box::new(increasing_y)
|
| 405 | }
|
| 406 | };
|
| 407 |
|
| 408 | ordered
|
| 409 | }*/
|
| 410 |
|
| 411 | /// Iterate over all tile indices in this header in `LineOrder::Increasing` order.
|
| 412 | pub fn blocks_increasing_y_order(&self) -> impl Iterator<Item = TileIndices> + ExactSizeIterator + DoubleEndedIterator {
|
| 413 | fn tiles_of(image_size: Vec2<usize>, tile_size: Vec2<usize>, level_index: Vec2<usize>) -> impl Iterator<Item=TileIndices> {
|
| 414 | fn divide_and_rest(total_size: usize, block_size: usize) -> impl Iterator<Item=(usize, usize)> {
|
| 415 | let block_count = compute_block_count(total_size, block_size);
|
| 416 | (0..block_count).map(move |block_index| (
|
| 417 | block_index, calculate_block_size(total_size, block_size, block_index).expect("block size calculation bug" )
|
| 418 | ))
|
| 419 | }
|
| 420 |
|
| 421 | divide_and_rest(image_size.height(), tile_size.height()).flat_map(move |(y_index, tile_height)|{
|
| 422 | divide_and_rest(image_size.width(), tile_size.width()).map(move |(x_index, tile_width)|{
|
| 423 | TileIndices {
|
| 424 | size: Vec2(tile_width, tile_height),
|
| 425 | location: TileCoordinates { tile_index: Vec2(x_index, y_index), level_index, },
|
| 426 | }
|
| 427 | })
|
| 428 | })
|
| 429 | }
|
| 430 |
|
| 431 | let vec: Vec<TileIndices> = {
|
| 432 | if let BlockDescription::Tiles(tiles) = self.blocks {
|
| 433 | match tiles.level_mode {
|
| 434 | LevelMode::Singular => {
|
| 435 | tiles_of(self.layer_size, tiles.tile_size, Vec2(0, 0)).collect()
|
| 436 | },
|
| 437 | LevelMode::MipMap => {
|
| 438 | mip_map_levels(tiles.rounding_mode, self.layer_size)
|
| 439 | .flat_map(move |(level_index, level_size)|{
|
| 440 | tiles_of(level_size, tiles.tile_size, Vec2(level_index, level_index))
|
| 441 | })
|
| 442 | .collect()
|
| 443 | },
|
| 444 | LevelMode::RipMap => {
|
| 445 | rip_map_levels(tiles.rounding_mode, self.layer_size)
|
| 446 | .flat_map(move |(level_index, level_size)| {
|
| 447 | tiles_of(level_size, tiles.tile_size, level_index)
|
| 448 | })
|
| 449 | .collect()
|
| 450 | }
|
| 451 | }
|
| 452 | }
|
| 453 | else {
|
| 454 | let tiles = Vec2(self.layer_size.0, self.compression.scan_lines_per_block());
|
| 455 | tiles_of(self.layer_size, tiles, Vec2(0, 0)).collect()
|
| 456 | }
|
| 457 | };
|
| 458 |
|
| 459 | vec.into_iter() // TODO without collect
|
| 460 | }
|
| 461 |
|
| 462 | /* TODO
|
| 463 | /// The block indices of this header, ordered as they would appear in the file.
|
| 464 | pub fn ordered_block_indices<'s>(&'s self, layer_index: usize) -> impl 's + Iterator<Item=BlockIndex> {
|
| 465 | self.enumerate_ordered_blocks().map(|(chunk_index, tile)|{
|
| 466 | let data_indices = self.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
|
| 467 |
|
| 468 | BlockIndex {
|
| 469 | layer: layer_index,
|
| 470 | level: tile.location.level_index,
|
| 471 | pixel_position: data_indices.position.to_usize("data indices start").expect("data index bug"),
|
| 472 | pixel_size: data_indices.size,
|
| 473 | }
|
| 474 | })
|
| 475 | }*/
|
| 476 |
|
| 477 | // TODO reuse this function everywhere
|
| 478 | /// The default pixel resolution of a single block (tile or scan line block).
|
| 479 | /// Not all blocks have this size, because they may be cutoff at the end of the image.
|
| 480 | pub fn max_block_pixel_size(&self) -> Vec2<usize> {
|
| 481 | match self.blocks {
|
| 482 | BlockDescription::ScanLines => Vec2(self.layer_size.0, self.compression.scan_lines_per_block()),
|
| 483 | BlockDescription::Tiles(tiles) => tiles.tile_size,
|
| 484 | }
|
| 485 | }
|
| 486 |
|
| 487 | /// Calculate the position of a block in the global infinite 2D space of a file. May be negative.
|
| 488 | pub fn get_block_data_window_pixel_coordinates(&self, tile: TileCoordinates) -> Result<IntegerBounds> {
|
| 489 | let data = self.get_absolute_block_pixel_coordinates(tile)?;
|
| 490 | Ok(data.with_origin(self.own_attributes.layer_position))
|
| 491 | }
|
| 492 |
|
| 493 | /// Calculate the pixel index rectangle inside this header. Is not negative. Starts at `0`.
|
| 494 | pub fn get_absolute_block_pixel_coordinates(&self, tile: TileCoordinates) -> Result<IntegerBounds> {
|
| 495 | if let BlockDescription::Tiles(tiles) = self.blocks {
|
| 496 | let Vec2(data_width, data_height) = self.layer_size;
|
| 497 |
|
| 498 | let data_width = compute_level_size(tiles.rounding_mode, data_width, tile.level_index.x());
|
| 499 | let data_height = compute_level_size(tiles.rounding_mode, data_height, tile.level_index.y());
|
| 500 | let absolute_tile_coordinates = tile.to_data_indices(tiles.tile_size, Vec2(data_width, data_height))?;
|
| 501 |
|
| 502 | if absolute_tile_coordinates.position.x() as i64 >= data_width as i64 || absolute_tile_coordinates.position.y() as i64 >= data_height as i64 {
|
| 503 | return Err(Error::invalid("data block tile index" ))
|
| 504 | }
|
| 505 |
|
| 506 | Ok(absolute_tile_coordinates)
|
| 507 | }
|
| 508 | else { // this is a scanline image
|
| 509 | debug_assert_eq!(tile.tile_index.0, 0, "block index calculation bug" );
|
| 510 |
|
| 511 | let (y, height) = calculate_block_position_and_size(
|
| 512 | self.layer_size.height(),
|
| 513 | self.compression.scan_lines_per_block(),
|
| 514 | tile.tile_index.y()
|
| 515 | )?;
|
| 516 |
|
| 517 | Ok(IntegerBounds {
|
| 518 | position: Vec2(0, usize_to_i32(y)),
|
| 519 | size: Vec2(self.layer_size.width(), height)
|
| 520 | })
|
| 521 | }
|
| 522 |
|
| 523 | // TODO deep data?
|
| 524 | }
|
| 525 |
|
| 526 | /// Return the tile index, converting scan line block coordinates to tile indices.
|
| 527 | /// Starts at `0` and is not negative.
|
| 528 | pub fn get_block_data_indices(&self, block: &CompressedBlock) -> Result<TileCoordinates> {
|
| 529 | Ok(match block {
|
| 530 | CompressedBlock::Tile(ref tile) => {
|
| 531 | tile.coordinates
|
| 532 | },
|
| 533 |
|
| 534 | CompressedBlock::ScanLine(ref block) => {
|
| 535 | let size = self.compression.scan_lines_per_block() as i32;
|
| 536 |
|
| 537 | let diff = block.y_coordinate.checked_sub(self.own_attributes.layer_position.y()).ok_or(Error::invalid("invalid header" ))?;
|
| 538 | let y = diff.checked_div(size).ok_or(Error::invalid("invalid header" ))?;
|
| 539 |
|
| 540 | if y < 0 {
|
| 541 | return Err(Error::invalid("scan block y coordinate" ));
|
| 542 | }
|
| 543 |
|
| 544 | TileCoordinates {
|
| 545 | tile_index: Vec2(0, y as usize),
|
| 546 | level_index: Vec2(0, 0)
|
| 547 | }
|
| 548 | },
|
| 549 |
|
| 550 | _ => return Err(Error::unsupported("deep data not supported yet" ))
|
| 551 | })
|
| 552 | }
|
| 553 |
|
| 554 | /// Computes the absolute tile coordinate data indices, which start at `0`.
|
| 555 | pub fn get_scan_line_block_tile_coordinates(&self, block_y_coordinate: i32) -> Result<TileCoordinates> {
|
| 556 | let size = self.compression.scan_lines_per_block() as i32;
|
| 557 |
|
| 558 | let diff = block_y_coordinate.checked_sub(self.own_attributes.layer_position.1).ok_or(Error::invalid("invalid header" ))?;
|
| 559 | let y = diff.checked_div(size).ok_or(Error::invalid("invalid header" ))?;
|
| 560 |
|
| 561 | if y < 0 {
|
| 562 | return Err(Error::invalid("scan block y coordinate" ));
|
| 563 | }
|
| 564 |
|
| 565 | Ok(TileCoordinates {
|
| 566 | tile_index: Vec2(0, y as usize),
|
| 567 | level_index: Vec2(0, 0)
|
| 568 | })
|
| 569 | }
|
| 570 |
|
| 571 | /// Maximum byte length of an uncompressed or compressed block, used for validation.
|
| 572 | pub fn max_block_byte_size(&self) -> usize {
|
| 573 | self.channels.bytes_per_pixel * match self.blocks {
|
| 574 | BlockDescription::Tiles(tiles) => tiles.tile_size.area(),
|
| 575 | BlockDescription::ScanLines => self.compression.scan_lines_per_block() * self.layer_size.width()
|
| 576 | // TODO What about deep data???
|
| 577 | }
|
| 578 | }
|
| 579 |
|
| 580 | /// Returns the number of bytes that the pixels of this header will require
|
| 581 | /// when stored without compression. Respects multi-resolution levels and subsampling.
|
| 582 | pub fn total_pixel_bytes(&self) -> usize {
|
| 583 | assert!(!self.deep);
|
| 584 |
|
| 585 | let pixel_count_of_levels = |size: Vec2<usize>| -> usize {
|
| 586 | match self.blocks {
|
| 587 | BlockDescription::ScanLines => size.area(),
|
| 588 | BlockDescription::Tiles(tile_description) => match tile_description.level_mode {
|
| 589 | LevelMode::Singular => size.area(),
|
| 590 |
|
| 591 | LevelMode::MipMap => mip_map_levels(tile_description.rounding_mode, size)
|
| 592 | .map(|(_, size)| size.area()).sum(),
|
| 593 |
|
| 594 | LevelMode::RipMap => rip_map_levels(tile_description.rounding_mode, size)
|
| 595 | .map(|(_, size)| size.area()).sum(),
|
| 596 | }
|
| 597 | }
|
| 598 | };
|
| 599 |
|
| 600 | self.channels.list.iter()
|
| 601 | .map(|channel: &ChannelDescription|
|
| 602 | pixel_count_of_levels(channel.subsampled_resolution(self.layer_size)) * channel.sample_type.bytes_per_sample()
|
| 603 | )
|
| 604 | .sum()
|
| 605 |
|
| 606 | }
|
| 607 |
|
| 608 | /// Approximates the maximum number of bytes that the pixels of this header will consume in a file.
|
| 609 | /// Due to compression, the actual byte size may be smaller.
|
| 610 | pub fn max_pixel_file_bytes(&self) -> usize {
|
| 611 | assert!(!self.deep);
|
| 612 |
|
| 613 | self.chunk_count * 64 // at most 64 bytes overhead for each chunk (header index, tile description, chunk size, and more)
|
| 614 | + self.total_pixel_bytes()
|
| 615 | }
|
| 616 |
|
| 617 | /// Validate this instance.
|
| 618 | pub fn validate(&self, is_multilayer: bool, long_names: &mut bool, strict: bool) -> UnitResult {
|
| 619 |
|
| 620 | self.data_window().validate(None)?;
|
| 621 | self.shared_attributes.display_window.validate(None)?;
|
| 622 |
|
| 623 | if strict {
|
| 624 | if is_multilayer {
|
| 625 | if self.own_attributes.layer_name.is_none() {
|
| 626 | return Err(missing_attribute("layer name for multi layer file" ));
|
| 627 | }
|
| 628 | }
|
| 629 |
|
| 630 | if self.blocks == BlockDescription::ScanLines && self.line_order == LineOrder::Unspecified {
|
| 631 | return Err(Error::invalid("unspecified line order in scan line images" ));
|
| 632 | }
|
| 633 |
|
| 634 | if self.layer_size == Vec2(0, 0) {
|
| 635 | return Err(Error::invalid("empty data window" ));
|
| 636 | }
|
| 637 |
|
| 638 | if self.shared_attributes.display_window.size == Vec2(0,0) {
|
| 639 | return Err(Error::invalid("empty display window" ));
|
| 640 | }
|
| 641 |
|
| 642 | if !self.shared_attributes.pixel_aspect.is_normal() || self.shared_attributes.pixel_aspect < 1.0e-6 || self.shared_attributes.pixel_aspect > 1.0e6 {
|
| 643 | return Err(Error::invalid("pixel aspect ratio" ));
|
| 644 | }
|
| 645 |
|
| 646 | if self.own_attributes.screen_window_width < 0.0 {
|
| 647 | return Err(Error::invalid("screen window width" ));
|
| 648 | }
|
| 649 | }
|
| 650 |
|
| 651 | let allow_subsampling = !self.deep && self.blocks == BlockDescription::ScanLines;
|
| 652 | self.channels.validate(allow_subsampling, self.data_window(), strict)?;
|
| 653 |
|
| 654 | for (name, value) in &self.shared_attributes.other {
|
| 655 | attribute::validate(name, value, long_names, allow_subsampling, self.data_window(), strict)?;
|
| 656 | }
|
| 657 |
|
| 658 | for (name, value) in &self.own_attributes.other {
|
| 659 | attribute::validate(name, value, long_names, allow_subsampling, self.data_window(), strict)?;
|
| 660 | }
|
| 661 |
|
| 662 | // this is only to check whether someone tampered with our precious values, to avoid writing an invalid file
|
| 663 | if self.chunk_count != compute_chunk_count(self.compression, self.layer_size, self.blocks) {
|
| 664 | return Err(Error::invalid("chunk count attribute" )); // TODO this may be an expensive check?
|
| 665 | }
|
| 666 |
|
| 667 | // check if attribute names appear twice
|
| 668 | if strict {
|
| 669 | for (name, _) in &self.shared_attributes.other {
|
| 670 | if self.own_attributes.other.contains_key(name) {
|
| 671 | return Err(Error::invalid(format!("duplicate attribute name: ` {}`" , name)));
|
| 672 | }
|
| 673 | }
|
| 674 |
|
| 675 | for &reserved in header::standard_names::ALL.iter() {
|
| 676 | let name = Text::from_bytes_unchecked(SmallVec::from_slice(reserved));
|
| 677 | if self.own_attributes.other.contains_key(&name) || self.shared_attributes.other.contains_key(&name) {
|
| 678 | return Err(Error::invalid(format!(
|
| 679 | "attribute name ` {}` is reserved and cannot be custom" ,
|
| 680 | Text::from_bytes_unchecked(reserved.into())
|
| 681 | )));
|
| 682 | }
|
| 683 | }
|
| 684 | }
|
| 685 |
|
| 686 | if self.deep {
|
| 687 | if strict {
|
| 688 | if self.own_attributes.layer_name.is_none() {
|
| 689 | return Err(missing_attribute("layer name for deep file" ));
|
| 690 | }
|
| 691 |
|
| 692 | if self.max_samples_per_pixel.is_none() {
|
| 693 | return Err(Error::invalid("missing max samples per pixel attribute for deepdata" ));
|
| 694 | }
|
| 695 | }
|
| 696 |
|
| 697 | match self.deep_data_version {
|
| 698 | Some(1) => {},
|
| 699 | Some(_) => return Err(Error::unsupported("deep data version" )),
|
| 700 | None => return Err(missing_attribute("deep data version" )),
|
| 701 | }
|
| 702 |
|
| 703 | if !self.compression.supports_deep_data() {
|
| 704 | return Err(Error::invalid("compression method does not support deep data" ));
|
| 705 | }
|
| 706 | }
|
| 707 |
|
| 708 | Ok(())
|
| 709 | }
|
| 710 |
|
| 711 | /// Read the headers without validating them.
|
| 712 | pub fn read_all(read: &mut PeekRead<impl Read>, version: &Requirements, pedantic: bool) -> Result<Headers> {
|
| 713 | if !version.is_multilayer() {
|
| 714 | Ok(smallvec![ Header::read(read, version, pedantic)? ])
|
| 715 | }
|
| 716 | else {
|
| 717 | let mut headers = SmallVec::new();
|
| 718 |
|
| 719 | while !sequence_end::has_come(read)? {
|
| 720 | headers.push(Header::read(read, version, pedantic)?);
|
| 721 | }
|
| 722 |
|
| 723 | Ok(headers)
|
| 724 | }
|
| 725 | }
|
| 726 |
|
| 727 | /// Without validation, write the headers to the byte stream.
|
| 728 | pub fn write_all(headers: &[Header], write: &mut impl Write, is_multilayer: bool) -> UnitResult {
|
| 729 | for header in headers {
|
| 730 | header.write(write)?;
|
| 731 | }
|
| 732 |
|
| 733 | if is_multilayer {
|
| 734 | sequence_end::write(write)?;
|
| 735 | }
|
| 736 |
|
| 737 | Ok(())
|
| 738 | }
|
| 739 |
|
| 740 | /// Iterate over all `(name, attribute_value)` pairs in this header that would be written to a file.
|
| 741 | /// The order of attributes is arbitrary and may change in future versions.
|
| 742 | /// Will always contain all strictly required attributes, such as channels, compression, data window, and similar.
|
| 743 | /// Hint: Use `attribute.kind_name()` to obtain the standardized name of the attribute type.
|
| 744 | /// Does not validate the header or attributes.
|
| 745 | // This function is used for writing the attributes to files.
|
| 746 | #[inline ]
|
| 747 | pub fn all_named_attributes(&self) -> impl '_ + Iterator<Item=(&TextSlice, AttributeValue)> {
|
| 748 | use std::iter::{once, once_with, empty};
|
| 749 | use crate::meta::header::standard_names::*;
|
| 750 | use AttributeValue::*;
|
| 751 |
|
| 752 | #[inline ] fn optional<'t, T: Clone>(
|
| 753 | name: &'t TextSlice,
|
| 754 | to_attribute: impl Fn(T) -> AttributeValue,
|
| 755 | value: &'t Option<T>
|
| 756 | )
|
| 757 | -> impl Iterator<Item=(&'t TextSlice, AttributeValue)>
|
| 758 | {
|
| 759 | value.as_ref().map(move |value| (name, to_attribute(value.clone()))).into_iter()
|
| 760 | }
|
| 761 |
|
| 762 | #[inline ] fn required<'s, T: Clone>(name: &'s TextSlice, to_attribute: impl Fn(T) -> AttributeValue, value: &'s T)
|
| 763 | -> impl Iterator<Item=(&'s TextSlice, AttributeValue)>
|
| 764 | {
|
| 765 | once((name, to_attribute((*value).clone())))
|
| 766 | }
|
| 767 |
|
| 768 | // used to type-check local variables. only requried because you cannot do `let i: impl Iterator<> = ...`
|
| 769 | #[inline ] fn expect_is_iter<'s, T: Iterator<Item=(&'s TextSlice, AttributeValue)>>(val: T) -> T { val }
|
| 770 |
|
| 771 | macro_rules! iter_all {
|
| 772 | ( $( $value:expr ),* ) => {
|
| 773 | empty() $( .chain( $value ) )*
|
| 774 | };
|
| 775 | }
|
| 776 |
|
| 777 | macro_rules! required_attributes {
|
| 778 | ( $($name: ident : $variant: ident = $value: expr),* ) => {
|
| 779 | expect_is_iter(iter_all!(
|
| 780 | $( required($name, $variant, $value) ),*
|
| 781 | ))
|
| 782 | };
|
| 783 | }
|
| 784 |
|
| 785 | macro_rules! optional_attributes {
|
| 786 | ( $($name: ident : $variant: ident = $value: expr),* ) => {
|
| 787 | expect_is_iter(iter_all!(
|
| 788 | $( optional($name, $variant, $value) ),*
|
| 789 | ))
|
| 790 | };
|
| 791 | }
|
| 792 |
|
| 793 | #[inline ] fn usize_as_i32(value: usize) -> AttributeValue {
|
| 794 | I32(i32::try_from(value).expect("usize exceeds i32 range" ))
|
| 795 | }
|
| 796 |
|
| 797 |
|
| 798 | let block_type_and_tiles = expect_is_iter(once_with(move ||{
|
| 799 | let (block_type, tiles) = match self.blocks {
|
| 800 | BlockDescription::ScanLines => (attribute::BlockType::ScanLine, None),
|
| 801 | BlockDescription::Tiles(tiles) => (attribute::BlockType::Tile, Some(tiles))
|
| 802 | };
|
| 803 |
|
| 804 | once((BLOCK_TYPE, BlockType(block_type)))
|
| 805 | .chain(tiles.map(|tiles| (TILES, TileDescription(tiles))))
|
| 806 | }).flatten());
|
| 807 |
|
| 808 | let data_window = expect_is_iter(once_with(move ||{
|
| 809 | (DATA_WINDOW, IntegerBounds(self.data_window()))
|
| 810 | }));
|
| 811 |
|
| 812 | // dwa writes compression parameters as attribute.
|
| 813 | let dwa_compr_level = expect_is_iter(
|
| 814 | once_with(move ||{
|
| 815 | match self.compression {
|
| 816 | attribute::Compression::DWAA(Some(level)) |
|
| 817 | attribute::Compression::DWAB(Some(level)) =>
|
| 818 | Some((DWA_COMPRESSION_LEVEL, F32(level))),
|
| 819 |
|
| 820 | _ => None
|
| 821 | }
|
| 822 | }).flatten()
|
| 823 | );
|
| 824 |
|
| 825 | let opt_core_attrs = optional_attributes!(
|
| 826 | DEEP_DATA_VERSION: I32 = &self.deep_data_version,
|
| 827 | MAX_SAMPLES: usize_as_i32 = &self.max_samples_per_pixel
|
| 828 | ).chain(block_type_and_tiles).chain(dwa_compr_level);
|
| 829 |
|
| 830 | let req_core_attrs = required_attributes!(
|
| 831 | // chunks is not actually required, but always computed in this library anyways
|
| 832 | CHUNKS: usize_as_i32 = &self.chunk_count,
|
| 833 |
|
| 834 | CHANNELS: ChannelList = &self.channels,
|
| 835 | COMPRESSION: Compression = &self.compression,
|
| 836 | LINE_ORDER: LineOrder = &self.line_order,
|
| 837 |
|
| 838 | DISPLAY_WINDOW: IntegerBounds = &self.shared_attributes.display_window,
|
| 839 | PIXEL_ASPECT: F32 = &self.shared_attributes.pixel_aspect,
|
| 840 |
|
| 841 | WINDOW_CENTER: FloatVec2 = &self.own_attributes.screen_window_center,
|
| 842 | WINDOW_WIDTH: F32 = &self.own_attributes.screen_window_width
|
| 843 | ).chain(data_window);
|
| 844 |
|
| 845 | let opt_attr = optional_attributes!(
|
| 846 | NAME: Text = &self.own_attributes.layer_name,
|
| 847 | WHITE_LUMINANCE: F32 = &self.own_attributes.white_luminance,
|
| 848 | ADOPTED_NEUTRAL: FloatVec2 = &self.own_attributes.adopted_neutral,
|
| 849 | RENDERING_TRANSFORM: Text = &self.own_attributes.rendering_transform_name,
|
| 850 | LOOK_MOD_TRANSFORM: Text = &self.own_attributes.look_modification_transform_name,
|
| 851 | X_DENSITY: F32 = &self.own_attributes.horizontal_density,
|
| 852 | OWNER: Text = &self.own_attributes.owner,
|
| 853 | COMMENTS: Text = &self.own_attributes.comments,
|
| 854 | CAPTURE_DATE: Text = &self.own_attributes.capture_date,
|
| 855 | UTC_OFFSET: F32 = &self.own_attributes.utc_offset,
|
| 856 | LONGITUDE: F32 = &self.own_attributes.longitude,
|
| 857 | LATITUDE: F32 = &self.own_attributes.latitude,
|
| 858 | ALTITUDE: F32 = &self.own_attributes.altitude,
|
| 859 | FOCUS: F32 = &self.own_attributes.focus,
|
| 860 | EXPOSURE_TIME: F32 = &self.own_attributes.exposure,
|
| 861 | APERTURE: F32 = &self.own_attributes.aperture,
|
| 862 | ISO_SPEED: F32 = &self.own_attributes.iso_speed,
|
| 863 | ENVIRONMENT_MAP: EnvironmentMap = &self.own_attributes.environment_map,
|
| 864 | KEY_CODE: KeyCode = &self.own_attributes.film_key_code,
|
| 865 | TIME_CODE: TimeCode = &self.shared_attributes.time_code,
|
| 866 | WRAP_MODES: Text = &self.own_attributes.wrap_mode_name,
|
| 867 | FRAMES_PER_SECOND: Rational = &self.own_attributes.frames_per_second,
|
| 868 | MULTI_VIEW: TextVector = &self.own_attributes.multi_view_names,
|
| 869 | WORLD_TO_CAMERA: Matrix4x4 = &self.own_attributes.world_to_camera,
|
| 870 | WORLD_TO_NDC: Matrix4x4 = &self.own_attributes.world_to_normalized_device,
|
| 871 | DEEP_IMAGE_STATE: Rational = &self.own_attributes.deep_image_state,
|
| 872 | ORIGINAL_DATA_WINDOW: IntegerBounds = &self.own_attributes.original_data_window,
|
| 873 | CHROMATICITIES: Chromaticities = &self.shared_attributes.chromaticities,
|
| 874 | PREVIEW: Preview = &self.own_attributes.preview,
|
| 875 | VIEW: Text = &self.own_attributes.view_name,
|
| 876 | NEAR: F32 = &self.own_attributes.near_clip_plane,
|
| 877 | FAR: F32 = &self.own_attributes.far_clip_plane,
|
| 878 | FOV_X: F32 = &self.own_attributes.horizontal_field_of_view,
|
| 879 | FOV_Y: F32 = &self.own_attributes.vertical_field_of_view,
|
| 880 | SOFTWARE: Text = &self.own_attributes.software_name
|
| 881 | );
|
| 882 |
|
| 883 | let other = self.own_attributes.other.iter()
|
| 884 | .chain(self.shared_attributes.other.iter())
|
| 885 | .map(|(name, val)| (name.as_slice(), val.clone())); // TODO no clone
|
| 886 |
|
| 887 | req_core_attrs
|
| 888 | .chain(opt_core_attrs)
|
| 889 | .chain(opt_attr)
|
| 890 | .chain(other)
|
| 891 | }
|
| 892 |
|
| 893 | /// Read the value without validating.
|
| 894 | pub fn read(read: &mut PeekRead<impl Read>, requirements: &Requirements, pedantic: bool) -> Result<Self> {
|
| 895 | let max_string_len = if requirements.has_long_names { 256 } else { 32 }; // TODO DRY this information
|
| 896 |
|
| 897 | // these required attributes will be filled when encountered while parsing
|
| 898 | let mut tiles = None;
|
| 899 | let mut block_type = None;
|
| 900 | let mut version = None;
|
| 901 | let mut chunk_count = None;
|
| 902 | let mut max_samples_per_pixel = None;
|
| 903 | let mut channels = None;
|
| 904 | let mut compression = None;
|
| 905 | let mut data_window = None;
|
| 906 | let mut display_window = None;
|
| 907 | let mut line_order = None;
|
| 908 | let mut dwa_compression_level = None;
|
| 909 |
|
| 910 | let mut layer_attributes = LayerAttributes::default();
|
| 911 | let mut image_attributes = ImageAttributes::new(IntegerBounds::zero());
|
| 912 |
|
| 913 | // read each attribute in this header
|
| 914 | while !sequence_end::has_come(read)? {
|
| 915 | let (attribute_name, value) = attribute::read(read, max_string_len)?;
|
| 916 |
|
| 917 | // if the attribute value itself is ok, record it
|
| 918 | match value {
|
| 919 | Ok(value) => {
|
| 920 | use crate::meta::header::standard_names as name;
|
| 921 | use crate::meta::attribute::AttributeValue::*;
|
| 922 |
|
| 923 | // if the attribute is a required attribute, set the corresponding variable directly.
|
| 924 | // otherwise, add the attribute to the vector of custom attributes
|
| 925 |
|
| 926 | // the following attributes will only be set if the type matches the commonly used type for that attribute
|
| 927 | match (attribute_name.as_slice(), value) {
|
| 928 | (name::BLOCK_TYPE, Text(value)) => block_type = Some(attribute::BlockType::parse(value)?),
|
| 929 | (name::TILES, TileDescription(value)) => tiles = Some(value),
|
| 930 | (name::CHANNELS, ChannelList(value)) => channels = Some(value),
|
| 931 | (name::COMPRESSION, Compression(value)) => compression = Some(value),
|
| 932 | (name::DATA_WINDOW, IntegerBounds(value)) => data_window = Some(value),
|
| 933 | (name::DISPLAY_WINDOW, IntegerBounds(value)) => display_window = Some(value),
|
| 934 | (name::LINE_ORDER, LineOrder(value)) => line_order = Some(value),
|
| 935 | (name::DEEP_DATA_VERSION, I32(value)) => version = Some(value),
|
| 936 |
|
| 937 | (name::MAX_SAMPLES, I32(value)) => max_samples_per_pixel = Some(
|
| 938 | i32_to_usize(value, "max sample count" )?
|
| 939 | ),
|
| 940 |
|
| 941 | (name::CHUNKS, I32(value)) => chunk_count = Some(
|
| 942 | i32_to_usize(value, "chunk count" )?
|
| 943 | ),
|
| 944 |
|
| 945 | (name::NAME, Text(value)) => layer_attributes.layer_name = Some(value),
|
| 946 | (name::WINDOW_CENTER, FloatVec2(value)) => layer_attributes.screen_window_center = value,
|
| 947 | (name::WINDOW_WIDTH, F32(value)) => layer_attributes.screen_window_width = value,
|
| 948 |
|
| 949 | (name::WHITE_LUMINANCE, F32(value)) => layer_attributes.white_luminance = Some(value),
|
| 950 | (name::ADOPTED_NEUTRAL, FloatVec2(value)) => layer_attributes.adopted_neutral = Some(value),
|
| 951 | (name::RENDERING_TRANSFORM, Text(value)) => layer_attributes.rendering_transform_name = Some(value),
|
| 952 | (name::LOOK_MOD_TRANSFORM, Text(value)) => layer_attributes.look_modification_transform_name = Some(value),
|
| 953 | (name::X_DENSITY, F32(value)) => layer_attributes.horizontal_density = Some(value),
|
| 954 |
|
| 955 | (name::OWNER, Text(value)) => layer_attributes.owner = Some(value),
|
| 956 | (name::COMMENTS, Text(value)) => layer_attributes.comments = Some(value),
|
| 957 | (name::CAPTURE_DATE, Text(value)) => layer_attributes.capture_date = Some(value),
|
| 958 | (name::UTC_OFFSET, F32(value)) => layer_attributes.utc_offset = Some(value),
|
| 959 | (name::LONGITUDE, F32(value)) => layer_attributes.longitude = Some(value),
|
| 960 | (name::LATITUDE, F32(value)) => layer_attributes.latitude = Some(value),
|
| 961 | (name::ALTITUDE, F32(value)) => layer_attributes.altitude = Some(value),
|
| 962 | (name::FOCUS, F32(value)) => layer_attributes.focus = Some(value),
|
| 963 | (name::EXPOSURE_TIME, F32(value)) => layer_attributes.exposure = Some(value),
|
| 964 | (name::APERTURE, F32(value)) => layer_attributes.aperture = Some(value),
|
| 965 | (name::ISO_SPEED, F32(value)) => layer_attributes.iso_speed = Some(value),
|
| 966 | (name::ENVIRONMENT_MAP, EnvironmentMap(value)) => layer_attributes.environment_map = Some(value),
|
| 967 | (name::KEY_CODE, KeyCode(value)) => layer_attributes.film_key_code = Some(value),
|
| 968 | (name::WRAP_MODES, Text(value)) => layer_attributes.wrap_mode_name = Some(value),
|
| 969 | (name::FRAMES_PER_SECOND, Rational(value)) => layer_attributes.frames_per_second = Some(value),
|
| 970 | (name::MULTI_VIEW, TextVector(value)) => layer_attributes.multi_view_names = Some(value),
|
| 971 | (name::WORLD_TO_CAMERA, Matrix4x4(value)) => layer_attributes.world_to_camera = Some(value),
|
| 972 | (name::WORLD_TO_NDC, Matrix4x4(value)) => layer_attributes.world_to_normalized_device = Some(value),
|
| 973 | (name::DEEP_IMAGE_STATE, Rational(value)) => layer_attributes.deep_image_state = Some(value),
|
| 974 | (name::ORIGINAL_DATA_WINDOW, IntegerBounds(value)) => layer_attributes.original_data_window = Some(value),
|
| 975 | (name::DWA_COMPRESSION_LEVEL, F32(value)) => dwa_compression_level = Some(value),
|
| 976 | (name::PREVIEW, Preview(value)) => layer_attributes.preview = Some(value),
|
| 977 | (name::VIEW, Text(value)) => layer_attributes.view_name = Some(value),
|
| 978 |
|
| 979 | (name::NEAR, F32(value)) => layer_attributes.near_clip_plane = Some(value),
|
| 980 | (name::FAR, F32(value)) => layer_attributes.far_clip_plane = Some(value),
|
| 981 | (name::FOV_X, F32(value)) => layer_attributes.horizontal_field_of_view = Some(value),
|
| 982 | (name::FOV_Y, F32(value)) => layer_attributes.vertical_field_of_view = Some(value),
|
| 983 | (name::SOFTWARE, Text(value)) => layer_attributes.software_name = Some(value),
|
| 984 |
|
| 985 | (name::PIXEL_ASPECT, F32(value)) => image_attributes.pixel_aspect = value,
|
| 986 | (name::TIME_CODE, TimeCode(value)) => image_attributes.time_code = Some(value),
|
| 987 | (name::CHROMATICITIES, Chromaticities(value)) => image_attributes.chromaticities = Some(value),
|
| 988 |
|
| 989 | // insert unknown attributes of these types into image attributes,
|
| 990 | // as these must be the same for all headers
|
| 991 | (_, value @ Chromaticities(_)) |
|
| 992 | (_, value @ TimeCode(_)) => {
|
| 993 | image_attributes.other.insert(attribute_name, value);
|
| 994 | },
|
| 995 |
|
| 996 | // insert unknown attributes into layer attributes
|
| 997 | (_, value) => {
|
| 998 | layer_attributes.other.insert(attribute_name, value);
|
| 999 | },
|
| 1000 |
|
| 1001 | }
|
| 1002 | },
|
| 1003 |
|
| 1004 | // in case the attribute value itself is not ok, but the rest of the image is
|
| 1005 | // only abort reading the image if desired
|
| 1006 | Err(error) => {
|
| 1007 | if pedantic { return Err(error); }
|
| 1008 | }
|
| 1009 | }
|
| 1010 | }
|
| 1011 |
|
| 1012 | // construct compression with parameters from properties
|
| 1013 | let compression = match (dwa_compression_level, compression) {
|
| 1014 | (Some(level), Some(Compression::DWAA(_))) => Some(Compression::DWAA(Some(level))),
|
| 1015 | (Some(level), Some(Compression::DWAB(_))) => Some(Compression::DWAB(Some(level))),
|
| 1016 | (_, other) => other,
|
| 1017 | // FIXME dwa compression level gets lost if any other compression is used later in the process
|
| 1018 | };
|
| 1019 |
|
| 1020 | let compression = compression.ok_or(missing_attribute("compression" ))?;
|
| 1021 | image_attributes.display_window = display_window.ok_or(missing_attribute("display window" ))?;
|
| 1022 |
|
| 1023 | let data_window = data_window.ok_or(missing_attribute("data window" ))?;
|
| 1024 | data_window.validate(None)?; // validate now to avoid errors when computing the chunk_count
|
| 1025 | layer_attributes.layer_position = data_window.position;
|
| 1026 |
|
| 1027 |
|
| 1028 | // validate now to avoid errors when computing the chunk_count
|
| 1029 | if let Some(tiles) = tiles { tiles.validate()?; }
|
| 1030 | let blocks = match block_type {
|
| 1031 | None if requirements.is_single_layer_and_tiled => {
|
| 1032 | BlockDescription::Tiles(tiles.ok_or(missing_attribute("tiles" ))?)
|
| 1033 | },
|
| 1034 | Some(BlockType::Tile) | Some(BlockType::DeepTile) => {
|
| 1035 | BlockDescription::Tiles(tiles.ok_or(missing_attribute("tiles" ))?)
|
| 1036 | },
|
| 1037 |
|
| 1038 | _ => BlockDescription::ScanLines,
|
| 1039 | };
|
| 1040 |
|
| 1041 | let computed_chunk_count = compute_chunk_count(compression, data_window.size, blocks);
|
| 1042 | if chunk_count.is_some() && pedantic && chunk_count != Some(computed_chunk_count) {
|
| 1043 | return Err(Error::invalid("chunk count not matching data size" ));
|
| 1044 | }
|
| 1045 |
|
| 1046 | let header = Header {
|
| 1047 | compression,
|
| 1048 |
|
| 1049 | // always compute ourselves, because we cannot trust anyone out there 😱
|
| 1050 | chunk_count: computed_chunk_count,
|
| 1051 |
|
| 1052 | layer_size: data_window.size,
|
| 1053 |
|
| 1054 | shared_attributes: image_attributes,
|
| 1055 | own_attributes: layer_attributes,
|
| 1056 |
|
| 1057 | channels: channels.ok_or(missing_attribute("channels" ))?,
|
| 1058 | line_order: line_order.unwrap_or(LineOrder::Unspecified),
|
| 1059 |
|
| 1060 | blocks,
|
| 1061 | max_samples_per_pixel,
|
| 1062 | deep_data_version: version,
|
| 1063 | deep: block_type == Some(BlockType::DeepScanLine) || block_type == Some(BlockType::DeepTile),
|
| 1064 | };
|
| 1065 |
|
| 1066 | Ok(header)
|
| 1067 | }
|
| 1068 |
|
| 1069 | /// Without validation, write this instance to the byte stream.
|
| 1070 | pub fn write(&self, write: &mut impl Write) -> UnitResult {
|
| 1071 | for (name, value) in self.all_named_attributes() {
|
| 1072 | attribute::write(name, &value, write)?;
|
| 1073 | }
|
| 1074 |
|
| 1075 | sequence_end::write(write)?;
|
| 1076 | Ok(())
|
| 1077 | }
|
| 1078 |
|
| 1079 | /// The rectangle describing the bounding box of this layer
|
| 1080 | /// within the infinite global 2D space of the file.
|
| 1081 | pub fn data_window(&self) -> IntegerBounds {
|
| 1082 | IntegerBounds::new(self.own_attributes.layer_position, self.layer_size)
|
| 1083 | }
|
| 1084 | }
|
| 1085 |
|
| 1086 |
|
| 1087 |
|
| 1088 | /// Collection of required attribute names.
|
| 1089 | pub mod standard_names {
|
| 1090 | macro_rules! define_required_attribute_names {
|
| 1091 | ( $($name: ident : $value: expr),* ) => {
|
| 1092 |
|
| 1093 | /// A list containing all reserved names.
|
| 1094 | pub const ALL: &'static [&'static [u8]] = &[
|
| 1095 | $( $value ),*
|
| 1096 | ];
|
| 1097 |
|
| 1098 | $(
|
| 1099 | /// The byte-string name of this required attribute as it appears in an exr file.
|
| 1100 | pub const $name: &'static [u8] = $value;
|
| 1101 | )*
|
| 1102 | };
|
| 1103 | }
|
| 1104 |
|
| 1105 | define_required_attribute_names! {
|
| 1106 | TILES: b"tiles" ,
|
| 1107 | NAME: b"name" ,
|
| 1108 | BLOCK_TYPE: b"type" ,
|
| 1109 | DEEP_DATA_VERSION: b"version" ,
|
| 1110 | CHUNKS: b"chunkCount" ,
|
| 1111 | MAX_SAMPLES: b"maxSamplesPerPixel" ,
|
| 1112 | CHANNELS: b"channels" ,
|
| 1113 | COMPRESSION: b"compression" ,
|
| 1114 | DATA_WINDOW: b"dataWindow" ,
|
| 1115 | DISPLAY_WINDOW: b"displayWindow" ,
|
| 1116 | LINE_ORDER: b"lineOrder" ,
|
| 1117 | PIXEL_ASPECT: b"pixelAspectRatio" ,
|
| 1118 | WINDOW_CENTER: b"screenWindowCenter" ,
|
| 1119 | WINDOW_WIDTH: b"screenWindowWidth" ,
|
| 1120 | WHITE_LUMINANCE: b"whiteLuminance" ,
|
| 1121 | ADOPTED_NEUTRAL: b"adoptedNeutral" ,
|
| 1122 | RENDERING_TRANSFORM: b"renderingTransform" ,
|
| 1123 | LOOK_MOD_TRANSFORM: b"lookModTransform" ,
|
| 1124 | X_DENSITY: b"xDensity" ,
|
| 1125 | OWNER: b"owner" ,
|
| 1126 | COMMENTS: b"comments" ,
|
| 1127 | CAPTURE_DATE: b"capDate" ,
|
| 1128 | UTC_OFFSET: b"utcOffset" ,
|
| 1129 | LONGITUDE: b"longitude" ,
|
| 1130 | LATITUDE: b"latitude" ,
|
| 1131 | ALTITUDE: b"altitude" ,
|
| 1132 | FOCUS: b"focus" ,
|
| 1133 | EXPOSURE_TIME: b"expTime" ,
|
| 1134 | APERTURE: b"aperture" ,
|
| 1135 | ISO_SPEED: b"isoSpeed" ,
|
| 1136 | ENVIRONMENT_MAP: b"envmap" ,
|
| 1137 | KEY_CODE: b"keyCode" ,
|
| 1138 | TIME_CODE: b"timeCode" ,
|
| 1139 | WRAP_MODES: b"wrapmodes" ,
|
| 1140 | FRAMES_PER_SECOND: b"framesPerSecond" ,
|
| 1141 | MULTI_VIEW: b"multiView" ,
|
| 1142 | WORLD_TO_CAMERA: b"worldToCamera" ,
|
| 1143 | WORLD_TO_NDC: b"worldToNDC" ,
|
| 1144 | DEEP_IMAGE_STATE: b"deepImageState" ,
|
| 1145 | ORIGINAL_DATA_WINDOW: b"originalDataWindow" ,
|
| 1146 | DWA_COMPRESSION_LEVEL: b"dwaCompressionLevel" ,
|
| 1147 | PREVIEW: b"preview" ,
|
| 1148 | VIEW: b"view" ,
|
| 1149 | CHROMATICITIES: b"chromaticities" ,
|
| 1150 | NEAR: b"near" ,
|
| 1151 | FAR: b"far" ,
|
| 1152 | FOV_X: b"fieldOfViewHorizontal" ,
|
| 1153 | FOV_Y: b"fieldOfViewVertical" ,
|
| 1154 | SOFTWARE: b"software"
|
| 1155 | }
|
| 1156 | }
|
| 1157 |
|
| 1158 |
|
| 1159 | impl Default for LayerAttributes {
|
| 1160 | fn default() -> Self {
|
| 1161 | Self {
|
| 1162 | layer_position: Vec2(0, 0),
|
| 1163 | screen_window_center: Vec2(0.0, 0.0),
|
| 1164 | screen_window_width: 1.0,
|
| 1165 | layer_name: None,
|
| 1166 | white_luminance: None,
|
| 1167 | adopted_neutral: None,
|
| 1168 | rendering_transform_name: None,
|
| 1169 | look_modification_transform_name: None,
|
| 1170 | horizontal_density: None,
|
| 1171 | owner: None,
|
| 1172 | comments: None,
|
| 1173 | capture_date: None,
|
| 1174 | utc_offset: None,
|
| 1175 | longitude: None,
|
| 1176 | latitude: None,
|
| 1177 | altitude: None,
|
| 1178 | focus: None,
|
| 1179 | exposure: None,
|
| 1180 | aperture: None,
|
| 1181 | iso_speed: None,
|
| 1182 | environment_map: None,
|
| 1183 | film_key_code: None,
|
| 1184 | wrap_mode_name: None,
|
| 1185 | frames_per_second: None,
|
| 1186 | multi_view_names: None,
|
| 1187 | world_to_camera: None,
|
| 1188 | world_to_normalized_device: None,
|
| 1189 | deep_image_state: None,
|
| 1190 | original_data_window: None,
|
| 1191 | preview: None,
|
| 1192 | view_name: None,
|
| 1193 | software_name: None,
|
| 1194 | near_clip_plane: None,
|
| 1195 | far_clip_plane: None,
|
| 1196 | horizontal_field_of_view: None,
|
| 1197 | vertical_field_of_view: None,
|
| 1198 | other: Default::default()
|
| 1199 | }
|
| 1200 | }
|
| 1201 | }
|
| 1202 |
|
| 1203 | impl std::fmt::Debug for LayerAttributes {
|
| 1204 | fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
| 1205 | let default_self = Self::default();
|
| 1206 |
|
| 1207 | let mut debug = formatter.debug_struct("LayerAttributes (default values omitted)" );
|
| 1208 |
|
| 1209 | // always debug the following field
|
| 1210 | debug.field("name" , &self.layer_name);
|
| 1211 |
|
| 1212 | macro_rules! debug_non_default_fields {
|
| 1213 | ( $( $name: ident ),* ) => { $(
|
| 1214 |
|
| 1215 | if self.$name != default_self.$name {
|
| 1216 | debug.field(stringify!($name), &self.$name);
|
| 1217 | }
|
| 1218 |
|
| 1219 | )* };
|
| 1220 | }
|
| 1221 |
|
| 1222 | // only debug these fields if they are not the default value
|
| 1223 | debug_non_default_fields! {
|
| 1224 | screen_window_center, screen_window_width,
|
| 1225 | white_luminance, adopted_neutral, horizontal_density,
|
| 1226 | rendering_transform_name, look_modification_transform_name,
|
| 1227 | owner, comments,
|
| 1228 | capture_date, utc_offset,
|
| 1229 | longitude, latitude, altitude,
|
| 1230 | focus, exposure, aperture, iso_speed,
|
| 1231 | environment_map, film_key_code, wrap_mode_name,
|
| 1232 | frames_per_second, multi_view_names,
|
| 1233 | world_to_camera, world_to_normalized_device,
|
| 1234 | deep_image_state, original_data_window,
|
| 1235 | preview, view_name,
|
| 1236 | vertical_field_of_view, horizontal_field_of_view,
|
| 1237 | near_clip_plane, far_clip_plane, software_name
|
| 1238 | }
|
| 1239 |
|
| 1240 | for (name, value) in &self.other {
|
| 1241 | debug.field(&format!(" \"{}\"" , name), value);
|
| 1242 | }
|
| 1243 |
|
| 1244 | // debug.finish_non_exhaustive() TODO
|
| 1245 | debug.finish()
|
| 1246 | }
|
| 1247 | }
|
| 1248 | |