-
Notifications
You must be signed in to change notification settings - Fork 624
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
Replace jpeg_decoder with zune-jpeg #1877
Conversation
This doesn't handle setting limits, and that may prove challenging with the current design. If I understand the draft code then it right now is hard-coded to use zune-jpeg's default limits. The specific challenge to fixing that is that |
There's a more general the limit on the dimensions, but JPEGs can trivially lie about their dimensions. And in case the JPEG declares its dimensions incorrectly, the So it's not a regression, but I do agree that something should be done about that in the long term. The thorny issue is buffering the entire input - the input alone could exceed the limit before we even try to decode it. I could make reading the input lazy, to make it possible to first call Alternatively we could read the first N bytes (up to some low limit, unless overridden?) and parse the headers in that slice only. I don't know if there is any kind of bound on the data required to determine the dimensions and colorspace, but my guess is "no". I suppose we could hardcode some sort of reasonable-sounding limit and ignore images that have the colorspace definition 100MB in? Or we could combine the two approaches and only read the input up to half of the currently set total memory limit? |
Hm, the longer term solution might be to do an API break to require Given that it isn't a regression, I'd be OK with merging this without limit support. However, given that zune-jpeg is outside of image-rs, we should make sure that they're amenable to adding the feature additions we'd need to implement our limits API. |
I think the only thing the decoder actually needs to support is capping the maximum amount of allocated memory. @etemesi254 what do you think? Would adding a memory limit (even at least a loosely adhered to) to |
What should it be based on? This is how much memory we allocate
This can be controlled by
We can't cap it any less |
Is there a |
For our purposes, simply being able to query an estimate of how much memory is required to decode a specific file should be enough. Our limits are also best effort so if a limit of 10 MB is requested, it is OK if the actual amount used is 11 MB. If a limit of 10 MB is requested, but 500 MB is allocated, that's less OK. The tricker part is that we will need to have a way of getting image metadata without buffering the entire image contents in memory. A user could hypothetically ask us to decode |
@Shnatsel making fancier decisions based on image metadata is actually part of the intention behind the current ImageDecoder API. You can query the image dimensions or total output bytes before deciding whether to decode the image. You can even base the cap on memory allocations based on those factors. |
Whatever buffer the library allocates. The |
This doesn't handle setting limits, and that may prove challenging with the current design. If I understand the draft code then it right now is hard-coded to use zune-jpeg's default limits. The specific challenge to fixing that is that ImageDecoder::set_limits may be called after new, which means that any allocations in the constructor must be of bounded size and any limits passed to zune-jpeg must be set after that method is called. This can be added, all we need is the ability to expose the options after they have been set.
Reading limits up until a certain bytes will probably blow up in your face, jpeg may have limits on how markers are arranged, but real world usage means no one will follow that, and if you do it strictly it will cause an image somewhere not to decode.
What if you would do reading+seeking until you hit the sof marker, this can be simplified by the fact that jpeg makers consist of 2 bytes -marker, 2 bytes length, if a marker is not SOF, you seek past its bytes until the next start up until you hit eof. Also the case about |
As a strategy for determining the length of the image to buffer, that sounds perfectly acceptable to me. Regarding stream length, it works out to |
/// Some decoders support scaling the image during decoding, | ||
/// but the current backend, `zune-jpeg`, doesn't, | ||
/// so this function currently does nothing | ||
/// and always returns the original dimensions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on the resolution of etemesi254/zune-image#103, it may make sense to just deprecate this method
Co-authored-by: Jonathan Behrens <[email protected]>
…e never going to be bit-exact
I've also implemented limits on dimensions because those were easy. Memory limits will come in a follow-up PR. |
This cuts peak memory consumption nearly in half - from 150MB to 84MB on this image So the loading of the full compressed image into memory is probably not a big deal after all. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a code pov of the new implementation this seems alright. All reasonable limits, color space, even taking care of backwards compatibility of features. The only thing bugging is the transition story for 16-bit images. What would we tell users who may rely on it. The basic options:
- Make a promise to follow up quick enough
- Accept the breakage (not very desirable...)
- Some path to migration to use
jpeg-decoder
directly while it's being worked on
@etemesi254 what do you think? |
16-bit JPEG seems to be a non-standard extension, neither Wikipedia nor a quick Google search mention anything about 16-bit support in JPEG. I dug up the original PR for
|
There was never a decision of how to handle such images. And here are some things that need clarification even before we see way forward.
Endianess I'm more of native endian person API, suggest another method, |
Sounds fine. Which crate does the conversion isn't so critical in the cases where it needs to be done once, as long as it only occurs once. Late conversion sometimes allows eliding it alltogether, early conversion allows the data reduction mentioned by you. Ideally, Anyways, on the question of api: returning data into a I don't want to start ¹ Maybe these should be approached as two separate uses, with two separate settings. One for providing some upper bound on required data fidelity, as well as feedback from the decoder on how it actually will be provided and in what layout. Trying to fit them into one design is hard; but that is probably inherent to the complex problem space. |
Some other projects and how they do it
So I do agree with bytemuck/image-canvas but would say it works well if it's weakly typed but enforced at runtime, i.e let it be a bag of bytes and at runtime you can have checks that make sure those invariants are upheld. You can uphold type variants by some Just my two cents |
Heh, that's quite neat. Not sure if you need to but FYI |
To summarize the interface question with regards to this PR: there's no need to change anything. 16-bit non-lossy is not standard (and certainly not baseline). As options, a fallback to the jpeg_decoder crate is still available—as is copying the existing bindings for ¹ Though one thing that does arise as a nice-to-have would be a way to override the default |
Hi @Shnatsel, Thanks for putting this together. Are there any blockers for merging this? |
Yes, something needs to be decided about Lossless JPEG. It is a rare format used in medical imaging but rarely found outside it, that |
@vsaase, I see you authored image-rs/jpeg-decoder#193 -- do you have any thoughts of the best way to handle this? |
I am not involved with DICOM and Rust anymore, maybe @Enet4 can comment on how this is currently handled? |
It would be valuable to know if Lossless JPEG is used through the |
Thank you for mentioning me. Lossless JPEG and extended JPEG support is indeed relevant for the DICOM-rs project, as it's one common form of compression found in medical imaging. It is worth keeping in mind that these forms of JPEG are standardized per the DICOM standard, although most sources seem to either be oblivious of such capabilities in JPEG or just assume them not to be officially standard. This confusion around JPEG is also one of the reasons why I'm hoping for JPEG-XL to thrive, although Chrome kind of hurt the chances... But I digress. 😶 As far as the DICOM-rs project goes, decoding of encapsulated JPEG pixel data already depends on The project also has a secondary tool I personally would be thrilled to be able to use |
Hi, lossless jpeg is standardized by the jpeg spec, but is rarely used outside of medical applications as noted, since it is a spec part, I am more welcome to accept a PR in case anyone has time to implement(it is beyond my scope/interest). Seems image supports lossless 16 bit images in grayscale ( image/src/codecs/jpeg/decoder.rs Lines 150 to 158 in 139420d
I'm not sure if that indicates lossless support in image, the only viable way to know if it would work would be to try and decode an image |
If However, that would be a DoS vector because Since removing Lossless JPEG from |
For posterity, this has been rebased in #2141 and merged! 🎉 |
Like #1876 but replaces
jpeg_decoder
withzune-jpeg
outright without breaking the API.TODO:
zune-jpeg
release with theError
impl (currently uses git)zune-jpeg
to support ICC profile extraction zune-jpeg: Support extracting the ICC profile etemesi254/zune-image#101Support lossless JPEG somehow for backwards compatibilityNewly added features in this PR:
Out of scope of this PR:
jpeg_decoder