avformat/mpeg: demux ivtv captions
[ffmpeg.git] / libavcodec / fflcms2.c
blob3b67e62d3afc00101f06228e85980ef3be1fe8af
1 /*
2 * Copyright (c) 2022 Niklas Haas
3 * This file is part of FFmpeg.
5 * FFmpeg is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * FFmpeg is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with FFmpeg; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "libavutil/csp.h"
22 #include "fflcms2.h"
24 static void log_cb(cmsContext ctx, cmsUInt32Number error, const char *str)
26 FFIccContext *s = cmsGetContextUserData(ctx);
27 av_log(s->avctx, AV_LOG_ERROR, "lcms2: [%"PRIu32"] %s\n", error, str);
30 int ff_icc_context_init(FFIccContext *s, void *avctx)
32 memset(s, 0, sizeof(*s));
33 s->avctx = avctx;
34 s->ctx = cmsCreateContext(NULL, s);
35 if (!s->ctx)
36 return AVERROR(ENOMEM);
38 cmsSetLogErrorHandlerTHR(s->ctx, log_cb);
39 return 0;
42 void ff_icc_context_uninit(FFIccContext *s)
44 for (int i = 0; i < FF_ARRAY_ELEMS(s->curves); i++)
45 cmsFreeToneCurve(s->curves[i]);
46 cmsDeleteContext(s->ctx);
47 memset(s, 0, sizeof(*s));
50 static int get_curve(FFIccContext *s, enum AVColorTransferCharacteristic trc,
51 cmsToneCurve **out_curve)
53 if (trc >= AVCOL_TRC_NB)
54 return AVERROR_INVALIDDATA;
56 if (s->curves[trc])
57 goto done;
59 switch (trc) {
60 case AVCOL_TRC_LINEAR:
61 s->curves[trc] = cmsBuildGamma(s->ctx, 1.0);
62 break;
63 case AVCOL_TRC_GAMMA22:
64 s->curves[trc] = cmsBuildGamma(s->ctx, 2.2);
65 break;
66 case AVCOL_TRC_GAMMA28:
67 s->curves[trc] = cmsBuildGamma(s->ctx, 2.8);
68 break;
69 case AVCOL_TRC_BT709:
70 case AVCOL_TRC_SMPTE170M:
71 case AVCOL_TRC_BT2020_10:
72 case AVCOL_TRC_BT2020_12:
73 s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
74 /* γ = */ 1/0.45,
75 /* a = */ 1/1.099296826809442,
76 /* b = */ 1 - 1/1.099296826809442,
77 /* c = */ 1/4.5,
78 /* d = */ 4.5 * 0.018053968510807,
79 });
80 break;
81 case AVCOL_TRC_SMPTE240M:
82 s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
83 /* γ = */ 1/0.45,
84 /* a = */ 1/1.1115,
85 /* b = */ 1 - 1/1.1115,
86 /* c = */ 1/4.0,
87 /* d = */ 4.0 * 0.0228,
88 });
89 break;
90 case AVCOL_TRC_LOG:
91 s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
92 /* a = */ 1.0,
93 /* b = */ 10.0,
94 /* c = */ 2.0,
95 /* d = */ -1.0,
96 /* e = */ 0.0
97 });
98 break;
99 case AVCOL_TRC_LOG_SQRT:
100 s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
101 /* a = */ 1.0,
102 /* b = */ 10.0,
103 /* c = */ 2.5,
104 /* d = */ -1.0,
105 /* e = */ 0.0
107 break;
108 case AVCOL_TRC_IEC61966_2_1:
109 s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
110 /* γ = */ 2.4,
111 /* a = */ 1/1.055,
112 /* b = */ 1 - 1/1.055,
113 /* c = */ 1/12.92,
114 /* d = */ 12.92 * 0.0031308,
116 break;
117 case AVCOL_TRC_SMPTE428:
118 s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 2, (double[3]) {
119 /* γ = */ 2.6,
120 /* a = */ pow(52.37/48.0, 1/2.6),
121 /* b = */ 0.0
123 break;
125 /* Can't be represented using the existing parametric tone curves.
126 * FIXME: use cmsBuildTabulatedToneCurveFloat instead */
127 case AVCOL_TRC_IEC61966_2_4:
128 case AVCOL_TRC_BT1361_ECG:
129 case AVCOL_TRC_SMPTE2084:
130 case AVCOL_TRC_ARIB_STD_B67:
131 return AVERROR_PATCHWELCOME;
133 default:
134 return AVERROR_INVALIDDATA;
137 if (!s->curves[trc])
138 return AVERROR(ENOMEM);
140 done:
141 *out_curve = s->curves[trc];
142 return 0;
145 int ff_icc_profile_generate(FFIccContext *s,
146 enum AVColorPrimaries color_prim,
147 enum AVColorTransferCharacteristic color_trc,
148 cmsHPROFILE *out_profile)
150 cmsToneCurve *tonecurve;
151 const AVColorPrimariesDesc *prim;
152 int ret;
154 if (!(prim = av_csp_primaries_desc_from_id(color_prim)))
155 return AVERROR_INVALIDDATA;
156 if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
157 return ret;
159 *out_profile = cmsCreateRGBProfileTHR(s->ctx,
160 &(cmsCIExyY) { av_q2d(prim->wp.x), av_q2d(prim->wp.y), 1.0 },
161 &(cmsCIExyYTRIPLE) {
162 .Red = { av_q2d(prim->prim.r.x), av_q2d(prim->prim.r.y), 1.0 },
163 .Green = { av_q2d(prim->prim.g.x), av_q2d(prim->prim.g.y), 1.0 },
164 .Blue = { av_q2d(prim->prim.b.x), av_q2d(prim->prim.b.y), 1.0 },
166 (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
169 return *out_profile == NULL ? AVERROR(ENOMEM) : 0;
172 int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
174 cmsUInt32Number size;
175 AVBufferRef *buf;
177 if (!cmsSaveProfileToMem(profile, NULL, &size))
178 return AVERROR_EXTERNAL;
180 buf = av_buffer_alloc(size);
181 if (!buf)
182 return AVERROR(ENOMEM);
184 if (!cmsSaveProfileToMem(profile, buf->data, &size) || size != buf->size) {
185 av_buffer_unref(&buf);
186 return AVERROR_EXTERNAL;
189 if (!av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, buf)) {
190 av_buffer_unref(&buf);
191 return AVERROR(ENOMEM);
194 return 0;
197 static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVCIExy *xy)
199 double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
200 xy->x = av_d2q(k * XYZ.X, 100000);
201 xy->y = av_d2q(k * XYZ.Y, 100000);
204 static av_always_inline AVRational abs_sub_q(AVRational r1, AVRational r2)
206 AVRational diff = av_sub_q(r1, r2);
207 /* denominator assumed to be positive */
208 return av_make_q(abs(diff.num), diff.den);
211 static const AVCIExy wp_d50 = { {3457, 10000}, {3585, 10000} }; /* CIE D50 */
213 int ff_icc_profile_sanitize(FFIccContext *s, cmsHPROFILE profile)
215 cmsCIEXYZ *white, fixed;
216 AVCIExy wpxy;
217 AVRational diff, z;
218 if (!profile)
219 return 0;
221 if (cmsGetEncodedICCversion(profile) >= 0x4000000) { // ICC v4
222 switch (cmsGetHeaderRenderingIntent(profile)) {
223 case INTENT_RELATIVE_COLORIMETRIC:
224 case INTENT_ABSOLUTE_COLORIMETRIC: ;
225 /* ICC v4 colorimetric profiles are specified to always use D50
226 * media white point, anything else is a violation of the spec.
227 * Sadly, such profiles are incredibly common (Apple...), so make
228 * an effort to fix them. */
229 if (!(white = cmsReadTag(profile, cmsSigMediaWhitePointTag)))
230 return AVERROR_INVALIDDATA;
231 XYZ_xy(*white, &wpxy);
232 diff = av_add_q(abs_sub_q(wpxy.x, wp_d50.x), abs_sub_q(wpxy.y, wp_d50.y));
233 if (av_cmp_q(diff, av_make_q(1, 1000)) > 0) {
234 av_log(s->avctx, AV_LOG_WARNING, "Invalid colorimetric ICCv4 "
235 "profile media white point tag (expected %.4f %.4f, "
236 "got %.4f %.4f)\n",
237 av_q2d(wp_d50.x), av_q2d(wp_d50.y),
238 av_q2d(wpxy.x), av_q2d(wpxy.y));
239 /* x+y+z = 1 */
240 z = av_sub_q(av_sub_q(av_make_q(1, 1), wp_d50.x), wp_d50.y);
241 fixed.X = av_q2d(av_div_q(wp_d50.x, wp_d50.y)) * white->Y;
242 fixed.Y = white->Y;
243 fixed.Z = av_q2d(av_div_q(z, wp_d50.y)) * white->Y;
244 if (!cmsWriteTag(profile, cmsSigMediaWhitePointTag, &fixed))
245 return AVERROR_EXTERNAL;
247 break;
248 default: break;
252 return 0;
255 int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
256 AVColorPrimariesDesc *out_primaries)
258 static const uint8_t testprimaries[4][3] = {
259 { 0xFF, 0, 0 }, /* red */
260 { 0, 0xFF, 0 }, /* green */
261 { 0, 0, 0xFF }, /* blue */
262 { 0xFF, 0xFF, 0xFF }, /* white */
265 AVWhitepointCoefficients *wp = &out_primaries->wp;
266 AVPrimaryCoefficients *prim = &out_primaries->prim;
267 cmsFloat64Number prev_adapt;
268 cmsHPROFILE xyz;
269 cmsHTRANSFORM tf;
270 cmsCIEXYZ dst[4];
272 xyz = cmsCreateXYZProfileTHR(s->ctx);
273 if (!xyz)
274 return AVERROR(ENOMEM);
276 /* We need to use an unadapted observer to get the raw values */
277 prev_adapt = cmsSetAdaptationStateTHR(s->ctx, 0.0);
278 tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
279 INTENT_ABSOLUTE_COLORIMETRIC,
280 /* Note: These flags mostly don't do anything
281 * anyway, but specify them regardless */
282 cmsFLAGS_NOCACHE |
283 cmsFLAGS_NOOPTIMIZE |
284 cmsFLAGS_LOWRESPRECALC |
285 cmsFLAGS_GRIDPOINTS(2));
286 cmsSetAdaptationStateTHR(s->ctx, prev_adapt);
287 cmsCloseProfile(xyz);
288 if (!tf) {
289 av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
290 return AVERROR_INVALIDDATA;
293 cmsDoTransform(tf, testprimaries, dst, 4);
294 cmsDeleteTransform(tf);
295 XYZ_xy(dst[0], &prim->r);
296 XYZ_xy(dst[1], &prim->g);
297 XYZ_xy(dst[2], &prim->b);
298 XYZ_xy(dst[3], wp);
299 return 0;
302 int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
303 enum AVColorTransferCharacteristic *out_trc)
305 /* 8-bit linear grayscale ramp */
306 static const uint8_t testramp[16][3] = {
307 { 1, 1, 1}, /* avoid exact zero due to log100 etc. */
308 { 17, 17, 17},
309 { 34, 34, 34},
310 { 51, 51, 51},
311 { 68, 68, 68},
312 { 85, 85, 85},
313 { 02, 02, 02},
314 {119, 119, 119},
315 {136, 136, 136},
316 {153, 153, 153},
317 {170, 170, 170},
318 {187, 187, 187},
319 {204, 204, 204},
320 {221, 221, 221},
321 {238, 238, 238},
322 {255, 255, 255},
325 double dst[FF_ARRAY_ELEMS(testramp)];
327 for (enum AVColorTransferCharacteristic trc = 0; trc < AVCOL_TRC_NB; trc++) {
328 cmsToneCurve *tonecurve;
329 cmsHPROFILE ref;
330 cmsHTRANSFORM tf;
331 double delta = 0.0;
332 if (get_curve(s, trc, &tonecurve) < 0)
333 continue;
335 ref = cmsCreateGrayProfileTHR(s->ctx, cmsD50_xyY(), tonecurve);
336 if (!ref)
337 return AVERROR(ENOMEM);
339 tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, ref, TYPE_GRAY_DBL,
340 INTENT_RELATIVE_COLORIMETRIC,
341 cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
342 cmsCloseProfile(ref);
343 if (!tf) {
344 av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
345 return AVERROR_INVALIDDATA;
348 cmsDoTransform(tf, testramp, dst, FF_ARRAY_ELEMS(dst));
349 cmsDeleteTransform(tf);
351 for (int i = 0; i < FF_ARRAY_ELEMS(dst); i++)
352 delta += fabs(testramp[i][0] / 255.0 - dst[i]);
353 if (delta < 0.01) {
354 *out_trc = trc;
355 return 0;
359 *out_trc = AVCOL_TRC_UNSPECIFIED;
360 return 0;