Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Channel groups (legacy layers) #147

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 51 additions & 0 deletions examples/5_read_legacy_layers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

#[macro_use]
extern crate smallvec;
extern crate rand;
extern crate half;


// exr imports
extern crate exr;

/// Read an image with channel groups from a file.
/// Some legacy software may group layers that contain a `.` in the layer name.
///
/// Note: This is an OpenEXR legacy strategy. OpenEXR supports layers natively since 2013.
/// Use the natively supported exrs `Layer` types instead, if possible.
///
fn main() {
use exr::prelude::*;

let image = read().no_deep_data()
.largest_resolution_level()

.rgba_channels(
|resolution, _| {
vec![vec![(f16::ZERO, f16::ZERO, f16::ZERO, f16::ZERO); resolution.width()]; resolution.height()]
},

// all samples will be converted to f32 (you can also use the enum `Sample` instead of `f32` here to retain the original data type from the file)
|vec, position, (r,g,b,a): (f16, f16, f16, f16)| {
vec[position.y()][position.x()] = (r,g,b,a)
}
)

.grouped_channels()
.first_valid_layer()
.all_attributes()
.on_progress(|progress| println!("progress: {:.1}", progress*100.0))
.from_file("tests/images/valid/openexr/MultiView/Fog.exr")
.unwrap();

// output a random color of each channel of each layer
for layer in &image.layer_data {
let (r,g,b,a) = layer.channel_data.pixels.first().unwrap().first().unwrap();

println!(
"top left color of layer `{}`: (r,g,b,a) = {:?}",
layer.attributes.layer_name.clone().unwrap_or_default(),
(r.to_f32(), g.to_f32(), b.to_f32(), a.to_f32())
)
}
}
49 changes: 20 additions & 29 deletions examples/5_write_legacy_layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ extern crate half;
// exr imports
extern crate exr;

// TODO create a dedicated reader and writer for this scenario

/// Generate an image with channel groups and write it to a file.
/// Some legacy software may group layers that contain a `.` in the layer name.
///
Expand All @@ -18,8 +16,6 @@ extern crate exr;
///
fn main() {
use exr::prelude::*;
// TODO simplify handling these types of layers using read() and write()

let size = Vec2(512, 512);

let create_channel = |name: &str| -> AnyChannel<FlatSamples> {
Expand All @@ -32,35 +28,30 @@ fn main() {
};


// The channels have the following structure:
//
// - Object
// - Red
// - Green
// - Blue
// - Alpha

// - Background
// - Red
// - Green
// - Blue

let foreground_r = create_channel("Object.R");
let foreground_g = create_channel("Object.G");
let foreground_b = create_channel("Object.B");
let foreground_a = create_channel("Object.A");

let background_r = create_channel("Background.R");
let background_g = create_channel("Background.G");
let background_b = create_channel("Background.B");

let layer = Layer::new(
size,
LayerAttributes::named("test-image"),
Encoding::FAST_LOSSLESS,
AnyChannels::sort(smallvec![ // the order does not actually matter
foreground_r, foreground_g, foreground_b, foreground_a,
background_r, background_g, background_b

ChannelGroups::from_list([
(
// the foreground layer will be rgba
"Foreground",
AnyChannels::sort(smallvec![
create_channel("R"), create_channel("G"),
create_channel("B"), create_channel("A"),
])
),

(
// the background layer will be rgb
"Background",
AnyChannels::sort(smallvec![
create_channel("R"),
create_channel("G"),
create_channel("B")
])
),
]),
);

Expand Down
10 changes: 5 additions & 5 deletions examples/7_custom_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,24 @@ fn main() {
|meta_data, chunk_writer|{


let blocks = meta_data.collect_ordered_blocks(|block_index|{
let channel_description = &meta_data.headers[block_index.layer].channels;
let blocks = meta_data.collect_ordered_blocks(|header, block_index|{
let channel_description = &header.channels;

// fill the image file contents with one of the precomputed random values,
// picking a different one per channel
UncompressedBlock::from_lines(channel_description, block_index, |line_mut|{
// TODO iterate mut instead??

let chan = line_mut.location.channel;
let channel_index = line_mut.location.channel;

if chan == 3 { // write time as depth (could also check for _meta.channels[chan].name == "Z")
if channel_description.list[channel_index].name.eq("Z") { // write time as depth
line_mut.write_samples(|_| start_time.elapsed().as_secs_f32())
.expect("write to line bug");
}

else { // write rgba color
line_mut
.write_samples(|sample_index| random_values[(sample_index + chan) % random_values.len()])
.write_samples(|sample_index| random_values[(sample_index + channel_index) % random_values.len()])
.expect("write to line bug");
}
})
Expand Down
4 changes: 2 additions & 2 deletions src/block/lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl<'s> LineRefMut<'s> {
debug_assert_eq!(slice.len(), self.location.sample_count, "slice size does not match the line width");
debug_assert_eq!(self.value.len(), self.location.sample_count * T::BYTE_SIZE, "sample type size does not match line byte size");

T::write_slice(&mut Cursor::new(self.value), slice)
T::write_slice(&mut Cursor::new(self.value), slice) // TODO this cant fail, return no result?
}

/// Iterate over all samples in this line, from left to right.
Expand All @@ -168,7 +168,7 @@ impl<'s> LineRefMut<'s> {
let mut write = Cursor::new(self.value);

for index in 0..self.location.sample_count {
T::write(get_sample(index), &mut write)?;
T::write(get_sample(index), &mut write)?; // TODO this cannot fail...? do not return a result?
}

Ok(())
Expand Down
44 changes: 29 additions & 15 deletions src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::block::chunk::{CompressedBlock, CompressedTileBlock, CompressedScanLi
use crate::meta::header::Header;
use crate::block::lines::{LineIndex, LineRef, LineSlice, LineRefMut};
use crate::meta::attribute::ChannelList;
use std::hash::Hash;


/// Specifies where a block of pixel data should be placed in the actual image.
Expand All @@ -40,7 +41,7 @@ pub struct BlockIndex {
pub pixel_position: Vec2<usize>,

/// Number of pixels in this block, extending to the right and downwards.
/// Stays the same across all resolution levels.
/// Stays the same across all resolution levels. Border tiles are smaller than average.
pub pixel_size: Vec2<usize>,

/// Index of the mip or rip level in the image.
Expand Down Expand Up @@ -92,7 +93,7 @@ pub fn write<W: Write + Seek>(
/// The blocks written to the file must be exactly in this order,
/// except for when the `LineOrder` is unspecified.
/// The index represents the block index, in increasing line order, within the header.
pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + Iterator<Item=(usize, BlockIndex)> {
pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + Iterator<Item=(&Header, usize, BlockIndex)> {
headers.iter().enumerate().flat_map(|(layer_index, header)|{
header.enumerate_ordered_blocks().map(move |(index_in_header, tile)|{
let data_indices = header.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
Expand All @@ -104,11 +105,17 @@ pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + I
pixel_size: data_indices.size,
};

(index_in_header, block)
(header, index_in_header, block)
})
})
}

impl BlockIndex {
/// The number of bytes required for the referenced uncompressed block
pub fn byte_size(&self, channels: &ChannelList) -> usize {
self.pixel_size.area() * channels.bytes_per_pixel
}
}

impl UncompressedBlock {

Expand Down Expand Up @@ -153,7 +160,7 @@ impl UncompressedBlock {
let header: &Header = headers.get(index.layer)
.expect("block layer index bug");

let expected_byte_size = header.channels.bytes_per_pixel * self.index.pixel_size.area(); // TODO sampling??
let expected_byte_size = self.index.byte_size(&header.channels); // header.channels.bytes_per_pixel * self.index.pixel_size.area(); // TODO sampling??
if expected_byte_size != data.len() {
panic!("get_line byte size should be {} but was {}", expected_byte_size, data.len());
}
Expand Down Expand Up @@ -224,24 +231,31 @@ impl UncompressedBlock {
Ok(())
}*/

/// Create an uncompressed block by filling bytes.
pub fn fill_block_data(
channels: &ChannelList, block_index: BlockIndex,
mut fill_bytes: impl FnMut(&mut[u8])
) -> Vec<u8> {
let mut block_bytes = vec![0_u8; block_index.byte_size(channels)];
fill_bytes(block_bytes.as_mut_slice());
block_bytes
}

// TODO from iterator??
/// Create an uncompressed block byte vector by requesting one line of samples after another.
pub fn collect_block_data_from_lines(
channels: &ChannelList, block_index: BlockIndex,
mut extract_line: impl FnMut(LineRefMut<'_>)
) -> Vec<u8>
{
let byte_count = block_index.pixel_size.area() * channels.bytes_per_pixel;
let mut block_bytes = vec![0_u8; byte_count];

for (byte_range, line_index) in LineIndex::lines_in_block(block_index, channels) {
extract_line(LineRefMut { // TODO subsampling
value: &mut block_bytes[byte_range],
location: line_index,
});
}

block_bytes
Self::fill_block_data(channels, block_index, |block_bytes|{
for (byte_range, line_index) in LineIndex::lines_in_block(block_index, channels) {
extract_line(LineRefMut { // TODO subsampling
value: &mut block_bytes[byte_range],
location: line_index,
});
}
})
}

/// Create an uncompressed block by requesting one line of samples after another.
Expand Down