From 556272bae9e3fd274020c0e5f70c136c8f627ebc Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 17 Apr 2025 17:29:20 +0300 Subject: [PATCH] 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 --- libweston/color-lcms/color-profile.c | 22 ++++++++++++++---- tests/alpha-blending-test.c | 13 ++++------- tests/color-icc-output-test.c | 16 ++++++------- tests/color_util.c | 4 ++-- tests/lcms_util.c | 4 ++++ tests/reference/alpha_blend-01.png | Bin 474 -> 466 bytes tests/reference/output_icc_alpha_blend-00.png | Bin 405 -> 401 bytes tests/reference/output_icc_alpha_blend-03.png | Bin 394 -> 397 bytes tests/reference/shaper_matrix-02.png | Bin 730 -> 744 bytes 9 files changed, 36 insertions(+), 23 deletions(-) diff --git a/libweston/color-lcms/color-profile.c b/libweston/color-lcms/color-profile.c index 7fe56d0bf..380c4d6ee 100644 --- a/libweston/color-lcms/color-profile.c +++ b/libweston/color-lcms/color-profile.c @@ -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; } diff --git a/tests/alpha-blending-test.c b/tests/alpha-blending-test.c index b7ea6c21b..1c4af369b 100644 --- a/tests/alpha-blending-test.c +++ b/tests/alpha-blending-test.c @@ -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); diff --git a/tests/color-icc-output-test.c b/tests/color-icc-output-test.c index e0c34afeb..6af224ee6 100644 --- a/tests/color-icc-output-test.c +++ b/tests/color-icc-output-test.c @@ -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 diff --git a/tests/color_util.c b/tests/color_util.c index 27a2627d3..64e947e72 100644 --- a/tests/color_util.c +++ b/tests/color_util.c @@ -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 diff --git a/tests/lcms_util.c b/tests/lcms_util.c index 8f904c78e..b50701b7c 100644 --- a/tests/lcms_util.c +++ b/tests/lcms_util.c @@ -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: diff --git a/tests/reference/alpha_blend-01.png b/tests/reference/alpha_blend-01.png index fd93bd4ba5b2f038d9972a5ea81dcc2608b098a7..b4a97a1026ee988e5a96c53388e43d9cd8129386 100644 GIT binary patch delta 422 zcmcb`e2IC2S$(^wi(^Q|oVPb@gAN-AumyDA7cSsfcerqcg}yDTvr=%v&6#VKSn%>J z{w`(o+W*t3vW?EO?k|)7{AJ6ZS#s0&zg!rzOMmL_rIYf!EkaK9Pd?>)^ZA}lu~FKV zAxUXj>Cdj5TXM;5<&*-|$xdlqGmF+&+9q(#+~I%X%*MlCMHU*h*Y6HLckiD1*6%Mv ztp2`Pk<+`>FEuNzY)h2&?-IFtS09$dy|kSc)L~uzi*x4W4^MfupWl+Qj*qiEx@*_x zAg5K~ua&EUwb80s%zB4bP z_~*u0%Wc~2MF*T$*2qpj6~3}YZkqgi`O`~FmoP9$v@&uqNcdINRsMY~$G}iG^*;k6 h!(l^`2zj{bpKQDR9F!JX-sfWg0#8>zmvv4FO#n8CzL5X` delta 430 zcmcb_e2aO4S$)5!i(^Q|oVPa|{g@4TS`(MAoe)tVuoxIx9A?K1lWB%+>L0*mnPU=U6yZ*<|LePZy>>`Q!U>mZjy_ zlus$MCNF+(RrRTI*5v-nbN2jR_wTzX1A_v$oCA=Y`{~c8U-yj}7#Qw|{%2%jV0ig~ gfkaXrLqiRtT%41V{N}2gKyw*9UHx3vIVCg!0DVBoX8-^I diff --git a/tests/reference/output_icc_alpha_blend-00.png b/tests/reference/output_icc_alpha_blend-00.png index beec77b82c81aa9b906e6898621b04ad8d61c91c..7ac543799953b0d18c3642f88a72b66e7fa92219 100644 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5BsiFWnThlRc+RcN1yklf4|?zH^rH6Dn?I5bpBC+W zXm9NFzy4E~ohRpG#h+YXtjy1!uj#%P_IHsTr^2t>qJO8IK9?h!e%Pk;=9=uiaw1Cq z)*UO-ayh!RZ_VR7md6ED+^!W~YZthv-ft+Ee5bkkLEQ(rA2&aK_1(9({=k}5-(JWk leDmOInEH$%B9n6J8~2q~2O}k615Z$hd%F6$taD0e0su+lu`2)o literal 405 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5BsiFWY#yU`YvX;q+5_jL7hS?83{1OWcprxySK diff --git a/tests/reference/output_icc_alpha_blend-03.png b/tests/reference/output_icc_alpha_blend-03.png index 9072beda2dfc48195d82d2aa4c3502549c5aba51..4112e66c40f7519832459a8362ae58da6f33b8a6 100644 GIT binary patch delta 352 zcmeBT?q!}}R`2BL;uumf=k4T!g@+AzSl+*QGTG>oXOt)FOPl)Rhn_k*EP2cH=fGQM z=1J^Mm(B#)Pq1q!|H!njQ~ZGv--dY-4+LIII;UJ)5yia3z@dxTD*K*nr{sY(?a$OS zgrt-dyI5A#Rvt6xpZ96ex!>BAZO@-=YxyE_Z?}u!g=?nFJCB^Ie_oOjH1)~lzD?c{ zrS}--Z2bGhP_jBa)kPuQ^Yz^H1=D`+6$*&W7LM3Bqkcix?rF1xp1&~t{?Sh{P3>@Y ze8P)$sqt=iXSNDN{aoR2rQ5>s(!*5?Trk}OvMgtImQ$@<5~p6Whw52sANW;Y@LS8URHy(a9QFcKl5vR@Mb!= SG^>FD2s~Z=T-G@yGywoQ%%KAS literal 394 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5BsiFW1ekd=8#bmhh%je~aw!UNO-$g>ukPCxx%`T@(7_2DK`Jha3P7%k zLH_PFwPkCV8kctb6!kjIk*5C4V}G$%EgVloqYzIPEX8!Q^r2hi&uu zgY~L){a>w@`u$D+*wgLjqBq{XY1a8Ky|~8pblTfU?eC{sbLL)ed$T>ZpR?2Q)y-q) zr&_*tcvgAlw4l2-muA|x$BHtQ>&`zh{^?=E_fNIXwXUxA@S0T>-Z}Om6#wK7fMS)78&qol`;+0IFD_+5i9m diff --git a/tests/reference/shaper_matrix-02.png b/tests/reference/shaper_matrix-02.png index 0abca0be8725e6f1feb8a72c1c9ce4de973c9a09..de7b6f767be41f4e2e065201d75b7858cbab3d5d 100644 GIT binary patch delta 702 zcmV;v0zv)S1?UBkHh-N-L_t(|ob8*jj@v*CL}y7!_BrPmm-rX*6@mZ1O`D`log!_Z z-MuCypMVYAEvFm}NeKomYD5(=9*;-J-|zR3zuj&jf4yF*#N~1s;?L*v5HIO;It}sL zwmn5JXDE#@~je+YE*x4=vllZcYjK%gFHsH6Nwp+4S$fB8rcAese$8{q^_SK z-}DRkc#dqsDY^A{IIBHAu?8pH6z7ptd)(v|OONSy&`UB1v0flmtS_LKRG>QX8rEZJ zN`)by?+r_IgAgdu8|V%6z2qGrF*ULQ5>w-71p3{-g#6$3IpqJ;*akgUv_VfbwmUcC z_m_}g_t>M9w0~NBXmRiyLBBfWDe>SZ$qST#S0{m+&X$;}#ic2g zvkhvH$|05cVCkARXku<;10<%#(FoM~_mHnW9tL)Bdn(*3CGFte7`Q-2ZV&h;|AN8@ z2kN-StV;r|qsaPUc3AM<2b{YMDu*?$ExaBm#k9@5h(8QDyU#3aZD zNKB1vfW*`|dIq@uh;7ifQ*492ZXZMb!-6*G%M#m~XOC^rlgC@1#7o*Nelj6jlk^+e zsfJHDD;%N_)U(sN80h!zCPr;8l$2y?EXM5>q1^ATc!#m*M)i zkZ-i927l&Mm#{N`=&SV~~w4E)mSWi|O@FHF+qan1jRSwq0C}dP0^Np_#Lm;A! zml#e3JPkTtav3f$H?jc|Q-im?rMJu1kYD{%$S)Qz!@0du2`{nR;}*L^R${k@cu5AX zj=`pymDuemsK>7z3^vRPxH`leyFdko*aE%>mp1@C56M^pPYJ$ufIN~4yk+OoqxxKi kOU#XIfW*|u21rbecbfi+BjHH#UH||907*qoM6N<$f|~eHV*mgE delta 688 zcmV;h0#E(u1=RoL2%H=A6a{?$T@R41+HSk( zI(U+Lf%ya|=uVvF@MoTp(55F$GREWako^6Am;CK^ll=91C5g-BGR2?I=P7;=+V@@Z zr_*UFf7>>arV`>yDYFt3B7Rna@&}>+#ebkA=RC!iQk=8{fqxYc0}xmmF#v(3f$Nu< z_5MTi_FM9;>(#ayuCNUrpmn{pQGSK|L8!v;Q~uF)z5{P&6+#C{q@I9B2?}A!biRJ1 zK`Shom*`N^n=a`N`5@7Iicf&R(ue^FERE3$^rwH7{Cj(q{NMIP^5=>+=&62^{9d0l ze`rs0D`D^RmH$yfWOdH75u-q;UucrZ8kL}nL=%!f`! z6XZ8|vnXuQU@PGBBtHmM@I7WFoGqH!l8;D$+vDK&P=71tp-~{P2x0&NOCts#urx;B z0iNHq4f^s^UxPmH+6KK8v_YQ=TzzJ3gPyYXG>JE}9c;}VY|stzH;21R@H&SjaC%8R z*rsMy9Bfc*HJ6_ue;`dI+2JIUC1zV;exiKvM+1RH5Cafc8slTQ?~-p_FA1%I>&Jgh ztX2C{cz>w|{9)5>-9grv!$sO8KMDcgiBEhkh9$H4ns5B-ummcoRpVEeA(EkldFbPD z3>R1%F#v(3!Pnkqx9ywc_g%X{rF@orc65Pq(k@VT?E-nyZjUFum6@44m1y^d_(b`9 zsfH!s>LlQueUb#~2}))q5--UtG1n@AMKdfR2`TtT3y??_E>K`?!~g`AMhrk;Y5W5M WF^%TOtLrrY0000