From fd3613018aeda8198090f2b7cbfc10fbf5869535 Mon Sep 17 00:00:00 2001 From: Elbert Ronnie <103196773+elbertronnie@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:53:05 +0530 Subject: [PATCH] Raw-rs: make decoder for ARW1 and ARW2 formats (#1775) * convert Tag into a trait * Create ARW 1 decoder * add decoder for sony tone curve table * create decoder for arw 2.1 format * add windsock.arw to the tests * create derive macro for Tag and use it in decoders * add license to tag-derive * add code to identify model * impl Display for Ifd * Code review * Fix type variable name * Fix compilation --------- Co-authored-by: Keavon Chambers --- Cargo.lock | 16 ++ Cargo.toml | 3 +- libraries/raw-rs/Cargo.toml | 2 + libraries/raw-rs/src/decoder/arw1.rs | 100 +++++++++ libraries/raw-rs/src/decoder/arw2.rs | 116 ++++++++++ libraries/raw-rs/src/decoder/mod.rs | 2 + libraries/raw-rs/src/decoder/uncompressed.rs | 57 +++-- libraries/raw-rs/src/lib.rs | 39 +++- libraries/raw-rs/src/tiff/file.rs | 10 +- libraries/raw-rs/src/tiff/mod.rs | 73 +++++-- libraries/raw-rs/src/tiff/tags.rs | 217 ++++++++++++++++--- libraries/raw-rs/src/tiff/types.rs | 155 +++++++------ libraries/raw-rs/src/tiff/values.rs | 26 +++ libraries/raw-rs/tag-derive/Cargo.toml | 16 ++ libraries/raw-rs/tag-derive/src/lib.rs | 46 ++++ libraries/raw-rs/tests/tests.rs | 2 +- 16 files changed, 720 insertions(+), 160 deletions(-) create mode 100644 libraries/raw-rs/src/decoder/arw1.rs create mode 100644 libraries/raw-rs/src/decoder/arw2.rs create mode 100644 libraries/raw-rs/tag-derive/Cargo.toml create mode 100644 libraries/raw-rs/tag-derive/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f3a0e857..b9344118 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,6 +618,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitstream-io" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c12d1856e42f0d817a835fe55853957c85c8c8a470114029143d3f12671446e" + [[package]] name = "block" version = "0.1.6" @@ -4770,9 +4776,11 @@ checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" name = "raw-rs" version = "0.0.1" dependencies = [ + "bitstream-io", "downloader", "libraw-rs", "num_enum 0.7.2", + "tag-derive", "thiserror", ] @@ -5895,6 +5903,14 @@ dependencies = [ "version-compare 0.2.0", ] +[[package]] +name = "tag-derive" +version = "0.0.1" +dependencies = [ + "quote", + "syn 2.0.66", +] + [[package]] name = "take_mut" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 5e822a99..a4ed553e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "libraries/dyn-any", "libraries/bezier-rs", "libraries/raw-rs", + "libraries/raw-rs/tag-derive", "website/other/bezier-rs-demos/wasm", ] resolver = "2" @@ -49,7 +50,7 @@ tempfile = "3" thiserror = "1.0" anyhow = "1.0.66" proc-macro2 = "1" -syn = { version = "2.0", default-features = false, features = ["full"] } +syn = { version = "2.0", default-features = false, features = ["full", "derive"] } quote = "1.0" axum = "0.6" chrono = "^0.4.23" diff --git a/libraries/raw-rs/Cargo.toml b/libraries/raw-rs/Cargo.toml index 53f9da9b..2a08e3a5 100644 --- a/libraries/raw-rs/Cargo.toml +++ b/libraries/raw-rs/Cargo.toml @@ -16,8 +16,10 @@ documentation = "https://docs.rs/raw-rs" raw-rs-tests = [] [dependencies] +bitstream-io = "2.3.0" num_enum = "0.7.2" thiserror = { workspace = true } +tag-derive = { path = "tag-derive" } [dev-dependencies] libraw-rs = "0.0.4" diff --git a/libraries/raw-rs/src/decoder/arw1.rs b/libraries/raw-rs/src/decoder/arw1.rs new file mode 100644 index 00000000..ba999caf --- /dev/null +++ b/libraries/raw-rs/src/decoder/arw1.rs @@ -0,0 +1,100 @@ +use crate::tiff::file::TiffRead; +use crate::tiff::tags::SonyDataOffset; +use crate::tiff::Ifd; +use crate::RawImage; + +use bitstream_io::{BitRead, BitReader, Endianness, BE}; +use std::io::{Read, Seek}; + +pub fn decode_a100(ifd: Ifd, file: &mut TiffRead) -> RawImage { + let data_offset = ifd.get_value::(file).unwrap(); + + let image_width = 3881; + let image_height = 2608; + + file.seek_from_start(data_offset).unwrap(); + let mut image = sony_arw_load_raw(image_width, image_height, &mut BitReader::<_, BE>::new(file)).unwrap(); + + let len = image.len(); + image[len - image_width..].fill(0); + + RawImage { + data: image, + width: image_width, + height: image_height, + } +} + +fn read_and_huffman_decode_file(huff: &[u16], file: &mut BitReader) -> u32 { + let number_of_bits = huff[0].into(); + let huffman_table = &huff[1..]; + + // `number_of_bits` will be no more than 32, so the result is put into a u32 + let bits: u32 = file.read(number_of_bits).unwrap(); + let bits = bits as usize; + + let bits_to_seek_from = huffman_table[bits].to_le_bytes()[1] as i64 - number_of_bits as i64; + file.seek_bits(std::io::SeekFrom::Current(bits_to_seek_from)).unwrap(); + + huffman_table[bits].to_le_bytes()[0].into() +} + +fn read_n_bits_from_file(number_of_bits: u32, file: &mut BitReader) -> u32 { + // `number_of_bits` will be no more than 32, so the result is put into a u32 + file.read(number_of_bits).unwrap() +} + +/// ljpeg is a lossless variant of JPEG which gets used for decoding the embedded (thumbnail) preview images in raw files +fn ljpeg_diff(huff: &[u16], file: &mut BitReader, dng_version: Option) -> i32 { + let length = read_and_huffman_decode_file(huff, file); + + if length == 16 && dng_version.map(|x| x >= 0x1010000).unwrap_or(true) { + return -32768; + } + + let diff = read_n_bits_from_file(length, file) as i32; + + if length == 0 || (diff & (1 << (length - 1))) == 0 { + diff - (1 << length) - 1 + } else { + diff + } +} + +fn sony_arw_load_raw(width: usize, height: usize, file: &mut BitReader) -> Option> { + const TABLE: [u16; 18] = [ + 0x0f11, 0x0f10, 0x0e0f, 0x0d0e, 0x0c0d, 0x0b0c, 0x0a0b, 0x090a, 0x0809, 0x0708, 0x0607, 0x0506, 0x0405, 0x0304, 0x0303, 0x0300, 0x0202, 0x0201, + ]; + + let mut huffman_table = [0_u16; 32770]; + // The first element is the number of bits to read + huffman_table[0] = 15; + + let mut n = 0; + for x in TABLE { + let first_byte = x >> 8; + let repeats = 0x8000 >> first_byte; + for _ in 0_u16..repeats { + n += 1; + huffman_table[n] = x; + } + } + + let mut sum = 0; + let mut image = vec![0_u16; width * height]; + for column in (0..width).rev() { + for row in (0..height).step_by(2).chain((1..height).step_by(2)) { + sum += ljpeg_diff(&huffman_table, file, None); + + if (sum >> 12) != 0 { + return None; + } + + if row < height { + image[row * width + column] = sum as u16; + } + } + } + + Some(image) +} diff --git a/libraries/raw-rs/src/decoder/arw2.rs b/libraries/raw-rs/src/decoder/arw2.rs new file mode 100644 index 00000000..35df771a --- /dev/null +++ b/libraries/raw-rs/src/decoder/arw2.rs @@ -0,0 +1,116 @@ +use crate::tiff::file::{Endian, TiffRead}; +use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, SonyToneCurve, StripByteCounts, StripOffsets, Tag}; +use crate::tiff::values::CurveLookupTable; +use crate::tiff::{Ifd, TiffError}; +use crate::RawImage; + +use std::io::{Read, Seek}; +use tag_derive::Tag; + +#[allow(dead_code)] +#[derive(Tag)] +struct Arw2Ifd { + image_width: ImageWidth, + image_height: ImageLength, + bits_per_sample: BitsPerSample, + compression: Compression, + cfa_pattern: CfaPattern, + cfa_pattern_dim: CfaPatternDim, + strip_offsets: StripOffsets, + strip_byte_counts: StripByteCounts, + sony_tone_curve: SonyToneCurve, +} + +pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { + let ifd = ifd.get_value::(file).unwrap(); + + assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len()); + assert!(ifd.strip_offsets.len() == 1); + assert!(ifd.compression == 32767); + + let image_width: usize = ifd.image_width.try_into().unwrap(); + let image_height: usize = ifd.image_height.try_into().unwrap(); + let bits_per_sample: usize = ifd.bits_per_sample.into(); + let [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim; + assert!(cfa_pattern_width == 2 && cfa_pattern_height == 2); + + file.seek_from_start(ifd.strip_offsets[0]).unwrap(); + let mut image = sony_arw2_load_raw(image_width, image_height, ifd.sony_tone_curve, file).unwrap(); + + // Converting the bps from 12 to 14 so that ARW 2.3.1 and 2.3.5 have the same 14 bps. + image.iter_mut().for_each(|x| *x <<= 2); + + RawImage { + data: image, + width: image_width, + height: image_height, + } +} + +fn as_u32(buffer: &[u8], endian: Endian) -> Option { + Some(match endian { + Endian::Little => u32::from_le_bytes(buffer.try_into().ok()?), + Endian::Big => u32::from_be_bytes(buffer.try_into().ok()?), + }) +} + +fn as_u16(buffer: &[u8], endian: Endian) -> Option { + Some(match endian { + Endian::Little => u16::from_le_bytes(buffer.try_into().ok()?), + Endian::Big => u16::from_be_bytes(buffer.try_into().ok()?), + }) +} + +fn sony_arw2_load_raw(width: usize, height: usize, curve: CurveLookupTable, file: &mut TiffRead) -> Option> { + let mut image = vec![0_u16; height * width]; + let mut data = vec![0_u8; width + 1]; + + for row in 0..height { + file.read_exact(&mut data[0..width]).unwrap(); + + let mut column = 0; + let mut data_index = 0; + + while column < width - 30 { + let data_value = as_u32(&data[data_index..][..4], file.endian()).unwrap(); + let max = (0x7ff & data_value) as u16; + let min = (0x7ff & data_value >> 11) as u16; + let index_to_set_max = 0x0f & data_value >> 22; + let index_to_set_min = 0x0f & data_value >> 26; + + let max_minus_min = max as i32 - min as i32; + let shift_by_bits = (0..4).find(|&shift| (0x80 << shift) > max_minus_min).unwrap_or(4); + + let mut pixel = [0_u16; 16]; + let mut bit = 30; + for i in 0..16 { + pixel[i] = match () { + _ if i as u32 == index_to_set_max => max, + _ if i as u32 == index_to_set_min => min, + _ => { + let result = as_u16(&data[(data_index + (bit >> 3))..][..2], file.endian()).unwrap(); + let result = ((result >> (bit & 7)) & 0x07f) << shift_by_bits; + + bit += 7; + + (result + min).min(0x7ff) + } + }; + } + + for value in pixel { + image[row * width + column] = curve.get((value << 1).into()) >> 2; + + // Skip between interlaced columns + column += 2; + } + + // Switch to the opposite interlaced columns + column -= if column & 1 == 0 { 31 } else { 1 }; + + data_index += 16; + } + } + + Some(image) +} diff --git a/libraries/raw-rs/src/decoder/mod.rs b/libraries/raw-rs/src/decoder/mod.rs index ab85db84..606b993a 100644 --- a/libraries/raw-rs/src/decoder/mod.rs +++ b/libraries/raw-rs/src/decoder/mod.rs @@ -1 +1,3 @@ +pub mod arw1; +pub mod arw2; pub mod uncompressed; diff --git a/libraries/raw-rs/src/decoder/uncompressed.rs b/libraries/raw-rs/src/decoder/uncompressed.rs index fae0934e..63fef5c4 100644 --- a/libraries/raw-rs/src/decoder/uncompressed.rs +++ b/libraries/raw-rs/src/decoder/uncompressed.rs @@ -1,38 +1,47 @@ use crate::tiff::file::TiffRead; -use crate::tiff::tags::{BITS_PER_SAMPLE, CFA_PATTERN, CFA_PATTERN_DIM, COMPRESSION, IMAGE_LENGTH, IMAGE_WIDTH, ROWS_PER_STRIP, SAMPLES_PER_PIXEL, STRIP_BYTE_COUNTS, STRIP_OFFSETS}; -use crate::tiff::Ifd; +use crate::tiff::tags::{BitsPerSample, CfaPattern, CfaPatternDim, Compression, ImageLength, ImageWidth, RowsPerStrip, StripByteCounts, StripOffsets, Tag}; +use crate::tiff::{Ifd, TiffError}; use crate::RawImage; use std::io::{Read, Seek}; +use tag_derive::Tag; + +#[allow(dead_code)] +#[derive(Tag)] +struct ArwUncompressedIfd { + image_width: ImageWidth, + image_height: ImageLength, + rows_per_strip: RowsPerStrip, + bits_per_sample: BitsPerSample, + compression: Compression, + cfa_pattern: CfaPattern, + cfa_pattern_dim: CfaPatternDim, + strip_offsets: StripOffsets, + strip_byte_counts: StripByteCounts, +} pub fn decode(ifd: Ifd, file: &mut TiffRead) -> RawImage { - let strip_offsets = ifd.get(STRIP_OFFSETS, file).unwrap(); - let strip_byte_counts = ifd.get(STRIP_BYTE_COUNTS, file).unwrap(); - assert!(strip_offsets.len() == strip_byte_counts.len()); + let ifd = ifd.get_value::(file).unwrap(); - let image_width: usize = ifd.get(IMAGE_WIDTH, file).unwrap().try_into().unwrap(); - let image_height: usize = ifd.get(IMAGE_LENGTH, file).unwrap().try_into().unwrap(); - let rows_per_strip: usize = ifd.get(ROWS_PER_STRIP, file).unwrap().try_into().unwrap(); - let bits_per_sample: usize = ifd.get(BITS_PER_SAMPLE, file).unwrap().into(); - let bytes_per_sample: usize = bits_per_sample.div_ceil(8); - let samples_per_pixel: usize = ifd.get(SAMPLES_PER_PIXEL, file).unwrap().into(); - let compression = ifd.get(COMPRESSION, file).unwrap(); - assert!(compression == 1); // 1 is the value for uncompressed format - // let photometric_interpretation = ifd.get(PHOTOMETRIC_INTERPRETATION, file).unwrap(); + assert!(ifd.strip_offsets.len() == ifd.strip_byte_counts.len()); + assert!(ifd.strip_offsets.len() == 1); + assert!(ifd.compression == 1); // 1 is the value for uncompressed format - let [cfa_pattern_width, cfa_pattern_height] = ifd.get(CFA_PATTERN_DIM, file).unwrap(); + let image_width: usize = ifd.image_width.try_into().unwrap(); + let image_height: usize = ifd.image_height.try_into().unwrap(); + let rows_per_strip: usize = ifd.rows_per_strip.try_into().unwrap(); + let bits_per_sample: usize = ifd.bits_per_sample.into(); + let [cfa_pattern_width, cfa_pattern_height] = ifd.cfa_pattern_dim; assert!(cfa_pattern_width == 2 && cfa_pattern_height == 2); - let cfa_pattern = ifd.get(CFA_PATTERN, file).unwrap(); - - let rows_per_strip_last = image_height % rows_per_strip; - let bytes_per_row = bytes_per_sample * samples_per_pixel * image_width; - let mut image: Vec = Vec::with_capacity(image_height * image_width); - for i in 0..strip_offsets.len() { - file.seek_from_start(strip_offsets[i]).unwrap(); - let row_count = if i == strip_offsets.len() { rows_per_strip_last } else { rows_per_strip }; - for _ in 0..row_count { + for i in 0..ifd.strip_offsets.len() { + file.seek_from_start(ifd.strip_offsets[i]).unwrap(); + + let last = i == ifd.strip_offsets.len(); + let rows = if last { image_height % rows_per_strip } else { rows_per_strip }; + + for _ in 0..rows { for _ in 0..image_width { image.push(file.read_u16().unwrap()); } diff --git a/libraries/raw-rs/src/lib.rs b/libraries/raw-rs/src/lib.rs index 5725a6cc..d068e052 100644 --- a/libraries/raw-rs/src/lib.rs +++ b/libraries/raw-rs/src/lib.rs @@ -1,11 +1,13 @@ pub mod decoder; pub mod tiff; +use tag_derive::Tag; +use tiff::file::TiffRead; +use tiff::tags::{Compression, ImageLength, ImageWidth, Model, StripByteCounts, SubIfd, Tag}; +use tiff::{Ifd, TiffError}; + use std::io::{Read, Seek}; use thiserror::Error; -use tiff::file::TiffRead; -use tiff::tags::{COMPRESSION, SUBIFD}; -use tiff::{Ifd, TiffError}; pub struct RawImage { pub data: Vec, @@ -20,21 +22,44 @@ pub struct Image { pub channels: u8, } +#[allow(dead_code)] +#[derive(Tag)] +struct ArwIfd { + image_width: ImageWidth, + image_height: ImageLength, + compression: Compression, + strip_byte_counts: StripByteCounts, +} + pub fn decode(reader: &mut R) -> Result { let mut file = TiffRead::new(reader)?; let ifd = Ifd::new_first_ifd(&mut file)?; // TODO: This is only for the tests to pass for now. Replace this with the correct implementation when the decoder is complete. - let subifd = ifd.get(SUBIFD, &mut file)?; + let model = ifd.get_value::(&mut file)?; - Ok(decoder::uncompressed::decode(subifd, &mut file)) + if model == "DSLR-A100" { + Ok(decoder::arw1::decode_a100(ifd, &mut file)) + } else { + let sub_ifd = ifd.get_value::(&mut file)?; + let arw_ifd = sub_ifd.get_value::(&mut file)?; + + if arw_ifd.compression == 1 { + Ok(decoder::uncompressed::decode(sub_ifd, &mut file)) + } else if arw_ifd.strip_byte_counts[0] == arw_ifd.image_width * arw_ifd.image_height { + Ok(decoder::arw2::decode(sub_ifd, &mut file)) + } else { + // TODO: implement for arw 1. + todo!() + } + } } -pub fn process_8bit(image: RawImage) -> Image { +pub fn process_8bit(_image: RawImage) -> Image { todo!() } -pub fn process_16bit(image: RawImage) -> Image { +pub fn process_16bit(_image: RawImage) -> Image { todo!() } diff --git a/libraries/raw-rs/src/tiff/file.rs b/libraries/raw-rs/src/tiff/file.rs index 2707cff4..9792fdbf 100644 --- a/libraries/raw-rs/src/tiff/file.rs +++ b/libraries/raw-rs/src/tiff/file.rs @@ -1,7 +1,7 @@ use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum Endian { +pub enum Endian { Little, Big, } @@ -15,7 +15,7 @@ impl TiffRead { pub fn new(mut reader: R) -> Result { let error = Error::new(ErrorKind::InvalidData, "Invalid Tiff format"); - let mut data = [0u8; 2]; + let mut data = [0_u8; 2]; reader.read_exact(&mut data)?; let endian = if data[0] == 0x49 && data[1] == 0x49 { Endian::Little @@ -36,6 +36,10 @@ impl TiffRead { Ok(Self { reader, endian }) } + + pub fn endian(&self) -> Endian { + self.endian + } } impl Read for TiffRead { @@ -61,7 +65,7 @@ impl TiffRead { } pub fn read_n(&mut self) -> Result<[u8; N]> { - let mut data = [0u8; N]; + let mut data = [0_u8; N]; self.read_exact(&mut data)?; Ok(data) } diff --git a/libraries/raw-rs/src/tiff/mod.rs b/libraries/raw-rs/src/tiff/mod.rs index 2a1bbde9..5ef5fe7e 100644 --- a/libraries/raw-rs/src/tiff/mod.rs +++ b/libraries/raw-rs/src/tiff/mod.rs @@ -4,13 +4,13 @@ mod types; pub mod values; use file::TiffRead; -use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; +use tags::Tag; + +use num_enum::{FromPrimitive, IntoPrimitive}; +use std::fmt::Display; use std::io::{Read, Seek}; use thiserror::Error; -use tags::Tag; -use types::TagType; - #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] #[repr(u16)] pub enum TagId { @@ -19,6 +19,8 @@ pub enum TagId { BitsPerSample = 0x102, Compression = 0x103, PhotometricInterpretation = 0x104, + Make = 0x10f, + Model = 0x110, StripOffsets = 0x111, SamplesPerPixel = 0x115, RowsPerStrip = 0x116, @@ -26,6 +28,7 @@ pub enum TagId { SubIfd = 0x14a, JpegOffset = 0x201, JpegLength = 0x202, + SonyToneCurve = 0x7010, CfaPatternDim = 0x828d, CfaPattern = 0x828e, @@ -34,26 +37,29 @@ pub enum TagId { } #[repr(u16)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] pub enum IfdTagType { - Ascii = 2, Byte = 1, + Ascii = 2, Short = 3, Long = 4, Rational = 5, SByte = 6, + Undefined = 7, SShort = 8, SLong = 9, SRational = 10, Float = 11, Double = 12, - Undefined = 7, + + #[num_enum(catch_all)] + Unknown(u16), } #[derive(Copy, Clone, Debug)] pub struct IfdEntry { tag: TagId, - type_: u16, + the_type: IfdTagType, count: u32, value: u32, } @@ -66,15 +72,15 @@ pub struct Ifd { } impl Ifd { - pub fn new_first_ifd(file: &mut TiffRead) -> std::io::Result { + pub fn new_first_ifd(file: &mut TiffRead) -> Result { file.seek_from_start(4)?; let current_ifd_offset = file.read_u32()?; Ifd::new_from_offset(file, current_ifd_offset) } - pub fn new_from_offset(file: &mut TiffRead, offset: u32) -> std::io::Result { + pub fn new_from_offset(file: &mut TiffRead, offset: u32) -> Result { if offset == 0 { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Ifd at offset zero does not exist")); + return Err(TiffError::InvalidOffset); } file.seek_from_start(offset)?; @@ -83,15 +89,15 @@ impl Ifd { let mut ifd_entries = Vec::with_capacity(num.into()); for _ in 0..num { let tag = file.read_u16()?.into(); - let type_ = file.read_u16()?; + let the_type = file.read_u16()?.into(); let count = file.read_u32()?; let value = file.read_u32()?; - ifd_entries.push(IfdEntry { tag, type_, count, value }); + ifd_entries.push(IfdEntry { tag, the_type, count, value }); } let next_ifd_offset = file.read_u32()?; - let next_ifd_offset = if next_ifd_offset == 0 { Some(next_ifd_offset) } else { None }; + let next_ifd_offset = if next_ifd_offset == 0 { None } else { Some(next_ifd_offset) }; Ok(Ifd { current_ifd_offset: offset, @@ -100,7 +106,7 @@ impl Ifd { }) } - fn next_ifd(&self, file: &mut TiffRead) -> std::io::Result { + fn next_ifd(&self, file: &mut TiffRead) -> Result { Ifd::new_from_offset(file, self.next_ifd_offset.unwrap_or(0)) } @@ -112,12 +118,33 @@ impl Ifd { self.ifd_entries.iter() } - pub fn get(&self, tag: Tag, file: &mut TiffRead) -> Result { - let tag_id = tag.id(); - let index: u32 = self.iter().position(|x| x.tag == tag_id).ok_or(TiffError::InvalidTag)?.try_into()?; + pub fn get_value(&self, file: &mut TiffRead) -> Result { + T::get(self, file) + } +} - file.seek_from_start(self.current_ifd_offset + 2 + 12 * index + 2)?; - T::read(file) +impl Display for Ifd { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("IFD offset: ")?; + self.current_ifd_offset.fmt(f)?; + f.write_str("\n")?; + + for ifd_entry in self.ifd_entries() { + f.write_fmt(format_args!( + "|- Tag: {:x?}, Type: {:?}, Count: {}, Value: {:x}\n", + ifd_entry.tag, ifd_entry.the_type, ifd_entry.count, ifd_entry.value + ))?; + } + + f.write_str("Next IFD offset: ")?; + if let Some(offset) = self.next_ifd_offset { + offset.fmt(f)?; + } else { + f.write_str("None")?; + } + f.write_str("\n")?; + + Ok(()) } } @@ -129,8 +156,10 @@ pub enum TiffError { InvalidType, #[error("The count was invalid")] InvalidCount, - #[error("The tag was invalid")] - InvalidTag, + #[error("The tag was missing")] + MissingTag, + #[error("The offset was invalid or zero")] + InvalidOffset, #[error("An error occurred when converting integer from one type to another")] ConversionError(#[from] std::num::TryFromIntError), #[error("An IO Error ocurred")] diff --git a/libraries/raw-rs/src/tiff/tags.rs b/libraries/raw-rs/src/tiff/tags.rs index e3a370d5..1162e8df 100644 --- a/libraries/raw-rs/src/tiff/tags.rs +++ b/libraries/raw-rs/src/tiff/tags.rs @@ -1,41 +1,188 @@ -use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeShort}; -use super::TagId; +use super::types::{Array, ConstArray, TagType, TypeByte, TypeIfd, TypeLong, TypeNumber, TypeShort, TypeSonyToneCurve, TypeString}; +use super::{Ifd, TagId, TiffError, TiffRead}; -pub struct Tag { - tag_id: TagId, - name: &'static str, - tag_type: std::marker::PhantomData, +use std::io::{Read, Seek}; + +pub trait SimpleTag { + type Type: TagType; + + const ID: TagId; + const NAME: &'static str; } -impl Tag { - const fn new(tag_id: TagId, name: &'static str) -> Self { - Tag { - tag_id, - name, - tag_type: std::marker::PhantomData, +pub struct ImageWidth; +pub struct ImageLength; +pub struct BitsPerSample; +pub struct Compression; +pub struct PhotometricInterpretation; +pub struct Make; +pub struct Model; +pub struct StripOffsets; +pub struct SamplesPerPixel; +pub struct RowsPerStrip; +pub struct StripByteCounts; +pub struct SubIfd; +pub struct JpegOffset; +pub struct JpegLength; +pub struct CfaPatternDim; +pub struct CfaPattern; +pub struct SonyDataOffset; +pub struct SonyToneCurve; + +impl SimpleTag for ImageWidth { + type Type = TypeNumber; + + const ID: TagId = TagId::ImageWidth; + const NAME: &'static str = "Image Width"; +} + +impl SimpleTag for ImageLength { + type Type = TypeNumber; + + const ID: TagId = TagId::ImageLength; + const NAME: &'static str = "Image Length"; +} + +impl SimpleTag for BitsPerSample { + type Type = TypeShort; + + const ID: TagId = TagId::BitsPerSample; + const NAME: &'static str = "Bits per Sample"; +} + +impl SimpleTag for Compression { + type Type = TypeShort; + + const ID: TagId = TagId::Compression; + const NAME: &'static str = "Compression"; +} + +impl SimpleTag for PhotometricInterpretation { + type Type = TypeShort; + + const ID: TagId = TagId::PhotometricInterpretation; + const NAME: &'static str = "Photometric Interpretation"; +} + +impl SimpleTag for Make { + type Type = TypeString; + + const ID: TagId = TagId::Make; + const NAME: &'static str = "Make"; +} + +impl SimpleTag for Model { + type Type = TypeString; + + const ID: TagId = TagId::Model; + const NAME: &'static str = "Model"; +} + +impl SimpleTag for StripOffsets { + type Type = Array; + + const ID: TagId = TagId::StripOffsets; + const NAME: &'static str = "Strip Offsets"; +} + +impl SimpleTag for SamplesPerPixel { + type Type = TypeShort; + + const ID: TagId = TagId::SamplesPerPixel; + const NAME: &'static str = "Samples per Pixel"; +} + +impl SimpleTag for RowsPerStrip { + type Type = TypeNumber; + + const ID: TagId = TagId::RowsPerStrip; + const NAME: &'static str = "Rows per Strip"; +} + +impl SimpleTag for StripByteCounts { + type Type = Array; + + const ID: TagId = TagId::StripByteCounts; + const NAME: &'static str = "Strip Byte Counts"; +} + +impl SimpleTag for SubIfd { + type Type = TypeIfd; + + const ID: TagId = TagId::SubIfd; + const NAME: &'static str = "SubIFD"; +} + +impl SimpleTag for JpegOffset { + type Type = TypeLong; + + const ID: TagId = TagId::JpegOffset; + const NAME: &'static str = "Jpeg Offset"; +} + +impl SimpleTag for JpegLength { + type Type = TypeLong; + + const ID: TagId = TagId::JpegLength; + const NAME: &'static str = "Jpeg Length"; +} + +impl SimpleTag for CfaPatternDim { + type Type = ConstArray; + + const ID: TagId = TagId::CfaPatternDim; + const NAME: &'static str = "CFA Pattern Dimension"; +} + +impl SimpleTag for CfaPattern { + type Type = Array; + + const ID: TagId = TagId::CfaPattern; + const NAME: &'static str = "CFA Pattern"; +} + +impl SimpleTag for SonyDataOffset { + type Type = TypeLong; + + const ID: TagId = TagId::SubIfd; + const NAME: &'static str = "Sony Data Offset"; +} + +impl SimpleTag for SonyToneCurve { + type Type = TypeSonyToneCurve; + + const ID: TagId = TagId::SonyToneCurve; + const NAME: &'static str = "Sony Tone Curve"; +} + +pub trait Tag { + type Output; + + fn get(ifd: &Ifd, file: &mut TiffRead) -> Result; +} + +impl Tag for T { + type Output = ::Output; + + fn get(ifd: &Ifd, file: &mut TiffRead) -> Result { + let tag_id = T::ID; + let index: u32 = ifd.iter().position(|x| x.tag == tag_id).ok_or(TiffError::MissingTag)?.try_into()?; + + file.seek_from_start(ifd.current_ifd_offset + 2 + 12 * index + 2)?; + T::Type::read(file) + } +} + +impl Tag for Option { + type Output = Option; + + fn get(ifd: &Ifd, file: &mut TiffRead) -> Result { + let result = T::get(ifd, file); + + match result { + Err(TiffError::MissingTag) => Ok(None), + Ok(x) => Ok(Some(x)), + Err(x) => Err(x), } } - - pub fn id(&self) -> TagId { - self.tag_id - } - - pub fn name(&self) -> &'static str { - self.name - } } - -pub const IMAGE_WIDTH: Tag = Tag::new(TagId::ImageWidth, "Image Width"); -pub const IMAGE_LENGTH: Tag = Tag::new(TagId::ImageLength, "Image Length"); -pub const BITS_PER_SAMPLE: Tag = Tag::new(TagId::BitsPerSample, "Bits per Sample"); -pub const COMPRESSION: Tag = Tag::new(TagId::Compression, "Compression"); -pub const PHOTOMETRIC_INTERPRETATION: Tag = Tag::new(TagId::PhotometricInterpretation, "Photometric Interpretation"); -pub const STRIP_OFFSETS: Tag> = Tag::new(TagId::StripOffsets, "Strip Offsets"); -pub const SAMPLES_PER_PIXEL: Tag = Tag::new(TagId::SamplesPerPixel, "Samples per Pixel"); -pub const ROWS_PER_STRIP: Tag = Tag::new(TagId::RowsPerStrip, "Rows per Strip"); -pub const STRIP_BYTE_COUNTS: Tag> = Tag::new(TagId::StripByteCounts, "Strip Byte Counts"); -pub const SUBIFD: Tag = Tag::new(TagId::SubIfd, "SubIFD"); -pub const JPEG_OFFSET: Tag = Tag::new(TagId::JpegOffset, "Jpeg Offset"); -pub const JPEG_LENGTH: Tag = Tag::new(TagId::JpegLength, "Jpeg Length"); -pub const CFA_PATTERN_DIM: Tag> = Tag::new(TagId::CfaPatternDim, "CFA Pattern Dimension"); -pub const CFA_PATTERN: Tag> = Tag::new(TagId::CfaPattern, "CFA Pattern"); diff --git a/libraries/raw-rs/src/tiff/types.rs b/libraries/raw-rs/src/tiff/types.rs index 5f1d9c69..8e255670 100644 --- a/libraries/raw-rs/src/tiff/types.rs +++ b/libraries/raw-rs/src/tiff/types.rs @@ -1,7 +1,7 @@ use std::io::{Read, Seek}; use super::file::TiffRead; -use super::values::Rational; +use super::values::{CurveLookupTable, Rational}; use super::{Ifd, IfdTagType, TiffError}; pub struct TypeAscii; @@ -24,16 +24,16 @@ pub struct TypeIfd; pub trait PrimitiveType { type Output; - fn get_size(type_: IfdTagType) -> Option; + fn get_size(the_type: IfdTagType) -> Option; - fn read_primitive(type_: IfdTagType, file: &mut TiffRead) -> Result; + fn read_primitive(the_type: IfdTagType, file: &mut TiffRead) -> Result; } impl PrimitiveType for TypeAscii { type Output = char; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Ascii => Some(1), _ => None, } @@ -52,8 +52,8 @@ impl PrimitiveType for TypeAscii { impl PrimitiveType for TypeByte { type Output = u8; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Byte => Some(1), _ => None, } @@ -67,8 +67,8 @@ impl PrimitiveType for TypeByte { impl PrimitiveType for TypeShort { type Output = u16; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Short => Some(2), _ => None, } @@ -82,8 +82,8 @@ impl PrimitiveType for TypeShort { impl PrimitiveType for TypeLong { type Output = u32; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Long => Some(4), _ => None, } @@ -97,16 +97,16 @@ impl PrimitiveType for TypeLong { impl PrimitiveType for TypeRational { type Output = Rational; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Rational => Some(8), _ => None, } } - fn read_primitive(type_: IfdTagType, file: &mut TiffRead) -> Result { - let numerator = TypeLong::read_primitive(type_, file)?; - let denominator = TypeLong::read_primitive(type_, file)?; + fn read_primitive(the_type: IfdTagType, file: &mut TiffRead) -> Result { + let numerator = TypeLong::read_primitive(the_type, file)?; + let denominator = TypeLong::read_primitive(the_type, file)?; Ok(Rational { numerator, denominator }) } @@ -115,8 +115,8 @@ impl PrimitiveType for TypeRational { impl PrimitiveType for TypeSByte { type Output = i8; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::SByte => Some(1), _ => None, } @@ -130,8 +130,8 @@ impl PrimitiveType for TypeSByte { impl PrimitiveType for TypeSShort { type Output = i16; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::SShort => Some(2), _ => None, } @@ -145,8 +145,8 @@ impl PrimitiveType for TypeSShort { impl PrimitiveType for TypeSLong { type Output = i32; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::SLong => Some(4), _ => None, } @@ -160,16 +160,16 @@ impl PrimitiveType for TypeSLong { impl PrimitiveType for TypeSRational { type Output = Rational; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::SRational => Some(8), _ => None, } } - fn read_primitive(type_: IfdTagType, file: &mut TiffRead) -> Result { - let numerator = TypeSLong::read_primitive(type_, file)?; - let denominator = TypeSLong::read_primitive(type_, file)?; + fn read_primitive(the_type: IfdTagType, file: &mut TiffRead) -> Result { + let numerator = TypeSLong::read_primitive(the_type, file)?; + let denominator = TypeSLong::read_primitive(the_type, file)?; Ok(Rational { numerator, denominator }) } @@ -178,8 +178,8 @@ impl PrimitiveType for TypeSRational { impl PrimitiveType for TypeFloat { type Output = f32; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Float => Some(4), _ => None, } @@ -193,8 +193,8 @@ impl PrimitiveType for TypeFloat { impl PrimitiveType for TypeDouble { type Output = f64; - fn get_size(type_: IfdTagType) -> Option { - match type_ { + fn get_size(the_type: IfdTagType) -> Option { + match the_type { IfdTagType::Double => Some(8), _ => None, } @@ -220,20 +220,20 @@ impl PrimitiveType for TypeUndefined { impl PrimitiveType for TypeNumber { type Output = u32; - fn get_size(type_: IfdTagType) -> Option { - match type_ { - IfdTagType::Byte => TypeByte::get_size(type_), - IfdTagType::Short => TypeShort::get_size(type_), - IfdTagType::Long => TypeLong::get_size(type_), + fn get_size(the_type: IfdTagType) -> Option { + match the_type { + IfdTagType::Byte => TypeByte::get_size(the_type), + IfdTagType::Short => TypeShort::get_size(the_type), + IfdTagType::Long => TypeLong::get_size(the_type), _ => None, } } - fn read_primitive(type_: IfdTagType, file: &mut TiffRead) -> Result { - Ok(match type_ { - IfdTagType::Byte => TypeByte::read_primitive(type_, file)?.into(), - IfdTagType::Short => TypeShort::read_primitive(type_, file)?.into(), - IfdTagType::Long => TypeLong::read_primitive(type_, file)?, + fn read_primitive(the_type: IfdTagType, file: &mut TiffRead) -> Result { + Ok(match the_type { + IfdTagType::Byte => TypeByte::read_primitive(the_type, file)?.into(), + IfdTagType::Short => TypeShort::read_primitive(the_type, file)?.into(), + IfdTagType::Long => TypeLong::read_primitive(the_type, file)?, _ => unreachable!(), }) } @@ -242,20 +242,20 @@ impl PrimitiveType for TypeNumber { impl PrimitiveType for TypeSNumber { type Output = i32; - fn get_size(type_: IfdTagType) -> Option { - match type_ { - IfdTagType::SByte => TypeSByte::get_size(type_), - IfdTagType::SShort => TypeSShort::get_size(type_), - IfdTagType::SLong => TypeSLong::get_size(type_), + fn get_size(the_type: IfdTagType) -> Option { + match the_type { + IfdTagType::SByte => TypeSByte::get_size(the_type), + IfdTagType::SShort => TypeSShort::get_size(the_type), + IfdTagType::SLong => TypeSLong::get_size(the_type), _ => None, } } - fn read_primitive(type_: IfdTagType, file: &mut TiffRead) -> Result { - Ok(match type_ { - IfdTagType::SByte => TypeSByte::read_primitive(type_, file)?.into(), - IfdTagType::SShort => TypeSShort::read_primitive(type_, file)?.into(), - IfdTagType::SLong => TypeSLong::read_primitive(type_, file)?, + fn read_primitive(the_type: IfdTagType, file: &mut TiffRead) -> Result { + Ok(match the_type { + IfdTagType::SByte => TypeSByte::read_primitive(the_type, file)?.into(), + IfdTagType::SShort => TypeSShort::read_primitive(the_type, file)?.into(), + IfdTagType::SLong => TypeSLong::read_primitive(the_type, file)?, _ => unreachable!(), }) } @@ -264,15 +264,12 @@ impl PrimitiveType for TypeSNumber { impl PrimitiveType for TypeIfd { type Output = Ifd; - fn get_size(type_: IfdTagType) -> Option { - match type_ { - IfdTagType::Long => Some(4), - _ => None, - } + fn get_size(the_type: IfdTagType) -> Option { + TypeLong::get_size(the_type) } - fn read_primitive(type_: IfdTagType, file: &mut TiffRead) -> Result { - let offset = TypeLong::read_primitive(type_, file)?; + fn read_primitive(the_type: IfdTagType, file: &mut TiffRead) -> Result { + let offset = TypeLong::read_primitive(the_type, file)?; Ok(Ifd::new_from_offset(file, offset)?) } } @@ -287,20 +284,20 @@ impl TagType for T { type Output = T::Output; fn read(file: &mut TiffRead) -> Result { - let type_ = IfdTagType::try_from(file.read_u16()?).map_err(|_| TiffError::InvalidType)?; + let the_type = IfdTagType::try_from(file.read_u16()?).map_err(|_| TiffError::InvalidType)?; let count = file.read_u32()?; if count != 1 { return Err(TiffError::InvalidCount); } - let size = T::get_size(type_).ok_or(TiffError::InvalidType)?; + let size = T::get_size(the_type).ok_or(TiffError::InvalidType)?; if count * size > 4 { let offset = file.read_u32()?; file.seek_from_start(offset)?; } - T::read_primitive(type_, file) + T::read_primitive(the_type, file) } } @@ -316,10 +313,10 @@ impl TagType for Array { type Output = Vec; fn read(file: &mut TiffRead) -> Result { - let type_ = IfdTagType::try_from(file.read_u16()?).map_err(|_| TiffError::InvalidType)?; + let the_type = IfdTagType::try_from(file.read_u16()?).map_err(|_| TiffError::InvalidType)?; let count = file.read_u32()?; - let size = T::get_size(type_).ok_or(TiffError::InvalidType)?; + let size = T::get_size(the_type).ok_or(TiffError::InvalidType)?; if count * size > 4 { let offset = file.read_u32()?; file.seek_from_start(offset)?; @@ -327,7 +324,7 @@ impl TagType for Array { let mut ans = Vec::with_capacity(count.try_into()?); for _ in 0..count { - ans.push(T::read_primitive(type_, file)?); + ans.push(T::read_primitive(the_type, file)?); } Ok(ans) } @@ -337,14 +334,14 @@ impl TagType for ConstArray { type Output = [T::Output; N]; fn read(file: &mut TiffRead) -> Result { - let type_ = IfdTagType::try_from(file.read_u16()?).map_err(|_| TiffError::InvalidType)?; + let the_type = IfdTagType::try_from(file.read_u16()?).map_err(|_| TiffError::InvalidType)?; let count = file.read_u32()?; if count != N.try_into()? { return Err(TiffError::InvalidCount); } - let size = T::get_size(type_).ok_or(TiffError::InvalidType)?; + let size = T::get_size(the_type).ok_or(TiffError::InvalidType)?; if count * size > 4 { let offset = file.read_u32()?; file.seek_from_start(offset)?; @@ -352,8 +349,32 @@ impl TagType for ConstArray { let mut ans = Vec::with_capacity(count.try_into()?); for _ in 0..count { - ans.push(T::read_primitive(type_, file)?); + ans.push(T::read_primitive(the_type, file)?); } ans.try_into().map_err(|_| TiffError::InvalidCount) } } + +pub struct TypeString; +pub struct TypeSonyToneCurve; + +impl TagType for TypeString { + type Output = String; + + fn read(file: &mut TiffRead) -> Result { + let string = Array::::read(file)?; + + // Skip the NUL character at the end + let len = string.len(); + Ok(string.into_iter().take(len - 1).collect()) + } +} + +impl TagType for TypeSonyToneCurve { + type Output = CurveLookupTable; + + fn read(file: &mut TiffRead) -> Result { + let values = ConstArray::::read(file)?; + Ok(CurveLookupTable::from_sony_tone_table(values)) + } +} diff --git a/libraries/raw-rs/src/tiff/values.rs b/libraries/raw-rs/src/tiff/values.rs index 407d82db..a5b04d04 100644 --- a/libraries/raw-rs/src/tiff/values.rs +++ b/libraries/raw-rs/src/tiff/values.rs @@ -2,3 +2,29 @@ pub struct Rational { pub numerator: T, pub denominator: T, } + +pub struct CurveLookupTable { + table: Vec, +} + +impl CurveLookupTable { + pub fn from_sony_tone_table(values: [u16; 4]) -> CurveLookupTable { + let mut sony_curve = [0, 0, 0, 0, 0, 4095]; + for i in 0..4 { + sony_curve[i + 1] = values[i] >> 2 & 0xfff; + } + + let mut table = vec![0_u16; (sony_curve[5] + 1).into()]; + for i in 0..5 { + for j in (sony_curve[i] + 1)..=sony_curve[i + 1] { + table[j as usize] = table[(j - 1) as usize] + (1 << i); + } + } + + CurveLookupTable { table } + } + + pub fn get(&self, x: usize) -> u16 { + self.table[x] + } +} diff --git a/libraries/raw-rs/tag-derive/Cargo.toml b/libraries/raw-rs/tag-derive/Cargo.toml new file mode 100644 index 00000000..fc129202 --- /dev/null +++ b/libraries/raw-rs/tag-derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tag-derive" +version = "0.0.1" +publish = false +edition = "2021" +authors = ["Graphite Authors "] +description = "Derive macro for the Tag trait in raw-rs" +license = "MIT OR Apache-2.0" +repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw-rs/tag-derive" + +[lib] +proc-macro = true + +[dependencies] +quote.workspace = true +syn.workspace = true diff --git a/libraries/raw-rs/tag-derive/src/lib.rs b/libraries/raw-rs/tag-derive/src/lib.rs new file mode 100644 index 00000000..eaaacce0 --- /dev/null +++ b/libraries/raw-rs/tag-derive/src/lib.rs @@ -0,0 +1,46 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{Data, DeriveInput, Fields}; + +#[proc_macro_derive(Tag)] +pub fn tag_derive(input: TokenStream) -> TokenStream { + let ast: DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + + let data_struct = if let Data::Struct(data_struct) = ast.data { + data_struct + } else { + panic!("Tag trait can only be derived for structs") + }; + + let named_fields = if let Fields::Named(named_fields) = data_struct.fields { + named_fields + } else { + panic!("Tag trait can only be derived for structs with named_fields") + }; + + let struct_idents: Vec<_> = named_fields.named.iter().map(|field| field.ident.clone().unwrap()).collect(); + let struct_types: Vec<_> = named_fields.named.iter().map(|field| field.ty.clone()).collect(); + + let new_name = format_ident!("_{}", name); + + let gen = quote! { + struct #new_name { + #( #struct_idents: <#struct_types as Tag>::Output ),* + } + + impl Tag for #name { + type Output = #new_name; + + fn get(ifd: &Ifd, file: &mut TiffRead) -> Result { + #( let #struct_idents = <#struct_types as Tag>::get(ifd, file)?; )* + Ok(#new_name { #( #struct_idents ),* }) + } + } + }; + + gen.into() +} diff --git a/libraries/raw-rs/tests/tests.rs b/libraries/raw-rs/tests/tests.rs index bb1b05f0..9fc6046c 100644 --- a/libraries/raw-rs/tests/tests.rs +++ b/libraries/raw-rs/tests/tests.rs @@ -9,7 +9,7 @@ use raw_rs::RawImage; use downloader::{Download, Downloader}; use libraw::Processor; -const TEST_FILES: [&str; 1] = ["ILCE-7M3-ARW2.3.5-blossoms.arw"]; +const TEST_FILES: [&str; 3] = ["ILCE-7M3-ARW2.3.5-blossoms.arw", "ILCE-7RM4-ARW2.3.5-kestrel.arw", "ILCE-6000-ARW2.3.1-windsock.arw"]; const BASE_URL: &str = "https://static.graphite.rs/test-data/libraries/raw-rs/"; const BASE_PATH: &str = "./tests/images";