diff --git a/Cargo.toml b/Cargo.toml index baf432f..1c3bdf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ categories = ["multimedia::images", "multimedia::encoding"] exclude = ["tests/images/*", "tests/fuzz_images/*"] [dependencies] +half = { version = "2.4.1" } weezl = "0.1.0" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } flate2 = "1.0.20" diff --git a/src/bytecast.rs b/src/bytecast.rs index 6e9d762..88d562c 100644 --- a/src/bytecast.rs +++ b/src/bytecast.rs @@ -12,6 +12,8 @@ //! TODO: Would like to use std-lib here. use std::{mem, slice}; +use half::f16; + macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => { pub(crate) fn $const(slice: &[$int]) -> &[u8] { assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); @@ -31,4 +33,5 @@ integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes); integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes); integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes); +integral_slice_as_bytes!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes); integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 97658f4..0137275 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -645,7 +645,12 @@ impl Image { self.jpeg_tables.as_deref().map(|a| &**a), )?; + eprintln!( + "{:?}, {:?}, {:?}", + self.predictor, self.bits_per_sample, self.sample_format + ); if output_row_stride == chunk_row_bytes as usize { + eprintln!("A"); let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize]; reader.read_exact(tile)?; @@ -662,6 +667,7 @@ impl Image { super::invert_colors(tile, color_type, self.sample_format); } } else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint { + eprintln!("B"); // The floating point predictor shuffles the padding bytes into the encoded output, so // this case is handled specially when needed. let mut encoded = vec![0u8; chunk_row_bytes]; @@ -679,6 +685,7 @@ impl Image { } } } else { + eprintln!("C"); for (i, row) in buf .chunks_mut(output_row_stride) .take(data_dims.1 as usize) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 60fb842..bbb0b6a 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -4,6 +4,7 @@ use std::io::{self, Read, Seek}; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; +use half::f16; use self::ifd::Directory; use self::image::Image; @@ -30,6 +31,8 @@ pub enum DecodingResult { U32(Vec), /// A vector of 64 bit unsigned ints U64(Vec), + /// A vector of 16 bit IEEE floats (held in u16) + F16(Vec), /// A vector of 32 bit IEEE floats F32(Vec), /// A vector of 64 bit IEEE floats @@ -93,6 +96,14 @@ impl DecodingResult { } } + fn new_f16(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / std::mem::size_of::() { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::F16(vec![f16::ZERO; size])) + } + } + fn new_i8(size: usize, limits: &Limits) -> TiffResult { if size > limits.decoding_buffer_size / std::mem::size_of::() { Err(TiffError::LimitsExceeded) @@ -131,6 +142,7 @@ impl DecodingResult { DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]), DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]), DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]), + DecodingResult::F16(ref mut buf) => DecodingBuffer::F16(&mut buf[start..]), DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]), DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]), DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]), @@ -151,6 +163,8 @@ pub enum DecodingBuffer<'a> { U32(&'a mut [u32]), /// A slice of 64 bit unsigned ints U64(&'a mut [u64]), + /// A slice of 16 bit IEEE floats + F16(&'a mut [f16]), /// A slice of 32 bit IEEE floats F32(&'a mut [f32]), /// A slice of 64 bit IEEE floats @@ -176,6 +190,7 @@ impl<'a> DecodingBuffer<'a> { DecodingBuffer::I32(buf) => bytecast::i32_as_ne_mut_bytes(buf), DecodingBuffer::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf), DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf), + DecodingBuffer::F16(buf) => bytecast::f16_as_ne_mut_bytes(buf), DecodingBuffer::F32(buf) => bytecast::f32_as_ne_mut_bytes(buf), DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf), } @@ -304,6 +319,19 @@ fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { } } +fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { + for i in samples..input.len() { + input[i] = input[i].wrapping_add(input[i - samples]); + } + + for (i, chunk) in output.chunks_mut(2).enumerate() { + chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([ + input[i], + input[input.len() / 4 + i], + ]))); + } +} + fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { for i in samples..input.len() { input[i] = input[i].wrapping_add(input[i - samples]); @@ -341,6 +369,7 @@ fn fix_endianness_and_predict( Predictor::FloatingPoint => { let mut buffer_copy = buf.to_vec(); match bit_depth { + 16 => predict_f16(&mut buffer_copy, buf, samples), 32 => predict_f32(&mut buffer_copy, buf, samples), 64 => predict_f64(&mut buffer_copy, buf, samples), _ => unreachable!("Caller should have validated arguments. Please file a bug."), @@ -989,6 +1018,7 @@ impl Decoder { )), }, SampleFormat::IEEEFP => match max_sample_bits { + 16 => DecodingResult::new_f16(buffer_size, &self.limits), 32 => DecodingResult::new_f32(buffer_size, &self.limits), 64 => DecodingResult::new_f64(buffer_size, &self.limits), n => Err(TiffError::UnsupportedError( diff --git a/tests/decode_fp16_images.rs b/tests/decode_fp16_images.rs new file mode 100644 index 0000000..e65684d --- /dev/null +++ b/tests/decode_fp16_images.rs @@ -0,0 +1,35 @@ +extern crate tiff; + +use tiff::decoder::{ifd, Decoder, DecodingResult}; +use tiff::tags::Tag; +use tiff::ColorType; + +use std::fs::File; +use std::path::PathBuf; + +const TEST_IMAGE_DIR: &str = "./tests/images/"; + +#[test] +fn test_ieee_fp16() { + let filenames = ["tile_1845_3473_13.tif"]; + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (513, 513) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::Gray(16) + ); + if let DecodingResult::F16(img_res) = decoder.read_image().unwrap() { + eprintln!("Num Pixels: {}", img_res.len()); + eprintln!("Pixel 0: {}", img_res[0]); + //assert_eq!(image_data, img_res); + } else { + panic!("Wrong data type"); + } + } +} diff --git a/tests/images/tile_1845_3473_13.tif b/tests/images/tile_1845_3473_13.tif new file mode 100644 index 0000000..0abd99f Binary files /dev/null and b/tests/images/tile_1845_3473_13.tif differ