etemesi254 / zune-image

A fast and memory efficient image library in Rust
Other
329 stars 30 forks source link

PNG: extend roundtrip fuzzing to low bit depths #76

Open Shnatsel opened 1 year ago

Shnatsel commented 1 year ago

Right now roundtrip fuzzing in PNG is disabled for bit depths lower than 8:

https://github.com/etemesi254/zune-image/blob/008f02ac24464870e0b18e5172a4db5cd59e0bb8/zune-png/fuzz/fuzz_targets/roundtrip.rs#L17

As noted here, zune-png expands the low bit depths up to 8 bits, but the fuzz target doesn't account for that.

Shnatsel commented 1 year ago

I've taken a stab at this, but the expansion function is tied to the rest of the decoding in not entirely trivial ways, so it's not just a matter of adding a wrapper that's only exposed when fuzzing as I hoped:

#[cfg(fuzzing)]
pub fn some_function() {
    some_function()
}
etemesi254 commented 1 year ago

It's tied in only one way that makes it annoying to handle small bit depths.

It's decode_interlaced part. Those passes expect it to be 8 bit.

And another annoying thing is that the spec contradicts itself

Images with lower bit depths < 8 and PLTE chunks exists for which the spec says we use entries from 0-(2^bitdepth)-1 Which makes sense, but then each PLTE chunk is 8 bits/1 byte.

Png with small bit depths look like, assuming a bit depth of 4, look like z= [AAAA | BBBB] with AAAA being the first pixel and BBBB being the second.

So in order to read pLTE chunks correctly, we have to shift AAAA, load it's plTE chunk, get the value, and pack it back into z.

That is a lot of work, and I'd prefer that we have the slogan correct and fast, with priority as specified.

I'd prefer I take this one on since you mentioned it is tied to the rest of the steps, it may not be easy and I may punt up and write a expansion to reduction pass instead, will close this when done.

Shnatsel commented 1 year ago

FWIW so far I'm looking at fuzzing the images with low bit depths but without a palette; e.g. 1-bit black-and-white ones. I haven't considered paletted images yet.

Although another contributor has opened a PR for the png crate adding paletted roundtrip too, perhaps the palette generation could be reused for fuzzing zune-png.

etemesi254 commented 1 year ago

I've been slowly working on this and it dawned to me that there is no fast way to do this and 8 bit/16 bit jpegs

I'll add a pass to convert 8 bit images to low bit depths after I finish the alloc friendly png decoder

Lokathor commented 1 year ago

Just reading this thread a bit, you may be misunderstanding how the PLTE chunk works. When bit depth is lower there's fewer entries but each entry is stil r8g8b8. Lower bit depths affect how you get each index, but once you have the index it's just image.palette.get(index).unwrap_or_default()

etemesi254 commented 1 year ago

st reading this thread a bit, you may be misunderstanding how the PLTE chunk works. When bit depth is lower there's fewer entries but each entry is stil r8g8b8.

That was the initial confusion I had , but I did come to learn about it as you did

But still, the problem again is that if you get the information from the ihdr chunk it reports < 8 bits, but then the PLTE chunk uses 8 bits.

So then meaning the headers would report < 8bpp but the image would be 8 bpp for you to get any meaningful image.

Lower bit depths affect how you get each index, but once you have the index it's just image.palette.get(index).unwrap_or_default()

No need for unwraping btw, bad for speed, just set it to black also allowed,

the crate does it like

https://github.com/etemesi254/zune-image/blob/a30c3e1e7ce1775d463efe3f7415818000ce7f82/zune-png/src/decoder.rs#L1126-L1163

Lokathor commented 1 year ago

But still, the problem again is that if you get the information from the ihdr chunk it reports < 8 bits, but then the PLTE chunk uses 8 bits.

So then meaning the headers would report < 8bpp but the image would be 8 bpp for you to get any meaningful image.

That's just a natural part of all image format decoding. It's the same thing with BMP files. More specifically it's how all paletted image systems work. "bits per pixel" with paletted color means "bits per palette index" (because each pixel is one index), and the actual palette entries are essentially always going to be some format that's bigger than the index itself (because that's the point).

EDIT: the thing with the palette is neat, I'll try using that soon.

etemesi254 commented 1 year ago

That's just a natural part of all image format decoding. It's the same thing with BMP files. More specifically it's how all paletted image systems work. "bits per pixel" with paletted color means "bits per palette index" (because each pixel is one index), and the actual palette entries are essentially always going to be some format that's bigger than the index itself (because that's the point).

Cool, didn't know bmp works like that too.

But again I don't see a use-case for supporting < 8bpp images since they make other parts slower as they have to take into consideration that hence why I explicitly expand to 8bpp.

EDIT: the thing with the palette is neat, I'll try using that soon.

Another cool feature of it is that the palette is 4 entries, which is 4*u8 = u32, hence for four component output, theoretically the compiler should optimize it to a single load and store but it fails on it

https://godbolt.org/z/WbEeMhPEq

Lokathor commented 1 year ago

I'd agree that, in general, the in-memory version of an image doesn't need to be less than 8 bits per channel. You really only need to support less than 8 bits per channel for storage on disk (either encoding or decoding).

(the exception here would be for systems that actually use data that's less than 8 bits per channel directly, but those are relatively rare these days)