/src/mpv/video/csputils.c
Line | Count | Source |
1 | | /* |
2 | | * Common code related to colorspaces and conversion |
3 | | * |
4 | | * Copyleft (C) 2009 Reimar Döffinger <Reimar.Doeffinger@gmx.de> |
5 | | * |
6 | | * This file is part of mpv. |
7 | | * |
8 | | * mpv is free software; you can redistribute it and/or |
9 | | * modify it under the terms of the GNU Lesser General Public |
10 | | * License as published by the Free Software Foundation; either |
11 | | * version 2.1 of the License, or (at your option) any later version. |
12 | | * |
13 | | * mpv is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public |
19 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
20 | | */ |
21 | | |
22 | | #include <stdint.h> |
23 | | #include <math.h> |
24 | | #include <assert.h> |
25 | | #include <libavutil/common.h> |
26 | | #include <libavcodec/avcodec.h> |
27 | | |
28 | | #include "mp_image.h" |
29 | | #include "csputils.h" |
30 | | #include "options/m_config.h" |
31 | | #include "options/m_option.h" |
32 | | |
33 | | const struct m_opt_choice_alternatives pl_csp_names[] = { |
34 | | {"auto", PL_COLOR_SYSTEM_UNKNOWN}, |
35 | | {"bt.601", PL_COLOR_SYSTEM_BT_601}, |
36 | | {"bt.709", PL_COLOR_SYSTEM_BT_709}, |
37 | | {"smpte-240m", PL_COLOR_SYSTEM_SMPTE_240M}, |
38 | | {"bt.2020-ncl", PL_COLOR_SYSTEM_BT_2020_NC}, |
39 | | {"bt.2020-cl", PL_COLOR_SYSTEM_BT_2020_C}, |
40 | | {"bt.2100-pq", PL_COLOR_SYSTEM_BT_2100_PQ}, |
41 | | {"bt.2100-hlg", PL_COLOR_SYSTEM_BT_2100_HLG}, |
42 | | {"dolbyvision", PL_COLOR_SYSTEM_DOLBYVISION}, |
43 | | {"rgb", PL_COLOR_SYSTEM_RGB}, |
44 | | {"xyz", PL_COLOR_SYSTEM_XYZ}, |
45 | | {"ycgco", PL_COLOR_SYSTEM_YCGCO}, |
46 | | #if PL_API_VER >= 358 |
47 | | {"ycgco-re", PL_COLOR_SYSTEM_YCGCO_RE}, |
48 | | {"ycgco-ro", PL_COLOR_SYSTEM_YCGCO_RO}, |
49 | | #endif |
50 | | {0} |
51 | | }; |
52 | | |
53 | | const struct m_opt_choice_alternatives pl_csp_levels_names[] = { |
54 | | {"auto", PL_COLOR_LEVELS_UNKNOWN}, |
55 | | {"limited", PL_COLOR_LEVELS_LIMITED}, |
56 | | {"full", PL_COLOR_LEVELS_FULL}, |
57 | | {0} |
58 | | }; |
59 | | |
60 | | const struct m_opt_choice_alternatives pl_csp_prim_names[] = { |
61 | | {"auto", PL_COLOR_PRIM_UNKNOWN}, |
62 | | {"bt.601-525", PL_COLOR_PRIM_BT_601_525}, |
63 | | {"bt.601-625", PL_COLOR_PRIM_BT_601_625}, |
64 | | {"bt.709", PL_COLOR_PRIM_BT_709}, |
65 | | {"bt.2020", PL_COLOR_PRIM_BT_2020}, |
66 | | {"bt.470m", PL_COLOR_PRIM_BT_470M}, |
67 | | {"apple", PL_COLOR_PRIM_APPLE}, |
68 | | {"adobe", PL_COLOR_PRIM_ADOBE}, |
69 | | {"prophoto", PL_COLOR_PRIM_PRO_PHOTO}, |
70 | | {"cie1931", PL_COLOR_PRIM_CIE_1931}, |
71 | | {"dci-p3", PL_COLOR_PRIM_DCI_P3}, |
72 | | {"display-p3", PL_COLOR_PRIM_DISPLAY_P3}, |
73 | | {"v-gamut", PL_COLOR_PRIM_V_GAMUT}, |
74 | | {"s-gamut", PL_COLOR_PRIM_S_GAMUT}, |
75 | | {"ebu3213", PL_COLOR_PRIM_EBU_3213}, |
76 | | {"film-c", PL_COLOR_PRIM_FILM_C}, |
77 | | {"aces-ap0", PL_COLOR_PRIM_ACES_AP0}, |
78 | | {"aces-ap1", PL_COLOR_PRIM_ACES_AP1}, |
79 | | {0} |
80 | | }; |
81 | | |
82 | | const struct m_opt_choice_alternatives pl_csp_trc_names[] = { |
83 | | {"auto", PL_COLOR_TRC_UNKNOWN}, |
84 | | {"bt.1886", PL_COLOR_TRC_BT_1886}, |
85 | | {"srgb", PL_COLOR_TRC_SRGB}, |
86 | | {"linear", PL_COLOR_TRC_LINEAR}, |
87 | | {"gamma1.8", PL_COLOR_TRC_GAMMA18}, |
88 | | {"gamma2.0", PL_COLOR_TRC_GAMMA20}, |
89 | | {"gamma2.2", PL_COLOR_TRC_GAMMA22}, |
90 | | {"gamma2.4", PL_COLOR_TRC_GAMMA24}, |
91 | | {"gamma2.6", PL_COLOR_TRC_GAMMA26}, |
92 | | {"gamma2.8", PL_COLOR_TRC_GAMMA28}, |
93 | | {"prophoto", PL_COLOR_TRC_PRO_PHOTO}, |
94 | | {"pq", PL_COLOR_TRC_PQ}, |
95 | | {"hlg", PL_COLOR_TRC_HLG}, |
96 | | {"v-log", PL_COLOR_TRC_V_LOG}, |
97 | | {"s-log1", PL_COLOR_TRC_S_LOG1}, |
98 | | {"s-log2", PL_COLOR_TRC_S_LOG2}, |
99 | | {"st428", PL_COLOR_TRC_ST428}, |
100 | | {0} |
101 | | }; |
102 | | |
103 | | const struct m_opt_choice_alternatives mp_csp_light_names[] = { |
104 | | {"auto", MP_CSP_LIGHT_AUTO}, |
105 | | {"display", MP_CSP_LIGHT_DISPLAY}, |
106 | | {"hlg", MP_CSP_LIGHT_SCENE_HLG}, |
107 | | {"709-1886", MP_CSP_LIGHT_SCENE_709_1886}, |
108 | | {"gamma1.2", MP_CSP_LIGHT_SCENE_1_2}, |
109 | | {0} |
110 | | }; |
111 | | |
112 | | const struct m_opt_choice_alternatives pl_chroma_names[] = { |
113 | | {"unknown", PL_CHROMA_UNKNOWN}, |
114 | | {"uhd", PL_CHROMA_TOP_LEFT}, |
115 | | {"mpeg2/4/h264",PL_CHROMA_LEFT}, |
116 | | {"mpeg1/jpeg", PL_CHROMA_CENTER}, |
117 | | {"top", PL_CHROMA_TOP_CENTER}, |
118 | | {"bottom-left", PL_CHROMA_BOTTOM_LEFT}, |
119 | | {"bottom", PL_CHROMA_BOTTOM_CENTER}, |
120 | | {0} |
121 | | }; |
122 | | |
123 | | const struct m_opt_choice_alternatives pl_alpha_names[] = { |
124 | | {"auto", PL_ALPHA_UNKNOWN}, |
125 | | {"straight", PL_ALPHA_INDEPENDENT}, |
126 | | {"premul", PL_ALPHA_PREMULTIPLIED}, |
127 | | #if PL_API_VER >= 344 |
128 | | {"none", PL_ALPHA_NONE}, |
129 | | #endif |
130 | | {0} |
131 | | }; |
132 | | |
133 | | // The short name _must_ match with what vf_stereo3d accepts (if supported). |
134 | | // The long name in comments is closer to the Matroska spec (StereoMode element). |
135 | | // The numeric index matches the Matroska StereoMode value. If you add entries |
136 | | // that don't match Matroska, make sure demux_mkv.c rejects them properly. |
137 | | const struct m_opt_choice_alternatives mp_stereo3d_names[] = { |
138 | | {"no", -1}, // disable/invalid |
139 | | {"mono", 0}, |
140 | | {"sbs2l", 1}, // "side_by_side_left" |
141 | | {"ab2r", 2}, // "top_bottom_right" |
142 | | {"ab2l", 3}, // "top_bottom_left" |
143 | | {"checkr", 4}, // "checkboard_right" (unsupported by vf_stereo3d) |
144 | | {"checkl", 5}, // "checkboard_left" (unsupported by vf_stereo3d) |
145 | | {"irr", 6}, // "row_interleaved_right" |
146 | | {"irl", 7}, // "row_interleaved_left" |
147 | | {"icr", 8}, // "column_interleaved_right" (unsupported by vf_stereo3d) |
148 | | {"icl", 9}, // "column_interleaved_left" (unsupported by vf_stereo3d) |
149 | | {"arcc", 10}, // "anaglyph_cyan_red" (Matroska: unclear which mode) |
150 | | {"sbs2r", 11}, // "side_by_side_right" |
151 | | {"agmc", 12}, // "anaglyph_green_magenta" (Matroska: unclear which mode) |
152 | | {"al", 13}, // "alternating frames left first" |
153 | | {"ar", 14}, // "alternating frames right first" |
154 | | {0} |
155 | | }; |
156 | | |
157 | | enum pl_color_system mp_csp_guess_colorspace(int width, int height) |
158 | 24.2k | { |
159 | 24.2k | return width >= 1280 || height > 576 ? PL_COLOR_SYSTEM_BT_709 : PL_COLOR_SYSTEM_BT_601; |
160 | 24.2k | } |
161 | | |
162 | | enum pl_color_primaries mp_csp_guess_primaries(int width, int height) |
163 | 19.2k | { |
164 | | // HD content |
165 | 19.2k | if (width >= 1280 || height > 576) |
166 | 130 | return PL_COLOR_PRIM_BT_709; |
167 | | |
168 | 19.1k | switch (height) { |
169 | 16 | case 576: // Typical PAL content, including anamorphic/squared |
170 | 16 | return PL_COLOR_PRIM_BT_601_625; |
171 | | |
172 | 1.34k | case 480: // Typical NTSC content, including squared |
173 | 1.34k | case 486: // NTSC Pro or anamorphic NTSC |
174 | 1.34k | return PL_COLOR_PRIM_BT_601_525; |
175 | | |
176 | 17.7k | default: // No good metric, just pick BT.709 to minimize damage |
177 | 17.7k | return PL_COLOR_PRIM_BT_709; |
178 | 19.1k | } |
179 | 19.1k | } |
180 | | |
181 | | // LMS<-XYZ revised matrix from CIECAM97, based on a linear transform and |
182 | | // normalized for equal energy on monochrome inputs |
183 | | static const pl_matrix3x3 m_cat97 = {{ |
184 | | { 0.8562, 0.3372, -0.1934 }, |
185 | | { -0.8360, 1.8327, 0.0033 }, |
186 | | { 0.0357, -0.0469, 1.0112 }, |
187 | | }}; |
188 | | |
189 | | // M := M * XYZd<-XYZs |
190 | | static void apply_chromatic_adaptation(struct pl_cie_xy src, |
191 | | struct pl_cie_xy dest, pl_matrix3x3 *mat) |
192 | 0 | { |
193 | | // If the white points are nearly identical, this is a wasteful identity |
194 | | // operation. |
195 | 0 | if (fabs(src.x - dest.x) < 1e-6 && fabs(src.y - dest.y) < 1e-6) |
196 | 0 | return; |
197 | | |
198 | | // XYZd<-XYZs = Ma^-1 * (I*[Cd/Cs]) * Ma |
199 | | // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html |
200 | | // For Ma, we use the CIECAM97 revised (linear) matrix |
201 | 0 | float C[3][2]; |
202 | |
|
203 | 0 | for (int i = 0; i < 3; i++) { |
204 | | // source cone |
205 | 0 | C[i][0] = m_cat97.m[i][0] * pl_cie_X(src) |
206 | 0 | + m_cat97.m[i][1] * 1 |
207 | 0 | + m_cat97.m[i][2] * pl_cie_Z(src); |
208 | | |
209 | | // dest cone |
210 | 0 | C[i][1] = m_cat97.m[i][0] * pl_cie_X(dest) |
211 | 0 | + m_cat97.m[i][1] * 1 |
212 | 0 | + m_cat97.m[i][2] * pl_cie_Z(dest); |
213 | 0 | } |
214 | | |
215 | | // tmp := I * [Cd/Cs] * Ma |
216 | 0 | pl_matrix3x3 tmp = {0}; |
217 | 0 | for (int i = 0; i < 3; i++) |
218 | 0 | tmp.m[i][i] = C[i][1] / C[i][0]; |
219 | |
|
220 | 0 | pl_matrix3x3_mul(&tmp, &m_cat97); |
221 | | |
222 | | // M := M * Ma^-1 * tmp |
223 | 0 | pl_matrix3x3 ma_inv = m_cat97; |
224 | 0 | pl_matrix3x3_invert(&ma_inv); |
225 | 0 | pl_matrix3x3_mul(mat, &ma_inv); |
226 | 0 | pl_matrix3x3_mul(mat, &tmp); |
227 | 0 | } |
228 | | |
229 | | // Get multiplication factor required if image data is fit within the LSBs of a |
230 | | // higher smaller bit depth fixed-point texture data. |
231 | | // This is broken. Use mp_get_csp_uint_mul(). |
232 | | double mp_get_csp_mul(enum pl_color_system csp, int input_bits, int texture_bits) |
233 | 0 | { |
234 | 0 | mp_assert(texture_bits >= input_bits); |
235 | | |
236 | | // Convenience for some irrelevant cases, e.g. rgb565 or disabling expansion. |
237 | 0 | if (!input_bits) |
238 | 0 | return 1; |
239 | | |
240 | | // RGB always uses the full range available. |
241 | 0 | if (csp == PL_COLOR_SYSTEM_RGB) |
242 | 0 | return ((1LL << input_bits) - 1.) / ((1LL << texture_bits) - 1.); |
243 | | |
244 | 0 | if (csp == PL_COLOR_SYSTEM_XYZ) |
245 | 0 | return 1; |
246 | | |
247 | | // High bit depth YUV uses a range shifted from 8 bit. |
248 | 0 | return (1LL << input_bits) / ((1LL << texture_bits) - 1.) * 255 / 256; |
249 | 0 | } |
250 | | |
251 | | // Return information about color fixed point representation.his is needed for |
252 | | // converting color from integer formats to or from float. Use as follows: |
253 | | // float_val = uint_val * m + o |
254 | | // uint_val = clamp(round((float_val - o) / m)) |
255 | | // See H.264/5 Annex E. |
256 | | // csp: colorspace |
257 | | // levels: full range flag |
258 | | // component: ID of the channel, as in mp_regular_imgfmt: |
259 | | // 1 is red/luminance/gray, 2 is green/Cb, 3 is blue/Cr, 4 is alpha. |
260 | | // bits: number of significant bits, e.g. 10 for yuv420p10, 16 for p010 |
261 | | // out_m: returns factor to multiply the uint number with |
262 | | // out_o: returns offset to add after multiplication |
263 | | void mp_get_csp_uint_mul(enum pl_color_system csp, enum pl_color_levels levels, |
264 | | int bits, int component, double *out_m, double *out_o) |
265 | 116k | { |
266 | 116k | uint16_t i_min = 0; |
267 | 116k | uint16_t i_max = (1u << bits) - 1; |
268 | 116k | double f_min = 0; // min. float value |
269 | | |
270 | 116k | if (csp != PL_COLOR_SYSTEM_RGB && component != 4) { |
271 | 113k | if (component == 2 || component == 3) { |
272 | 73.8k | f_min = (1u << (bits - 1)) / -(double)i_max; // force center => 0 |
273 | | |
274 | 73.8k | if (levels != PL_COLOR_LEVELS_FULL && bits >= 8) { |
275 | 61.4k | i_min = 16 << (bits - 8); // => -0.5 |
276 | 61.4k | i_max = 240 << (bits - 8); // => 0.5 |
277 | 61.4k | f_min = -0.5; |
278 | 61.4k | } |
279 | 73.8k | } else { |
280 | 39.5k | if (levels != PL_COLOR_LEVELS_FULL && bits >= 8) { |
281 | 33.2k | i_min = 16 << (bits - 8); // => 0 |
282 | 33.2k | i_max = 235 << (bits - 8); // => 1 |
283 | 33.2k | } |
284 | 39.5k | } |
285 | 113k | } |
286 | | |
287 | 116k | *out_m = 1.0 / (i_max - i_min); |
288 | 116k | *out_o = (1 + f_min) - i_max * *out_m; |
289 | 116k | } |
290 | | |
291 | | /* Fill in the Y, U, V vectors of a yuv-to-rgb conversion matrix |
292 | | * based on the given luma weights of the R, G and B components (lr, lg, lb). |
293 | | * lr+lg+lb is assumed to equal 1. |
294 | | * This function is meant for colorspaces satisfying the following |
295 | | * conditions (which are true for common YUV colorspaces): |
296 | | * - The mapping from input [Y, U, V] to output [R, G, B] is linear. |
297 | | * - Y is the vector [1, 1, 1]. (meaning input Y component maps to 1R+1G+1B) |
298 | | * - U maps to a value with zero R and positive B ([0, x, y], y > 0; |
299 | | * i.e. blue and green only). |
300 | | * - V maps to a value with zero B and positive R ([x, y, 0], x > 0; |
301 | | * i.e. red and green only). |
302 | | * - U and V are orthogonal to the luma vector [lr, lg, lb]. |
303 | | * - The magnitudes of the vectors U and V are the minimal ones for which |
304 | | * the image of the set Y=[0...1],U=[-0.5...0.5],V=[-0.5...0.5] under the |
305 | | * conversion function will cover the set R=[0...1],G=[0...1],B=[0...1] |
306 | | * (the resulting matrix can be converted for other input/output ranges |
307 | | * outside this function). |
308 | | * Under these conditions the given parameters lr, lg, lb uniquely |
309 | | * determine the mapping of Y, U, V to R, G, B. |
310 | | */ |
311 | | static void luma_coeffs(struct pl_transform3x3 *mat, float lr, float lg, float lb) |
312 | 0 | { |
313 | 0 | mp_assert(fabs(lr+lg+lb - 1) < 1e-6); |
314 | 0 | *mat = (struct pl_transform3x3) { |
315 | 0 | { {{1, 0, 2 * (1-lr) }, |
316 | 0 | {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg }, |
317 | 0 | {1, 2 * (1-lb), 0 }} }, |
318 | | // Constant coefficients (mat->c) not set here |
319 | 0 | }; |
320 | 0 | } |
321 | | |
322 | | // get the coefficients of the yuv -> rgb conversion matrix |
323 | | void mp_get_csp_matrix(struct mp_csp_params *params, struct pl_transform3x3 *m) |
324 | 0 | { |
325 | 0 | enum pl_color_system colorspace = params->repr.sys; |
326 | 0 | if (colorspace <= PL_COLOR_SYSTEM_UNKNOWN || colorspace >= PL_COLOR_SYSTEM_COUNT) |
327 | 0 | colorspace = PL_COLOR_SYSTEM_BT_601; |
328 | | // Not supported. TODO: replace with pl_color_repr_decode |
329 | 0 | if (colorspace == PL_COLOR_SYSTEM_BT_2100_PQ || |
330 | 0 | colorspace == PL_COLOR_SYSTEM_BT_2100_HLG || |
331 | 0 | colorspace == PL_COLOR_SYSTEM_DOLBYVISION) { |
332 | 0 | colorspace = PL_COLOR_SYSTEM_BT_2020_NC; |
333 | 0 | } |
334 | 0 | enum pl_color_levels levels_in = params->repr.levels; |
335 | 0 | if (levels_in <= PL_COLOR_LEVELS_UNKNOWN || levels_in >= PL_COLOR_LEVELS_COUNT) |
336 | 0 | levels_in = PL_COLOR_LEVELS_LIMITED; |
337 | |
|
338 | 0 | switch (colorspace) { |
339 | 0 | case PL_COLOR_SYSTEM_BT_601: luma_coeffs(m, 0.299, 0.587, 0.114 ); break; |
340 | 0 | case PL_COLOR_SYSTEM_BT_709: luma_coeffs(m, 0.2126, 0.7152, 0.0722); break; |
341 | 0 | case PL_COLOR_SYSTEM_SMPTE_240M: luma_coeffs(m, 0.2122, 0.7013, 0.0865); break; |
342 | 0 | case PL_COLOR_SYSTEM_BT_2020_NC: luma_coeffs(m, 0.2627, 0.6780, 0.0593); break; |
343 | 0 | case PL_COLOR_SYSTEM_BT_2020_C: { |
344 | | // Note: This outputs into the [-0.5,0.5] range for chroma information. |
345 | | // If this clips on any VO, a constant 0.5 coefficient can be added |
346 | | // to the chroma channels to normalize them into [0,1]. This is not |
347 | | // currently needed by anything, though. |
348 | 0 | *m = (struct pl_transform3x3){{{{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}}}; |
349 | 0 | break; |
350 | 0 | } |
351 | 0 | case PL_COLOR_SYSTEM_RGB: { |
352 | 0 | *m = (struct pl_transform3x3){{{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}}; |
353 | 0 | levels_in = -1; |
354 | 0 | break; |
355 | 0 | } |
356 | 0 | case PL_COLOR_SYSTEM_XYZ: { |
357 | | // For lack of anything saner to do, just assume the caller wants |
358 | | // DCI-P3 primaries, which is a reasonable assumption. |
359 | 0 | const struct pl_raw_primaries *dst = pl_raw_primaries_get(PL_COLOR_PRIM_DCI_P3); |
360 | 0 | pl_matrix3x3 mat = pl_get_xyz2rgb_matrix(dst); |
361 | | // DCDM X'Y'Z' is expected to have equal energy white point (EG 432-1 Annex H) |
362 | 0 | apply_chromatic_adaptation((struct pl_cie_xy){1.0/3.0, 1.0/3.0}, dst->white, &mat); |
363 | 0 | *m = (struct pl_transform3x3) { .mat = mat }; |
364 | 0 | levels_in = -1; |
365 | 0 | break; |
366 | 0 | } |
367 | 0 | case PL_COLOR_SYSTEM_YCGCO: { |
368 | 0 | *m = (struct pl_transform3x3) { |
369 | 0 | {{{1, -1, 1}, |
370 | 0 | {1, 1, 0}, |
371 | 0 | {1, -1, -1}}}, |
372 | 0 | }; |
373 | 0 | break; |
374 | 0 | } |
375 | 0 | default: |
376 | 0 | MP_ASSERT_UNREACHABLE(); |
377 | 0 | }; |
378 | |
|
379 | 0 | if (params->is_float) |
380 | 0 | levels_in = -1; |
381 | |
|
382 | 0 | if ((colorspace == PL_COLOR_SYSTEM_BT_601 || colorspace == PL_COLOR_SYSTEM_BT_709 || |
383 | 0 | colorspace == PL_COLOR_SYSTEM_SMPTE_240M || colorspace == PL_COLOR_SYSTEM_BT_2020_NC)) |
384 | 0 | { |
385 | | // Hue is equivalent to rotating input [U, V] subvector around the origin. |
386 | | // Saturation scales [U, V]. |
387 | 0 | float huecos = params->gray ? 0 : params->saturation * cos(params->hue); |
388 | 0 | float huesin = params->gray ? 0 : params->saturation * sin(params->hue); |
389 | 0 | for (int i = 0; i < 3; i++) { |
390 | 0 | float u = m->mat.m[i][1], v = m->mat.m[i][2]; |
391 | 0 | m->mat.m[i][1] = huecos * u - huesin * v; |
392 | 0 | m->mat.m[i][2] = huesin * u + huecos * v; |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | // The values below are written in 0-255 scale - thus bring s into range. |
397 | 0 | double s = |
398 | 0 | mp_get_csp_mul(colorspace, params->input_bits, params->texture_bits) / 255; |
399 | | // NOTE: The yuvfull ranges as presented here are arguably ambiguous, |
400 | | // and conflict with at least the full-range YCbCr/ICtCp values as defined |
401 | | // by ITU-R BT.2100. If somebody ever complains about full-range YUV looking |
402 | | // different from their reference display, this comment is probably why. |
403 | 0 | struct yuvlevels { double ymin, ymax, cmax, cmid; } |
404 | 0 | yuvlim = { 16*s, 235*s, 240*s, 128*s }, |
405 | 0 | yuvfull = { 0*s, 255*s, 255*s, 128*s }, |
406 | 0 | anyfull = { 0*s, 255*s, 255*s/2, 0 }, // cmax picked to make cmul=ymul |
407 | 0 | yuvlev; |
408 | 0 | switch (levels_in) { |
409 | 0 | case PL_COLOR_LEVELS_LIMITED: yuvlev = yuvlim; break; |
410 | 0 | case PL_COLOR_LEVELS_FULL: yuvlev = yuvfull; break; |
411 | 0 | case -1: yuvlev = anyfull; break; |
412 | 0 | default: |
413 | 0 | MP_ASSERT_UNREACHABLE(); |
414 | 0 | } |
415 | | |
416 | 0 | int levels_out = params->levels_out; |
417 | 0 | if (levels_out <= PL_COLOR_LEVELS_UNKNOWN || levels_out >= PL_COLOR_LEVELS_COUNT) |
418 | 0 | levels_out = PL_COLOR_LEVELS_FULL; |
419 | 0 | struct rgblevels { double min, max; } |
420 | 0 | rgblim = { 16/255., 235/255. }, |
421 | 0 | rgbfull = { 0, 1 }, |
422 | 0 | rgblev; |
423 | 0 | switch (levels_out) { |
424 | 0 | case PL_COLOR_LEVELS_LIMITED: rgblev = rgblim; break; |
425 | 0 | case PL_COLOR_LEVELS_FULL: rgblev = rgbfull; break; |
426 | 0 | default: |
427 | 0 | MP_ASSERT_UNREACHABLE(); |
428 | 0 | } |
429 | | |
430 | 0 | double ymul = (rgblev.max - rgblev.min) / (yuvlev.ymax - yuvlev.ymin); |
431 | 0 | double cmul = (rgblev.max - rgblev.min) / (yuvlev.cmax - yuvlev.cmid) / 2; |
432 | | |
433 | | // Contrast scales the output value range (gain) |
434 | 0 | ymul *= params->contrast; |
435 | 0 | cmul *= params->contrast; |
436 | |
|
437 | 0 | for (int i = 0; i < 3; i++) { |
438 | 0 | m->mat.m[i][0] *= ymul; |
439 | 0 | m->mat.m[i][1] *= cmul; |
440 | 0 | m->mat.m[i][2] *= cmul; |
441 | | // Set c so that Y=umin,UV=cmid maps to RGB=min (black to black), |
442 | | // also add brightness offset (black lift) |
443 | 0 | m->c[i] = rgblev.min - m->mat.m[i][0] * yuvlev.ymin |
444 | 0 | - (m->mat.m[i][1] + m->mat.m[i][2]) * yuvlev.cmid |
445 | 0 | + params->brightness; |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | // Set colorspace related fields in p from f. Don't touch other fields. |
450 | | void mp_csp_set_image_params(struct mp_csp_params *params, |
451 | | const struct mp_image_params *imgparams) |
452 | 0 | { |
453 | 0 | struct mp_image_params p = *imgparams; |
454 | 0 | mp_image_params_guess_csp(&p); // ensure consistency |
455 | 0 | params->repr = p.repr; |
456 | 0 | params->color = p.color; |
457 | 0 | } |
458 | | |
459 | | enum mp_csp_equalizer_param { |
460 | | MP_CSP_EQ_BRIGHTNESS, |
461 | | MP_CSP_EQ_CONTRAST, |
462 | | MP_CSP_EQ_HUE, |
463 | | MP_CSP_EQ_SATURATION, |
464 | | MP_CSP_EQ_GAMMA, |
465 | | MP_CSP_EQ_COUNT, |
466 | | }; |
467 | | |
468 | | // Default initialization with 0 is enough, except for the capabilities field |
469 | | struct mp_csp_equalizer_opts { |
470 | | // Value for each property is in the range [-100.0, 100.0]. |
471 | | // 0.0 is default, meaning neutral or no change. |
472 | | float values[MP_CSP_EQ_COUNT]; |
473 | | int output_levels; |
474 | | }; |
475 | | |
476 | | #define OPT_BASE_STRUCT struct mp_csp_equalizer_opts |
477 | | |
478 | | const struct m_sub_options mp_csp_equalizer_conf = { |
479 | | .opts = (const m_option_t[]) { |
480 | | {"brightness", OPT_FLOAT(values[MP_CSP_EQ_BRIGHTNESS]), |
481 | | M_RANGE(-100, 100)}, |
482 | | {"saturation", OPT_FLOAT(values[MP_CSP_EQ_SATURATION]), |
483 | | M_RANGE(-100, 100)}, |
484 | | {"contrast", OPT_FLOAT(values[MP_CSP_EQ_CONTRAST]), |
485 | | M_RANGE(-100, 100)}, |
486 | | {"hue", OPT_FLOAT(values[MP_CSP_EQ_HUE]), |
487 | | M_RANGE(-100, 100)}, |
488 | | {"gamma", OPT_FLOAT(values[MP_CSP_EQ_GAMMA]), |
489 | | M_RANGE(-100, 100)}, |
490 | | {"video-output-levels", |
491 | | OPT_CHOICE_C(output_levels, pl_csp_levels_names)}, |
492 | | {0} |
493 | | }, |
494 | | .size = sizeof(struct mp_csp_equalizer_opts), |
495 | | .change_flags = UPDATE_VIDEO, |
496 | | }; |
497 | | |
498 | | // Copy settings from eq into params. |
499 | | static void mp_csp_copy_equalizer_values(struct mp_csp_params *params, |
500 | | const struct mp_csp_equalizer_opts *eq) |
501 | 0 | { |
502 | 0 | params->brightness = eq->values[MP_CSP_EQ_BRIGHTNESS] / 100.0; |
503 | 0 | params->contrast = (eq->values[MP_CSP_EQ_CONTRAST] + 100) / 100.0; |
504 | 0 | params->hue = eq->values[MP_CSP_EQ_HUE] / 100.0 * M_PI; |
505 | 0 | params->saturation = (eq->values[MP_CSP_EQ_SATURATION] + 100) / 100.0; |
506 | 0 | params->gamma = exp(log(8.0) * eq->values[MP_CSP_EQ_GAMMA] / 100.0); |
507 | 0 | params->levels_out = eq->output_levels; |
508 | 0 | } |
509 | | |
510 | | struct mp_csp_equalizer_state *mp_csp_equalizer_create(void *ta_parent, |
511 | | struct mpv_global *global) |
512 | 208 | { |
513 | 208 | struct m_config_cache *c = m_config_cache_alloc(ta_parent, global, |
514 | 208 | &mp_csp_equalizer_conf); |
515 | | // The terrible, terrible truth. |
516 | 208 | return (struct mp_csp_equalizer_state *)c; |
517 | 208 | } |
518 | | |
519 | | bool mp_csp_equalizer_state_changed(struct mp_csp_equalizer_state *state) |
520 | 0 | { |
521 | 0 | struct m_config_cache *c = (struct m_config_cache *)state; |
522 | 0 | return m_config_cache_update(c); |
523 | 0 | } |
524 | | |
525 | | void mp_csp_equalizer_state_get(struct mp_csp_equalizer_state *state, |
526 | | struct mp_csp_params *params) |
527 | 0 | { |
528 | 0 | struct m_config_cache *c = (struct m_config_cache *)state; |
529 | 0 | m_config_cache_update(c); |
530 | 0 | struct mp_csp_equalizer_opts *opts = c->opts; |
531 | 0 | mp_csp_copy_equalizer_values(params, opts); |
532 | 0 | } |
533 | | |
534 | | // Multiply the color in c with the given matrix. |
535 | | // i/o is {R, G, B} or {Y, U, V} (depending on input/output and matrix), using |
536 | | // a fixed point representation with the given number of bits (so for bits==8, |
537 | | // [0,255] maps to [0,1]). The output is clipped to the range as needed. |
538 | | void mp_map_fixp_color(struct pl_transform3x3 *matrix, int ibits, int in[3], |
539 | | int obits, int out[3]) |
540 | 0 | { |
541 | 0 | for (int i = 0; i < 3; i++) { |
542 | 0 | double val = matrix->c[i]; |
543 | 0 | for (int x = 0; x < 3; x++) |
544 | 0 | val += matrix->mat.m[i][x] * in[x] / ((1 << ibits) - 1); |
545 | 0 | int ival = lrint(val * ((1 << obits) - 1)); |
546 | | out[i] = av_clip(ival, 0, (1 << obits) - 1); |
547 | 0 | } |
548 | 0 | } |