1 /*****************************************************************************
2 
3         FmtAvs.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/fnc.h"
28 #include "fstb/fnc.h"
29 #include "fmtcavs/FmtAvs.h"
30 #include "avisynth.h"
31 
32 #include <stdexcept>
33 
34 #include <cassert>
35 #include <cctype>
36 
37 
38 
39 namespace fmtcavs
40 {
41 
42 
43 
44 /*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
45 
46 
47 
48 constexpr int	FmtAvs::_max_css;
49 
50 
51 
FmtAvs(std::string fmt_str)52 FmtAvs::FmtAvs (std::string fmt_str)
53 {
54 	if (conv_from_str (fmt_str) != 0)
55 	{
56 		throw std::runtime_error ("Bad video format string");
57 	}
58 }
59 
60 
61 
FmtAvs(const VideoInfo & vi)62 FmtAvs::FmtAvs (const VideoInfo &vi) noexcept
63 {
64 	conv_from_vi (vi);
65 }
66 
67 
68 
invalidate()69 void	FmtAvs::invalidate () noexcept
70 {
71 	_bitdepth    = -1;
72 	_col_fam     = fmtcl::ColorFamily_INVALID;
73 	_planar_flag = false;
74 	_alpha_flag  = false;
75 	_subspl_h    = -1;
76 	_subspl_v    = -1;
77 }
78 
79 
80 
81 // Converts from an Avisynth colorspace
82 // http://avisynth.nl/index.php/Convert
83 // Returns 0 if everything is OK.
conv_from_str(std::string fmt_str)84 int	FmtAvs::conv_from_str (std::string fmt_str)
85 {
86 	invalidate ();
87 
88 	fmt_str = remove_outer_spaces (fmt_str);
89 	fstb::conv_to_lower_case (fmt_str);
90 
91 	if (is_eq_leftstr_and_eat (fmt_str, "rgb"))
92 	{
93 		_col_fam = fmtcl::ColorFamily_RGB;
94 		_subspl_h = 0; // Required for consistency
95 		_subspl_v = 0;
96 		if (fmt_str.empty ())
97 		{
98 			return -1; // Nothing specified after "RGB"
99 		}
100 
101 		if (fmt_str.front () == 'a')
102 		{
103 			_alpha_flag = true;
104 			fmt_str.erase (0, 1);
105 			if (fmt_str.empty ())
106 			{
107 				return -1; // We need the bitdepth
108 			}
109 		}
110 
111 		// Planar
112 		if (check_planar_bits_and_eat (fmt_str, _bitdepth))
113 		{
114 			_planar_flag = true;
115 			if (_bitdepth < 0)
116 			{
117 				return -1; // Ill-formed string or invalid bitdepth
118 			}
119 		}
120 
121 		// Interleaved
122 		else
123 		{
124 			if (_planar_flag)
125 			{
126 				return -1; // Interleaved format is always RGBxx, not RGBAxx
127 			}
128 			size_t         pos      = 0;
129 			const int      val_bits = std::stoi (fmt_str, &pos, 10);
130 			if (fmt_str.length () != pos)
131 			{
132 				return -1; // Garbage after RGBxx
133 			}
134 			switch (val_bits)
135 			{
136 			case 24: _bitdepth =  8; _alpha_flag = false; break;
137 			case 32: _bitdepth =  8; _alpha_flag = true;  break;
138 			case 48: _bitdepth = 16; _alpha_flag = false; break;
139 			case 64: _bitdepth = 16; _alpha_flag = true;  break;
140 			default: return -1; // Invalid bitdepth/alpha combination
141 			}
142 		}
143 	}
144 
145 	else if (is_eq_leftstr_and_eat (fmt_str, "yuy2"))
146 	{
147 		_col_fam  = fmtcl::ColorFamily_YUV;
148 		_bitdepth = 8;
149 		_subspl_h = 1;
150 		_subspl_v = 0;
151 
152 		if (! fmt_str.empty ())
153 		{
154 			return -1; // Garbage after YUY2
155 		}
156 	}
157 
158 	else if (is_eq_leftstr_and_eat (fmt_str, "yuv"))
159 	{
160 		_col_fam     = fmtcl::ColorFamily_YUV;
161 		_planar_flag = true;
162 
163 		if (fmt_str.front () == 'a')
164 		{
165 			_alpha_flag = true;
166 			fmt_str.erase (0, 1);
167 			if (fmt_str.empty ())
168 			{
169 				return -1; // We need chroma subsampling + planar bitdepth
170 			}
171 		}
172 
173 		size_t         pos = 0;
174 		const int      css = std::stoi (fmt_str, &pos, 10);
175 		fmt_str.erase (0, pos);
176 		switch (css)
177 		{
178 		case 420: _subspl_h = 1; _subspl_v = 1; break;
179 		case 422: _subspl_h = 1; _subspl_v = 0; break;
180 		case 444: _subspl_h = 0; _subspl_v = 0; break;
181 		default: return -1; // Invalid chroma subsampling
182 		}
183 
184 		if (! check_planar_bits_and_eat (fmt_str, _bitdepth))
185 		{
186 			return -1;
187 		}
188 		if (_bitdepth < 0)
189 		{
190 			return -1; // Ill-formed string or invalid bitdepth
191 		}
192 	}
193 
194 	else if (is_eq_leftstr_and_eat (fmt_str, "yv"))
195 	{
196 		_col_fam     = fmtcl::ColorFamily_YUV;
197 		_planar_flag = true;
198 		_bitdepth    = 8;
199 		size_t         pos      = 0;
200 		const int      val_bits = std::stoi (fmt_str, &pos, 10);
201 		if (fmt_str.length () != pos)
202 		{
203 			return -1; // Garbage after YVxx
204 		}
205 		switch (val_bits)
206 		{
207 		case 12:  _subspl_h = 1; _subspl_v = 1; break;
208 		case 16:  _subspl_h = 1; _subspl_v = 0; break;
209 		case 24:  _subspl_h = 0; _subspl_v = 0; break;
210 		case 411: _subspl_h = 2; _subspl_v = 0; break;
211 		default: return -1; // Invalid chroma subsampling
212 		}
213 	}
214 
215 	else if (is_eq_leftstr_and_eat (fmt_str, "y"))
216 	{
217 		_col_fam     = fmtcl::ColorFamily_GRAY;
218 		_planar_flag = true;
219 		_subspl_h    = 0; // Required for consistency
220 		_subspl_v    = 0;
221 		_bitdepth    = check_bits_and_eat (fmt_str, false);
222 		if (_bitdepth < 0)
223 		{
224 			return -1; // Ill-formed or unsupported bitdepth
225 		}
226 	}
227 
228 	return 0;
229 }
230 
231 
232 
233 // Assumes pixel_type is valid
conv_from_vi(const VideoInfo & vi)234 void	FmtAvs::conv_from_vi (const VideoInfo &vi)
235 {
236 	invalidate ();
237 
238 	_bitdepth    = vi.BitsPerComponent ();
239 	_planar_flag = vi.IsPlanar ();
240 	_alpha_flag  = avsutl::has_alpha (vi);
241 
242 	if (avsutl::is_rgb (vi))
243 	{
244 		_col_fam  = fmtcl::ColorFamily_RGB;
245 		_subspl_h = 0; // Required for consistency
246 		_subspl_v = 0;
247 	}
248 	else if (vi.IsY ())
249 	{
250 		_col_fam  = fmtcl::ColorFamily_GRAY;
251 		_subspl_h = 0; // Required for consistency
252 		_subspl_v = 0;
253 	}
254 	else
255 	{
256 		assert (vi.IsYUV () || vi.IsYUVA ());
257 		_col_fam  = fmtcl::ColorFamily_YUV;
258 		_subspl_h = ((vi.pixel_type >> VideoInfo::CS_Shift_Sub_Width ) + 1) & 3;
259 		_subspl_v = ((vi.pixel_type >> VideoInfo::CS_Shift_Sub_Height) + 1) & 3;
260 	}
261 }
262 
263 
264 
265 // Returns 0 if conversion is OK.
conv_to_vi(VideoInfo & vi) const266 int	FmtAvs::conv_to_vi (VideoInfo &vi) const
267 {
268 	assert (is_valid ());
269 
270 	int            pixel_type = 0;
271 
272 	switch (_bitdepth)
273 	{
274 	case 8:  pixel_type |= VideoInfo::CS_Sample_Bits_8;  break;
275 	case 10: pixel_type |= VideoInfo::CS_Sample_Bits_10; break;
276 	case 12: pixel_type |= VideoInfo::CS_Sample_Bits_12; break;
277 	case 14: pixel_type |= VideoInfo::CS_Sample_Bits_14; break;
278 	case 16: pixel_type |= VideoInfo::CS_Sample_Bits_16; break;
279 	case 32: pixel_type |= VideoInfo::CS_Sample_Bits_32; break;
280 	default: return -1; // Bitdepth not supported
281 	}
282 
283 	if (_col_fam == fmtcl::ColorFamily_GRAY)
284 	{
285 		// Chroma subsampling is ignored
286 		if (_alpha_flag)
287 		{
288 			return -1;
289 		}
290 		pixel_type |= VideoInfo::CS_GENERIC_Y;
291 	}
292 
293 	else if (_col_fam == fmtcl::ColorFamily_RGB)
294 	{
295 		// Chroma subsampling is ignored
296 		pixel_type |= VideoInfo::CS_BGR;
297 		pixel_type |= (_alpha_flag)
298 			? VideoInfo::CS_RGBA_TYPE
299 			: VideoInfo::CS_RGB_TYPE;
300 
301 		if (_planar_flag)
302 		{
303 			pixel_type |= VideoInfo::CS_PLANAR;
304 		}
305 		else
306 		{
307 			if (_bitdepth != 8 && _bitdepth != 16)
308 			{
309 				return -1;
310 			}
311 			pixel_type |= VideoInfo::CS_INTERLEAVED;
312 		}
313 	}
314 
315 	else if (_col_fam == fmtcl::ColorFamily_YUV)
316 	{
317 		if (_planar_flag)
318 		{
319 			pixel_type |= VideoInfo::CS_PLANAR;
320 			pixel_type |= VideoInfo::CS_VPlaneFirst;
321 			pixel_type |= (_alpha_flag) ? VideoInfo::CS_YUVA : VideoInfo::CS_YUV;
322 
323 			switch ((_subspl_v << 4) | _subspl_h)
324 			{
325 			case 0x00:
326 				pixel_type |= VideoInfo::CS_Sub_Height_1 | VideoInfo::CS_Sub_Width_1;
327 				break;
328 			case 0x01:
329 				pixel_type |= VideoInfo::CS_Sub_Height_1 | VideoInfo::CS_Sub_Width_2;
330 				break;
331 			case 0x11:
332 				pixel_type |= VideoInfo::CS_Sub_Height_2 | VideoInfo::CS_Sub_Width_2;
333 				break;
334 			case 0x02:
335 				pixel_type |= VideoInfo::CS_Sub_Height_1 | VideoInfo::CS_Sub_Width_4;
336 				if (_alpha_flag || _bitdepth != 8)
337 				{
338 					return -1; // Only YV411 in 4:1:1
339 				}
340 				break;
341 			/**** To do: should we allow 4:1:0 (YUV9)? It is in the API but it
342 				seems Avisynth filter don't use it.
343 				http://avisynth.nl/index.php/Avisynthplus_color_formats
344 			***/
345 			default:
346 				return -1; // Invalid chroma subsampling
347 			}
348 		}
349 		else
350 		{
351 			// Only YUY2 allowed with interleaved formats
352 			if (_bitdepth != 8 || _alpha_flag || _subspl_h != 1 || _subspl_v != 0)
353 			{
354 				return -1;
355 			}
356 			pixel_type = VideoInfo::CS_YUY2;
357 		}
358 	}
359 
360 	else
361 	{
362 		return -1;
363 	}
364 
365 	vi.pixel_type = pixel_type;
366 
367 	return 0;
368 }
369 
370 
371 
372 // Makes sure all the field are initialised, but doesn't guarantee
373 // that it corresponds to a valid avisynth colorspace.
is_valid() const374 bool	FmtAvs::is_valid () const noexcept
375 {
376 	return (
377 		   _bitdepth >  0 && _col_fam  >= 0
378 		&& _subspl_h >= 0 && _subspl_v >= 0);
379 }
380 
381 
382 
set_bitdepth(int bitdepth)383 void	FmtAvs::set_bitdepth (int bitdepth) noexcept
384 {
385 	assert (is_bitdepth_valid (bitdepth));
386 
387 	_bitdepth = bitdepth;
388 }
389 
390 
391 
get_bitdepth() const392 int	FmtAvs::get_bitdepth () const noexcept
393 {
394 	assert (is_valid ());
395 
396 	return _bitdepth;
397 }
398 
399 
400 
is_float() const401 bool	FmtAvs::is_float () const noexcept
402 {
403 	assert (is_valid ());
404 
405 	return (_bitdepth >= 32);
406 }
407 
408 
409 
set_col_fam(fmtcl::ColorFamily col_fam)410 void	FmtAvs::set_col_fam (fmtcl::ColorFamily col_fam) noexcept
411 {
412 	assert (col_fam >= 0);
413 	assert (col_fam < fmtcl::ColorFamily_NBR_ELT);
414 	assert (col_fam != fmtcl::ColorFamily_YCGCO);
415 
416 	_col_fam = col_fam;
417 }
418 
419 
420 
get_col_fam() const421 fmtcl::ColorFamily	FmtAvs::get_col_fam () const noexcept
422 {
423 	assert (is_valid ());
424 
425 	return _col_fam;
426 }
427 
428 
429 
is_planar() const430 bool	FmtAvs::is_planar () const noexcept
431 {
432 	assert (is_valid ());
433 
434 	return _planar_flag;
435 }
436 
437 
438 
has_alpha() const439 bool	FmtAvs::has_alpha () const noexcept
440 {
441 	assert (is_valid ());
442 
443 	return _alpha_flag;
444 }
445 
446 
447 
set_subspl_h(int ss)448 void	FmtAvs::set_subspl_h (int ss) noexcept
449 {
450 	assert (ss >= 0);
451 	assert (ss <= _max_css);
452 
453 	_subspl_h = ss;
454 }
455 
456 
457 
get_subspl_h() const458 int	FmtAvs::get_subspl_h () const noexcept
459 {
460 	assert (is_valid ());
461 
462 	return _subspl_h;
463 }
464 
465 
466 
set_subspl_v(int ss)467 void	FmtAvs::set_subspl_v (int ss) noexcept
468 {
469 	assert (ss >= 0);
470 	assert (ss <= _max_css);
471 
472 	_subspl_v = ss;
473 }
474 
475 
476 
get_subspl_v() const477 int	FmtAvs::get_subspl_v () const noexcept
478 {
479 	assert (is_valid ());
480 
481 	return _subspl_v;
482 }
483 
484 
485 
get_nbr_comp_non_alpha() const486 int	FmtAvs::get_nbr_comp_non_alpha () const noexcept
487 {
488 	assert (is_valid ());
489 
490 	return (_col_fam == fmtcl::ColorFamily_GRAY) ? 1 : 3;
491 }
492 
493 
494 
is_bitdepth_valid(int bitdepth)495 bool	FmtAvs::is_bitdepth_valid (int bitdepth) noexcept
496 {
497 	return (
498 		   bitdepth ==  8
499 		|| bitdepth == 10
500 		|| bitdepth == 12
501 		|| bitdepth == 14
502 		|| bitdepth == 16
503 		|| bitdepth == 32
504 	);
505 }
506 
507 
508 
509 /*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
510 
511 
512 
513 /*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
514 
515 
516 
remove_outer_spaces(std::string str)517 std::string	FmtAvs::remove_outer_spaces (std::string str)
518 {
519 	while (! str.empty () && std::isspace (str.back ()) != 0)
520 	{
521 		str.pop_back ();
522 	}
523 	while (! str.empty () && std::isspace (str.front ()) != 0)
524 	{
525 		str.erase (0, 1);
526 	}
527 
528 	return str;
529 }
530 
531 
532 
is_eq_leftstr_and_eat(std::string & str,std::string stest)533 bool	FmtAvs::is_eq_leftstr_and_eat (std::string &str, std::string stest)
534 {
535 	const auto     len = stest.length ();
536 	if (str.substr (0, len) == stest)
537 	{
538 		str.erase (0, len);
539 		return true;
540 	}
541 
542 	return false;
543 }
544 
545 
546 
547 // str must be lower-case
548 // If the string is ill-formed, returns true and res < 0
549 // As the pxx value should be last, the string is considered ill-formed
550 // if there are characters after the resolution.
check_planar_bits_and_eat(std::string & str,int & res)551 bool	FmtAvs::check_planar_bits_and_eat (std::string &str, int &res)
552 {
553 	if (! str.empty () && str.front () == 'p')
554 	{
555 		str.erase (0, 1);
556 		if (str.empty ())
557 		{
558 			res = -1;
559 			return true;
560 		}
561 		res = check_bits_and_eat (str, true);
562 		return true;
563 	}
564 
565 	return false;
566 }
567 
568 
569 
check_bits_and_eat(std::string & str,bool allow_s_flag)570 int	FmtAvs::check_bits_and_eat (std::string &str, bool allow_s_flag)
571 {
572 	int            res = -1;
573 
574 	if (str.empty ())
575 	{
576 		return -1;
577 	}
578 
579 	if (str [0] == 's')
580 	{
581 		if (! allow_s_flag)
582 		{
583 			return -1;
584 		}
585 		res = 32;
586 		str.erase (0, 1);
587 	}
588 	else
589 	{
590 		size_t         pos = 0;
591 		res = std::stoi (str, &pos, 10);
592 		switch (res)
593 		{
594 		case 8:
595 		case 10:
596 		case 12:
597 		case 14:
598 		case 16:
599 		case 32:
600 			break;
601 		default:
602 			return -1;
603 		}
604 		str.erase (0, pos);
605 	}
606 
607 	if (! str.empty ())
608 	{
609 		return -1;
610 	}
611 
612 	return res;
613 }
614 
615 
616 
617 }  // namespace fmtcavs
618 
619 
620 
621 /*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
622