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