| /* GStreamer |
| * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> |
| * Library <2002> Ronald Bultje <rbultje@ronald.bitfreak.net> |
| * Copyright (C) 2007 David A. Schleef <ds@schleef.org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <math.h> |
| |
| #include "video-color.h" |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| #define GST_CAT_DEFAULT ensure_debug_category() |
| static GstDebugCategory * |
| ensure_debug_category (void) |
| { |
| static gsize cat_gonce = 0; |
| |
| if (g_once_init_enter (&cat_gonce)) { |
| gsize cat_done; |
| |
| cat_done = (gsize) _gst_debug_category_new ("video-color", 0, |
| "video-color object"); |
| |
| g_once_init_leave (&cat_gonce, cat_done); |
| } |
| |
| return (GstDebugCategory *) cat_gonce; |
| } |
| #else |
| #define ensure_debug_category() /* NOOP */ |
| #endif /* GST_DISABLE_GST_DEBUG */ |
| |
| typedef struct |
| { |
| const gchar *name; |
| GstVideoColorimetry color; |
| } ColorimetryInfo; |
| |
| #define MAKE_COLORIMETRY(n,r,m,t,p) { GST_VIDEO_COLORIMETRY_ ##n, \ |
| { GST_VIDEO_COLOR_RANGE ##r, GST_VIDEO_COLOR_MATRIX_ ##m, \ |
| GST_VIDEO_TRANSFER_ ##t, GST_VIDEO_COLOR_PRIMARIES_ ##p } } |
| |
| #define GST_VIDEO_COLORIMETRY_NONAME NULL |
| |
| #define DEFAULT_YUV_SD 0 |
| #define DEFAULT_YUV_HD 1 |
| #define DEFAULT_RGB 3 |
| #define DEFAULT_YUV_UHD 4 |
| #define DEFAULT_GRAY 5 |
| #define DEFAULT_UNKNOWN 6 |
| |
| static const ColorimetryInfo colorimetry[] = { |
| MAKE_COLORIMETRY (BT601, _16_235, BT601, BT709, SMPTE170M), |
| MAKE_COLORIMETRY (BT709, _16_235, BT709, BT709, BT709), |
| MAKE_COLORIMETRY (SMPTE240M, _16_235, SMPTE240M, SMPTE240M, SMPTE240M), |
| MAKE_COLORIMETRY (SRGB, _0_255, RGB, SRGB, BT709), |
| MAKE_COLORIMETRY (BT2020, _16_235, BT2020, BT2020_12, BT2020), |
| MAKE_COLORIMETRY (NONAME, _0_255, BT601, UNKNOWN, UNKNOWN), |
| MAKE_COLORIMETRY (NONAME, _UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN), |
| }; |
| |
| static const ColorimetryInfo * |
| gst_video_get_colorimetry (const gchar * s) |
| { |
| gint i; |
| |
| for (i = 0; colorimetry[i].name; i++) { |
| if (g_str_equal (colorimetry[i].name, s)) |
| return &colorimetry[i]; |
| } |
| return NULL; |
| } |
| |
| #define CI_IS_EQUAL(ci,i) (((ci)->range == (i)->range) && \ |
| ((ci)->matrix == (i)->matrix) && \ |
| ((ci)->transfer == (i)->transfer) && \ |
| ((ci)->primaries == (i)->primaries)) |
| |
| #define IS_EQUAL(ci,i) CI_IS_EQUAL(&(ci)->color, (i)) |
| |
| #define IS_UNKNOWN(ci) (IS_EQUAL (&colorimetry[DEFAULT_UNKNOWN], ci)) |
| |
| /** |
| * gst_video_colorimetry_from_string: |
| * @cinfo: a #GstVideoColorimetry |
| * @color: a colorimetry string |
| * |
| * Parse the colorimetry string and update @cinfo with the parsed |
| * values. |
| * |
| * Returns: %TRUE if @color points to valid colorimetry info. |
| */ |
| gboolean |
| gst_video_colorimetry_from_string (GstVideoColorimetry * cinfo, |
| const gchar * color) |
| { |
| const ColorimetryInfo *ci; |
| gboolean res = FALSE; |
| |
| if ((ci = gst_video_get_colorimetry (color))) { |
| *cinfo = ci->color; |
| res = TRUE; |
| } else { |
| gint r, m, t, p; |
| |
| if (sscanf (color, "%d:%d:%d:%d", &r, &m, &t, &p) == 4) { |
| cinfo->range = r; |
| cinfo->matrix = m; |
| cinfo->transfer = t; |
| cinfo->primaries = p; |
| res = TRUE; |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * gst_video_colorimetry_to_string: |
| * @cinfo: a #GstVideoColorimetry |
| * |
| * Make a string representation of @cinfo. |
| * |
| * Returns: a string representation of @cinfo. |
| */ |
| gchar * |
| gst_video_colorimetry_to_string (const GstVideoColorimetry * cinfo) |
| { |
| gint i; |
| |
| for (i = 0; colorimetry[i].name; i++) { |
| if (IS_EQUAL (&colorimetry[i], cinfo)) { |
| return g_strdup (colorimetry[i].name); |
| } |
| } |
| if (!IS_UNKNOWN (cinfo)) { |
| return g_strdup_printf ("%d:%d:%d:%d", cinfo->range, cinfo->matrix, |
| cinfo->transfer, cinfo->primaries); |
| } |
| return NULL; |
| } |
| |
| /** |
| * gst_video_colorimetry_matches: |
| * @cinfo: a #GstVideoInfo |
| * @color: a colorimetry string |
| * |
| * Check if the colorimetry information in @info matches that of the |
| * string @color. |
| * |
| * Returns: %TRUE if @color conveys the same colorimetry info as the color |
| * information in @info. |
| */ |
| gboolean |
| gst_video_colorimetry_matches (const GstVideoColorimetry * cinfo, |
| const gchar * color) |
| { |
| const ColorimetryInfo *ci; |
| |
| if ((ci = gst_video_get_colorimetry (color))) |
| return IS_EQUAL (ci, cinfo); |
| |
| return FALSE; |
| } |
| |
| /** |
| * gst_video_color_range_offsets: |
| * @range: a #GstVideoColorRange |
| * @info: a #GstVideoFormatInfo |
| * @offset: (out caller-allocates) (array fixed-size=4): output offsets |
| * @scale: (out caller-allocates) (array fixed-size=4): output scale |
| * |
| * Compute the offset and scale values for each component of @info. For each |
| * component, (c[i] - offset[i]) / scale[i] will scale the component c[i] to the |
| * range [0.0 .. 1.0]. |
| * |
| * The reverse operation (c[i] * scale[i]) + offset[i] can be used to convert |
| * the component values in range [0.0 .. 1.0] back to their representation in |
| * @info and @range. |
| */ |
| void |
| gst_video_color_range_offsets (GstVideoColorRange range, |
| const GstVideoFormatInfo * info, gint offset[GST_VIDEO_MAX_COMPONENTS], |
| gint scale[GST_VIDEO_MAX_COMPONENTS]) |
| { |
| gboolean yuv; |
| |
| yuv = GST_VIDEO_FORMAT_INFO_IS_YUV (info); |
| |
| switch (range) { |
| default: |
| case GST_VIDEO_COLOR_RANGE_0_255: |
| offset[0] = 0; |
| if (yuv) { |
| offset[1] = 1 << (info->depth[1] - 1); |
| offset[2] = 1 << (info->depth[2] - 1); |
| } else { |
| offset[1] = 0; |
| offset[2] = 0; |
| } |
| scale[0] = (1 << info->depth[0]) - 1; |
| scale[1] = (1 << info->depth[1]) - 1; |
| scale[2] = (1 << info->depth[2]) - 1; |
| break; |
| case GST_VIDEO_COLOR_RANGE_16_235: |
| offset[0] = 1 << (info->depth[0] - 4); |
| scale[0] = 219 << (info->depth[0] - 8); |
| if (yuv) { |
| offset[1] = 1 << (info->depth[1] - 1); |
| offset[2] = 1 << (info->depth[2] - 1); |
| scale[1] = 224 << (info->depth[1] - 8); |
| scale[2] = 224 << (info->depth[2] - 8); |
| } else { |
| offset[1] = 1 << (info->depth[1] - 4); |
| offset[2] = 1 << (info->depth[2] - 4); |
| scale[1] = 219 << (info->depth[1] - 8); |
| scale[2] = 219 << (info->depth[2] - 8); |
| } |
| break; |
| } |
| /* alpha channel is always full range */ |
| offset[3] = 0; |
| scale[3] = (1 << info->depth[3]) - 1; |
| |
| GST_DEBUG ("scale: %d %d %d %d", scale[0], scale[1], scale[2], scale[3]); |
| GST_DEBUG ("offset: %d %d %d %d", offset[0], offset[1], offset[2], offset[3]); |
| } |
| |
| /** |
| * gst_video_colorimetry_is_equal: |
| * @cinfo: a #GstVideoColorimetry |
| * @other: another #GstVideoColorimetry |
| * |
| * Compare the 2 colorimetry sets for equality |
| * |
| * Returns: %TRUE if @cinfo and @other are equal. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_video_colorimetry_is_equal (const GstVideoColorimetry * cinfo, |
| const GstVideoColorimetry * other) |
| { |
| g_return_val_if_fail (cinfo != NULL, FALSE); |
| g_return_val_if_fail (other != NULL, FALSE); |
| |
| return CI_IS_EQUAL (cinfo, other); |
| } |
| |
| #define WP_C 0.31006, 0.31616 |
| #define WP_D65 0.31271, 0.32902 |
| |
| static const GstVideoColorPrimariesInfo color_primaries[] = { |
| {GST_VIDEO_COLOR_PRIMARIES_UNKNOWN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, |
| {GST_VIDEO_COLOR_PRIMARIES_BT709, WP_D65, 0.64, 0.33, 0.30, 0.60, 0.15, 0.06}, |
| {GST_VIDEO_COLOR_PRIMARIES_BT470M, WP_C, 0.67, 0.33, 0.21, 0.71, 0.14, 0.08}, |
| {GST_VIDEO_COLOR_PRIMARIES_BT470BG, WP_D65, 0.64, 0.33, 0.29, 0.60, 0.15, |
| 0.06}, |
| {GST_VIDEO_COLOR_PRIMARIES_SMPTE170M, WP_D65, 0.63, 0.34, 0.31, 0.595, 0.155, |
| 0.07}, |
| {GST_VIDEO_COLOR_PRIMARIES_SMPTE240M, WP_D65, 0.63, 0.34, 0.31, 0.595, 0.155, |
| 0.07}, |
| {GST_VIDEO_COLOR_PRIMARIES_FILM, WP_C, 0.681, 0.319, 0.243, 0.692, 0.145, |
| 0.049}, |
| {GST_VIDEO_COLOR_PRIMARIES_BT2020, WP_D65, 0.708, 0.292, 0.170, 0.797, 0.131, |
| 0.046}, |
| {GST_VIDEO_COLOR_PRIMARIES_ADOBERGB, WP_D65, 0.64, 0.33, 0.21, 0.71, 0.15, |
| 0.06} |
| }; |
| |
| /** |
| * gst_video_color_primaries_get_info: |
| * @primaries: a #GstVideoColorPrimaries |
| * |
| * Get information about the chromaticity coordinates of @primaries. |
| * |
| * Returns: a #GstVideoColorPrimariesInfo for @primaries. |
| * |
| * Since: 1.6 |
| */ |
| const GstVideoColorPrimariesInfo * |
| gst_video_color_primaries_get_info (GstVideoColorPrimaries primaries) |
| { |
| g_return_val_if_fail ((gint) primaries < |
| G_N_ELEMENTS (color_primaries), NULL); |
| |
| return &color_primaries[primaries]; |
| } |
| |
| /** |
| * gst_video_color_matrix_get_Kr_Kb: |
| * @matrix: a #GstVideoColorMatrix |
| * @Kr: (out): result red channel coefficient |
| * @Kb: (out): result blue channel coefficient |
| * |
| * Get the coefficients used to convert between Y'PbPr and R'G'B' using @matrix. |
| * |
| * When: |
| * |
| * |[ |
| * 0.0 <= [Y',R',G',B'] <= 1.0) |
| * (-0.5 <= [Pb,Pr] <= 0.5) |
| * ]| |
| * |
| * the general conversion is given by: |
| * |
| * |[ |
| * Y' = Kr*R' + (1-Kr-Kb)*G' + Kb*B' |
| * Pb = (B'-Y')/(2*(1-Kb)) |
| * Pr = (R'-Y')/(2*(1-Kr)) |
| * ]| |
| * |
| * and the other way around: |
| * |
| * |[ |
| * R' = Y' + Cr*2*(1-Kr) |
| * G' = Y' - Cb*2*(1-Kb)*Kb/(1-Kr-Kb) - Cr*2*(1-Kr)*Kr/(1-Kr-Kb) |
| * B' = Y' + Cb*2*(1-Kb) |
| * ]| |
| * |
| * Returns: TRUE if @matrix was a YUV color format and @Kr and @Kb contain valid |
| * values. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_video_color_matrix_get_Kr_Kb (GstVideoColorMatrix matrix, gdouble * Kr, |
| gdouble * Kb) |
| { |
| gboolean res = TRUE; |
| |
| switch (matrix) { |
| /* RGB */ |
| default: |
| case GST_VIDEO_COLOR_MATRIX_RGB: |
| res = FALSE; |
| break; |
| /* YUV */ |
| case GST_VIDEO_COLOR_MATRIX_FCC: |
| *Kr = 0.30; |
| *Kb = 0.11; |
| break; |
| case GST_VIDEO_COLOR_MATRIX_BT709: |
| *Kr = 0.2126; |
| *Kb = 0.0722; |
| break; |
| case GST_VIDEO_COLOR_MATRIX_BT601: |
| *Kr = 0.2990; |
| *Kb = 0.1140; |
| break; |
| case GST_VIDEO_COLOR_MATRIX_SMPTE240M: |
| *Kr = 0.212; |
| *Kb = 0.087; |
| break; |
| case GST_VIDEO_COLOR_MATRIX_BT2020: |
| *Kr = 0.2627; |
| *Kb = 0.0593; |
| break; |
| } |
| GST_DEBUG ("matrix: %d, Kr %f, Kb %f", matrix, *Kr, *Kb); |
| |
| return res; |
| } |
| |
| /** |
| * gst_video_color_transfer_encode: |
| * @func: a #GstVideoTransferFunction |
| * @val: a value |
| * |
| * Convert @val to its gamma encoded value. |
| * |
| * For a linear value L in the range [0..1], conversion to the non-linear |
| * (gamma encoded) L' is in general performed with a power function like: |
| * |
| * |[ |
| * L' = L ^ (1 / gamma) |
| * ]| |
| * |
| * Depending on @func, different formulas might be applied. Some formulas |
| * encode a linear segment in the lower range. |
| * |
| * Returns: the gamme encoded value of @val |
| * |
| * Since: 1.6 |
| */ |
| gdouble |
| gst_video_color_transfer_encode (GstVideoTransferFunction func, gdouble val) |
| { |
| gdouble res; |
| |
| switch (func) { |
| case GST_VIDEO_TRANSFER_UNKNOWN: |
| case GST_VIDEO_TRANSFER_GAMMA10: |
| default: |
| res = val; |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA18: |
| res = pow (val, 1.0 / 1.8); |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA20: |
| res = pow (val, 1.0 / 2.0); |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA22: |
| res = pow (val, 1.0 / 2.2); |
| break; |
| case GST_VIDEO_TRANSFER_BT709: |
| if (val < 0.018) |
| res = 4.5 * val; |
| else |
| res = 1.099 * pow (val, 0.45) - 0.099; |
| break; |
| case GST_VIDEO_TRANSFER_SMPTE240M: |
| if (val < 0.0228) |
| res = val * 4.0; |
| else |
| res = 1.1115 * pow (val, 0.45) - 0.1115; |
| break; |
| case GST_VIDEO_TRANSFER_SRGB: |
| if (val <= 0.0031308) |
| res = 12.92 * val; |
| else |
| res = 1.055 * pow (val, 1.0 / 2.4) - 0.055; |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA28: |
| res = pow (val, 1 / 2.8); |
| break; |
| case GST_VIDEO_TRANSFER_LOG100: |
| if (val < 0.01) |
| res = 0.0; |
| else |
| res = 1.0 + log10 (val) / 2.0; |
| break; |
| case GST_VIDEO_TRANSFER_LOG316: |
| if (val < 0.0031622777) |
| res = 0.0; |
| else |
| res = 1.0 + log10 (val) / 2.5; |
| break; |
| case GST_VIDEO_TRANSFER_BT2020_12: |
| if (val < 0.0181) |
| res = 4.5 * val; |
| else |
| res = 1.0993 * pow (val, 0.45) - 0.0993; |
| break; |
| case GST_VIDEO_TRANSFER_ADOBERGB: |
| res = pow (val, 1.0 / 2.19921875); |
| break; |
| } |
| return res; |
| } |
| |
| /** |
| * gst_video_color_transfer_decode: |
| * @func: a #GstVideoTransferFunction |
| * @val: a value |
| * |
| * Convert @val to its gamma decoded value. This is the inverse operation of |
| * @gst_video_color_transfer_encode(). |
| * |
| * For a non-linear value L' in the range [0..1], conversion to the linear |
| * L is in general performed with a power function like: |
| * |
| * |[ |
| * L = L' ^ gamma |
| * ]| |
| * |
| * Depending on @func, different formulas might be applied. Some formulas |
| * encode a linear segment in the lower range. |
| * |
| * Returns: the gamme decoded value of @val |
| * |
| * Since: 1.6 |
| */ |
| gdouble |
| gst_video_color_transfer_decode (GstVideoTransferFunction func, gdouble val) |
| { |
| gdouble res; |
| |
| switch (func) { |
| case GST_VIDEO_TRANSFER_UNKNOWN: |
| case GST_VIDEO_TRANSFER_GAMMA10: |
| default: |
| res = val; |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA18: |
| res = pow (val, 1.8); |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA20: |
| res = pow (val, 2.0); |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA22: |
| res = pow (val, 2.2); |
| break; |
| case GST_VIDEO_TRANSFER_BT709: |
| if (val < 0.081) |
| res = val / 4.5; |
| else |
| res = pow ((val + 0.099) / 1.099, 1.0 / 0.45); |
| break; |
| case GST_VIDEO_TRANSFER_SMPTE240M: |
| if (val < 0.0913) |
| res = val / 4.0; |
| else |
| res = pow ((val + 0.1115) / 1.1115, 1.0 / 0.45); |
| break; |
| case GST_VIDEO_TRANSFER_SRGB: |
| if (val <= 0.04045) |
| res = val / 12.92; |
| else |
| res = pow ((val + 0.055) / 1.055, 2.4); |
| break; |
| case GST_VIDEO_TRANSFER_GAMMA28: |
| res = pow (val, 2.8); |
| break; |
| case GST_VIDEO_TRANSFER_LOG100: |
| if (val == 0.0) |
| res = 0.0; |
| else |
| res = pow (10.0, 2.0 * (val - 1.0)); |
| break; |
| case GST_VIDEO_TRANSFER_LOG316: |
| if (val == 0.0) |
| res = 0.0; |
| else |
| res = pow (10.0, 2.5 * (val - 1.0)); |
| break; |
| case GST_VIDEO_TRANSFER_BT2020_12: |
| if (val < 0.08145) |
| res = val / 4.5; |
| else |
| res = pow ((val + 0.0993) / 1.0993, 1.0 / 0.45); |
| break; |
| case GST_VIDEO_TRANSFER_ADOBERGB: |
| res = pow (val, 2.19921875); |
| break; |
| } |
| return res; |
| } |