-
Notifications
You must be signed in to change notification settings - Fork 622
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
Improve ImageDecoder trait #1869
Conversation
While working on image-rs I got the idea that this trait could go into something like an This would allow the format-specific crates to already implement |
Yeah, it would be nice to restructure that way. The challenge is that we have to be very confident in the trait definitions we pick, because making any backwards incompatible changes to them would cause tons of ecosystem churn |
The impact from a breaking change in another trait could be mitigated by some choices in usage. It's technically possible to depend on multiple versions of a single crate. What's hard is using the trait only in such ways that bumping to a new version is not a breaking change downstream. For instance, consider an opaque wrapper that happens to implement |
I think a progress callback is an essential feature for such a mature library. It's fine to not have it for small prototypes, but any real ui app or game would want to display some kind of progress. Instead, we should work on adding it to the decoders and encoders. For hdr, for example, it would be trivial to implement. I think it's more valuable to have it, even if support is rare, than to remove it and add it later again. Here's something we could try: Code up a wrapper around |
Retaining
|
About your second point: There is a way to tack-on progress on existing decoders: Observe the bytes that they read. But its only meaningful if we can make two assumptions:
if the assumptions don't hold, the progress can still be useful, but will be imprecise |
i sketched the idea at #2014 about your first point: i think a maybe An advantage of |
I think this is the most interesting part. Why don't most implementations provide that functionality? How can we take away the pain of implementing those features? Looking at the implementation of the hdr decoder, which has a loop that goes through all the scan lines. The implementation interest is something we cannot change. But we can make it easier for the implementations to add support for the features. The think core problem might be this: Those functions make assumptions about the internal file format layout. They can be easily implemented if the file stores scanlines from top to bottom. In any other case, they is no easy way to implement that functionality, or it would be extemely inefficient to the point of uselessness (example: to_reader reads the whole file and then just returns all the bytes at once, defeating the point of providing a reader). Many formats don't even read scanlines, but tiles. Maybe it would make sense to relax the requirements, by allowing the reader to read contents in arbitrary order, instead of forcing scanlines top to bottom. This might be a nice decider api to work with (but still cumbersome for implementations): let decoder = open("x.jpg")?;
let reader = decoder.reader(); // or optionally .sub_rect_reader(rect)
let mut pixel_buffer = vec![0_u8, reader.total_byte_size()];
loop {
let progress = reader.read_next(&mut pixel_buffer)?;
// note: the decoder can write any number of pixels anywhere into the buffer, in whatever order they appear in the file.
// this might be scanlines or tiles or even individual pixels, or even the whole image.
// maybe the progress object also returns the location of the pixels that were changed?
// the intermediate buffer and pixel coordinates can be used for an incremental image display
if progress.done() { return Ok(pixel_buffer) }
} This API would allow us to build a lot of convenience and abstraction on top of it. trait Decoder {
// ...
fn read_next(&self, pixel_buffer: &mut [u8]) -> Result<Progress>;
}
// we know the total number of pixels, so knowing the indices of the pixels that changed is enough to compute a reliable progress ratio: progress.bytes_writtyen_to_pixel_buffer.len() as f32 / total_byte_size as f32;
enum Progress {
ProcessedImageTile (TileIndices),
ProcessedPixelBufferBytes (Range<usize>),
// .. possibly more
Done,
} This design requires the decoders to keep some state, which might be cumbersome. Maybe we can abuse async/await syntax to write code that looks like a generator function? This design also will not work with external decoders that currently have a closure/callback mechanism for the progress. Unfortunately, this question will remain: What about the decoders that have external implementations and don't support any of this? |
Another thing that makes it annoying to convert decoders is that you want to reasonably throttle how often you call the progress callback. Too often and you'll destroy performance. Not often enough and the user won't get progress indications. Even just checking "should I invoke the callback" too often might interfere with SIMD and slow down our fastest encoders (which can hit multiple hundred million pixels per second!) I also realized that the current function signature won't work when trying to make the trait object safe. No methods with generic arguments can be called on a trait object. At which point, perhaps we might as well make a separate |
This sketches out possible changes we may want to make to the ImageDecoder trait in the next major release:
Adds a constructor (all non-deprecated implementations had one, but it wasn't part of the trait).CombinesImageDecoder
andImageDecoderRect
into_reader
,*_with_progress
, andscanline_bytes
methods. This is the aspect I'm least sure about, but few backends had meaningful implementations of these and it cuts down on the number of methods on the trait. Some decoders will still be able to support incremental decoding by doing multipleread_rect
callsThe object safety change in particular makes our internal usage of the trait much nicer. Instead of having to work with a DecoderVisitor trait, we can rely on dynamic dispatch.
For now I've held on updating the various format decoders. That'll be a bunch of work so it makes sense to settle on what the API should be first.Edit: scaled back some of these changes and updated all the format decoders.