Skip to content

Commit

Permalink
jxl-oxide-cli: Improve image viewer consistency with BT.709 images
Browse files Browse the repository at this point in the history
  • Loading branch information
tirr-c committed Jan 18, 2025
1 parent 2818bef commit 3f3dbf7
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 17 deletions.
43 changes: 42 additions & 1 deletion crates/jxl-color/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pub struct ColorTransformBuilder {
detect_peak: bool,
srgb_icc: bool,
from_pq: bool,
xyb_from_bt709: bool,
}

impl Default for ColorTransformBuilder {
Expand All @@ -139,6 +140,7 @@ impl ColorTransformBuilder {
detect_peak: false,
srgb_icc: false,
from_pq: false,
xyb_from_bt709: false,
}
}

Expand All @@ -157,6 +159,11 @@ impl ColorTransformBuilder {
self
}

pub fn xyb_from_bt709(&mut self, xyb_from_bt709: bool) -> &mut Self {
self.xyb_from_bt709 = xyb_from_bt709;
self
}

pub fn build(
self,
from: &ColorEncodingWithProfile,
Expand Down Expand Up @@ -211,6 +218,7 @@ impl ColorTransform {
detect_peak,
srgb_icc,
from_pq,
xyb_from_bt709,
} = builder;
let connecting_tf = if srgb_icc {
TransferFunction::Srgb
Expand Down Expand Up @@ -249,6 +257,7 @@ impl ColorTransform {
}

let mut ops = Vec::new();
let mut need_bt709_fix = false;

let mut current_encoding = match from.encoding {
ColourEncoding::IccProfile(_) => {
Expand Down Expand Up @@ -287,6 +296,15 @@ impl ColorTransform {
rendering_intent,
..
}) => {
let to_bt709 = matches!(
to.encoding,
ColourEncoding::Enum(EnumColourEncoding {
tf: TransferFunction::Bt709,
..
})
);
need_bt709_fix = xyb_from_bt709 && !to_bt709;

let inv_mat = oim.inv_mat;
#[rustfmt::skip]
let matrix = [
Expand Down Expand Up @@ -361,6 +379,28 @@ impl ColorTransform {
}
};

if need_bt709_fix {
// Correct using BT.1886 EOTF.
ops.push(ColorTransformOp::TransferFunction {
tf: TransferFunction::Bt709,
hdr_params: HdrParams {
luminances: [0f32; 3],
intensity_target,
min_nits,
},
inverse: false,
});
ops.push(ColorTransformOp::TransferFunction {
tf: TransferFunction::Bt709,
hdr_params: HdrParams {
luminances: [0f32; 3],
intensity_target,
min_nits,
},
inverse: true,
});
}

let target_encoding = match &to.encoding {
ColourEncoding::Enum(encoding) => encoding,
ColourEncoding::IccProfile(_) => {
Expand Down Expand Up @@ -1091,8 +1131,9 @@ fn apply_inverse_transfer_function(
}
}
TransferFunction::Bt709 => {
// Matches Chromium and Firefox behavior.
for ch in channels {
tf::bt709_to_linear(ch);
tf::srgb_to_linear(ch);
}
}
TransferFunction::Unknown => {}
Expand Down
11 changes: 0 additions & 11 deletions crates/jxl-color/src/tf/bt709.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,3 @@ unsafe fn linear_to_bt709_x86_64_avx2(samples: &mut [f32]) -> &mut [f32] {

remainder
}

pub fn bt709_to_linear(samples: &mut [f32]) {
for x in samples {
let a = *x;
*x = if a <= 0.081 {
a / 4.5
} else {
crate::fastmath::fast_powf_generic(a.mul_add(1.0 / 1.099, 0.099 / 1.099), 1.0 / 0.45)
};
}
}
24 changes: 19 additions & 5 deletions crates/jxl-oxide-cli/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,25 @@ pub(crate) fn write_png<W: Write>(

let mut writer = encoder.write_header()?;

tracing::debug!("Embedding ICC profile");
let compressed_icc = miniz_oxide::deflate::compress_to_vec_zlib(&source_icc, 7);
let mut iccp_chunk_data = vec![b'0', 0, 0];
iccp_chunk_data.extend(compressed_icc);
writer.write_chunk(png::chunk::iCCP, &iccp_chunk_data)?;
let skip_icc = matches!(
metadata.colour_encoding,
jxl_oxide::color::ColourEncoding::Enum(jxl_oxide::color::EnumColourEncoding {
tf: jxl_oxide::color::TransferFunction::Bt709,
..
})
);

if skip_icc {
// XXX: This is necessary because libjxl encodes BT.709 images with BT.709 inverse
// OETF, but some image viewers use BT.1886 EOTF when it encounters BT.709 CICP tag.
tracing::debug!("Skipping ICC profile, because the transfer function is BT.709");
} else {
tracing::debug!("Embedding ICC profile");
let compressed_icc = miniz_oxide::deflate::compress_to_vec_zlib(&source_icc, 7);
let mut iccp_chunk_data = vec![b'0', 0, 0];
iccp_chunk_data.extend(compressed_icc);
writer.write_chunk(png::chunk::iCCP, &iccp_chunk_data)?;
}

if let Some(cicp) = cicp {
tracing::debug!(cicp = format_args!("{:?}", cicp), "Writing cICP chunk");
Expand Down
9 changes: 9 additions & 0 deletions crates/jxl-render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,9 +825,18 @@ impl RenderContext {
tracing::trace!(requested_color_encoding = ?self.requested_color_encoding);
tracing::trace!(do_ycbcr = frame_header.do_ycbcr);

let is_bt709 = matches!(
header_color_encoding,
ColourEncoding::Enum(EnumColourEncoding {
tf: jxl_color::TransferFunction::Bt709,
..
})
);

let mut transform = jxl_color::ColorTransform::builder();
transform.set_srgb_icc(!self.cms.supports_linear_tf());
transform.from_pq(self.suggested_hdr_tf() == Some(jxl_color::TransferFunction::Pq));
transform.xyb_from_bt709(metadata.xyb_encoded && is_bt709);
let transform = transform.build(
&frame_color_encoding,
&self.requested_color_encoding,
Expand Down

0 comments on commit 3f3dbf7

Please sign in to comment.