1 /*****************************************************************************
2
3 Matrix.cpp
4 Author: Laurent de Soras, 2021
5
6 --- Legal stuff ---
7
8 This program is free software. It comes without any warranty, to
9 the extent permitted by applicable law. You can redistribute it
10 and/or modify it under the terms of the Do What The Fuck You Want
11 To Public License, Version 2, as published by Sam Hocevar. See
12 http://www.wtfpl.net/ for more details.
13
14 *Tab=3***********************************************************************/
15
16
17
18 #if defined (_MSC_VER)
19 #pragma warning (1 : 4130 4223 4705 4706)
20 #pragma warning (4 : 4355 4786 4800)
21 #endif
22
23
24
25 /*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
26
27 #include "avsutl/CsPlane.h"
28 #include "fmtcavs/CpuOpt.h"
29 #include "fmtcavs/fnc.h"
30 #include "fmtcavs/function_names.h"
31 #include "fmtcavs/Matrix.h"
32 #include "fmtcl/fnc.h"
33 #include "fmtcl/MatrixUtil.h"
34 #include "fstb/fnc.h"
35
36 #include <cassert>
37
38
39
40 namespace fmtcavs
41 {
42
43
44
45 /*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
46
47
48
Matrix(::IScriptEnvironment & env,const::AVSValue & args)49 Matrix::Matrix (::IScriptEnvironment &env, const ::AVSValue &args)
50 : Inherited (env, args [Param_CLIP_SRC].AsClip ())
51 , _clip_src_sptr (args [Param_CLIP_SRC].AsClip ())
52 , _vi_src (vi)
53 , _plane_out (args [Param_SINGLEOUT].AsInt (-1))
54 {
55 const CpuOpt cpu_opt (args [Param_CPUOPT]);
56 const bool sse_flag = cpu_opt.has_sse ();
57 const bool sse2_flag = cpu_opt.has_sse2 ();
58 const bool avx_flag = cpu_opt.has_avx ();
59 const bool avx2_flag = cpu_opt.has_avx2 ();
60
61 _proc_uptr = std::make_unique <fmtcl::MatrixProc> (
62 sse_flag, sse2_flag, avx_flag, avx2_flag
63 );
64
65 // Checks the input clip
66 if (! _vi_src.IsPlanar ())
67 {
68 env.ThrowError (fmtcavs_MATRIX ": input must be planar.");
69 }
70
71 const FmtAvs fmt_src (_vi_src);
72 if (fmt_src.get_subspl_h () != 0 || fmt_src.get_subspl_v () != 0)
73 {
74 env.ThrowError (fmtcavs_MATRIX ": input must be 4:4:4.");
75 }
76 const int nbr_planes_src = _vi_src.NumComponents ();
77 if (fmt_src.get_nbr_comp_non_alpha () != _nbr_planes_proc)
78 {
79 env.ThrowError (
80 fmtcavs_MATRIX ": greyscale format not supported as input."
81 );
82 }
83 const int bd_src = fmt_src.get_bitdepth ();
84 if ( ( bd_src < 8
85 || bd_src > 12)
86 && bd_src != 14
87 && bd_src != 16
88 && bd_src != 32 )
89 {
90 env.ThrowError (fmtcavs_MATRIX ": pixel bitdepth not supported.");
91 }
92
93 // Plane index for single plane output (0-2), or a negative number if all
94 // planes are processed.
95 if (_plane_out >= _nbr_planes_proc)
96 {
97 env.ThrowError (fmtcavs_MATRIX
98 ": singleout is a plane index and must be -1 or ranging from 0 to 3."
99 );
100 }
101
102 // Destination colorspace
103 bool force_col_fam_flag;
104 FmtAvs fmt_dst = get_output_colorspace (
105 env, args, fmt_src, _plane_out, force_col_fam_flag
106 );
107
108 // Preliminary matrix test: deduces the target color family if unspecified
109 if ( ! force_col_fam_flag
110 && fmt_dst.get_col_fam () != fmtcl::ColorFamily_GRAY)
111 {
112 int def_count = 0;
113 def_count += args [Param_MAT ].Defined () ? 1 : 0;
114 def_count += args [Param_MATS].Defined () ? 1 : 0;
115 def_count += args [Param_MATD].Defined () ? 1 : 0;
116 if (def_count == 1)
117 {
118 std::string tmp_mat;
119 tmp_mat = args [Param_MAT ].AsString (tmp_mat.c_str ());
120 tmp_mat = args [Param_MATS].AsString (tmp_mat.c_str ());
121 tmp_mat = args [Param_MATD].AsString (tmp_mat.c_str ());
122 fstb::conv_to_lower_case (tmp_mat);
123
124 const auto tmp_csp = find_cs_from_mat_str (env, tmp_mat, false);
125 fmt_dst = find_dst_col_fam (tmp_csp, fmt_dst, fmt_src);
126 }
127 }
128
129 const int nbr_expected_coef = _nbr_planes_proc * (_nbr_planes_proc + 1);
130
131 bool mat_init_flag = false;
132 fmtcl::Mat4 mat_main; // Main matrix, float input, float output
133
134 // Matrix presets
135 std::string mat (args [Param_MAT].AsString (""));
136 const bool mats_default_flag =
137 (fmt_src.get_col_fam () == fmtcl::ColorFamily_YUV);
138 const bool matd_default_flag =
139 ( fmt_dst.get_col_fam () == fmtcl::ColorFamily_YUV
140 || ( fmt_dst.get_col_fam () == fmtcl::ColorFamily_GRAY
141 && fmt_src.get_col_fam () != fmtcl::ColorFamily_YUV));
142 std::string mats ((mats_default_flag) ? mat : "");
143 std::string matd ((matd_default_flag) ? mat : "");
144 mats = args [Param_MATS].AsString (mats.c_str ());
145 matd = args [Param_MATD].AsString (matd.c_str ());
146 _csp_out = fmtcl::ColorSpaceH265_UNSPECIFIED;
147 if (! mats.empty () || ! matd.empty ())
148 {
149 fstb::conv_to_lower_case (mats);
150 fstb::conv_to_lower_case (matd);
151 fmtcl::MatrixUtil::select_def_mat (mats, fmt_src.get_col_fam ());
152 fmtcl::MatrixUtil::select_def_mat (matd, fmt_dst.get_col_fam ());
153
154 fmtcl::Mat4 m2s;
155 fmtcl::Mat4 m2d;
156 if (fmtcl::MatrixUtil::make_mat_from_str (m2s, mats, true) != 0)
157 {
158 env.ThrowError (
159 fmtcavs_MATRIX ": unknown source matrix identifier."
160 );
161 }
162 if (fmtcl::MatrixUtil::make_mat_from_str (m2d, matd, false) != 0)
163 {
164 env.ThrowError (
165 fmtcavs_MATRIX ": unknown destination matrix identifier."
166 );
167 }
168 _csp_out = find_cs_from_mat_str (env, matd, false);
169
170 mat_main = m2d * m2s;
171 mat_init_flag = true;
172 }
173
174 // Alpha plane processing, if any
175 _proc_alpha_uptr = std::make_unique <fmtcavs::ProcAlpha> (
176 fmt_dst, fmt_src, vi.width, vi.height, cpu_opt
177 );
178
179 // Custom coefficients
180 const auto coef_list =
181 extract_array_f (env, args [Param_COEF], fmtcavs_MATRIX ", coef");
182 const int nbr_coef = int (coef_list.size ());
183 const bool custom_mat_flag = (nbr_coef > 0);
184 const int nbr_proc_planes_src = fmt_src.get_nbr_comp_non_alpha ();
185 const int nbr_proc_planes_dst = fmt_dst.get_nbr_comp_non_alpha ();
186 if (custom_mat_flag)
187 {
188 if (nbr_coef != nbr_expected_coef)
189 {
190 env.ThrowError (
191 fmtcavs_MATRIX ": coef has a wrong number of elements."
192 );
193 }
194
195 for (int y = 0; y < _nbr_planes_proc + 1; ++y)
196 {
197 for (int x = 0; x < _nbr_planes_proc + 1; ++x)
198 {
199 mat_main [y] [x] = (x == y) ? 1 : 0;
200
201 if ( (x < nbr_proc_planes_src || x == _nbr_planes_proc)
202 && y < nbr_proc_planes_dst)
203 {
204 const int index = y * (nbr_proc_planes_src + 1) + x;
205 mat_main [y] [x] = coef_list [index];
206 }
207 }
208 }
209
210 mat_init_flag = true;
211 }
212
213 if (! mat_init_flag)
214 {
215 env.ThrowError (fmtcavs_MATRIX
216 ": you must specify a matrix preset or a custom coefficient list."
217 );
218 }
219
220 // Fixes the output colorspace to a valid H265 colorspace
221 switch (_csp_out)
222 {
223 case fmtcl::ColorSpaceH265_LMS:
224 _csp_out = fmtcl::ColorSpaceH265_RGB;
225 break;
226 case fmtcl::ColorSpaceH265_ICTCP_PQ:
227 case fmtcl::ColorSpaceH265_ICTCP_HLG:
228 _csp_out = fmtcl::ColorSpaceH265_ICTCP;
229 break;
230 default:
231 // Nothing to do
232 break;
233 }
234
235 // Sets the output colorspace accordingly
236 if (_plane_out < 0)
237 {
238 if (_csp_out != fmtcl::ColorSpaceH265_UNSPECIFIED)
239 {
240 const auto final_cf =
241 fmtcl::MatrixUtil::find_cf_from_cs (_csp_out, false);
242 fmt_dst.set_col_fam (final_cf);
243 }
244 }
245
246 // Checks the output colorspace
247 if ( fmt_dst.is_float () != fmt_src.is_float ()
248 || fmt_dst.get_bitdepth () < fmt_src.get_bitdepth ()
249 || fmt_dst.get_subspl_h () != fmt_src.get_subspl_h ()
250 || fmt_dst.get_subspl_v () != fmt_src.get_subspl_v ())
251 {
252 env.ThrowError (fmtcavs_MATRIX
253 ": specified output colorspace is not compatible with the input."
254 );
255 }
256
257 // Output format is validated
258 if (fmt_dst.conv_to_vi (vi) != 0)
259 {
260 env.ThrowError (fmtcavs_MATRIX ": illegal output colorspace.");
261 }
262
263 // Range
264 _fulls_flag = args [Param_FULLS].AsBool (
265 fmtcl::is_full_range_default (fmt_src.get_col_fam ())
266 );
267 _fulld_flag = args [Param_FULLD].AsBool (
268 fmtcl::is_full_range_default (fmt_dst.get_col_fam ())
269 );
270 _range_def_flag = args [Param_FULLD].Defined ();
271
272 prepare_matrix_coef (
273 env, *_proc_uptr, mat_main,
274 fmt_dst, _fulld_flag,
275 fmt_src, _fulls_flag,
276 _csp_out, _plane_out
277 );
278 }
279
280
281
282 // mat should be already converted to lower case
find_cs_from_mat_str(::IScriptEnvironment & env,const std::string & mat,bool allow_2020cl_flag)283 fmtcl::ColorSpaceH265 Matrix::find_cs_from_mat_str (::IScriptEnvironment &env, const std::string &mat, bool allow_2020cl_flag)
284 {
285 const auto cs =
286 fmtcl::MatrixUtil::find_cs_from_mat_str (mat, allow_2020cl_flag);
287 if (cs == fmtcl::ColorSpaceH265_UNDEF)
288 {
289 env.ThrowError ("Unknown matrix identifier.");
290 }
291
292 return cs;
293 }
294
295
296
GetFrame(int n,::IScriptEnvironment * env_ptr)297 ::PVideoFrame __stdcall Matrix::GetFrame (int n, ::IScriptEnvironment *env_ptr)
298 {
299 ::PVideoFrame src_sptr = _clip_src_sptr->GetFrame (n, env_ptr);
300 ::PVideoFrame dst_sptr = build_new_frame (*env_ptr, vi, &src_sptr);
301
302 const auto pa { build_mat_proc (
303 vi, dst_sptr, _vi_src, src_sptr, (_plane_out >= 0)
304 ) };
305 _proc_uptr->process (pa);
306
307 // Alpha plane now
308 _proc_alpha_uptr->process_plane (dst_sptr, src_sptr);
309
310 // Frame properties
311 if (supports_props ())
312 {
313 ::AVSMap * props_ptr = env_ptr->getFramePropsRW (dst_sptr);
314
315 if (_range_def_flag)
316 {
317 const int cr_val = (_fulld_flag) ? 0 : 1;
318 env_ptr->propSetInt (
319 props_ptr, "_ColorRange", cr_val, ::PROPAPPENDMODE_REPLACE
320 );
321 }
322
323 if ( _csp_out != fmtcl::ColorSpaceH265_UNSPECIFIED
324 && _csp_out <= fmtcl::ColorSpaceH265_ISO_RANGE_LAST)
325 {
326 env_ptr->propSetInt (
327 props_ptr, "_Matrix" , int (_csp_out), ::PROPAPPENDMODE_REPLACE
328 );
329 env_ptr->propSetInt (
330 props_ptr, "_ColorSpace", int (_csp_out), ::PROPAPPENDMODE_REPLACE
331 );
332 }
333 else
334 {
335 env_ptr->propDeleteKey (props_ptr, "_Matrix");
336 env_ptr->propDeleteKey (props_ptr, "_ColorSpace");
337 }
338 }
339
340 return dst_sptr;
341 }
342
343
344
345 /*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
346
347
348
349 /*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
350
351
352
353 constexpr int Matrix::_nbr_planes_proc;
354
355
356
get_output_colorspace(::IScriptEnvironment & env,const::AVSValue & args,const FmtAvs & fmt_src,int & plane_out,bool & force_col_fam_flag)357 FmtAvs Matrix::get_output_colorspace (::IScriptEnvironment &env, const ::AVSValue &args, const FmtAvs &fmt_src, int &plane_out, bool &force_col_fam_flag)
358 {
359 force_col_fam_flag = false;
360
361 FmtAvs fmt_dst = fmt_src;
362
363 // Full colorspace
364 if (args [Param_CSP].Defined ())
365 {
366 if (fmt_dst.conv_from_str (args [Param_CSP].AsString ()) != 0)
367 {
368 env.ThrowError (fmtcavs_MATRIX ": invalid output colorspace.");
369 }
370 else
371 {
372 force_col_fam_flag = true;
373 }
374 }
375
376 if (! fmt_dst.is_planar ())
377 {
378 env.ThrowError (fmtcavs_MATRIX ": output colorspace must be planar.");
379 }
380
381 if (args [Param_COL_FAM].Defined ())
382 {
383 force_col_fam_flag = true;
384 const auto col_fam =
385 conv_str_to_colfam (args [Param_COL_FAM].AsString ());
386 if (col_fam == fmtcl::ColorFamily_INVALID)
387 {
388 env.ThrowError (fmtcavs_MATRIX ": invalid col_fam.");
389 }
390 fmt_dst.set_col_fam (col_fam);
391 }
392
393 if (plane_out >= 0)
394 {
395 fmt_dst.set_col_fam (fmtcl::ColorFamily_GRAY);
396 }
397 else if (fmt_dst.get_col_fam () == fmtcl::ColorFamily_GRAY)
398 {
399 plane_out = 0;
400 }
401
402 const int bitdepth = args [Param_BITS].AsInt (fmt_dst.get_bitdepth ());
403 if (! FmtAvs::is_bitdepth_valid (bitdepth))
404 {
405 env.ThrowError (fmtcavs_MATRIX ": invalid bitdepth.");
406 }
407 else
408 {
409 fmt_dst.set_bitdepth (bitdepth);
410 }
411
412 return fmt_dst;
413 }
414
415
416
find_dst_col_fam(fmtcl::ColorSpaceH265 tmp_csp,FmtAvs fmt_dst,const FmtAvs & fmt_src)417 FmtAvs Matrix::find_dst_col_fam (fmtcl::ColorSpaceH265 tmp_csp, FmtAvs fmt_dst, const FmtAvs &fmt_src)
418 {
419 fmtcl::ColorFamily alt_cf = fmtcl::ColorFamily_INVALID;
420
421 switch (tmp_csp)
422 {
423 case fmtcl::ColorSpaceH265_RGB:
424 case fmtcl::ColorSpaceH265_BT709:
425 case fmtcl::ColorSpaceH265_FCC:
426 case fmtcl::ColorSpaceH265_BT470BG:
427 case fmtcl::ColorSpaceH265_SMPTE170M:
428 case fmtcl::ColorSpaceH265_SMPTE240M:
429 case fmtcl::ColorSpaceH265_YCGCO:
430 case fmtcl::ColorSpaceH265_BT2020NCL:
431 case fmtcl::ColorSpaceH265_BT2020CL:
432 case fmtcl::ColorSpaceH265_YDZDX:
433 case fmtcl::ColorSpaceH265_CHRODERNCL:
434 case fmtcl::ColorSpaceH265_CHRODERCL:
435 case fmtcl::ColorSpaceH265_ICTCP:
436 case fmtcl::ColorSpaceH265_ICTCP_PQ:
437 case fmtcl::ColorSpaceH265_ICTCP_HLG:
438 alt_cf = fmtcl::ColorFamily_YUV;
439 break;
440
441 case fmtcl::ColorSpaceH265_LMS:
442 alt_cf = fmtcl::ColorFamily_RGB;
443 break;
444
445 default:
446 // Nothing
447 break;
448 }
449
450 if (alt_cf != fmtcl::ColorFamily_INVALID)
451 {
452 const auto col_fam_src = fmt_src.get_col_fam ();
453 if (col_fam_src == fmtcl::ColorFamily_RGB)
454 {
455 fmt_dst.set_col_fam (alt_cf);
456 }
457 else if (col_fam_src == alt_cf)
458 {
459 fmt_dst.set_col_fam (fmtcl::ColorFamily_RGB);
460 }
461 }
462
463 return fmt_dst;
464 }
465
466
467
468 } // namespace fmtcavs
469
470
471
472 /*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
473