Skip to content

Conversation

sunipkm
Copy link
Contributor

@sunipkm sunipkm commented Jun 2, 2024

I license past and future contributions under the dual MIT/Apache-2.0 license,
allowing licensees to choose either at their option.

f32 encoding/decoding support is added without additional byte alignment requirements. Removed 2-byte alignment requirement for u16 encoding/decoding. Added TIFF compression support.

@fintelia
Copy link
Contributor

fintelia commented Jun 2, 2024

CI seems to be failing because this exposes the tiff crate's Compressor type

@sunipkm
Copy link
Contributor Author

sunipkm commented Aug 10, 2024

I somehow missed this, just pushed changes that hide tiff crate's Compressor type.

@sunipkm
Copy link
Contributor Author

sunipkm commented Sep 10, 2024

@fintelia can you please take a look at this?

@fintelia
Copy link
Contributor

fintelia commented Sep 12, 2024

I've been taking a hiatus from most PR review and issue triage because I got burned out following the 0.25 release. Another reviewer might have a chance to review this at some point

@197g 197g self-requested a review September 12, 2024 23:56
@sunipkm
Copy link
Contributor Author

sunipkm commented Sep 13, 2024

@fintelia I hope you feel better soon!

Copy link
Member

@197g 197g left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bindings themselves look quite alright, I think the lower-level code could be cleaned up quite a lot with help of bytemuck and the standard library. Quite boilerplatey.

Copy link
Contributor

@fintelia fintelia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ideally should have been two PRs for f32 support and compression respectively


impl Default for CompressionType {
fn default() -> Self {
CompressionType::Deflate(Default::default())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, LZW is more common for TIFF files so that should probably be the default.

Copy link
Member

@197g 197g Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting LZW would seem to be more a historical accident. If we're going with libtiff as upstream we might want to also look into zstd support: http://libtiff.maptools.org/v4.0.10.html And I think its default option is no compression?

Please note that COMPRESSION_ZSTD is self-assigned the id 50000
by the libtiff project and is not officially registered with Adobe
since Adobe's registration function is defunct.

Yikes.

We should be making our own comparison in terms of size / speed. Based on the compression test this might favor deflate if Balanced achieves comparable size to LZW. Which would not be all that surprising, LZW is the oldest of the bunch.

out_path.push(path.file_name().unwrap());
let encoder = TiffEncoder::new_with_compression(
fs::File::create(&out_path).unwrap(),
CompressionType::Deflate(TiffDeflateLevel::Balanced),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How long does this test take? I could imagine that compressing all the input files with balanced compression could take a while, though I don't know how many / how large they are.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.35s and 0.53s respectively. Without the IO for compressed output it is 0.33 and 0.45 so I'll remove the file write. Seems fine although may we want to restrict it to a particular set of files with enough coverage at some point.

Comment on lines 473 to 477
/// It is best to view the options as a _hint_ to the implementation on the smallest or fastest
/// option for encoding a particular image. That is, using options that map directly to a TIFF
/// image parameter will use this parameter where possible. But variants that have no direct
/// mapping may be interpreted differently in minor versions. The exact output is expressly
/// __not__ part of the SemVer stability guarantee.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a more natural note to put on CompressionType::Deflate to explain that the compression levels don't have well defined meaning. Getting the requested compression algorithm seems like something that should be guaranteed.

Comment on lines 164 to 165
Some(SampleFormat::Uint) if num_bits <= 16 => Ok(()),
Some(SampleFormat::IEEEFP) if num_bits == 32 => Ok(()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the implicitness of "num_bits <= 16 means integer" and "num_bits == 32 means float". But really this should be addressed by having the tiff crate expose a way to directly get the sample format, so I'm OK with doing it this way for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not implicit, the SampleFormat is itself parsed from a tiff tag. They just happen to not overlap, too. We do not have fp16 support in tiff (due to predictor math) and we do not support loading u32/i32 channels into image. But the match here would be unique even without the conditions. Nonetheless, the tiff API should return all that information in one struct instead of strewn over ColorType and manual tag fetching so this will change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implicit is probably the wrong word. The concern is that the tiff::ColorType::RGB(32) => ColorType::Rgb32F line above only works because of the checks that are happening here. The result is you have to read the implementation of check_sample_format to understand what is being checked, and only afterwards can you understand why new can match on color type without checking the sample format.

If new had access to both the sample format and sample type, the logic could be more straightforward:

match sample_format {
    SampleFormat::Uint => match color_type {
        RGB(8) => ColorType::Rgb8,
        ...
    },
    SampleFormat::IEEEFP => match color_type {
        RGB(32) => ColorType::Rgb32F
        ...
    }
}

Comment on lines 513 to 518
match self.comp {
Compression::Uncompressed => {}
Compression::Lzw | Compression::Deflate(_) | Compression::Packbits => {
encoder = encoder.with_compression(self.comp);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
match self.comp {
Compression::Uncompressed => {}
Compression::Lzw | Compression::Deflate(_) | Compression::Packbits => {
encoder = encoder.with_compression(self.comp);
}
}
if self.comp != Compression::Uncompressed {
encoder = encoder.with_compression(self.comp);
}

@197g
Copy link
Member

197g commented Jul 21, 2025

This ideally should have been two PRs for f32 support and compression respectively

Alright, I'll split it up. It also seems to me like f32 support was simpler than the compression part.

@maxall41
Copy link

maxall41 commented Jul 29, 2025

I think Iv'e found an issue with either this PR or #2534. I started by forking the image-rs main branch and then I used the following commands to merge this PR into the branch x on my fork:

git remote add sunipkm https://github.com/sunipkm/image-rs.git
git fetch origin
git fetch sunipkm
git checkout origin/main
git checkout -b merge-tiff-compress
git merge sunipkm/tiff-compress
git push origin merge-tiff-compress

I then setup my application to use the merge-tiff-compress branch on my fork with the following:

image = { git = "https://github.com/maxall41/image.git", branch = "merge-tiff-compress" }

With this setup I am able to successfully load all of the tiff images in the test suite added in PR #2534. But with the attached image I get the following error while attempting to decode:

The encoder or decoder for Tiff does not support the color type `Unknown(32)`

Tiff info:

❯ tiffinfo reproduce.tif
TIFFReadDirectory: Warning, Unknown field with tag 50838 (0xc696) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 50839 (0xc697) encountered.
=== TIFF directory 0 ===
TIFF Directory at offset 0x8 (8)
  Subfile Type: (0 = 0x0)
  Image Width: 424 Image Length: 546
  Bits/Sample: 32
  Sample Format: IEEE floating point
  Compression Scheme: None
  Photometric Interpretation: min-is-black
  Samples/Pixel: 1
  Rows/Strip: 546
  Planar Configuration: single image plane
  ImageDescription: ImageJ=1.54p
min=0.0
max=1.0

  Tag 50838: 12,22,14,20,8
  Tag 50839: 73,74,73,74,112,114,111,112,0,0,0,4,0,83,0,108,0,105,0,99,0,101,0,95,0,76,0,97,0,98,0,101,0,108,0,99,0,108,0,97,0,115,0,115,0,32,0,49,0,85,0,110,0,105,0,113,0,117,0,101,0,78,0,97,0,109,0,101,0,116,0,114,0,117,0,101

Image to reproduce issue: https://drive.google.com/file/d/1qcnFq-LCde0SFxxuKNgCSnr4sq2OnpuS/view?usp=sharing

I am able to load this image in ImageJ2/Fiji and Apple Preview.

@fintelia
Copy link
Contributor

@maxall41 This PR adds RGB and RGBA 32-bit floating point support, but notably doesn't add support for single-channel floating point images like the one you attached.

Though it would probably be good to adjust the error message...

@maxall41
Copy link

@fintelia Ah that explains it. I might write a PR to add support...

@maxall41
Copy link

Just made a PR: #2536

@Shnatsel Shnatsel added the kind: new features An operation that does not exist yet, add draft tag if appropriate label Aug 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: new features An operation that does not exist yet, add draft tag if appropriate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants