color-lcms: change stock sRGB to true power-2.2

The sRGB expected display behavior uses the pure power-law with exponent
2.2, not the two-piece sRGB transfer function.
cmsCreate_sRGBProfileTHR() used the two-piece TF, now we use the proper
display TF.

This is particularly meaningful when implicit sRGB content is converted
to HDR formats, in order to maintain the stimuli reproduction near zero.

cmlcms_send_image_desc_info() is already sending this, it doesn't need
fixing.

Changing the curve also changes the error tolerances. The change is
theoretically a no-op, but the curve and its inverse and temporary
rounding add error. The new curve is more prone to error, so it is not
surprising we need to raise the tolerance. The color transformation does
end up as power-2.2 analytical form and I do not think it is ever
lowered to a LUT in alpha-blending test, so there is no obvious fix
improving the accuracy. The worst case point in alpha-blending still
occurs at the very same point as before.

The test reference images are updated for the same reason, they would
fail otherwise.

Both alpha-blending and color-icc-output contain the same sRGB-optical
sub-test, hence the same error tolerance.

It is surprising to have to increase the ICC roundtrip error tolerance
in color-icc-output test, given that the curves are passed as parametric
to LittleCMS, and adobeRGB case works with the old tolerance even. I did
not investigate further.

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
This commit is contained in:
Pekka Paalanen 2025-04-17 17:29:20 +03:00 committed by Pekka Paalanen
parent 718f7f56df
commit 556272bae9
9 changed files with 36 additions and 23 deletions

View file

@ -550,24 +550,36 @@ make_icc_file_description(struct lcmsProfilePtr profile,
}
/**
* Build stock sRGB profile used as fallback
*
* Build stock profile which available for clients unaware of color management
* BT.709 primaries with gamma-2.2 transfer characteristic. This is the
* expected sRGB display response.
*/
bool
cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm)
{
struct lcmsProfilePtr profile;
static const cmsCIExyY D65 = { 0.3127, 0.3290, 1.0 };
static const cmsCIExyYTRIPLE bt709 = {
{ 0.6400, 0.3300, 1.0 },
{ 0.3000, 0.6000, 1.0 },
{ 0.1500, 0.0600, 1.0 }
};
cmsToneCurve *gamma22[3];
struct lcmsProfilePtr profile = { NULL };
struct cmlcms_md5_sum md5sum;
char *desc = NULL;
const char *err_msg = NULL;
profile.p = cmsCreate_sRGBProfileTHR(cm->lcms_ctx);
gamma22[0] = gamma22[1] = gamma22[2] = cmsBuildGamma(cm->lcms_ctx, 2.2);
if (gamma22[0])
profile.p = cmsCreateRGBProfileTHR(cm->lcms_ctx, &D65, &bt709, gamma22);
cmsFreeToneCurve(gamma22[0]);
if (!profile.p) {
weston_log("color-lcms: error: cmsCreate_sRGBProfileTHR failed\n");
weston_log("color-lcms: error: failed to create stock sRGB profile.\n");
return false;
}
if (!cmsMD5computeID(profile.p)) {
weston_log("Failed to compute MD5 for ICC profile\n");
weston_log("Failed to compute MD5 for stock sRGB profile.\n");
goto err_close;
}

View file

@ -221,17 +221,14 @@ check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot,
#endif
/*
* Allow for +/- 1.5 code points of error in non-linear 8-bit channel
* value. This is necessary for the BLEND_LINEAR case.
* Allow for +/- 1.72 code points of two-norm error in non-linear
* 8-bit channel value. This is necessary for the BLEND_LINEAR case.
*
* With llvmpipe, we could go as low as +/- 0.65 code points of error
* and still pass.
*
* AMD Polaris 11 would be ok with +/- 1.0 code points error threshold
* llvmpipe would be ok with +/- 1.0 code points error threshold
* if not for one particular case of blending (a=254, r=0) into r=255,
* which results in error of 1.29 code points.
* which results in error of 1.701 code points.
*/
const float tolerance = 1.5f / 255.f;
const float tolerance = 1.72f / 255.f;
uint32_t *bg_row = get_middle_row(bg);
uint32_t *fg_row = get_middle_row(fg);

View file

@ -57,9 +57,9 @@ const struct lcms_pipeline pipeline_sRGB = {
.Green = { 0.300, 0.600, 1.0 },
.Blue = { 0.150, 0.060, 1.0 }
},
.pre_fn = TRANSFER_FN_SRGB,
.pre_fn = TRANSFER_FN_POWER2_2_EOTF,
.mat = WESTON_MAT3F_IDENTITY,
.post_fn = TRANSFER_FN_SRGB_INVERSE
.post_fn = TRANSFER_FN_POWER2_2_EOTF_INVERSE
};
const struct lcms_pipeline pipeline_adobeRGB = {
@ -69,7 +69,7 @@ const struct lcms_pipeline pipeline_adobeRGB = {
.Green = { 0.210, 0.710, 1.0 },
.Blue = { 0.150, 0.060, 1.0 }
},
.pre_fn = TRANSFER_FN_SRGB,
.pre_fn = TRANSFER_FN_POWER2_2_EOTF,
.mat = WESTON_MAT3F(
0.715127, 0.284868, 0.000005,
0.000001, 0.999995, 0.000004,
@ -84,7 +84,7 @@ const struct lcms_pipeline pipeline_BT2020 = {
.Green = { 0.170, 0.797, 1.0 },
.Blue = { 0.131, 0.046, 1.0 }
},
.pre_fn = TRANSFER_FN_SRGB,
.pre_fn = TRANSFER_FN_POWER2_2_EOTF,
.mat = WESTON_MAT3F(
0.627402, 0.329292, 0.043306,
0.069095, 0.919544, 0.011360,
@ -131,12 +131,12 @@ struct setup_args {
static const struct setup_args my_setup_args[] = {
/* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance, vcgt_exponents */
{ { "sRGB->sRGB MAT" }, 0, &pipeline_sRGB, 0.0, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->sRGB MAT VCGT" }, 3, &pipeline_sRGB, 0.8, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
{ { "sRGB->sRGB MAT VCGT" }, 3, &pipeline_sRGB, 0.9, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
{ { "sRGB->adobeRGB MAT" }, 1, &pipeline_adobeRGB, 1.6, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->adobeRGB MAT VCGT" }, 4, &pipeline_adobeRGB, 1.0, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
{ { "sRGB->BT2020 MAT" }, 2, &pipeline_BT2020, 1.1, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->sRGB CLUT" }, 0, &pipeline_sRGB, 0.0, 17, PTYPE_CLUT, 0.0005 },
{ { "sRGB->sRGB CLUT VCGT" }, 3, &pipeline_sRGB, 0.9, 17, PTYPE_CLUT, 0.0005, {1.1, 1.2, 1.3} },
{ { "sRGB->sRGB CLUT" }, 0, &pipeline_sRGB, 1.8, 17, PTYPE_CLUT, 0.01 },
{ { "sRGB->sRGB CLUT VCGT" }, 3, &pipeline_sRGB, 1.3, 17, PTYPE_CLUT, 0.01, {1.1, 1.2, 1.3} },
{ { "sRGB->adobeRGB CLUT" }, 1, &pipeline_adobeRGB, 1.8, 17, PTYPE_CLUT, 0.0065 },
{ { "sRGB->adobeRGB CLUT VCGT" }, 4, &pipeline_adobeRGB, 1.1, 17, PTYPE_CLUT, 0.0065, {1.1, 1.2, 1.3} },
};
@ -527,7 +527,7 @@ check_blend_pattern(struct buffer *bg_buf,
fclose(dump);
/* Test success condition: */
return diffstat.two_norm.max < 1.5f / 255.0f;
return diffstat.two_norm.max < 1.72f / 255.0f;
}
static uint32_t

View file

@ -284,13 +284,13 @@ color_float_apply_curve(enum transfer_fn fn, struct color_float c)
void
sRGB_linearize(struct color_float *cf)
{
*cf = color_float_apply_curve(TRANSFER_FN_SRGB, *cf);
*cf = color_float_apply_curve(TRANSFER_FN_POWER2_2_EOTF, *cf);
}
void
sRGB_delinearize(struct color_float *cf)
{
*cf = color_float_apply_curve(TRANSFER_FN_SRGB_INVERSE, *cf);
*cf = color_float_apply_curve(TRANSFER_FN_POWER2_2_EOTF_INVERSE, *cf);
}
struct color_float

View file

@ -175,6 +175,10 @@ build_MPE_curve(cmsContext ctx, enum transfer_fn fn)
return build_MPE_curve_power(ctx, 563.0 / 256.0);
case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE:
return build_MPE_curve_power(ctx, 256.0 / 563.0);
case TRANSFER_FN_POWER2_2_EOTF:
return build_MPE_curve_power(ctx, 2.2);
case TRANSFER_FN_POWER2_2_EOTF_INVERSE:
return build_MPE_curve_power(ctx, 1.0 / 2.2);
case TRANSFER_FN_POWER2_4_EOTF:
return build_MPE_curve_power(ctx, 2.4);
case TRANSFER_FN_POWER2_4_EOTF_INVERSE:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 B

After

Width:  |  Height:  |  Size: 744 B