Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include <math.h>
19 : : #include "shaders.h"
20 : :
21 : : #include <libplacebo/tone_mapping.h>
22 : : #include <libplacebo/shaders/icc.h>
23 : :
24 : : const struct pl_icc_params pl_icc_default_params = { PL_ICC_DEFAULTS };
25 : :
26 : : #ifdef PL_HAVE_LCMS
27 : :
28 : : #include <lcms2.h>
29 : : #include <lcms2_plugin.h>
30 : :
31 : : struct icc_priv {
32 : : pl_log log;
33 : : pl_cache cache; // for backwards compatibility
34 : : cmsContext cms;
35 : : cmsHPROFILE profile;
36 : : cmsHPROFILE approx; // approximation profile
37 : : float a, b, scale; // approxmation tone curve parameters and scaling
38 : : cmsCIEXYZ *white, black;
39 : : float gamma_stddev;
40 : : uint64_t lut_sig;
41 : : };
42 : :
43 : 0 : static void error_callback(cmsContext cms, cmsUInt32Number code,
44 : : const char *msg)
45 : : {
46 : 0 : pl_log log = cmsGetContextUserData(cms);
47 : 0 : pl_err(log, "lcms2: [%d] %s", (int) code, msg);
48 : 0 : }
49 : :
50 : 0 : static void set_callback(void *priv, pl_cache_obj obj)
51 : : {
52 : : pl_icc_object icc = priv;
53 : 0 : icc->params.cache_save(icc->params.cache_priv, obj.key, obj.data, obj.size);
54 : 0 : }
55 : :
56 : 0 : static pl_cache_obj get_callback(void *priv, uint64_t key)
57 : : {
58 : : pl_icc_object icc = priv;
59 : 0 : int s_r = icc->params.size_r, s_g = icc->params.size_g, s_b = icc->params.size_b;
60 : 0 : size_t data_size = s_r * s_g * s_b * sizeof(uint16_t[4]);
61 : 0 : void *data = pl_alloc(NULL, data_size);
62 : 0 : bool ok = icc->params.cache_load(icc->params.cache_priv, key, data, data_size);
63 [ # # ]: 0 : if (!ok) {
64 : 0 : pl_free(data);
65 : 0 : return (pl_cache_obj) {0};
66 : : }
67 : :
68 : 0 : return (pl_cache_obj) {
69 : : .key = key,
70 : : .data = data,
71 : : .size = data_size,
72 : : .free = pl_free,
73 : : };
74 : : }
75 : :
76 : 29 : void pl_icc_close(pl_icc_object *picc)
77 : : {
78 : 29 : pl_icc_object icc = *picc;
79 [ + + ]: 29 : if (!icc)
80 : : return;
81 : :
82 : 11 : struct icc_priv *p = PL_PRIV(icc);
83 : 11 : cmsCloseProfile(p->approx);
84 : 11 : cmsCloseProfile(p->profile);
85 : 11 : cmsDeleteContext(p->cms);
86 : 11 : pl_cache_destroy(&p->cache);
87 : 11 : pl_free_ptr((void **) picc);
88 : : }
89 : :
90 : 11 : static bool detect_csp(struct pl_icc_object_t *icc)
91 : : {
92 : 11 : struct icc_priv *p = PL_PRIV(icc);
93 : : cmsHTRANSFORM tf;
94 : 11 : cmsHPROFILE xyz = cmsCreateXYZProfileTHR(p->cms);
95 [ - + ]: 11 : if (!xyz)
96 : : return false;
97 : :
98 : : // We need to use an unadapted observer to get the raw values
99 : 11 : cmsFloat64Number prev_adapt = cmsSetAdaptationStateTHR(p->cms, 0.0);
100 : 11 : tf = cmsCreateTransformTHR(p->cms, p->profile, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
101 : : INTENT_ABSOLUTE_COLORIMETRIC,
102 : : /* Note: These flags mostly don't do anything
103 : : * anyway, but specify them regardless */
104 : : cmsFLAGS_NOCACHE |
105 : : cmsFLAGS_NOOPTIMIZE);
106 : 11 : cmsSetAdaptationStateTHR(p->cms, prev_adapt);
107 : 11 : cmsCloseProfile(xyz);
108 [ - + ]: 11 : if (!tf)
109 : : return false;
110 : :
111 : : enum {
112 : : RED,
113 : : GREEN,
114 : : BLUE,
115 : : WHITE,
116 : : BLACK,
117 : : GRAY,
118 : : RAMP,
119 : : };
120 : :
121 : : static const uint8_t test[][3] = {
122 : : [RED] = { 0xFF, 0, 0 },
123 : : [GREEN] = { 0, 0xFF, 0 },
124 : : [BLUE] = { 0, 0, 0xFF },
125 : : [WHITE] = { 0xFF, 0xFF, 0xFF },
126 : : [BLACK] = { 0x00, 0x00, 0x00 },
127 : : [GRAY] = { 0x80, 0x80, 0x80 },
128 : :
129 : : // Grayscale ramp (excluding endpoints)
130 : : #define V(d) { d, d, d }
131 : : V(0x01), V(0x02), V(0x03), V(0x04), V(0x05), V(0x06), V(0x07),
132 : : V(0x08), V(0x09), V(0x0A), V(0x0B), V(0x0C), V(0x0D), V(0x0E), V(0x0F),
133 : : V(0x10), V(0x11), V(0x12), V(0x13), V(0x14), V(0x15), V(0x16), V(0x17),
134 : : V(0x18), V(0x19), V(0x1A), V(0x1B), V(0x1C), V(0x1D), V(0x1E), V(0x1F),
135 : : V(0x20), V(0x21), V(0x22), V(0x23), V(0x24), V(0x25), V(0x26), V(0x27),
136 : : V(0x28), V(0x29), V(0x2A), V(0x2B), V(0x2C), V(0x2D), V(0x2E), V(0x2F),
137 : : V(0x30), V(0x31), V(0x32), V(0x33), V(0x34), V(0x35), V(0x36), V(0x37),
138 : : V(0x38), V(0x39), V(0x3A), V(0x3B), V(0x3C), V(0x3D), V(0x3E), V(0x3F),
139 : : V(0x40), V(0x41), V(0x42), V(0x43), V(0x44), V(0x45), V(0x46), V(0x47),
140 : : V(0x48), V(0x49), V(0x4A), V(0x4B), V(0x4C), V(0x4D), V(0x4E), V(0x4F),
141 : : V(0x50), V(0x51), V(0x52), V(0x53), V(0x54), V(0x55), V(0x56), V(0x57),
142 : : V(0x58), V(0x59), V(0x5A), V(0x5B), V(0x5C), V(0x5D), V(0x5E), V(0x5F),
143 : : V(0x60), V(0x61), V(0x62), V(0x63), V(0x64), V(0x65), V(0x66), V(0x67),
144 : : V(0x68), V(0x69), V(0x6A), V(0x6B), V(0x6C), V(0x6D), V(0x6E), V(0x6F),
145 : : V(0x70), V(0x71), V(0x72), V(0x73), V(0x74), V(0x75), V(0x76), V(0x77),
146 : : V(0x78), V(0x79), V(0x7A), V(0x7B), V(0x7C), V(0x7D), V(0x7E), V(0x7F),
147 : : V(0x80), V(0x81), V(0x82), V(0x83), V(0x84), V(0x85), V(0x86), V(0x87),
148 : : V(0x88), V(0x89), V(0x8A), V(0x8B), V(0x8C), V(0x8D), V(0x8E), V(0x8F),
149 : : V(0x90), V(0x91), V(0x92), V(0x93), V(0x94), V(0x95), V(0x96), V(0x97),
150 : : V(0x98), V(0x99), V(0x9A), V(0x9B), V(0x9C), V(0x9D), V(0x9E), V(0x9F),
151 : : V(0xA0), V(0xA1), V(0xA2), V(0xA3), V(0xA4), V(0xA5), V(0xA6), V(0xA7),
152 : : V(0xA8), V(0xA9), V(0xAA), V(0xAB), V(0xAC), V(0xAD), V(0xAE), V(0xAF),
153 : : V(0xB0), V(0xB1), V(0xB2), V(0xB3), V(0xB4), V(0xB5), V(0xB6), V(0xB7),
154 : : V(0xB8), V(0xB9), V(0xBA), V(0xBB), V(0xBC), V(0xBD), V(0xBE), V(0xBF),
155 : : V(0xC0), V(0xC1), V(0xC2), V(0xC3), V(0xC4), V(0xC5), V(0xC6), V(0xC7),
156 : : V(0xC8), V(0xC9), V(0xCA), V(0xCB), V(0xCC), V(0xCD), V(0xCE), V(0xCF),
157 : : V(0xD0), V(0xD1), V(0xD2), V(0xD3), V(0xD4), V(0xD5), V(0xD6), V(0xD7),
158 : : V(0xD8), V(0xD9), V(0xDA), V(0xDB), V(0xDC), V(0xDD), V(0xDE), V(0xDF),
159 : : V(0xE0), V(0xE1), V(0xE2), V(0xE3), V(0xE4), V(0xE5), V(0xE6), V(0xE7),
160 : : V(0xE8), V(0xE9), V(0xEA), V(0xEB), V(0xEC), V(0xED), V(0xEE), V(0xEF),
161 : : V(0xF0), V(0xF1), V(0xF2), V(0xF3), V(0xF4), V(0xF5), V(0xF6), V(0xF7),
162 : : V(0xF8), V(0xF9), V(0xFA), V(0xFB), V(0xFC), V(0xFD), V(0xFE),
163 : : #undef V
164 : : };
165 : :
166 : 11 : cmsCIEXYZ dst[PL_ARRAY_SIZE(test)] = {0};
167 : 11 : cmsDoTransform(tf, test, dst, PL_ARRAY_SIZE(dst));
168 : 11 : cmsDeleteTransform(tf);
169 : :
170 : : // Read primaries from transformed RGBW values
171 : 11 : struct pl_raw_primaries *measured = &icc->csp.hdr.prim;
172 : 11 : measured->red = pl_cie_from_XYZ(dst[RED].X, dst[RED].Y, dst[RED].Z);
173 : 11 : measured->green = pl_cie_from_XYZ(dst[GREEN].X, dst[GREEN].Y, dst[GREEN].Z);
174 : 11 : measured->blue = pl_cie_from_XYZ(dst[BLUE].X, dst[BLUE].Y, dst[BLUE].Z);
175 : 11 : measured->white = pl_cie_from_XYZ(dst[WHITE].X, dst[WHITE].Y, dst[WHITE].Z);
176 : :
177 : : // Detect best containing gamut
178 : : const struct pl_raw_primaries *best = NULL;
179 [ + - ]: 45 : for (enum pl_color_primaries prim = 1; prim < PL_COLOR_PRIM_COUNT; prim++) {
180 : 45 : const struct pl_raw_primaries *raw = pl_raw_primaries_get(prim);
181 [ + - + + ]: 45 : if (!icc->csp.primaries && pl_raw_primaries_similar(raw, measured)) {
182 : 11 : icc->containing_primaries = icc->csp.primaries = prim;
183 : : best = raw;
184 : 11 : break;
185 : : }
186 : :
187 [ + - - - ]: 34 : if (pl_primaries_superset(raw, measured) &&
188 [ # # ]: 0 : (!best || pl_primaries_superset(best, raw)))
189 : : {
190 : 0 : icc->containing_primaries = prim;
191 : : best = raw;
192 : : }
193 : : }
194 : :
195 [ - + ]: 11 : if (!best) {
196 : 0 : PL_WARN(p, "ICC profile too wide to handle, colors may be clipped!");
197 : 0 : icc->containing_primaries = PL_COLOR_PRIM_ACES_AP0;
198 : : }
199 : :
200 : : // Detect match for known transfer functions
201 : 11 : const float contrast = icc->csp.hdr.max_luma / icc->csp.hdr.min_luma;
202 : : float best_errsum = 0.0f;
203 [ + + ]: 187 : for (enum pl_color_transfer trc = 1; trc < PL_COLOR_TRC_COUNT; trc++) {
204 : 176 : struct pl_color_space ref = {
205 : 176 : .primaries = icc->csp.primaries,
206 : : .transfer = trc,
207 : : .hdr.max_luma = PL_COLOR_SDR_WHITE,
208 : 176 : .hdr.min_luma = PL_COLOR_SDR_WHITE * contrast,
209 : : };
210 : :
211 : : float errsum = 0.0f;
212 [ + + ]: 44880 : for (int i = RAMP; i < PL_ARRAY_SIZE(dst); i++) {
213 : 44704 : const float x = test[i][0] / 255.0;
214 : 44704 : float color[3] = { x, x, x };
215 : 44704 : pl_color_linearize(&ref, color);
216 : 44704 : const float delta = dst[i].Y - color[0];
217 : 44704 : errsum += delta * delta;
218 : : }
219 : : const int N = PL_ARRAY_SIZE(dst) - RAMP;
220 : : const float tolerance = 5e-3f; // 0.5% stddev(error), around JND
221 [ + + ]: 176 : if (errsum > N * PL_SQUARE(tolerance))
222 : 156 : continue;
223 : :
224 [ + + + - ]: 20 : if (!icc->csp.transfer || errsum < best_errsum) {
225 : 20 : icc->csp.transfer = trc;
226 : : best_errsum = errsum;
227 : : }
228 : : }
229 : :
230 : : // TODO: re-use pl_shader_linearize() and a built-in parametric
231 : : // profile, instead of a pure power gamma approximation?
232 : :
233 : : // Rough estimate of overall gamma and starting point for curve black point
234 [ + - ]: 11 : const float y_approx = dst[GRAY].Y ? log(dst[GRAY].Y) / log(0.5) : 1.0f;
235 : 11 : const float kb = fmaxf(dst[BLACK].Y, 0.0f);
236 : 11 : float b = powf(kb, 1 / y_approx);
237 : :
238 : : // Estimate mean and stddev of gamma (Welford's method)
239 : : float M = 0.0, S = 0.0;
240 : : int k = 1;
241 [ + + ]: 2805 : for (int i = RAMP; i < PL_ARRAY_SIZE(dst); i++) { // exclude primaries
242 [ + - - + ]: 2794 : if (dst[i].Y <= 0 || dst[i].Y >= 1)
243 : 0 : continue;
244 : 2794 : float src = (1 - b) * (test[i][0] / 255.0) + b;
245 : 2794 : float y = log(dst[i].Y) / log(src);
246 : : float tmpM = M;
247 : 2794 : M += (y - tmpM) / k;
248 : 2794 : S += (y - tmpM) * (y - M);
249 : 2794 : k++;
250 : :
251 : : // Update estimate of black point according to current gamma estimate
252 : 2794 : b = powf(kb, 1 / M);
253 : : }
254 : 11 : S = sqrt(S / (k - 1));
255 : :
256 [ - + ]: 11 : if (M <= 0) {
257 : 0 : PL_ERR(p, "Arithmetic error in ICC profile gamma estimation? "
258 : : "Please open an issue");
259 : 0 : return false;
260 : : }
261 : :
262 : 11 : icc->gamma = M;
263 : 11 : p->gamma_stddev = S;
264 : 11 : return true;
265 : : }
266 : :
267 : 11 : static bool detect_contrast(struct pl_icc_object_t *icc,
268 : : struct pl_icc_params *params)
269 : : {
270 : 11 : struct icc_priv *p = PL_PRIV(icc);
271 : 11 : enum pl_rendering_intent intent = params->intent;
272 : : struct pl_hdr_metadata *hdr = &icc->csp.hdr;
273 : :
274 : : /* LittleCMS refuses to detect an intent in absolute colorimetric intent,
275 : : * so fall back to relative colorimetric since we only care about the
276 : : * brightness value here */
277 [ - + ]: 11 : if (intent == PL_INTENT_ABSOLUTE_COLORIMETRIC)
278 : : intent = PL_INTENT_RELATIVE_COLORIMETRIC;
279 [ - + ]: 11 : if (!cmsDetectDestinationBlackPoint(&p->black, p->profile, intent, 0)) {
280 : : /*
281 : : * v4 ICC profiles have a black point tag but only for
282 : : * perceptual/saturation intents. So we change the rendering intent
283 : : * to perceptual if we are provided a v4 ICC profile.
284 : : */
285 [ # # # # ]: 0 : if (cmsGetEncodedICCversion(p->profile) >= 0x4000000 && intent != PL_INTENT_PERCEPTUAL) {
286 : 0 : params->intent = PL_INTENT_PERCEPTUAL;
287 : 0 : return detect_contrast(icc, params);
288 : : }
289 : :
290 : 0 : PL_ERR(p, "Failed detecting ICC profile black point!");
291 : 0 : return false;
292 : : }
293 : :
294 : 11 : float max_luma = params->max_luma;
295 : 11 : p->white = cmsReadTag(p->profile, cmsSigLuminanceTag);
296 [ - + ]: 11 : if (max_luma <= 0)
297 [ # # ]: 0 : max_luma = p->white ? p->white->Y : PL_COLOR_SDR_WHITE;
298 : :
299 : 11 : hdr->max_luma = max_luma;
300 : 11 : hdr->min_luma = p->black.Y * max_luma;
301 [ - + ]: 11 : hdr->min_luma = PL_MAX(hdr->min_luma, 1e-6); // prevent true 0
302 : 11 : return true;
303 : : }
304 : :
305 : 11 : static void infer_clut_size(struct pl_icc_object_t *icc)
306 : : {
307 : 11 : struct icc_priv *p = PL_PRIV(icc);
308 : : struct pl_icc_params *params = &icc->params;
309 [ - + - - : 11 : if (params->size_r && params->size_g && params->size_b) {
- - ]
310 : 0 : PL_DEBUG(p, "Using fixed 3DLUT size: %dx%dx%d",
311 : : (int) params->size_r, (int) params->size_g, (int) params->size_b);
312 : 0 : return;
313 : : }
314 : :
315 : : #define REQUIRE_SIZE(N) \
316 : : params->size_r = PL_MAX(params->size_r, N); \
317 : : params->size_g = PL_MAX(params->size_g, N); \
318 : : params->size_b = PL_MAX(params->size_b, N)
319 : :
320 : : // Default size for sanity
321 : 11 : REQUIRE_SIZE(9);
322 : :
323 : : // Ensure enough precision to track the (absolute) black point
324 [ - + ]: 11 : if (p->black.Y > 1e-4) {
325 : 0 : float black_rel = powf(p->black.Y, 1.0f / icc->gamma);
326 : 0 : int min_size = 2 * (int) ceilf(1.0f / black_rel);
327 : 0 : REQUIRE_SIZE(min_size);
328 : : }
329 : :
330 : : // Ensure enough precision to track the gamma curve
331 [ + - ]: 11 : if (p->gamma_stddev > 1e-2) {
332 : 11 : REQUIRE_SIZE(65);
333 [ # # ]: 0 : } else if (p->gamma_stddev > 1e-3) {
334 : 0 : REQUIRE_SIZE(33);
335 [ # # ]: 0 : } else if (p->gamma_stddev > 1e-4) {
336 : 0 : REQUIRE_SIZE(17);
337 : : }
338 : :
339 : : // Ensure enough precision to track any internal CLUTs
340 : : cmsPipeline *pipe = NULL;
341 [ - + - ]: 11 : switch (icc->params.intent) {
342 : 0 : case PL_INTENT_SATURATION:
343 : 0 : pipe = cmsReadTag(p->profile, cmsSigBToA2Tag);
344 [ # # ]: 0 : if (pipe)
345 : : break;
346 : : // fall through
347 : : case PL_INTENT_RELATIVE_COLORIMETRIC:
348 : : case PL_INTENT_ABSOLUTE_COLORIMETRIC:
349 : : default:
350 : 11 : pipe = cmsReadTag(p->profile, cmsSigBToA1Tag);
351 [ + - ]: 11 : if (pipe)
352 : : break;
353 : : // fall through
354 : : case PL_INTENT_PERCEPTUAL:
355 : 11 : pipe = cmsReadTag(p->profile, cmsSigBToA0Tag);
356 : : break;
357 : : }
358 : :
359 [ + - ]: 11 : if (!pipe) {
360 [ - + - ]: 11 : switch (icc->params.intent) {
361 : 0 : case PL_INTENT_SATURATION:
362 : 0 : pipe = cmsReadTag(p->profile, cmsSigAToB2Tag);
363 [ # # ]: 0 : if (pipe)
364 : : break;
365 : : // fall through
366 : : case PL_INTENT_RELATIVE_COLORIMETRIC:
367 : : case PL_INTENT_ABSOLUTE_COLORIMETRIC:
368 : : default:
369 : 11 : pipe = cmsReadTag(p->profile, cmsSigAToB1Tag);
370 [ + - ]: 11 : if (pipe)
371 : : break;
372 : : // fall through
373 : : case PL_INTENT_PERCEPTUAL:
374 : 11 : pipe = cmsReadTag(p->profile, cmsSigAToB0Tag);
375 : 11 : break;
376 : : }
377 : : }
378 : :
379 [ - + ]: 11 : if (pipe) {
380 : 0 : for (cmsStage *stage = cmsPipelineGetPtrToFirstStage(pipe);
381 [ # # ]: 0 : stage; stage = cmsStageNext(stage))
382 : : {
383 [ # # ]: 0 : switch (cmsStageType(stage)) {
384 : 0 : case cmsSigCLutElemType: ;
385 : 0 : _cmsStageCLutData *data = cmsStageData(stage);
386 [ # # ]: 0 : if (data->Params->nInputs != 3)
387 : 0 : continue;
388 : 0 : params->size_r = PL_MAX(params->size_r, data->Params->nSamples[0]);
389 : 0 : params->size_g = PL_MAX(params->size_g, data->Params->nSamples[1]);
390 : 0 : params->size_b = PL_MAX(params->size_b, data->Params->nSamples[2]);
391 : 0 : break;
392 : :
393 : 0 : default:
394 : 0 : continue;
395 : : }
396 : : }
397 : : }
398 : :
399 : : // Clamp the output size to make sure profiles are not too large
400 : 11 : params->size_r = PL_MIN(params->size_r, 129);
401 : 11 : params->size_g = PL_MIN(params->size_g, 129);
402 : 11 : params->size_b = PL_MIN(params->size_b, 129);
403 : :
404 : : // Constrain the total LUT size to roughly 1M entries
405 : : const size_t max_size = 1000000;
406 : 11 : size_t total_size = params->size_r * params->size_g * params->size_b;
407 [ - + ]: 11 : if (total_size > max_size) {
408 : 0 : float factor = powf((float) max_size / total_size, 1/3.0f);
409 : 0 : params->size_r = ceilf(factor * params->size_r);
410 : 0 : params->size_g = ceilf(factor * params->size_g);
411 : 0 : params->size_b = ceilf(factor * params->size_b);
412 : : }
413 : : }
414 : :
415 : 11 : static bool icc_init(struct pl_icc_object_t *icc)
416 : : {
417 : 11 : struct icc_priv *p = PL_PRIV(icc);
418 : 11 : struct pl_icc_params *params = &icc->params;
419 [ - + ]: 11 : if (params->intent < 0 || params->intent > PL_INTENT_ABSOLUTE_COLORIMETRIC)
420 : 0 : params->intent = cmsGetHeaderRenderingIntent(p->profile);
421 : :
422 [ - + ]: 11 : if (!detect_contrast(icc, params))
423 : : return false;
424 [ - + ]: 11 : if (!detect_csp(icc))
425 : : return false;
426 : 11 : infer_clut_size(icc);
427 : :
428 : : // Create approximation profile. Use a tone-curve based on a BT.1886-style
429 : : // pure power curve, with an approximation gamma matched to the ICC
430 : : // profile. We stretch the luminance range *before* the input to the gamma
431 : : // function, to avoid numerical issues near the black point. (This removes
432 : : // the need for a separate linear section)
433 : : //
434 : : // Y = scale * (aX + b)^y, where Y = PCS luma and X = encoded value ([0-1])
435 : 11 : p->scale = pl_hdr_rescale(PL_HDR_NITS, PL_HDR_NORM, icc->csp.hdr.max_luma);
436 : 11 : p->b = powf(icc->csp.hdr.min_luma / icc->csp.hdr.max_luma, 1.0f / icc->gamma);
437 : 11 : p->a = (1 - p->b);
438 : 11 : cmsToneCurve *curve = cmsBuildParametricToneCurve(p->cms, 2,
439 : 11 : (double[3]) { icc->gamma, p->a, p->b });
440 [ - + ]: 11 : if (!curve)
441 : : return false;
442 : :
443 : : const struct pl_raw_primaries *prim =
444 : 11 : pl_raw_primaries_get(icc->containing_primaries);
445 : 11 : cmsCIExyY wp_xyY = { prim->white.x, prim->white.y, 1.0 };
446 : 11 : cmsCIExyYTRIPLE prim_xyY = {
447 : 11 : .Red = { prim->red.x, prim->red.y, 1.0 },
448 : 11 : .Green = { prim->green.x, prim->green.y, 1.0 },
449 : 11 : .Blue = { prim->blue.x, prim->blue.y, 1.0 },
450 : : };
451 : :
452 : 22 : p->approx = cmsCreateRGBProfileTHR(p->cms, &wp_xyY, &prim_xyY,
453 : 11 : (cmsToneCurve *[3]){ curve, curve, curve });
454 : 11 : cmsFreeToneCurve(curve);
455 [ - + ]: 11 : if (!p->approx)
456 : : return false;
457 : :
458 : : // We need to create an ICC V2 profile because ICC V4 perceptual profiles
459 : : // have normalized semantics, but we want colorimetric mapping with BPC
460 : 11 : cmsSetHeaderRenderingIntent(p->approx, icc->params.intent);
461 : 11 : cmsSetProfileVersion(p->approx, 2.2);
462 : :
463 : : // Hash all parameters affecting the generated 3DLUT
464 : : p->lut_sig = CACHE_KEY_ICC_3DLUT;
465 [ + - ]: 11 : pl_hash_merge(&p->lut_sig, icc->signature);
466 : 11 : pl_hash_merge(&p->lut_sig, params->intent);
467 : 11 : pl_hash_merge(&p->lut_sig, params->size_r);
468 : 11 : pl_hash_merge(&p->lut_sig, params->size_g);
469 : 11 : pl_hash_merge(&p->lut_sig, params->size_b);
470 : 11 : pl_hash_merge(&p->lut_sig, params->force_bpc);
471 : 11 : union { double d; uint64_t u; } v = { .d = icc->csp.hdr.max_luma };
472 : : pl_hash_merge(&p->lut_sig, v.u);
473 : : // min luma depends only on the max luma and profile
474 : :
475 : : // Backwards compatibility with old caching API
476 [ + - - + : 11 : if ((params->cache_save || params->cache_load) && !params->cache) {
- - ]
477 [ # # # # ]: 0 : p->cache = pl_cache_create(pl_cache_params(
478 : : .log = p->log,
479 : : .set = params->cache_save ? set_callback : NULL,
480 : : .get = params->cache_load ? get_callback : NULL,
481 : : .priv = icc,
482 : : ));
483 : : }
484 : :
485 : : // Dump profile information
486 : 11 : PL_INFO(p, "Opened ICC profile:");
487 [ - + ]: 11 : if (p->white) {
488 : 0 : PL_DEBUG(p, " Raw white point: X=%.2f Y=%.2f Z=%.2f cd/m^2",
489 : : p->white->X, p->white->Y, p->white->Z);
490 : : }
491 : 11 : PL_DEBUG(p, " Raw black point: X=%.6f%% Y=%.6f%% Z=%.6f%%",
492 : : p->black.X * 100, p->black.Y * 100, p->black.Z * 100);
493 : 11 : PL_INFO(p, " Contrast = %.0f cd/m^2 : %.3f mcd/m^2 ≈ %.0f : 1",
494 : : icc->csp.hdr.max_luma, icc->csp.hdr.min_luma * 1000,
495 : : icc->csp.hdr.max_luma / icc->csp.hdr.min_luma);
496 : :
497 [ + - ]: 11 : if (icc->csp.primaries) {
498 : 11 : PL_INFO(p, " Detected primaries: %s",
499 : : pl_color_primaries_name(icc->csp.primaries));
500 : : } else {
501 : : const struct pl_raw_primaries *raw = &icc->csp.hdr.prim;
502 : 0 : PL_DEBUG(p, " Measured primaries:");
503 : 0 : PL_DEBUG(p, " White: x=%.6f, y=%.6f", raw->white.x, raw->white.y);
504 : 0 : PL_DEBUG(p, " Red: x=%.3f, y=%.3f", raw->red.x, raw->red.y);
505 : 0 : PL_DEBUG(p, " Green: x=%.3f, y=%.3f", raw->green.x, raw->green.y);
506 : 0 : PL_DEBUG(p, " Blue: x=%.3f, y=%.3f", raw->blue.x, raw->blue.y);
507 : 0 : PL_INFO(p, " Containing primaries: %s",
508 : : pl_color_primaries_name(icc->containing_primaries));
509 : : }
510 : :
511 [ + + ]: 11 : if (icc->csp.transfer) {
512 : 10 : PL_INFO(p, " Transfer function: %s",
513 : : pl_color_transfer_name(icc->csp.transfer));
514 : : } else {
515 [ + - ]: 2 : PL_INFO(p, " Approximation gamma: %.3f (stddev %.1f%s)",
516 : : icc->gamma, p->gamma_stddev,
517 : : p->gamma_stddev > 0.5 ? ", inaccurate!" : "");
518 : : }
519 : :
520 : : return true;
521 : : }
522 : :
523 : 11 : pl_icc_object pl_icc_open(pl_log log, const struct pl_icc_profile *profile,
524 : : const struct pl_icc_params *params)
525 : : {
526 [ - + ]: 11 : if (!profile->len)
527 : : return NULL;
528 : :
529 : 11 : struct pl_icc_object_t *icc = pl_zalloc_obj(NULL, icc, struct icc_priv);
530 : 11 : struct icc_priv *p = PL_PRIV(icc);
531 [ + + ]: 11 : icc->params = params ? *params : pl_icc_default_params;
532 : 11 : icc->signature = profile->signature;
533 : 11 : p->log = log;
534 : 11 : p->cms = cmsCreateContext(NULL, (void *) log);
535 [ - + ]: 11 : if (!p->cms) {
536 : 0 : PL_ERR(p, "Failed creating LittleCMS context!");
537 : 0 : goto error;
538 : : }
539 : :
540 : 11 : cmsSetLogErrorHandlerTHR(p->cms, error_callback);
541 : 11 : PL_DEBUG(p, "Opening new ICC profile");
542 : 11 : p->profile = cmsOpenProfileFromMemTHR(p->cms, profile->data, profile->len);
543 [ - + ]: 11 : if (!p->profile) {
544 : 0 : PL_ERR(p, "Failed opening ICC profile");
545 : 0 : goto error;
546 : : }
547 : :
548 [ - + ]: 11 : if (cmsGetColorSpace(p->profile) != cmsSigRgbData) {
549 : 0 : PL_ERR(p, "Invalid ICC profile: not RGB");
550 : 0 : goto error;
551 : : }
552 : :
553 [ - + ]: 11 : if (!icc_init(icc))
554 : 0 : goto error;
555 : :
556 : 11 : return icc;
557 : :
558 : 0 : error:
559 : 0 : pl_icc_close((pl_icc_object *) &icc);
560 : 0 : return NULL;
561 : : }
562 : :
563 : 0 : static bool icc_reopen(pl_icc_object kicc, const struct pl_icc_params *params)
564 : : {
565 : : struct pl_icc_object_t *icc = (struct pl_icc_object_t *) kicc;
566 : 0 : struct icc_priv *p = PL_PRIV(icc);
567 : 0 : cmsCloseProfile(p->approx);
568 : 0 : pl_cache_destroy(&p->cache);
569 : :
570 : 0 : *icc = (struct pl_icc_object_t) {
571 : 0 : .params = *params,
572 : 0 : .signature = icc->signature,
573 : : };
574 : :
575 : 0 : *p = (struct icc_priv) {
576 : 0 : .log = p->log,
577 : 0 : .cms = p->cms,
578 : 0 : .profile = p->profile,
579 : : };
580 : :
581 : 0 : PL_DEBUG(p, "Reinitializing ICC profile in-place");
582 : 0 : return icc_init(icc);
583 : : }
584 : :
585 : 16 : bool pl_icc_update(pl_log log, pl_icc_object *out_icc,
586 : : const struct pl_icc_profile *profile,
587 : : const struct pl_icc_params *params)
588 : : {
589 [ + - ]: 16 : params = PL_DEF(params, &pl_icc_default_params);
590 : 16 : pl_icc_object icc = *out_icc;
591 [ - + ]: 16 : if (!icc && !profile)
592 : : return false; // nothing to update
593 : :
594 [ + - ]: 16 : uint64_t sig = profile ? profile->signature : icc->signature;
595 [ + + - + ]: 16 : if (!icc || icc->signature != sig) {
596 [ - + ]: 8 : pl_assert(profile);
597 : 8 : pl_icc_close(&icc);
598 : 8 : *out_icc = icc = pl_icc_open(log, profile, params);
599 : 8 : return icc != NULL;
600 : : }
601 : :
602 [ + - ]: 8 : int size_r = PL_DEF(params->size_r, icc->params.size_r);
603 [ + - ]: 8 : int size_g = PL_DEF(params->size_g, icc->params.size_g);
604 [ + - ]: 8 : int size_b = PL_DEF(params->size_b, icc->params.size_b);
605 : 16 : bool compat = params->intent == icc->params.intent &&
606 [ + - ]: 8 : params->max_luma == icc->params.max_luma &&
607 [ + - ]: 8 : params->force_bpc == icc->params.force_bpc &&
608 [ + - ]: 8 : size_r == icc->params.size_r &&
609 [ + - + - ]: 16 : size_g == icc->params.size_g &&
610 [ - + ]: 8 : size_b == icc->params.size_b;
611 : : if (compat)
612 : : return true;
613 : :
614 : : // ICC signature is the same but parameters are different, re-open in-place
615 [ # # ]: 0 : if (!icc_reopen(icc, params)) {
616 : 0 : pl_icc_close(&icc);
617 : 0 : *out_icc = NULL;
618 : 0 : return false;
619 : : }
620 : :
621 : : return true;
622 : : }
623 : :
624 : 8 : static void fill_lut(void *datap, const struct sh_lut_params *params, bool decode)
625 : : {
626 : 8 : pl_icc_object icc = params->priv;
627 : 8 : struct icc_priv *p = PL_PRIV(icc);
628 [ + + ]: 8 : cmsHPROFILE srcp = decode ? p->profile : p->approx;
629 [ + + ]: 8 : cmsHPROFILE dstp = decode ? p->approx : p->profile;
630 : 8 : int s_r = params->width, s_g = params->height, s_b = params->depth;
631 : :
632 : : pl_clock_t start = pl_clock_now();
633 : 8 : cmsHTRANSFORM tf = cmsCreateTransformTHR(p->cms, srcp, TYPE_RGB_16,
634 : : dstp, TYPE_RGBA_16,
635 : 8 : icc->params.intent,
636 : : cmsFLAGS_BLACKPOINTCOMPENSATION |
637 : : cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
638 [ + - ]: 8 : if (!tf)
639 : : return;
640 : :
641 : : pl_clock_t after_transform = pl_clock_now();
642 : 8 : pl_log_cpu_time(p->log, start, after_transform, "creating ICC transform");
643 : :
644 : 8 : uint16_t *tmp = pl_alloc(NULL, s_r * 3 * sizeof(tmp[0]));
645 [ + + ]: 528 : for (int b = 0; b < s_b; b++) {
646 [ + + ]: 34320 : for (int g = 0; g < s_g; g++) {
647 : : // Transform a single line of the output buffer
648 [ + + ]: 2230800 : for (int r = 0; r < s_r; r++) {
649 : 2197000 : tmp[r * 3 + 0] = r * 65535 / (s_r - 1);
650 : 2197000 : tmp[r * 3 + 1] = g * 65535 / (s_g - 1);
651 : 2197000 : tmp[r * 3 + 2] = b * 65535 / (s_b - 1);
652 : : }
653 : :
654 : 33800 : size_t offset = (b * s_g + g) * s_r * 4;
655 : 33800 : uint16_t *data = ((uint16_t *) datap) + offset;
656 : 33800 : cmsDoTransform(tf, tmp, data, s_r);
657 : :
658 [ + - ]: 33800 : if (!icc->params.force_bpc)
659 : 33800 : continue;
660 : :
661 : : // Fix the black point manually. Work-around for "improper"
662 : : // profiles, as black point compensation should already have
663 : : // taken care of this normally.
664 : : const uint16_t knee = 16u << 8;
665 [ # # # # ]: 0 : if (tmp[0] >= knee || tmp[1] >= knee)
666 : 0 : continue;
667 [ # # ]: 0 : for (int r = 0; r < s_r; r++) {
668 : 0 : uint16_t s = (2 * tmp[1] + tmp[2] + tmp[r * 3]) >> 2;
669 [ # # ]: 0 : if (s >= knee)
670 : : break;
671 [ # # ]: 0 : for (int c = 0; c < 3; c++)
672 : 0 : data[r * 3 + c] = (s * data[r * 3 + c] + (knee - s) * s) >> 12;
673 : : }
674 : : }
675 : : }
676 : :
677 : 8 : pl_log_cpu_time(p->log, after_transform, pl_clock_now(), "generating ICC 3DLUT");
678 : 8 : cmsDeleteTransform(tf);
679 : 8 : pl_free(tmp);
680 : : }
681 : :
682 : 4 : static void fill_decode(void *datap, const struct sh_lut_params *params)
683 : : {
684 : 4 : fill_lut(datap, params, true);
685 : 4 : }
686 : :
687 : 4 : static void fill_encode(void *datap, const struct sh_lut_params *params)
688 : : {
689 : 4 : fill_lut(datap, params, false);
690 : 4 : }
691 : :
692 : : static pl_cache get_cache(pl_icc_object icc, pl_shader sh)
693 : : {
694 : : struct icc_priv *p = PL_PRIV(icc);
695 [ + - + - ]: 16 : return PL_DEF(icc->params.cache, PL_DEF(p->cache, SH_CACHE(sh)));
696 : : }
697 : :
698 : 8 : void pl_icc_decode(pl_shader sh, pl_icc_object icc, pl_shader_obj *lut_obj,
699 : : struct pl_color_space *out_csp)
700 : : {
701 : 8 : struct icc_priv *p = PL_PRIV(icc);
702 [ + - ]: 8 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
703 : 0 : return;
704 : :
705 : 8 : pl_fmt fmt = pl_find_fmt(SH_GPU(sh), PL_FMT_UNORM, 4, 16, 16, PL_FMT_CAP_LINEAR);
706 [ - + ]: 8 : if (!fmt) {
707 : 0 : SH_FAIL(sh, "Failed finding ICC 3DLUT texture format!");
708 : 0 : return;
709 : : }
710 : :
711 [ + - ]: 16 : ident_t lut = sh_lut(sh, sh_lut_params(
712 : : .object = lut_obj,
713 : : .var_type = PL_VAR_FLOAT,
714 : : .method = SH_LUT_TETRAHEDRAL,
715 : : .fmt = fmt,
716 : : .width = icc->params.size_r,
717 : : .height = icc->params.size_g,
718 : : .depth = icc->params.size_b,
719 : : .comps = 4,
720 : : .signature = p->lut_sig,
721 : : .fill = fill_decode,
722 : : .cache = get_cache(icc, sh),
723 : : .priv = (void *) icc,
724 : : ));
725 : :
726 [ - + ]: 8 : if (!lut) {
727 : 0 : SH_FAIL(sh, "pl_icc_decode: failed generating LUT object");
728 : 0 : return;
729 : : }
730 : :
731 : : // Y = scale * (aX + b)^y
732 : 8 : sh_describe(sh, "ICC 3DLUT");
733 : 8 : GLSL("// pl_icc_decode \n"
734 : : "{ \n"
735 : : "color.rgb = "$"(color.rgb).rgb; \n"
736 : : "color.rgb = "$" * color.rgb + vec3("$"); \n"
737 : : "color.rgb = pow(color.rgb, vec3("$")); \n"
738 : : "color.rgb = "$" * color.rgb; \n"
739 : : "} \n",
740 : : lut,
741 : : SH_FLOAT(p->a), SH_FLOAT(p->b),
742 : : SH_FLOAT(icc->gamma),
743 : : SH_FLOAT(p->scale));
744 : :
745 [ + - ]: 8 : if (out_csp) {
746 : 8 : *out_csp = (struct pl_color_space) {
747 : 8 : .primaries = icc->containing_primaries,
748 : : .transfer = PL_COLOR_TRC_LINEAR,
749 : 8 : .hdr = icc->csp.hdr,
750 : : };
751 : : }
752 : : }
753 : :
754 : 8 : void pl_icc_encode(pl_shader sh, pl_icc_object icc, pl_shader_obj *lut_obj)
755 : : {
756 : 8 : struct icc_priv *p = PL_PRIV(icc);
757 [ + - ]: 8 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
758 : 0 : return;
759 : :
760 : 8 : pl_fmt fmt = pl_find_fmt(SH_GPU(sh), PL_FMT_UNORM, 4, 16, 16, PL_FMT_CAP_LINEAR);
761 [ - + ]: 8 : if (!fmt) {
762 : 0 : SH_FAIL(sh, "Failed finding ICC 3DLUT texture format!");
763 : 0 : return;
764 : : }
765 : :
766 [ + - ]: 16 : ident_t lut = sh_lut(sh, sh_lut_params(
767 : : .object = lut_obj,
768 : : .var_type = PL_VAR_FLOAT,
769 : : .method = SH_LUT_TETRAHEDRAL,
770 : : .fmt = fmt,
771 : : .width = icc->params.size_r,
772 : : .height = icc->params.size_g,
773 : : .depth = icc->params.size_b,
774 : : .comps = 4,
775 : : .signature = ~p->lut_sig, // avoid confusion with decoding LUTs
776 : : .fill = fill_encode,
777 : : .cache = get_cache(icc, sh),
778 : : .priv = (void *) icc,
779 : : ));
780 : :
781 [ - + ]: 8 : if (!lut) {
782 : 0 : SH_FAIL(sh, "pl_icc_encode: failed generating LUT object");
783 : 0 : return;
784 : : }
785 : :
786 : : // X = 1/a * (Y/scale)^(1/y) - b/a
787 : 8 : sh_describe(sh, "ICC 3DLUT");
788 : 8 : GLSL("// pl_icc_encode \n"
789 : : "{ \n"
790 : : "color.rgb = max(color.rgb, 0.0); \n"
791 : : "color.rgb = 1.0/"$" * color.rgb; \n"
792 : : "color.rgb = pow(color.rgb, vec3("$")); \n"
793 : : "color.rgb = 1.0/"$" * color.rgb - "$"; \n"
794 : : "color.rgb = "$"(color.rgb).rgb; \n"
795 : : "} \n",
796 : : SH_FLOAT(p->scale),
797 : : SH_FLOAT(1.0f / icc->gamma),
798 : : SH_FLOAT(p->a), SH_FLOAT(p->b / p->a),
799 : : lut);
800 : : }
801 : :
802 : : #else // !PL_HAVE_LCMS
803 : :
804 : : void pl_icc_close(pl_icc_object *picc) {};
805 : : pl_icc_object pl_icc_open(pl_log log, const struct pl_icc_profile *profile,
806 : : const struct pl_icc_params *pparams)
807 : : {
808 : : pl_err(log, "libplacebo compiled without LittleCMS 2 support!");
809 : : return NULL;
810 : : }
811 : :
812 : : bool pl_icc_update(pl_log log, pl_icc_object *obj,
813 : : const struct pl_icc_profile *profile,
814 : : const struct pl_icc_params *params)
815 : : {
816 : : static bool warned;
817 : : if (!warned) {
818 : : pl_err(log, "libplacebo compiled without LittleCMS 2 support!");
819 : : warned = true;
820 : : }
821 : : *obj = NULL;
822 : : return false;
823 : : }
824 : :
825 : : void pl_icc_decode(pl_shader sh, pl_icc_object icc, pl_shader_obj *lut_obj,
826 : : struct pl_color_space *out_csp)
827 : : {
828 : : pl_unreachable(); // can't get a pl_icc_object
829 : : }
830 : :
831 : : void pl_icc_encode(pl_shader sh, pl_icc_object icc, pl_shader_obj *lut_obj)
832 : : {
833 : : pl_unreachable();
834 : : }
835 : :
836 : : #endif
|