-
Notifications
You must be signed in to change notification settings - Fork 31
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
Support DisplayP3
and BT.2020 + PQ
#96
base: main
Are you sure you want to change the base?
Support DisplayP3
and BT.2020 + PQ
#96
Conversation
In the current implementation, 10-bit signals can be stored, but they are derived from 8-bit signals, offering no visual improvement. All incoming data has been assumed to be 8-bit sRGB. This PR takes the first step in removing these assumptions by enabling the storage of signals with higher bit depth. It lays the groundwork for full support of writing HDR images.
f933360
to
b5a9219
Compare
b5a9219
to
e01354c
Compare
Ten, | ||
/// Pick 8 or 10 depending on image format and decoder compatibility | ||
Auto, |
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.
Please don't remove the Auto option. I plan to keep it around for at least a year longer.
@@ -84,7 +191,8 @@ pub struct Encoder { | |||
impl Encoder { | |||
/// Start here | |||
#[must_use] | |||
pub fn new() -> Self { | |||
// Assumptions about color spaces shouldn't be made since it can't reliably be deduced automatically. | |||
pub fn new(color_space: ColorSpace) -> Self { |
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.
Please keep the public API of ravif
backwards-compatible. This crate's biggest user is the image
crate, and I'd like to stay compatible with it. Users of the image
crate are slow to upgrade it, so if I made a semver-major release of ravif
, it would take quite a while before majority of its users got it. I prefer to be able to release improvements to all existing users quickly.
This unfortunately means that the old use of ColorSpace
needs to stay for now. You could rename this one to InputColorSpace
, or maybe ColorGamut
?.
If it makes anything simpler, I suggest not supporting P3/2020 in 8-bit. There isn't enough precision in 8 bits for this to make sense, and I plan to remove 8-bit support eventually. You could just add dedicated non-generic 10-bit encoding functions that support wide gamut, and leave 8-bit code to rot.
@@ -110,11 +219,12 @@ impl Encoder { | |||
#[doc(hidden)] | |||
#[deprecated(note = "Renamed to with_bit_depth")] | |||
pub fn with_depth(self, depth: Option<u8>) -> Self { | |||
self.with_bit_depth(depth.map(|d| if d >= 10 { BitDepth::Ten } else { BitDepth::Eight }).unwrap_or(BitDepth::Auto)) | |||
self.with_bit_depth(depth.map(|d| if d >= 10 { BitDepth::Ten } else { BitDepth::Eight }).unwrap_or(BitDepth::Ten)) |
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.
Auto
is important for me.
let buffer = new_alpha.as_ref().map(|b| b.as_ref()).unwrap_or(in_buffer); | ||
let use_alpha = buffer.pixels().any(|px| px.a != 255); | ||
let buffer = new_alpha.as_ref().map(|b: &Img<Vec<Rgba<P>>>| b.as_ref()).unwrap_or(in_buffer); | ||
let use_alpha = buffer.pixels().any(|px| px.a != P::cast_from(255)); |
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.
This looks buggy. You've removed comment that this function is for 8-bit inputs only, but check against 255 looks like a bug for 10-bit depths. Please write a unit test to prove that this is not buggy.
@@ -284,83 +370,45 @@ impl Encoder { | |||
/// | |||
/// returns AVIF file, size of color metadata | |||
#[inline] | |||
pub fn encode_rgb(&self, buffer: Img<&[RGB8]>) -> Result<EncodedImage, Error> { | |||
pub fn encode_rgb<P: Pixel + Default>(&self, buffer: Img<&[Rgb<P>]>) -> Result<EncodedImage, Error> { |
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.
I don't want to expose the messy rav1e::Pixel
in the public API.
Please keep encode_rgb
unchanged for API backwards-compatibility, and add a separate method like encode_rgb_10bit
that takes Rgb<u16>
.
let mut out = Vec::with_capacity(img.width() * img.height()); | ||
loop9::loop9_img(img, |_, _, top, mid, bot| { | ||
out.push(if mid.curr.a == 255 { | ||
out.push(if mid.curr.a == P::cast_from(255) { |
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.
This is a bug for 10-bit depths
let mut avg = sum.map(|c| (c / 9) as u8); | ||
if mid.curr.a == 0 { | ||
avg.with_alpha(0) | ||
let sum: Rgb<P> = chain(&top, &mid, &bot).map(|px| px.rgb()).sum(); |
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.
This is a bug for 8-bit depths. It's a sum of 9 pixels, so it will overflow u8
.
T: Pixel + Default, | ||
{ | ||
let alpha = Into::<u32>::into(alpha); | ||
let rounded = Into::<u32>::into(px) * alpha / 255 * 255; |
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.
This is a bug for 10-bit depths
let low = ((rounded + 16) / alpha) as u8; | ||
let hi = ((rounded + 239) / alpha) as u8; | ||
let low = (rounded + 16) / alpha; | ||
let hi = (rounded + 239) / alpha; |
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.
This is a bug for 10-bit depths too
.arg(Arg::new("color-space") | ||
.short('C') | ||
.long("color-space") | ||
.value_name("srgb/displayp3/rec2020pq") |
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.
This will not work for the cavif
tool. This command-line flag should not exist.
Input images for this program are not read with naive byte-by-byte copying of pixel values. All input images are color-managed and explicitly converted to sRGB, including destroying gamut of images in Rec.2020 and DisplayP3.
If you then take the always-sRGB color data and label it as P3 in AVIF metadata, then decoders will be fooled into interpreting values as in P3, and incorrectly stretch them over a wide gamut, giving a falsely boosted saturation with shifted primaries. Or when reading such AVIF for an sRGB screen, the P3 to sRGB conversion will crush the values and make them clearly different from the actual sRGB input.
To make such hacky relabelling work, users would need to generate P3 files, and then incorrectly relabel pixels as being in sRGB, to create an encoding error in the opposite way of the encoding error caused by this option.
If this was text, it would be like requiring users to type ’
mojibake to get ’
printed in the output.
That's not a good practice, and that's not a good application interface.
For the ravif
crate, it's fine to take user-supplied label, since it takes raw pixel data, so it's up to the caller to generate correct colors or decode input images properly themselves.
But cavif
does reading of images itself, so support for wide-gamut inputs unfortunately requires changing how image loading works.
This PR adds support for storing
Display P3
andRec.2020 + PQ
aside from sRGB. Functionality to specify which color space to use has also been added tocavif-rs
. Be mindful that exported HDR images are not well supported on the web.Isolated changes: a8e10d4...e01354c