History

fflcms2: move to libavcodec

We will need this helper inside libavcodec in the future, so move it there, leaving behind an #include to the raw source file in its old location in libvfilter. This approach is inspired by the handling of vulkan.c, and avoids us needing to expose any of it publicly (or semi-publicly) in e.g. libavutil, thus avoiding any ABI headaches. It's debatable whether the actual code belongs in libavcodec or libavfilter, but I decided to put it into libavcodec because it conceptually deals with encoding and decoding ICC profiles, and will be used to decode embedded ICC profiles in image files.

Niklas Haas committed 4 years ago

Commit 1cbd4551 parent e0691ea5 files · +401 −357Browse files

Showing the changes to libavfilter (2 of 5 files). Show all

Unified · Split

2 files
  1. libavfilter/fflcms2.c +1 −293
  2. libavfilter/fflcms2.h +1 −64
libavfilter/fflcms2.c+1−293
@@ -1,5 +1,4 @@
1/*1/*
2 * Copyright (c) 2022 Niklas Haas
3 * This file is part of FFmpeg.2 * This file is part of FFmpeg.
4 *3 *
5 * FFmpeg is free software; you can redistribute it and/or4 * FFmpeg is free software; you can redistribute it and/or
@@ -17,295 +16,4 @@ · 11 unmodified lines
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */17 */
19 18
20#include "libavutil/color_utils.h"19#include "libavcodec/fflcms2.c"
21#include "libavutil/csp.h"
22
23#include "fflcms2.h"
24
25static 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
31int 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
43void 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
51static 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
141done:
142 *out_curve = s->curves[trc];
143 return 0;
144}
145
146int 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
173int 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
198static 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
205int 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
252int 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}
libavfilter/fflcms2.h+1−64
@@ -1,5 +1,4 @@
1/*1/*
2 * Copyright (c) 2022 Niklas Haas
3 * This file is part of FFmpeg.2 * This file is part of FFmpeg.
4 *3 *
5 * FFmpeg is free software; you can redistribute it and/or4 * FFmpeg is free software; you can redistribute it and/or
@@ -17,71 +16,9 @@ · 11 unmodified lines
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */17 */
19 18
20/**
21 * @file
22 * Various functions for dealing with ICC profiles
23 */
24
25#ifndef AVFILTER_FFLCMS2_H19#ifndef AVFILTER_FFLCMS2_H
26#define AVFILTER_FFLCMS2_H20#define AVFILTER_FFLCMS2_H
27 21
28#include "libavutil/csp.h"22#include "libavcodec/fflcms2.h"
29#include "libavutil/frame.h"
30#include "libavutil/pixfmt.h"
31
32#include <lcms2.h>
33
34typedef struct FFIccContext {
35 void *avctx;
36 cmsContext ctx;
37 cmsToneCurve *curves[AVCOL_TRC_NB]; /* tone curve cache */
38} FFIccContext;
39
40/**
41 * Initializes an FFIccContext. This must be done prior to using it.
42 *
43 * Returns 0 on success, or a negative error code.
44 */
45int ff_icc_context_init(FFIccContext *s, void *avctx);
46void ff_icc_context_uninit(FFIccContext *s);
47
48/**
49 * Generate an ICC profile for a given combination of color primaries and
50 * transfer function. Both values must be set to valid entries (not
51 * "undefined") for this function to work.
52 *
53 * Returns 0 on success, or a negative error code.
54 */
55int ff_icc_profile_generate(FFIccContext *s,
56 enum AVColorPrimaries color_prim,
57 enum AVColorTransferCharacteristic color_trc,
58 cmsHPROFILE *out_profile);
59
60/**
61 * Attach an ICC profile to a frame. Helper wrapper around cmsSaveProfileToMem
62 * and av_frame_new_side_data_from_buf.
63 *
64 * Returns 0 on success, or a negative error code.
65 */
66int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame);
67
68/**
69 * Read the color primaries and white point coefficients encoded by an ICC
70 * profile, and return the raw values in `out_primaries`.
71 *
72 * Returns 0 on success, or a negative error code.
73 */
74int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
75 AVColorPrimariesDesc *out_primaries);
76
77/**
78 * Attempt detecting the transfer characteristic that best approximates the
79 * transfer function encoded by an ICC profile. Sets `out_trc` to
80 * AVCOL_TRC_UNSPECIFIED if no clear match can be identified.
81 *
82 * Returns 0 on success (including no match), or a negative error code.
83 */
84int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
85 enum AVColorTransferCharacteristic *out_trc);
86 23
87#endif /* AVFILTER_FFLCMS2_H */24#endif /* AVFILTER_FFLCMS2_H */

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t