Implement floating-point predictor (Predictor 3) encoding
Add support for encoding TIFF images with the floating-point predictor, which improves compression ratios for floating-point image data. The floating-point predictor works by: 1. Converting floats to big-endian bytes 2. Shuffling bytes so all first bytes are together, all second bytes, etc. 3. Applying byte-level horizontal differencing across the buffer This complements the existing decoding support for Predictor 3. Supported types: Gray32Float, Gray64Float, RGB32Float, RGB64Float, RGBA32Float, RGBA64Float, CMYK32Float, CMYK64Float. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ac8be1177
commit
3f2066bb20
4 files changed
+267
-14
No files matched your search
+113
-8
@@ -1,5 +1,66 @@
|
|||||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
||||||
|
|
||||||
|
/// Apply floating-point predictor encoding to a row of f32 samples.
|
||||||
|
///
|
||||||
|
/// The floating-point predictor works by:
|
||||||
|
/// 1. Converting each f32 to big-endian bytes
|
||||||
|
/// 2. Shuffling bytes so all first bytes are together, all second bytes, etc.
|
||||||
|
/// 3. Applying byte-level horizontal difference (wrapping_sub) across the entire buffer
|
||||||
|
///
|
||||||
|
/// The decoder reverses this by:
|
||||||
|
/// 1. Applying cumulative sum (wrapping_add) on the entire byte stream
|
||||||
|
/// 2. De-shuffling bytes back into f32 values
|
||||||
|
fn fp_predict_f32(row: &[f32], samples: usize, result: &mut Vec<u8>) {
|
||||||
|
let num_values = row.len();
|
||||||
|
let byte_count = num_values * 4;
|
||||||
|
let start_len = result.len();
|
||||||
|
|
||||||
|
// Reserve space for the output
|
||||||
|
result.reserve(byte_count);
|
||||||
|
|
||||||
|
// Step 1 & 2: Convert to big-endian and shuffle bytes
|
||||||
|
// All first bytes, then all second bytes, etc.
|
||||||
|
for byte_idx in 0..4 {
|
||||||
|
for &value in row {
|
||||||
|
let bytes = value.to_be_bytes();
|
||||||
|
result.push(bytes[byte_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Apply byte-level horizontal difference across the entire buffer
|
||||||
|
// We iterate backwards so that when we compute output[i] - output[i-samples],
|
||||||
|
// output[i-samples] still has its original value.
|
||||||
|
// The prediction spans across quarter boundaries, just like the decoder's cumulative sum.
|
||||||
|
let output = &mut result[start_len..];
|
||||||
|
for i in (samples..byte_count).rev() {
|
||||||
|
output[i] = output[i].wrapping_sub(output[i - samples]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply floating-point predictor encoding to a row of f64 samples.
|
||||||
|
fn fp_predict_f64(row: &[f64], samples: usize, result: &mut Vec<u8>) {
|
||||||
|
let num_values = row.len();
|
||||||
|
let byte_count = num_values * 8;
|
||||||
|
let start_len = result.len();
|
||||||
|
|
||||||
|
// Reserve space for the output
|
||||||
|
result.reserve(byte_count);
|
||||||
|
|
||||||
|
// Step 1 & 2: Convert to big-endian and shuffle bytes
|
||||||
|
for byte_idx in 0..8 {
|
||||||
|
for &value in row {
|
||||||
|
let bytes = value.to_be_bytes();
|
||||||
|
result.push(bytes[byte_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Apply byte-level horizontal difference across the entire buffer
|
||||||
|
let output = &mut result[start_len..];
|
||||||
|
for i in (samples..byte_count).rev() {
|
||||||
|
output[i] = output[i].wrapping_sub(output[i - samples]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! integer_horizontal_predict {
|
macro_rules! integer_horizontal_predict {
|
||||||
() => {
|
() => {
|
||||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
||||||
@@ -23,6 +84,10 @@ macro_rules! integer_horizontal_predict {
|
|||||||
.map(|(prev, current)| current.wrapping_sub(*prev)),
|
.map(|(prev, current)| current.wrapping_sub(*prev)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(_: &[Self::Inner], _: &mut Vec<u8>) {
|
||||||
|
unreachable!("floating-point predictor is only valid for floating-point sample types")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +103,10 @@ pub trait ColorType {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat];
|
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||||
|
|
||||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
||||||
|
|
||||||
|
/// Apply floating-point predictor encoding to a row of samples.
|
||||||
|
/// This is only implemented for floating-point types; integer types will panic.
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray8;
|
pub struct Gray8;
|
||||||
@@ -108,7 +177,11 @@ impl ColorType for Gray32Float {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +213,11 @@ impl ColorType for Gray64Float {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +257,13 @@ impl ColorType for RGB32Float {
|
|||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,8 +283,13 @@ impl ColorType for RGB64Float {
|
|||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +329,13 @@ impl ColorType for RGBA32Float {
|
|||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,8 +355,13 @@ impl ColorType for RGBA64Float {
|
|||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +403,11 @@ impl ColorType for CMYK32Float {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +429,11 @@ impl ColorType for CMYK64Float {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||||
unreachable!()
|
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||||
|
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-4
@@ -33,9 +33,10 @@ use self::writer::*;
|
|||||||
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
||||||
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
||||||
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
||||||
/// ratio over the unpredicted compression !
|
/// ratio over the unpredicted compression!
|
||||||
///
|
///
|
||||||
/// [Predictor::FloatingPoint] is currently not supported.
|
/// [Predictor::Horizontal] is for integer sample types.
|
||||||
|
/// [Predictor::FloatingPoint] is for floating-point sample types (f32, f64).
|
||||||
pub type Predictor = crate::tags::Predictor;
|
pub type Predictor = crate::tags::Predictor;
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub type DeflateLevel = compression::DeflateLevel;
|
pub type DeflateLevel = compression::DeflateLevel;
|
||||||
@@ -635,11 +636,14 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
|||||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
||||||
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
||||||
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
||||||
|
// Horizontal predictor is not valid for floating-point types
|
||||||
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
||||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||||
}
|
}
|
||||||
|
// Floating-point predictor is only valid for IEEEFP sample format
|
||||||
|
(Predictor::FloatingPoint, _, SampleFormat::IEEEFP) => Ok(()),
|
||||||
(Predictor::FloatingPoint, _, _) => {
|
(Predictor::FloatingPoint, _, _) => {
|
||||||
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
|
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
@@ -773,7 +777,14 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
}
|
}
|
||||||
self.encoder.write_data(row_result.as_slice())?
|
self.encoder.write_data(row_result.as_slice())?
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
Predictor::FloatingPoint => {
|
||||||
|
let byte_size = mem::size_of::<T::Inner>();
|
||||||
|
let mut row_result = Vec::with_capacity(value.len() * byte_size);
|
||||||
|
for row in value.chunks_exact(self.row_samples as usize) {
|
||||||
|
T::floating_point_predict(row, &mut row_result);
|
||||||
|
}
|
||||||
|
self.encoder.write_data(row_result.as_slice())?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let byte_count = self.encoder.last_written() as usize;
|
let byte_count = self.encoder.last_written() as usize;
|
||||||
|
|||||||
+3
-1
@@ -357,7 +357,9 @@ pub enum Predictor(u16) {
|
|||||||
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
||||||
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
||||||
Horizontal = 2,
|
Horizontal = 2,
|
||||||
/// Not currently supported
|
/// Floating-point predictor for IEEE floating-point sample types.
|
||||||
|
///
|
||||||
|
/// This predictor byte-shuffles the data and applies byte-level horizontal differencing.
|
||||||
FloatingPoint = 3,
|
FloatingPoint = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+136
-1
@@ -1,7 +1,7 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
||||||
use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
|
use tiff::encoder::{colortype, Ifd, Ifd8, Predictor, SRational, TiffEncoder};
|
||||||
use tiff::tags::Tag;
|
use tiff::tags::Tag;
|
||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
@@ -379,6 +379,141 @@ fn test_ycbcr_u8_roundtrip() {
|
|||||||
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test that encoding with the floating-point predictor works correctly
|
||||||
|
/// and can be decoded back to the original data.
|
||||||
|
#[test]
|
||||||
|
fn test_gray_f32_floating_point_predictor() {
|
||||||
|
// Generate test data: a gradient of f32 values
|
||||||
|
let width = 32u32;
|
||||||
|
let height = 32u32;
|
||||||
|
let mut image_data: Vec<f32> = Vec::with_capacity((width * height) as usize);
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
// Create a gradient pattern
|
||||||
|
image_data.push((x as f32 + y as f32 * 0.1) / 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = Cursor::new(Vec::new());
|
||||||
|
{
|
||||||
|
let mut tiff = TiffEncoder::new(&mut file)
|
||||||
|
.unwrap()
|
||||||
|
.with_predictor(Predictor::FloatingPoint);
|
||||||
|
tiff.write_image::<colortype::Gray32Float>(width, height, &image_data)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(32));
|
||||||
|
|
||||||
|
let mut buffer = DecodingResult::F32(vec![]);
|
||||||
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
if let DecodingResult::F32(img_res) = buffer {
|
||||||
|
assert_eq!(image_data.len(), img_res.len());
|
||||||
|
for (orig, decoded) in image_data.iter().zip(img_res.iter()) {
|
||||||
|
assert_eq!(
|
||||||
|
*orig, *decoded,
|
||||||
|
"Mismatch at value: orig={}, decoded={}",
|
||||||
|
orig, decoded
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Wrong data type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test floating-point predictor with RGB f32 data
|
||||||
|
#[test]
|
||||||
|
fn test_rgb_f32_floating_point_predictor() {
|
||||||
|
let width = 16u32;
|
||||||
|
let height = 16u32;
|
||||||
|
let mut image_data: Vec<f32> = Vec::with_capacity((width * height * 3) as usize);
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
// RGB pattern
|
||||||
|
image_data.push(x as f32 / width as f32); // R
|
||||||
|
image_data.push(y as f32 / height as f32); // G
|
||||||
|
image_data.push((x + y) as f32 / (width + height) as f32); // B
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = Cursor::new(Vec::new());
|
||||||
|
{
|
||||||
|
let mut tiff = TiffEncoder::new(&mut file)
|
||||||
|
.unwrap()
|
||||||
|
.with_predictor(Predictor::FloatingPoint);
|
||||||
|
tiff.write_image::<colortype::RGB32Float>(width, height, &image_data)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(32));
|
||||||
|
|
||||||
|
let mut buffer = DecodingResult::F32(vec![]);
|
||||||
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
if let DecodingResult::F32(img_res) = buffer {
|
||||||
|
assert_eq!(image_data.len(), img_res.len());
|
||||||
|
for (i, (orig, decoded)) in image_data.iter().zip(img_res.iter()).enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
*orig, *decoded,
|
||||||
|
"Mismatch at index {}: orig={}, decoded={}",
|
||||||
|
i, orig, decoded
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Wrong data type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test floating-point predictor with f64 data
|
||||||
|
#[test]
|
||||||
|
fn test_gray_f64_floating_point_predictor() {
|
||||||
|
let width = 16u32;
|
||||||
|
let height = 16u32;
|
||||||
|
let mut image_data: Vec<f64> = Vec::with_capacity((width * height) as usize);
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
image_data.push((x as f64 + y as f64 * 0.1) / 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = Cursor::new(Vec::new());
|
||||||
|
{
|
||||||
|
let mut tiff = TiffEncoder::new(&mut file)
|
||||||
|
.unwrap()
|
||||||
|
.with_predictor(Predictor::FloatingPoint);
|
||||||
|
tiff.write_image::<colortype::Gray64Float>(width, height, &image_data)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(64));
|
||||||
|
|
||||||
|
let mut buffer = DecodingResult::F64(vec![]);
|
||||||
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
if let DecodingResult::F64(img_res) = buffer {
|
||||||
|
assert_eq!(image_data.len(), img_res.len());
|
||||||
|
for (orig, decoded) in image_data.iter().zip(img_res.iter()) {
|
||||||
|
assert_eq!(
|
||||||
|
*orig, *decoded,
|
||||||
|
"Mismatch: orig={}, decoded={}",
|
||||||
|
orig, decoded
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Wrong data type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait AssertDecode {
|
trait AssertDecode {
|
||||||
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
||||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||||
|
|||||||
Reference in new issue
Block a user