• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 Niklas Haas
3  * This file is part of FFmpeg.
4  *
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.
9  *
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.
14  *
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
18  */
19 
20 #include "libavutil/color_utils.h"
21 #include "libavutil/csp.h"
22 
23 #include "fflcms2.h"
24 
log_cb(cmsContext ctx,cmsUInt32Number error,const char * str)25 static void log_cb(cmsContext ctx, cmsUInt32Number error, const char *str)
26 {
27     FFIccContext *s = cmsGetContextUserData(ctx);
28     av_log(s->avctx, AV_LOG_ERROR, "lcms2: [%"PRIu32"] %s\n", error, str);
29 }
30 
ff_icc_context_init(FFIccContext * s,void * avctx)31 int ff_icc_context_init(FFIccContext *s, void *avctx)
32 {
33     memset(s, 0, sizeof(*s));
34     s->avctx = avctx;
35     s->ctx = cmsCreateContext(NULL, s);
36     if (!s->ctx)
37         return AVERROR(ENOMEM);
38 
39     cmsSetLogErrorHandlerTHR(s->ctx, log_cb);
40     return 0;
41 }
42 
ff_icc_context_uninit(FFIccContext * s)43 void ff_icc_context_uninit(FFIccContext *s)
44 {
45     for (int i = 0; i < FF_ARRAY_ELEMS(s->curves); i++)
46         cmsFreeToneCurve(s->curves[i]);
47     cmsDeleteContext(s->ctx);
48     memset(s, 0, sizeof(*s));
49 }
50 
get_curve(FFIccContext * s,enum AVColorTransferCharacteristic trc,cmsToneCurve ** out_curve)51 static int get_curve(FFIccContext *s, enum AVColorTransferCharacteristic trc,
52                      cmsToneCurve **out_curve)
53 {
54     if (trc >= AVCOL_TRC_NB)
55         return AVERROR_INVALIDDATA;
56 
57     if (s->curves[trc])
58         goto done;
59 
60     switch (trc) {
61     case AVCOL_TRC_LINEAR:
62         s->curves[trc] = cmsBuildGamma(s->ctx, 1.0);
63         break;
64     case AVCOL_TRC_GAMMA22:
65         s->curves[trc] = cmsBuildGamma(s->ctx, 2.2);
66         break;
67     case AVCOL_TRC_GAMMA28:
68         s->curves[trc] = cmsBuildGamma(s->ctx, 2.8);
69         break;
70     case AVCOL_TRC_BT709:
71     case AVCOL_TRC_SMPTE170M:
72     case AVCOL_TRC_BT2020_10:
73     case AVCOL_TRC_BT2020_12:
74         s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
75             /* γ = */ 1/0.45,
76             /* a = */ 1/1.099296826809442,
77             /* b = */ 1 - 1/1.099296826809442,
78             /* c = */ 1/4.5,
79             /* d = */ 4.5 * 0.018053968510807,
80         });
81         break;
82     case AVCOL_TRC_SMPTE240M:
83         s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
84             /* γ = */ 1/0.45,
85             /* a = */ 1/1.1115,
86             /* b = */ 1 - 1/1.1115,
87             /* c = */ 1/4.0,
88             /* d = */ 4.0 * 0.0228,
89         });
90         break;
91     case AVCOL_TRC_LOG:
92         s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
93             /* a = */ 1.0,
94             /* b = */ 10.0,
95             /* c = */ 2.0,
96             /* d = */ -1.0,
97             /* e = */ 0.0
98         });
99         break;
100     case AVCOL_TRC_LOG_SQRT:
101         s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
102             /* a = */ 1.0,
103             /* b = */ 10.0,
104             /* c = */ 2.5,
105             /* d = */ -1.0,
106             /* e = */ 0.0
107         });
108         break;
109     case AVCOL_TRC_IEC61966_2_1:
110         s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
111             /* γ = */ 2.4,
112             /* a = */ 1/1.055,
113             /* b = */ 1 - 1/1.055,
114             /* c = */ 1/12.92,
115             /* d = */ 12.92 * 0.0031308,
116         });
117         break;
118     case AVCOL_TRC_SMPTE428:
119         s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 2, (double[3]) {
120             /* γ = */ 2.6,
121             /* a = */ pow(52.37/48.0, 1/2.6),
122             /* b = */ 0.0
123         });
124         break;
125 
126     /* Can't be represented using the existing parametric tone curves.
127      * FIXME: use cmsBuildTabulatedToneCurveFloat instead */
128     case AVCOL_TRC_IEC61966_2_4:
129     case AVCOL_TRC_BT1361_ECG:
130     case AVCOL_TRC_SMPTE2084:
131     case AVCOL_TRC_ARIB_STD_B67:
132         return AVERROR_PATCHWELCOME;
133 
134     default:
135         return AVERROR_INVALIDDATA;
136     }
137 
138     if (!s->curves[trc])
139         return AVERROR(ENOMEM);
140 
141 done:
142     *out_curve = s->curves[trc];
143     return 0;
144 }
145 
ff_icc_profile_generate(FFIccContext * s,enum AVColorPrimaries color_prim,enum AVColorTransferCharacteristic color_trc,cmsHPROFILE * out_profile)146 int ff_icc_profile_generate(FFIccContext *s,
147                             enum AVColorPrimaries color_prim,
148                             enum AVColorTransferCharacteristic color_trc,
149                             cmsHPROFILE *out_profile)
150 {
151     cmsToneCurve *tonecurve;
152     const AVColorPrimariesDesc *prim;
153     int ret;
154 
155     if (!(prim = av_csp_primaries_desc_from_id(color_prim)))
156         return AVERROR_INVALIDDATA;
157     if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
158         return ret;
159 
160     *out_profile = cmsCreateRGBProfileTHR(s->ctx,
161         &(cmsCIExyY) { av_q2d(prim->wp.x), av_q2d(prim->wp.y), 1.0 },
162         &(cmsCIExyYTRIPLE) {
163             .Red    = { av_q2d(prim->prim.r.x), av_q2d(prim->prim.r.y), 1.0 },
164             .Green  = { av_q2d(prim->prim.g.x), av_q2d(prim->prim.g.y), 1.0 },
165             .Blue   = { av_q2d(prim->prim.b.x), av_q2d(prim->prim.b.y), 1.0 },
166         },
167         (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
168     );
169 
170     return *out_profile == NULL ? AVERROR(ENOMEM) : 0;
171 }
172 
ff_icc_profile_attach(FFIccContext * s,cmsHPROFILE profile,AVFrame * frame)173 int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
174 {
175     cmsUInt32Number size;
176     AVBufferRef *buf;
177 
178     if (!cmsSaveProfileToMem(profile, NULL, &size))
179         return AVERROR_EXTERNAL;
180 
181     buf = av_buffer_alloc(size);
182     if (!buf)
183         return AVERROR(ENOMEM);
184 
185     if (!cmsSaveProfileToMem(profile, buf->data, &size) || size != buf->size) {
186         av_buffer_unref(&buf);
187         return AVERROR_EXTERNAL;
188     }
189 
190     if (!av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, buf)) {
191         av_buffer_unref(&buf);
192         return AVERROR(ENOMEM);
193     }
194 
195     return 0;
196 }
197 
XYZ_xy(cmsCIEXYZ XYZ,AVCIExy * xy)198 static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVCIExy *xy)
199 {
200     double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
201     xy->x = av_d2q(k * XYZ.X, 100000);
202     xy->y = av_d2q(k * XYZ.Y, 100000);
203 }
204 
ff_icc_profile_read_primaries(FFIccContext * s,cmsHPROFILE profile,AVColorPrimariesDesc * out_primaries)205 int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
206                                   AVColorPrimariesDesc *out_primaries)
207 {
208     static const uint8_t testprimaries[4][3] = {
209         { 0xFF,    0,    0 }, /* red */
210         {    0, 0xFF,    0 }, /* green */
211         {    0,    0, 0xFF }, /* blue */
212         { 0xFF, 0xFF, 0xFF }, /* white */
213     };
214 
215     AVWhitepointCoefficients *wp = &out_primaries->wp;
216     AVPrimaryCoefficients *prim = &out_primaries->prim;
217     cmsFloat64Number prev_adapt;
218     cmsHPROFILE xyz;
219     cmsHTRANSFORM tf;
220     cmsCIEXYZ dst[4];
221 
222     xyz = cmsCreateXYZProfileTHR(s->ctx);
223     if (!xyz)
224         return AVERROR(ENOMEM);
225 
226     /* We need to use an unadapted observer to get the raw values */
227     prev_adapt = cmsSetAdaptationStateTHR(s->ctx, 0.0);
228     tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
229                                INTENT_ABSOLUTE_COLORIMETRIC,
230                                /* Note: These flags mostly don't do anything
231                                 * anyway, but specify them regardless */
232                                cmsFLAGS_NOCACHE |
233                                cmsFLAGS_NOOPTIMIZE |
234                                cmsFLAGS_LOWRESPRECALC |
235                                cmsFLAGS_GRIDPOINTS(2));
236     cmsSetAdaptationStateTHR(s->ctx, prev_adapt);
237     cmsCloseProfile(xyz);
238     if (!tf) {
239         av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
240         return AVERROR_INVALIDDATA;
241     }
242 
243     cmsDoTransform(tf, testprimaries, dst, 4);
244     cmsDeleteTransform(tf);
245     XYZ_xy(dst[0], &prim->r);
246     XYZ_xy(dst[1], &prim->g);
247     XYZ_xy(dst[2], &prim->b);
248     XYZ_xy(dst[3], wp);
249     return 0;
250 }
251 
ff_icc_profile_detect_transfer(FFIccContext * s,cmsHPROFILE profile,enum AVColorTransferCharacteristic * out_trc)252 int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
253                                    enum AVColorTransferCharacteristic *out_trc)
254 {
255     /* 8-bit linear grayscale ramp */
256     static const uint8_t testramp[16][3] = {
257         {  1,   1,   1}, /* avoid exact zero due to log100 etc. */
258         { 17,  17,  17},
259         { 34,  34,  34},
260         { 51,  51,  51},
261         { 68,  68,  68},
262         { 85,  85,  85},
263         { 02,  02,  02},
264         {119, 119, 119},
265         {136, 136, 136},
266         {153, 153, 153},
267         {170, 170, 170},
268         {187, 187, 187},
269         {204, 204, 204},
270         {221, 221, 221},
271         {238, 238, 238},
272         {255, 255, 255},
273     };
274 
275     double dst[FF_ARRAY_ELEMS(testramp)];
276 
277     for (enum AVColorTransferCharacteristic trc = 0; trc < AVCOL_TRC_NB; trc++) {
278         cmsToneCurve *tonecurve;
279         cmsHPROFILE ref;
280         cmsHTRANSFORM tf;
281         double delta = 0.0;
282         if (get_curve(s, trc, &tonecurve) < 0)
283             continue;
284 
285         ref = cmsCreateGrayProfileTHR(s->ctx, cmsD50_xyY(), tonecurve);
286         if (!ref)
287             return AVERROR(ENOMEM);
288 
289         tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, ref, TYPE_GRAY_DBL,
290                                    INTENT_RELATIVE_COLORIMETRIC,
291                                    cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
292         cmsCloseProfile(ref);
293         if (!tf) {
294             av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
295             return AVERROR_INVALIDDATA;
296         }
297 
298         cmsDoTransform(tf, testramp, dst, FF_ARRAY_ELEMS(dst));
299         cmsDeleteTransform(tf);
300 
301         for (int i = 0; i < FF_ARRAY_ELEMS(dst); i++)
302             delta += fabs(testramp[i][0] / 255.0 - dst[i]);
303         if (delta < 0.01) {
304             *out_trc = trc;
305             return 0;
306         }
307     }
308 
309     *out_trc = AVCOL_TRC_UNSPECIFIED;
310     return 0;
311 }
312