video: Add support for BT2020 colorspace (UHD)
[platform/upstream/gstreamer.git] / gst-libs / gst / video / video-color.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Library       <2002> Ronald Bultje <rbultje@ronald.bitfreak.net>
4  * Copyright (C) 2007 David A. Schleef <ds@schleef.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include <string.h>
27 #include <stdio.h>
28 #include <math.h>
29
30 #include "video-color.h"
31
32 typedef struct
33 {
34   const gchar *name;
35   GstVideoColorimetry color;
36 } ColorimetryInfo;
37
38 #define MAKE_COLORIMETRY(n,r,m,t,p) { GST_VIDEO_COLORIMETRY_ ##n, \
39   { GST_VIDEO_COLOR_RANGE ##r, GST_VIDEO_COLOR_MATRIX_ ##m, \
40   GST_VIDEO_TRANSFER_ ##t, GST_VIDEO_COLOR_PRIMARIES_ ##p } }
41
42 #define GST_VIDEO_COLORIMETRY_NONAME  NULL
43
44 #define DEFAULT_YUV_SD  0
45 #define DEFAULT_YUV_HD  1
46 #define DEFAULT_RGB     3
47 #define DEFAULT_GRAY    4
48 #define DEFAULT_UNKNOWN 5
49 #define DEFAULT_YUV_UHD 6
50
51 static const ColorimetryInfo colorimetry[] = {
52   MAKE_COLORIMETRY (BT601, _16_235, BT601, BT709, SMPTE170M),
53   MAKE_COLORIMETRY (BT709, _16_235, BT709, BT709, BT709),
54   MAKE_COLORIMETRY (SMPTE240M, _16_235, SMPTE240M, SMPTE240M, SMPTE240M),
55   MAKE_COLORIMETRY (SRGB, _0_255, RGB, SRGB, BT709),
56   MAKE_COLORIMETRY (NONAME, _0_255, BT601, UNKNOWN, UNKNOWN),
57   MAKE_COLORIMETRY (NONAME, _UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN),
58   MAKE_COLORIMETRY (BT2020, _16_235, BT2020, BT2020_12, BT2020),
59 };
60
61 static const ColorimetryInfo *
62 gst_video_get_colorimetry (const gchar * s)
63 {
64   gint i;
65
66   for (i = 0; colorimetry[i].name; i++) {
67     if (g_str_equal (colorimetry[i].name, s))
68       return &colorimetry[i];
69   }
70   return NULL;
71 }
72
73 #define IS_EQUAL(ci,i) (((ci)->color.range == (i)->range) && \
74                         ((ci)->color.matrix == (i)->matrix) && \
75                         ((ci)->color.transfer == (i)->transfer) && \
76                         ((ci)->color.primaries == (i)->primaries))
77
78 #define IS_UNKNOWN(ci) (IS_EQUAL (&colorimetry[DEFAULT_UNKNOWN], ci))
79
80 /**
81  * gst_video_colorimetry_from_string:
82  * @cinfo: a #GstVideoColorimetry
83  * @color: a colorimetry string
84  *
85  * Parse the colorimetry string and update @cinfo with the parsed
86  * values.
87  *
88  * Returns: #TRUE if @color points to valid colorimetry info.
89  */
90 gboolean
91 gst_video_colorimetry_from_string (GstVideoColorimetry * cinfo,
92     const gchar * color)
93 {
94   const ColorimetryInfo *ci;
95   gboolean res = FALSE;
96
97   if ((ci = gst_video_get_colorimetry (color))) {
98     *cinfo = ci->color;
99     res = TRUE;
100   } else {
101     gint r, m, t, p;
102
103     if (sscanf (color, "%d:%d:%d:%d", &r, &m, &t, &p) == 4) {
104       cinfo->range = r;
105       cinfo->matrix = m;
106       cinfo->transfer = t;
107       cinfo->primaries = p;
108       res = TRUE;
109     }
110   }
111   return res;
112 }
113
114 /**
115  * gst_video_colorimetry_to_string:
116  * @cinfo: a #GstVideoColorimetry
117  *
118  * Make a string representation of @cinfo.
119  *
120  * Returns: a string representation of @cinfo.
121  */
122 gchar *
123 gst_video_colorimetry_to_string (GstVideoColorimetry * cinfo)
124 {
125   gint i;
126
127   for (i = 0; colorimetry[i].name; i++) {
128     if (IS_EQUAL (&colorimetry[i], cinfo)) {
129       return g_strdup (colorimetry[i].name);
130     }
131   }
132   if (!IS_UNKNOWN (cinfo)) {
133     return g_strdup_printf ("%d:%d:%d:%d", cinfo->range, cinfo->matrix,
134         cinfo->transfer, cinfo->primaries);
135   }
136   return NULL;
137 }
138
139 /**
140  * gst_video_colorimetry_matches:
141  * @cinfo: a #GstVideoInfo
142  * @color: a colorimetry string
143  *
144  * Check if the colorimetry information in @info matches that of the
145  * string @color.
146  *
147  * Returns: #TRUE if @color conveys the same colorimetry info as the color
148  * information in @info.
149  */
150 gboolean
151 gst_video_colorimetry_matches (GstVideoColorimetry * cinfo, const gchar * color)
152 {
153   const ColorimetryInfo *ci;
154
155   if ((ci = gst_video_get_colorimetry (color)))
156     return IS_EQUAL (ci, cinfo);
157
158   return FALSE;
159 }
160
161 /**
162  * gst_video_color_range_offsets:
163  * @range: a #GstVideoColorRange
164  * @info: a #GstVideoFormatInfo
165  * @offset: (out): output offsets
166  * @scale: (out): output scale
167  *
168  * Compute the offset and scale values for each component of @info. For each
169  * component, (c[i] - offset[i]) / scale[i] will scale the component c[i] to the
170  * range [0.0 .. 1.0].
171  *
172  * The reverse operation (c[i] * scale[i]) + offset[i] can be used to convert
173  * the component values in range [0.0 .. 1.0] back to their representation in
174  * @info and @range.
175  */
176 void
177 gst_video_color_range_offsets (GstVideoColorRange range,
178     const GstVideoFormatInfo * info, gint offset[GST_VIDEO_MAX_COMPONENTS],
179     gint scale[GST_VIDEO_MAX_COMPONENTS])
180 {
181   gboolean yuv;
182
183   yuv = GST_VIDEO_FORMAT_INFO_IS_YUV (info);
184
185   switch (range) {
186     default:
187     case GST_VIDEO_COLOR_RANGE_0_255:
188       offset[0] = 0;
189       if (yuv) {
190         offset[1] = 1 << (info->depth[1] - 1);
191         offset[2] = 1 << (info->depth[2] - 1);
192       } else {
193         offset[1] = 0;
194         offset[2] = 0;
195       }
196       scale[0] = (1 << info->depth[0]) - 1;
197       scale[1] = (1 << info->depth[1]) - 1;
198       scale[2] = (1 << info->depth[2]) - 1;
199       break;
200     case GST_VIDEO_COLOR_RANGE_16_235:
201       offset[0] = 1 << (info->depth[0] - 4);
202       scale[0] = 219 << (info->depth[0] - 8);
203       if (yuv) {
204         offset[1] = 1 << (info->depth[1] - 1);
205         offset[2] = 1 << (info->depth[2] - 1);
206         scale[1] = 224 << (info->depth[1] - 8);
207         scale[2] = 224 << (info->depth[2] - 8);
208       } else {
209         offset[1] = 1 << (info->depth[1] - 4);
210         offset[2] = 1 << (info->depth[2] - 4);
211         scale[1] = 219 << (info->depth[1] - 8);
212         scale[2] = 219 << (info->depth[2] - 8);
213       }
214       break;
215   }
216   /* alpha channel is always full range */
217   offset[3] = 0;
218   scale[3] = (1 << info->depth[3]) - 1;
219
220   GST_DEBUG ("scale: %d %d %d %d", scale[0], scale[1], scale[2], scale[3]);
221   GST_DEBUG ("offset: %d %d %d %d", offset[0], offset[1], offset[2], offset[3]);
222 }
223
224
225 #define WP_C    0.31006, 0.31616
226 #define WP_D65  0.31271, 0.32902
227
228 static const GstVideoColorPrimariesInfo color_primaries[] = {
229   {GST_VIDEO_COLOR_PRIMARIES_UNKNOWN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
230   {GST_VIDEO_COLOR_PRIMARIES_BT709, WP_D65, 0.64, 0.33, 0.30, 0.60, 0.15, 0.06},
231   {GST_VIDEO_COLOR_PRIMARIES_BT470M, WP_C, 0.67, 0.33, 0.21, 0.71, 0.14, 0.08},
232   {GST_VIDEO_COLOR_PRIMARIES_BT470BG, WP_D65, 0.64, 0.33, 0.29, 0.60, 0.15,
233       0.06},
234   {GST_VIDEO_COLOR_PRIMARIES_SMPTE170M, WP_D65, 0.63, 0.34, 0.31, 0.595, 0.155,
235       0.07},
236   {GST_VIDEO_COLOR_PRIMARIES_SMPTE240M, WP_D65, 0.63, 0.34, 0.31, 0.595, 0.155,
237       0.07},
238   {GST_VIDEO_COLOR_PRIMARIES_FILM, WP_C, 0.681, 0.319, 0.243, 0.692, 0.145,
239       0.049},
240   {GST_VIDEO_COLOR_PRIMARIES_BT2020, WP_D65, 0.708, 0.292, 0.170, 0.797, 0.131,
241       0.046}
242 };
243
244 /**
245  * gst_video_color_primaries_get_info:
246  * @primaries: a #GstVideoColorPrimaries
247  *
248  * Get information about the chromaticity coordinates of @primaries.
249  *
250  * Returns: a #GstVideoColorPrimariesInfo for @primaries.
251  *
252  * Since: 1.6
253  */
254 const GstVideoColorPrimariesInfo *
255 gst_video_color_primaries_get_info (GstVideoColorPrimaries primaries)
256 {
257   g_return_val_if_fail (primaries <
258       (GstVideoColorPrimaries) G_N_ELEMENTS (color_primaries), NULL);
259
260   return &color_primaries[primaries];
261 }
262
263 /**
264  * gst_video_color_matrix_get_Kr_Kb:
265  * @matrix: a #GstVideoColorMatrix
266  * @Kr: result red channel coefficient
267  * @Kb: result blue channel coefficient
268  *
269  * Get the coefficients used to convert between Y'PbPr and R'G'B' using @matrix.
270  *
271  * When:
272  *
273  * |[
274  *   0.0 <= [Y',R',G',B'] <= 1.0)
275  *   (-0.5 <= [Pb,Pr] <= 0.5)
276  * ]|
277  *
278  * the general conversion is given by:
279  *
280  * |[
281  *   Y' = Kr*R' + (1-Kr-Kb)*G' + Kb*B'
282  *   Pb = (B'-Y')/(2*(1-Kb))
283  *   Pr = (R'-Y')/(2*(1-Kr))
284  * ]|
285  *
286  * and the other way around:
287  *
288  * |[
289  *   R' = Y' + Cr*2*(1-Kr)
290  *   G' = Y' - Cb*2*(1-Kb)*Kb/(1-Kr-Kb) - Cr*2*(1-Kr)*Kr/(1-Kr-Kb)
291  *   B' = Y' + Cb*2*(1-Kb)
292  * ]|
293  *
294  * Returns: TRUE if @matrix was a YUV color format and @Kr and @Kb contain valid
295  *    values.
296  *
297  * Since: 1.6
298  */
299 gboolean
300 gst_video_color_matrix_get_Kr_Kb (GstVideoColorMatrix matrix, gdouble * Kr,
301     gdouble * Kb)
302 {
303   gboolean res = TRUE;
304
305   switch (matrix) {
306       /* RGB */
307     default:
308     case GST_VIDEO_COLOR_MATRIX_RGB:
309       res = FALSE;
310       break;
311       /* YUV */
312     case GST_VIDEO_COLOR_MATRIX_FCC:
313       *Kr = 0.30;
314       *Kb = 0.11;
315       break;
316     case GST_VIDEO_COLOR_MATRIX_BT709:
317       *Kr = 0.2126;
318       *Kb = 0.0722;
319       break;
320     case GST_VIDEO_COLOR_MATRIX_BT601:
321       *Kr = 0.2990;
322       *Kb = 0.1140;
323       break;
324     case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
325       *Kr = 0.212;
326       *Kb = 0.087;
327       break;
328     case GST_VIDEO_COLOR_MATRIX_BT2020:
329       *Kr = 0.2627;
330       *Kb = 0.0593;
331       break;
332   }
333   GST_DEBUG ("matrix: %d, Kr %f, Kb %f", matrix, *Kr, *Kb);
334
335   return res;
336 }
337
338 /**
339  * gst_video_color_transfer_encode:
340  * @func: a #GstVideoTransferFunction
341  * @val: a value
342  *
343  * Convert @val to its gamma encoded value.
344  *
345  * For a linear value L in the range [0..1], conversion to the non-linear
346  * (gamma encoded) L' is in general performed with a power function like:
347  *
348  * |[
349  *    L' = L ^ (1 / gamma)
350  * ]|
351  *
352  * Depending on @func, different formulas might be applied. Some formulas
353  * encode a linear segment in the lower range.
354  *
355  * Returns: the gamme encoded value of @val
356  *
357  * Since: 1.6
358  */
359 gdouble
360 gst_video_color_transfer_encode (GstVideoTransferFunction func, gdouble val)
361 {
362   gdouble res;
363
364   switch (func) {
365     case GST_VIDEO_TRANSFER_UNKNOWN:
366     case GST_VIDEO_TRANSFER_GAMMA10:
367     default:
368       res = val;
369       break;
370     case GST_VIDEO_TRANSFER_GAMMA18:
371       res = pow (val, 1.0 / 1.8);
372       break;
373     case GST_VIDEO_TRANSFER_GAMMA20:
374       res = pow (val, 1.0 / 2.0);
375       break;
376     case GST_VIDEO_TRANSFER_GAMMA22:
377       res = pow (val, 1.0 / 2.2);
378       break;
379     case GST_VIDEO_TRANSFER_BT709:
380       if (val < 0.018)
381         res = 4.5 * val;
382       else
383         res = 1.099 * pow (val, 0.45) - 0.099;
384       break;
385     case GST_VIDEO_TRANSFER_SMPTE240M:
386       if (val < 0.0228)
387         res = val * 4.0;
388       else
389         res = 1.1115 * pow (val, 0.45) - 0.1115;
390       break;
391     case GST_VIDEO_TRANSFER_SRGB:
392       if (val <= 0.0031308)
393         res = 12.92 * val;
394       else
395         res = 1.055 * pow (val, 1.0 / 2.4) - 0.055;
396       break;
397     case GST_VIDEO_TRANSFER_GAMMA28:
398       res = pow (val, 1 / 2.8);
399       break;
400     case GST_VIDEO_TRANSFER_LOG100:
401       if (val < 0.01)
402         res = 0.0;
403       else
404         res = 1.0 + log10 (val) / 2.0;
405       break;
406     case GST_VIDEO_TRANSFER_LOG316:
407       if (val < 0.0031622777)
408         res = 0.0;
409       else
410         res = 1.0 + log10 (val) / 2.5;
411       break;
412     case GST_VIDEO_TRANSFER_BT2020_12:
413       if (val < 0.0181)
414         res = 4.5 * val;
415       else
416         res = 1.0993 * pow (val, 0.45) - 0.0993;
417       break;
418   }
419   return res;
420 }
421
422 /**
423  * gst_video_color_transfer_decode:
424  * @func: a #GstVideoTransferFunction
425  * @val: a value
426  *
427  * Convert @val to its gamma decoded value. This is the inverse operation of
428  * @gst_video_color_transfer_encode().
429  *
430  * For a non-linear value L' in the range [0..1], conversion to the linear
431  * L is in general performed with a power function like:
432  *
433  * |[
434  *    L = L' ^ gamma
435  * ]|
436  *
437  * Depending on @func, different formulas might be applied. Some formulas
438  * encode a linear segment in the lower range.
439  *
440  * Returns: the gamme decoded value of @val
441  *
442  * Since: 1.6
443  */
444 gdouble
445 gst_video_color_transfer_decode (GstVideoTransferFunction func, gdouble val)
446 {
447   gdouble res;
448
449   switch (func) {
450     case GST_VIDEO_TRANSFER_UNKNOWN:
451     case GST_VIDEO_TRANSFER_GAMMA10:
452     default:
453       res = val;
454       break;
455     case GST_VIDEO_TRANSFER_GAMMA18:
456       res = pow (val, 1.8);
457       break;
458     case GST_VIDEO_TRANSFER_GAMMA20:
459       res = pow (val, 2.0);
460       break;
461     case GST_VIDEO_TRANSFER_GAMMA22:
462       res = pow (val, 2.2);
463       break;
464     case GST_VIDEO_TRANSFER_BT709:
465       if (val < 0.081)
466         res = val / 4.5;
467       else
468         res = pow ((val + 0.099) / 1.099, 1.0 / 0.45);
469       break;
470     case GST_VIDEO_TRANSFER_SMPTE240M:
471       if (val < 0.0913)
472         res = val / 4.0;
473       else
474         res = pow ((val + 0.1115) / 1.1115, 1.0 / 0.45);
475       break;
476     case GST_VIDEO_TRANSFER_SRGB:
477       if (val <= 0.04045)
478         res = val / 12.92;
479       else
480         res = pow ((val + 0.055) / 1.055, 2.4);
481       break;
482     case GST_VIDEO_TRANSFER_GAMMA28:
483       res = pow (val, 2.8);
484       break;
485     case GST_VIDEO_TRANSFER_LOG100:
486       if (val == 0.0)
487         res = 0.0;
488       else
489         res = pow (10.0, 2.0 * (val - 1.0));
490       break;
491     case GST_VIDEO_TRANSFER_LOG316:
492       if (val == 0.0)
493         res = 0.0;
494       else
495         res = pow (10.0, 2.5 * (val - 1.0));
496       break;
497     case GST_VIDEO_TRANSFER_BT2020_12:
498       if (val < 0.08145)
499         res = val / 4.5;
500       else
501         res = pow ((val + 0.0993) / 1.0993, 1.0 / 0.45);
502       break;
503   }
504   return res;
505 }