GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 0.0% 0 / 0 / 3052
Functions: 0.0% 0 / 0 / 178
Branches: 0.0% 0 / 0 / 2718

filters/source.cpp
Line Branch Exec Source
1 // Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al.
2 // http://avisynth.nl
3
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit
17 // http://www.gnu.org/copyleft/gpl.html .
18 //
19 // Linking Avisynth statically or dynamically with other modules is making a
20 // combined work based on Avisynth. Thus, the terms and conditions of the GNU
21 // General Public License cover the whole combination.
22 //
23 // As a special exception, the copyright holders of Avisynth give you
24 // permission to link Avisynth with independent modules that communicate with
25 // Avisynth solely through the interfaces defined in avisynth.h, regardless of the license
26 // terms of these independent modules, and to copy and distribute the
27 // resulting combined work under terms of your choice, provided that
28 // every copy of the combined work is accompanied by a complete copy of
29 // the source code of Avisynth (the version of Avisynth used to produce the
30 // combined work), being distributed under the terms of the GNU General
31 // Public License plus this exception. An independent module is a module
32 // which is not derived from or based on Avisynth, such as 3rd-party filters,
33 // import and export plugins, or graphical user interfaces.
34
35
36 #include "../core/internal.h"
37 #include "../convert/convert_matrix.h"
38 #include "../convert/convert_helper.h"
39 #include "colorbars_const.h"
40 #include "transform.h"
41 #ifdef AVS_WINDOWS
42 #include "AviSource/avi_source.h"
43 #endif
44 #include "../convert/convert_planar.h"
45
46 #define PI 3.1415926535897932384626433832795
47 #include <ctime>
48 #include <cmath>
49 #include <new>
50 #include <cassert>
51 #include <stdint.h>
52 #include <algorithm>
53
54 #define XP_LAMBDA_CAPTURE_FIX(x) (void)(x)
55
56 /********************************************************************
57 ********************************************************************/
58
59 enum {
60 COLOR_MODE_RGB = 0,
61 COLOR_MODE_YUV
62 };
63
64 class StaticImage : public IClip {
65 const VideoInfo vi;
66 const PVideoFrame frame;
67 bool parity;
68
69 public:
70 StaticImage(const VideoInfo& _vi, const PVideoFrame& _frame, bool _parity)
71 : vi(_vi), frame(_frame), parity(_parity) {}
72 PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) {
73 AVS_UNUSED(n);
74 AVS_UNUSED(env);
75 return frame;
76 }
77 void __stdcall GetAudio(void* buf, int64_t start, int64_t count, IScriptEnvironment* env) {
78 AVS_UNUSED(start);
79 AVS_UNUSED(env);
80 memset(buf, 0, (size_t)vi.BytesFromAudioSamples(count));
81 }
82 const VideoInfo& __stdcall GetVideoInfo() { return vi; }
83 bool __stdcall GetParity(int n) { return (vi.IsFieldBased() ? (n&1) : false) ^ parity; }
84 int __stdcall SetCacheHints(int cachehints,int frame_range)
85 {
86 AVS_UNUSED(frame_range);
87 switch (cachehints)
88 {
89 case CACHE_DONT_CACHE_ME:
90 return 1;
91 case CACHE_GET_MTMODE:
92 return MT_NICE_FILTER;
93 case CACHE_GET_DEV_TYPE:
94 return DEV_TYPE_CPU;
95 case CACHE_GET_CHILD_DEV_TYPE:
96 return DEV_TYPE_ANY; // any type is ok because this clip does not require child's frames.
97 default:
98 return 0;
99 }
100 }
101 };
102
103 // For any frame number, this clip returns the first frame of a child clip .
104 // This clip makes cache effective and reduce unnecessary frame transfer.
105 class SingleFrame : public GenericVideoFilter {
106 public:
107 SingleFrame(PClip _child) : GenericVideoFilter(_child) {}
108 PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) { return child->GetFrame(0, env); }
109 int __stdcall SetCacheHints(int cachehints, int frame_range)
110 {
111 switch (cachehints)
112 {
113 case CACHE_DONT_CACHE_ME:
114 return 1;
115 case CACHE_GET_MTMODE:
116 return MT_NICE_FILTER;
117 case CACHE_GET_DEV_TYPE:
118 return (child->GetVersion() >= 5) ? child->SetCacheHints(CACHE_GET_DEV_TYPE, 0) : 0;
119 default:
120 return 0;
121 }
122 }
123 };
124
125
126 static PVideoFrame CreateBlankFrame(const VideoInfo& vi, int color, int mode, const int *colors, const float *colors_f, bool color_is_array, IScriptEnvironment* env) {
127
128 if (!vi.HasVideo()) return 0;
129
130 PVideoFrame frame = env->NewVideoFrame(vi);
131 // no frame property origin
132
133 // but we set Rec601 (ST170) if YUV
134 auto props = env->getFramePropsRW(frame);
135 int theMatrix = vi.IsRGB() ? Matrix_e::AVS_MATRIX_RGB : Matrix_e::AVS_MATRIX_ST170_M;
136 int theColorRange = vi.IsRGB() ? ColorRange_Compat_e::AVS_COLORRANGE_FULL : ColorRange_Compat_e::AVS_COLORRANGE_LIMITED;
137 update_Matrix_and_ColorRange(props, theMatrix, theColorRange, env);
138
139 // RGB 8->16 bit: not << 8 like YUV but 0..255 -> 0..65535 or 0..1023 for 10 bit
140 int pixelsize = vi.ComponentSize();
141 int bits_per_pixel = vi.BitsPerComponent();
142 int max_pixel_value = (1 << bits_per_pixel) - 1;
143 auto rgbcolor8to16 = [](uint8_t color8, int max_pixel_value) { return (uint16_t)(color8 * max_pixel_value / 255); };
144
145 // int color holds the "old" 8 bit color values that are scaled automatically to the right bitmap
146 // new in avs+: if color_is_array, color values are filled as-is, no conversion or any shift occurs
147
148 if (vi.IsPlanar()) {
149
150 bool isyuvlike = vi.IsYUV() || vi.IsYUVA();
151
152 if (color_is_array) {
153 // works from colors or colors_f: as-is
154 // color order in the array: RGBA or YUVA
155 int planes_y[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A };
156 int planes_r[4] = { PLANAR_R, PLANAR_G, PLANAR_B, PLANAR_A };
157 int *planes = isyuvlike ? planes_y : planes_r;
158
159 for (int p = 0; p < vi.NumComponents(); p++)
160 {
161 int plane = planes[p];
162 BYTE *dstp = frame->GetWritePtr(plane);
163 int rowsize = frame->GetRowSize(plane);
164 int pitch = frame->GetPitch(plane);
165 int height = frame->GetHeight(plane);
166 switch (pixelsize) {
167 case 1: fill_plane<uint8_t>(dstp, height, rowsize, pitch, clamp(colors[p], 0, 0xFF)); break;
168 case 2: fill_plane<uint16_t>(dstp, height, rowsize, pitch, clamp(colors[p], 0, (1 << vi.BitsPerComponent()) - 1)); break;
169 case 4: fill_plane<float>(dstp, height, rowsize, pitch, colors_f[p]); break;
170 }
171 }
172 }
173 else {
174 int color_yuv = (mode == COLOR_MODE_YUV) ? color : RGB2YUV_Rec601(color);
175
176 int val_i = 0;
177
178 int planes_y[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A };
179 int planes_r[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A };
180 int *planes = isyuvlike ? planes_y : planes_r;
181
182 for (int p = 0; p < vi.NumComponents(); p++)
183 {
184 int plane = planes[p];
185 // color order: ARGB or AYUV
186 // (8)-8-8-8 bit color from int parameter
187 if (isyuvlike) {
188 switch (plane) {
189 case PLANAR_A: val_i = (color >> 24) & 0xff; break;
190 case PLANAR_Y: val_i = (color_yuv >> 16) & 0xff; break;
191 case PLANAR_U: val_i = (color_yuv >> 8) & 0xff; break;
192 case PLANAR_V: val_i = color_yuv & 0xff; break;
193 }
194 if (bits_per_pixel != 32)
195 val_i = val_i << (bits_per_pixel - 8);
196 }
197 else {
198 // planar RGB
199 switch (plane) {
200 case PLANAR_A: val_i = (color >> 24) & 0xff; break;
201 case PLANAR_R: val_i = (color >> 16) & 0xff; break;
202 case PLANAR_G: val_i = (color >> 8) & 0xff; break;
203 case PLANAR_B: val_i = color & 0xff; break;
204 }
205 if (bits_per_pixel != 32)
206 val_i = rgbcolor8to16(val_i, max_pixel_value);
207 }
208
209
210 BYTE *dstp = frame->GetWritePtr(plane);
211 int size = frame->GetPitch(plane) * frame->GetHeight(plane);
212
213 switch (pixelsize) {
214 case 1:
215 memset(dstp, val_i, size);
216 break;
217 case 2:
218 val_i = clamp(val_i, 0, (1 << vi.BitsPerComponent()) - 1);
219 std::fill_n((uint16_t *)dstp, size / sizeof(uint16_t), (uint16_t)val_i);
220 break; // 2 pixels at a time
221 default: // case 4:
222 float val_f;
223 if (plane == PLANAR_U || plane == PLANAR_V) {
224 float shift = 0.0f;
225 val_f = float(val_i - 128) / 255.0f + shift; // 32 bit float chroma 128=0.5
226 }
227 else {
228 val_f = float(val_i) / 255.0f;
229 }
230 std::fill_n((float *)dstp, size / sizeof(float), val_f);
231 }
232 }
233 }
234 return frame;
235 } // if planar
236
237 BYTE* p = frame->GetWritePtr();
238 int size = frame->GetPitch() * frame->GetHeight();
239
240 if (vi.IsYUY2()) {
241 int color_yuv =(mode == COLOR_MODE_YUV) ? color : RGB2YUV_Rec601(color);
242 if (color_is_array) {
243 color_yuv = (clamp(colors[0], 0, max_pixel_value) << 16) | (clamp(colors[1], 0, max_pixel_value) << 8) | (clamp(colors[2], 0, max_pixel_value));
244 }
245 uint32_t d = ((color_yuv>>16)&255) * 0x010001 + ((color_yuv>>8)&255) * 0x0100 + (color_yuv&255) * 0x01000000;
246 for (int i=0; i<size; i+=4)
247 *(uint32_t *)(p+i) = d;
248 } else if (vi.IsRGB24()) {
249 const uint8_t color_b = color_is_array ? clamp(colors[2], 0, max_pixel_value) : (uint8_t)(color & 0xFF);
250 const uint8_t color_g = color_is_array ? clamp(colors[1], 0, max_pixel_value) : (uint8_t)(color >> 8);
251 const uint8_t color_r = color_is_array ? clamp(colors[0], 0, max_pixel_value) : (uint8_t)(color >> 16);
252 const int rowsize = frame->GetRowSize();
253 const int pitch = frame->GetPitch();
254 for (int y=frame->GetHeight();y>0;y--) {
255 for (int i=0; i<rowsize; i+=3) {
256 p[i] = color_b;
257 p[i+1] = color_g;
258 p[i+2] = color_r;
259 }
260 p+=pitch;
261 }
262 } else if (vi.IsRGB32()) {
263 uint32_t c;
264 c = color;
265 if (color_is_array) {
266 uint32_t r = clamp(colors[0], 0, max_pixel_value);
267 uint32_t g = clamp(colors[1], 0, max_pixel_value);
268 uint32_t b = clamp(colors[2], 0, max_pixel_value);
269 uint32_t a = clamp(colors[3], 0, max_pixel_value);
270 c = (a << 24) + (r << 16) + (g << 8) + (b);
271 }
272 std::fill_n((uint32_t *)p, size / 4, c);
273 //for (int i=0; i<size; i+=4)
274 // *(unsigned*)(p+i) = color;
275 } else if (vi.IsRGB48()) {
276 const uint16_t color_b = color_is_array ? clamp(colors[2], 0, max_pixel_value) : rgbcolor8to16(color & 0xFF, max_pixel_value);
277 uint16_t r = color_is_array ? clamp(colors[0], 0, max_pixel_value) : rgbcolor8to16((color >> 16) & 0xFF, max_pixel_value);
278 uint16_t g = color_is_array ? clamp(colors[1], 0, max_pixel_value) : rgbcolor8to16((color >> 8 ) & 0xFF, max_pixel_value);
279 const uint32_t color_rg = (r << 16) + (g);
280 const int rowsize = frame->GetRowSize() / sizeof(uint16_t);
281 const int pitch = frame->GetPitch() / sizeof(uint16_t);
282 uint16_t* p16 = reinterpret_cast<uint16_t*>(p);
283 for (int y=frame->GetHeight();y>0;y--) {
284 for (int i=0; i<rowsize; i+=3) {
285 p16[i] = color_b; // b
286 *reinterpret_cast<uint32_t*>(p16+i+1) = color_rg; // gr
287 }
288 p16 += pitch;
289 }
290 } else if (vi.IsRGB64()) {
291 uint64_t r, g, b, a;
292 r = color_is_array ? clamp(colors[0], 0, max_pixel_value) : rgbcolor8to16((color >> 16) & 0xFF, max_pixel_value);
293 g = color_is_array ? clamp(colors[1], 0, max_pixel_value) : rgbcolor8to16((color >> 8 ) & 0xFF, max_pixel_value);
294 b = color_is_array ? clamp(colors[2], 0, max_pixel_value) : rgbcolor8to16((color ) & 0xFF, max_pixel_value);
295 a = color_is_array ? clamp(colors[3], 0, max_pixel_value) : rgbcolor8to16((color >> 24) & 0xFF, max_pixel_value);
296 uint64_t color64 = (a << 48) + (r << 32) + (g << 16) + (b);
297 std::fill_n(reinterpret_cast<uint64_t*>(p), size / sizeof(uint64_t), color64);
298 }
299 return frame;
300 }
301
302
303 static AVSValue __cdecl Create_BlankClip(AVSValue args, void*, IScriptEnvironment* env) {
304 VideoInfo vi_default;
305 memset(&vi_default, 0, sizeof(VideoInfo));
306
307 VideoInfo vi = vi_default;
308
309 vi_default.fps_denominator=1;
310 vi_default.fps_numerator=24;
311 vi_default.height=480;
312 vi_default.pixel_type=VideoInfo::CS_BGR32;
313 vi_default.num_frames=240;
314 vi_default.width=640;
315 vi_default.audio_samples_per_second=44100;
316 vi_default.nchannels=1;
317 vi_default.num_audio_samples=44100*10;
318 vi_default.sample_type=SAMPLE_INT16;
319 vi_default.SetFieldBased(false);
320 bool parity=false;
321
322 AVSValue args0 = args[0];
323
324 // param#12: "clip" overrides
325 if (args0.Defined() && args0.ArraySize() == 1 && !args[12].Defined()) {
326 vi_default = args0[0].AsClip()->GetVideoInfo();
327 parity = args0[0].AsClip()->GetParity(0);
328 }
329 else if (args0.Defined() && args0.ArraySize() != 0) {
330 // when "clip" is defined then beginning clip parameter is forbidden
331 env->ThrowError("BlankClip: Only 1 Template clip allowed.");
332 }
333 else if (args[12].Defined()) {
334 // supplied "clip" parameter
335 vi_default = args[12].AsClip()->GetVideoInfo();
336 parity = args[12].AsClip()->GetParity(0);
337 }
338
339 bool defHasVideo = vi_default.HasVideo();
340 bool defHasAudio = vi_default.HasAudio();
341
342 // If no default video
343 if ( !defHasVideo ) {
344 vi_default.fps_numerator=24;
345 vi_default.fps_denominator=1;
346
347 vi_default.num_frames = 240;
348
349 // If specify Width or Height or Pixel_Type
350 if ( args[2].Defined() || args[3].Defined() || args[4].Defined() ) {
351 vi_default.width=640;
352 vi_default.height=480;
353 vi_default.pixel_type=VideoInfo::CS_BGR32;
354
355 vi_default.SetFieldBased(false);
356 parity=false;
357 }
358 }
359
360 // If no default audio but specify Audio_rate or Channels or Sample_Type
361 if ( !defHasAudio && ( args[7].Defined() || args[8].Defined() || args[9].Defined() ) ) {
362 vi_default.audio_samples_per_second=44100;
363 vi_default.nchannels=1;
364 vi_default.sample_type=SAMPLE_INT16;
365 }
366
367 vi.width = args[2].AsInt(vi_default.width);
368 vi.height = args[3].AsInt(vi_default.height);
369
370 if (args[4].Defined()) {
371 int pixel_type = GetPixelTypeFromName(args[4].AsString());
372 if(pixel_type == VideoInfo::CS_UNKNOWN)
373 {
374 env->ThrowError("BlankClip: pixel_type must be \"RGB32\", \"RGB24\", \"YV12\", \"YV24\", \"YV16\", \"Y8\", \n"\
375 "\"YUV420P?\",\"YUV422P?\",\"YUV444P?\",\"Y?\",\n"\
376 "\"RGB48\",\"RGB64\",\"RGBP\",\"RGBP?\",\n"\
377 "\"YV411\" or \"YUY2\"");
378 }
379 vi.pixel_type = pixel_type;
380 }
381 else {
382 vi.pixel_type = vi_default.pixel_type;
383 }
384
385 if (!vi.pixel_type)
386 vi.pixel_type = VideoInfo::CS_BGR32;
387
388
389 double n = args[5].AsDblDef(double(vi_default.fps_numerator));
390
391 if (args[5].Defined() && !args[6].Defined()) {
392 unsigned d = 1;
393 while (n < 16777216 && d < 16777216) { n*=2; d*=2; }
394 vi.SetFPS(int(n+0.5), d);
395 } else {
396 vi.SetFPS(int(n+0.5), args[6].AsInt(vi_default.fps_denominator));
397 }
398
399 vi.image_type = vi_default.image_type; // Copy any field sense from template
400
401 vi.audio_samples_per_second = args[7].AsInt(vi_default.audio_samples_per_second);
402
403 if (args[8].IsBool())
404 vi.nchannels = args[8].AsBool() ? 2 : 1; // stereo=True
405 else if (args[8].IsInt())
406 vi.nchannels = args[8].AsInt(); // channels=2
407 else
408 vi.nchannels = vi_default.nchannels;
409
410 if (args[9].IsBool())
411 vi.sample_type = args[9].AsBool() ? SAMPLE_INT16 : SAMPLE_FLOAT; // sixteen_bit=True
412 else if (args[9].IsString()) {
413 const char* sample_type_string = args[9].AsString();
414 if (!lstrcmpi(sample_type_string, "8bit" )) { // sample_type="8Bit"
415 vi.sample_type = SAMPLE_INT8;
416 } else if (!lstrcmpi(sample_type_string, "16bit")) { // sample_type="16Bit"
417 vi.sample_type = SAMPLE_INT16;
418 } else if (!lstrcmpi(sample_type_string, "24bit")) { // sample_type="24Bit"
419 vi.sample_type = SAMPLE_INT24;
420 } else if (!lstrcmpi(sample_type_string, "32bit")) { // sample_type="32Bit"
421 vi.sample_type = SAMPLE_INT32;
422 } else if (!lstrcmpi(sample_type_string, "float")) { // sample_type="Float"
423 vi.sample_type = SAMPLE_FLOAT;
424 } else {
425 env->ThrowError("BlankClip: sample_type must be \"8bit\", \"16bit\", \"24bit\", \"32bit\" or \"float\"");
426 }
427 } else
428 vi.sample_type = vi_default.sample_type;
429
430 // If we got an Audio only default clip make the default duration the same
431 if (!defHasVideo && defHasAudio) {
432 const int64_t denom = Int32x32To64(vi.fps_denominator, vi_default.audio_samples_per_second);
433 vi_default.num_frames = int((vi_default.num_audio_samples * vi.fps_numerator + denom - 1) / denom); // ceiling
434 }
435
436 vi.num_frames = args[1].AsInt(vi_default.num_frames);
437
438 vi.width++; // cheat HasVideo() call for Audio Only clips
439 vi.num_audio_samples = vi.AudioSamplesFromFrames(vi.num_frames);
440 vi.width--;
441
442 int color = args[10].AsInt(0);
443 int mode = COLOR_MODE_RGB;
444 if (args[11].Defined()) {
445 if (color != 0) // Not quite 100% test
446 env->ThrowError("BlankClip: color and color_yuv are mutually exclusive");
447 if (!vi.IsYUV() && !vi.IsYUVA())
448 env->ThrowError("BlankClip: color_yuv only valid for YUV color spaces");
449 color = args[11].AsInt();
450 mode=COLOR_MODE_YUV;
451 }
452
453 int colors[4] = { 0 };
454 float colors_f[4] = { 0.0 };
455 bool color_is_array = false;
456
457 if (args.ArraySize() >= 14) {
458 // new colors parameter
459 if (args[13].Defined()) // colors
460 {
461 if (!args[13].IsArray())
462 env->ThrowError("BlankClip: colors must be an array");
463 int color_count = args[13].ArraySize();
464 if (color_count < vi.NumComponents())
465 env->ThrowError("BlankClip: 'colors' size %d is less than component count %d", color_count, vi.NumComponents());
466 int pixelsize = vi.ComponentSize();
467 int bits_per_pixel = vi.BitsPerComponent();
468 for (int i = 0; i < color_count; i++) {
469 const float c = args[13][i].AsFloatf(0.0);
470 if (pixelsize == 4)
471 colors_f[i] = c;
472 else {
473 const int color = (int)(c + 0.5f);
474 if (color >= (1 << bits_per_pixel) || color < 0)
475 env->ThrowError("BlankClip: invalid color value (%d) for %d-bit video format", color, bits_per_pixel);
476 colors[i] = color;
477 }
478 }
479 color_is_array = true;
480 }
481 }
482
483 PClip clip = new StaticImage(vi, CreateBlankFrame(vi, color, mode, colors, colors_f, color_is_array, env), parity);
484
485 // wrap in OnCPU to support multi devices
486 AVSValue arg[2]{ clip, 1 }; // prefetch=1: enable cache but not thread
487 return new SingleFrame(env->Invoke("OnCPU", AVSValue(arg, 2)).AsClip());
488 }
489
490
491 /********************************************************************
492 ********************************************************************/
493
494 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
495 // in text-overlay.cpp
496 extern bool GetTextBoundingBox(const char* text, const char* fontname,
497 int size, bool bold, bool italic, int align, int* width, int* height, bool utf8);
498 #endif
499
500 extern bool GetTextBoundingBoxFixed(const char* text, const char* fontname, int size, bool bold,
501 bool italic, int align, int& width, int& height, bool utf8);
502
503
504 PClip Create_MessageClip(const char* message, int width, int height, int pixel_type, bool shrink,
505 int textcolor, int halocolor, int bgcolor,
506 int fps_numerator, int fps_denominator, int num_frames,
507 bool utf8,
508 IScriptEnvironment* env) {
509 int size;
510 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
511 // MessageClip produces a clip containing a text message.Used internally for error reporting.
512 // The font face is "Arial".
513 // The font size is between 24 points and 9 points - chosen to fit, if possible,
514 // in the width by height clip.
515 // The pixeltype is RGB32.
516
517 for (size = 24*8; /*size>=9*8*/; size-=4) {
518 int text_width, text_height;
519 GetTextBoundingBox(message, "Arial", size, true, false, TA_TOP | TA_CENTER, &text_width, &text_height, utf8);
520 text_width = ((text_width>>3)+8+7) & ~7; // mod 8
521 text_height = ((text_height>>3)+8+1) & ~1; // mod 2
522 if (size <= 9 * 8 || ((width <= 0 || text_width <= width) && (height <= 0 || text_height <= height))) {
523 if (width <= 0 || (shrink && width > text_width))
524 width = text_width;
525 if (height <= 0 || (shrink && height > text_height))
526 height = text_height;
527 break;
528 }
529 }
530 #else
531 constexpr int MAX_SIZE = 24; // Terminus 12,14,16,18,20,22,24,28,32
532 constexpr int MIN_SIZE = 12;
533 for (size = MAX_SIZE; /*size>=9*/; size -= 2) {
534 int text_width, text_height;
535 #ifdef AVS_POSIX
536 bool utf8 = true;
537 #endif
538 GetTextBoundingBoxFixed(message, "Terminus", size, true, false, 0 /* align */, text_width, text_height, utf8);
539 text_width = (text_width + 8 + 7) & ~7; // mod 8
540 text_height = (text_height + 8 + 1) & ~1; // mod 2
541 if (size <= MIN_SIZE || ((width <= 0 || text_width <= width) && (height <= 0 || text_height <= height))) {
542 if (width <= 0 || (shrink && width > text_width))
543 width = text_width;
544 if (height <= 0 || (shrink && height > text_height))
545 height = text_height;
546 break;
547 }
548 }
549
550 size *= 8; // back to GDI units
551 #endif
552
553 VideoInfo vi;
554 memset(&vi, 0, sizeof(vi));
555 vi.width = width;
556 vi.height = height;
557 vi.pixel_type = pixel_type;
558 vi.fps_numerator = fps_numerator > 0 ? fps_numerator : 24;
559 vi.fps_denominator = fps_denominator > 0 ? fps_denominator : 1;
560 vi.num_frames = num_frames > 0 ? num_frames : 240;
561
562 PVideoFrame frame = CreateBlankFrame(vi, bgcolor, COLOR_MODE_RGB, nullptr, nullptr, false, env);
563 env->ApplyMessageEx(&frame, vi, message, size, textcolor, halocolor, bgcolor, utf8);
564 PClip clip = new StaticImage(vi, frame, false);
565
566 // wrap in OnCPU to support multi devices
567 AVSValue args[2]{ clip, 1 }; // prefetch=1: enable cache but not thread
568 return new SingleFrame(env->Invoke("OnCPU", AVSValue(args, 2)).AsClip());
569 };
570
571 AVSValue __cdecl Create_MessageClip(AVSValue args, void*, IScriptEnvironment* env) {
572 const bool utf8_default =
573 #ifdef AVS_POSIX
574 true
575 #else
576 false
577 #endif
578 ;
579
580 return Create_MessageClip(args[0].AsString(), args[1].AsInt(-1),
581 args[2].AsInt(-1), VideoInfo::CS_BGR32, args[3].AsBool(false),
582 args[4].AsInt(0xFFFFFF), args[5].AsInt(0), args[6].AsInt(0),
583 -1, -1, -1, // fps_numerator, fps_denominator, num_frames: auto
584 args[7].AsBool(utf8_default), // utf8
585 env);
586 }
587
588
589 /*******************************************************************
590 *
591 * ColorBarsHD for YUV 444 formats (Rec.709)
592 *
593 *********************************************************************/
594
595 static void GetYUVRec709fromRGB(double R, double G, double B, double& dY, double& dU, double& dV)
596 {
597 // See 3.2 from https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf
598 double Kr, Kb;
599 GetKrKb(AVS_MATRIX_BT709, Kr, Kb);
600 dY = Kr * R + (1.0 - Kr - Kb) * G + Kb * B;
601 dU = (B - dY) / (2.0 * (1.0 - Kb));
602 dV = (R - dY) / (2.0 * (1.0 - Kr));
603 }
604
605 template<typename pixel_t>
606 static void draw_colorbarsHD_444(uint8_t *pY8, uint8_t *pU8, uint8_t *pV8, int pitchY, int pitchUV, int w, int h, int bits_per_pixel)
607 {
608 pixel_t *pY = reinterpret_cast<pixel_t *>(pY8);
609 pixel_t *pU = reinterpret_cast<pixel_t *>(pU8);
610 pixel_t *pV = reinterpret_cast<pixel_t *>(pV8);
611 pitchY /= sizeof(pixel_t);
612 pitchUV /= sizeof(pixel_t);
613
614 const int shift = sizeof(pixel_t) == 4 ? 0 : (bits_per_pixel - 8);
615
616 // Also for float target we make "limited" range
617 bits_conv_constants luma, chroma;
618 // RGB is source, YUV is destination
619 // For RGB source / Y destination (both luma-like):
620 const bool full_scale_s = true; // full scale reference
621 const bool full_scale_d = false; // narrow range reference
622 get_bits_conv_constants(luma, false, full_scale_s, full_scale_d, 32, 32);
623 // For UV destination (chroma behavior):
624 // Note: we only need dst_span for UV, so we use full_scale_d for both params
625 get_bits_conv_constants(chroma, true, full_scale_s, full_scale_d, 32, 32);
626
627 double float_offset = luma.dst_offset;
628 double float_scale = luma.mul_factor; // 219.0 / 255.0;
629 double float_uv_scale = chroma.mul_factor;
630
631 // Nearest 16:9 pixel exact sizes
632 // 56*X x 12*Y
633 // 728 x 480 ntsc anamorphic
634 // 728 x 576 pal anamorphic
635 // 840 x 480
636 // 1008 x 576
637 // 1288 x 720 <- default
638 // 1456 x 1080 hd anamorphic
639 // 1904 x 1080
640 /*
641 ARIB STD-B28 Version 1.0-E1
642
643 *1: 75W/100W/I+: Choice from 75% white, 100% white and +I signal. Avisynth: I+.
644 *2: can be changed to any value other than the standard values in accordance with the operation purpose by the user
645
646 |<-------------------------------------------------------- a -------------------------------------------------->|
647 | |<------------------------------------------3/4 a --------------------------------->| |
648 |<---- d ---->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<---- d ---->|
649
650 +-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-------------+ -----------
651 | | | | | | | | | | ^ ^
652 | | | | | | | | | | | |
653 Pattern 1 | 40% | 75% | 75% | 75% | 75% | 75% | 75% | 75% | 40% | | |
654 | Grey | White | Yellow | Cyan | Green | Magenta | Red | Blue | Grey | 7/12b| |
655 | *2 | | | | | | | | *2 | | |
656 | | | | | | | | | | V |
657 +-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-------------+ ------- |
658 Pattern 2 | 100% Cyan |75W/100W/I+| 75% white (chroma set signal) | 100% blue | 1/12b| | b
659 +-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-------------+ ------ |
660 Pattern 3 | 100% Yellow | Y ramp | 100% red | 1/12b| |
661 +-------------+-----------------+-----------+-----------+-------+----+----+---+---+---+-----------+-------------+ ------- |
662 | *2 | | | | | | | | | | *2 | ^ |
663 Pattern 4 | 15% | 0% | 100% | 0% |-2% | 0 |+2%| 0 |+4%| 0% | 15% | 3/12 | |
664 | Grey | Black | White | Black | | | | | | Black | Grey | b V V
665 +-------------+-----------------+-----------+-----------+-------+----+----+---+---+---+-----------+-------------+ -----------
666
667 |<---- d ---->|<---- 3/2 c ---->|<--------- 2c -------->|<5/6c->|1/3c|1/3c|1/3|1/3|1/3|<--- c --->|<---- d ---->|
668
669 a:b = 16:9
670
671
672 2021: SMPTE RP 219-1:2014
673 *1: can be changed to any value other than the standard values in accordance with the operation purpose by the user
674 *2: 75W/100W/+I/-I: Choice from 75% white, 100% white and +I or -I signal. Avisynth: 100% White.
675 *3: Choice from 0% Black or +Q (Left from Y ramp) Avisynth: 0% Black.
676 *4: can be changed to any value other than the standard values in accordance with the operation purpose by the user
677 *5: Choice from 0% Black, Sub-black valley. Avisynth: 0% Black.
678 The sub-black valley signal shall begin at the 0% black level, shall decrease in a linear ramp to the minimum permitted level at the mid-point,
679 and shall increase in a linear ramp to the 0% black level at the end of the black bar.
680 *6: Choice from 100% White, Super-white Peak. Avisynth: 100% White.
681 The super-white peak signal shall begin at the 100% white level, shall increase in a linear ramp to the maximum permitted level at the midpoint,
682 and shall decrease in a linear ramp to the 100% white level at the end of the white bar.
683
684 |<-------------------------------------------------------- a -------------------------------------------------->|
685 | |<------------------------------------------3/4 a --------------------------------->| |
686 |<---- d ---->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<--- c --->|<---- d ---->|
687
688 +-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-------------+ -----------
689 | | | | | | | | | | ^ ^
690 | | | | | | | | | | | |
691 Pattern 1 | 40% | 75% | 75% | 75% | 75% | 75% | 75% | 75% | 40% | | |
692 | Grey | White | Yellow | Cyan | Green | Magenta | Red | Blue | Grey | 7/12b| |
693 | *1 | | | | | | | | *1 | | |
694 | | | | | | | | | | V |
695 +-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-------------+ ------- |
696 Pattern 2 | 100% Cyan |75/100W/I-+| 75% white (chroma set signal) | 100% blue | 1/12b| | b
697 +-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-------------+ ------ |
698 Pattern 3 | 100% Yellow |0%Blk or +Q| Y ramp | 100% White| 100% red | 1/12b| |
699 +-------------+-----------+-----+-----------+-----------+-------+----+----+---+---+---+-----------+-------------+ ------- |
700 | *4 | 0% Black *5| 100% White *6| | | | | | | | *4 | ^ |
701 Pattern 4 | 15% |0% Blk or SubBlck|100%White/SuperWhtePeak| 0% |-2% | 0 |+2%| 0 |+4%| 0% | 15% | 3/12 | |
702 | Grey | 0% Black | 100% White | Black | | | | | | Black | Grey | b V V
703 +-------------+-----------------+-----------+-----------+-------+----+----+---+---+---+-----------+-------------+ -----------
704
705 |<---- d ---->|<---- 3/2 c ---->|<--------- 2c -------->|<5/6c->|1/3c|1/3c|1/3|1/3|1/3|<--- c --->|<---- d ---->|
706
707 a:b = 16:9
708
709 */
710
711 int y = 0;
712
713 const int c = (w * 3 + 14) / 28; // 1/7th of 3/4 of width
714 const int d = (w - c * 7 + 1) / 2; // remaining 1/8th of width
715
716 const int p4 = (3 * h + 6) / 12; // 3/12th of height
717 const int p23 = (h + 6) / 12; // 1/12th of height
718 const int p1 = h - p23 * 2 - p4; // remaining 7/12th of height
719
720 /*
721 // 75% Rec709 -- Grey40 Grey75 Yellow Cyan Green Magenta Red Blue
722 static const BYTE pattern1Y[] = { 104, 180, 168, 145, 133, 63, 51, 28 };
723 static const BYTE pattern1U[] = { 128, 128, 44, 147, 63, 193, 109, 212 };
724 static const BYTE pattern1V[] = { 128, 128, 136, 44, 52, 204, 212, 120 };
725 // 3.7.6: replaced the 8 bit table (inaccurate base for higher bitdepths) with accurate
726 // RGB values with double precision RGB to YUV conversion.
727 */
728
729 // Define as double-precision gamma-encoded signal levels (E'R, E'G, E'B).
730 // 0.75 = 75% of encoded signal swing, not a linear-light value.
731 // Convert to Rec.709 YUV for target bitdepth and limited/full range later.
732 static const double pattern1R[] = { 0.4, 0.75, 0.75, 0.00, 0.00, 0.75, 0.75, 0.00 };
733 static const double pattern1G[] = { 0.4, 0.75, 0.75, 0.75, 0.75, 0.00, 0.00, 0.00 };
734 static const double pattern1B[] = { 0.4, 0.75, 0.00, 0.75, 0.00, 0.75, 0.00, 0.75 };
735
736 // Helper to process and write a pixel based on RGB input
737 auto ProcessPixel = [&](double r, double g, double b, int targetX) {
738 XP_LAMBDA_CAPTURE_FIX(float_scale);
739 XP_LAMBDA_CAPTURE_FIX(float_offset);
740 XP_LAMBDA_CAPTURE_FIX(float_uv_scale);
741 XP_LAMBDA_CAPTURE_FIX(shift);
742 double dY, dU, dV;
743 GetYUVRec709fromRGB(r, g, b, dY, dU, dV);
744
745 if constexpr (std::is_same<pixel_t, float>::value) {
746 pY[targetX] = (pixel_t)(dY * float_scale + float_offset);
747 pU[targetX] = (pixel_t)(dU * float_uv_scale);
748 pV[targetX] = (pixel_t)(dV * float_uv_scale);
749 }
750 else {
751 // High-precision calculation for 10/12/16-bit
752 pY[targetX] = (pixel_t)(((dY * 219.0 + 16.0) * (1 << shift)) + 0.5);
753 pU[targetX] = (pixel_t)(((dU * 224.0 + 128.0) * (1 << shift)) + 0.5);
754 pV[targetX] = (pixel_t)(((dV * 224.0 + 128.0) * (1 << shift)) + 0.5);
755 }
756 };
757
758 // ColorbarsHD produces "limited", and since Avisynth handles "limited" 32 bit float, so we adjust it as well.
759
760 // Pattern 1
761
762 for (; y < p1; ++y) {
763 int x = 0;
764 // 40% grey
765 for (; x < d; ++x) {
766 ProcessPixel(pattern1R[0], pattern1G[0], pattern1B[0], x);
767 }
768 // 75% White, Yellow, Cyan, Green, Magenta, Red, Blue
769 for (int i = 1; i < 8; i++) {
770 for (int j = 0; j < c; ++j, ++x) {
771 ProcessPixel(pattern1R[i], pattern1G[i], pattern1B[i], x);
772 }
773 }
774 for (; x < w; ++x) {
775 ProcessPixel(pattern1R[0], pattern1G[0], pattern1B[0], x);
776 }
777 pY += pitchY; pU += pitchUV; pV += pitchUV;
778 }
779
780 /*
781 SMPTE RP 219 / EG 1: +I Signal Reference (Rec. 709 / HD)
782 * The +I (In-phase) signal is defined by its analog IRE levels:
783 R = 41.2545 IRE, G = 16.6946 IRE, B = 0 IRE.
784 * Normalized Linear RGB (IRE/100): R: 0.412545, G: 0.166946, B: 0.000000
785 -----------------------------------------------------------
786 Bit-Depth | Y (Luma) | U (Cb) | V (Cr) |
787 -----------------------------------------------------------
788 8-bit | 61 (3D) | 103 (67) | 157 (9D) |
789 10-bit | 245 (0F5) | 412 (19C) | 629 (275) |
790 16-bit | 15707 (3D5B) | 26368 (6700) | 40249 (9D39) |
791 -----------------------------------------------------------
792 See also https://www.arib.or.jp/english/html/overview/doc/6-STD-B28v1_0-E1.pdf
793 and
794 Wikipedia (https://en.wikipedia.org/wiki/SMPTE_color_bars) (2026)
795
796 Pre 3.7.6 old table, containing precalculated 8 bit values
797 // 100% Rec709 Cyan Blue Yellow Red +I Grey75 White
798 static const BYTE pattern23Y[] = { 188, 32, 219, 63, 61, 180, 235 };
799 static const BYTE pattern23U[] = { 154, 240, 16, 102, 103, 128, 128 };
800 static const BYTE pattern23V[] = { 16, 118, 138, 240, 157, 128, 128 };
801
802 */
803 // Pattern 2
804
805 // 0: Cyan100, 1: Blue100, 2: Yellow100, 3: Red100, 4: +I, 5: Grey75, 6: White100
806 static const double pattern23R[] = { 0.0, 0.0, 1.0, 1.0, PLUS_I_R_YUV, 0.75, 1.0 };
807 static const double pattern23G[] = { 1.0, 0.0, 1.0, 0.0, PLUS_I_G_YUV, 0.75, 1.0 };
808 static const double pattern23B[] = { 1.0, 1.0, 0.0, 0.0, PLUS_I_B_YUV, 0.75, 1.0 };
809
810 // For pattern 2 and 3
811 auto ProcessBar = [&](int index, int endX, int& currentX) {
812 XP_LAMBDA_CAPTURE_FIX(float_scale);
813 XP_LAMBDA_CAPTURE_FIX(float_offset);
814 XP_LAMBDA_CAPTURE_FIX(float_uv_scale);
815 XP_LAMBDA_CAPTURE_FIX(shift);
816 double dY, dU, dV;
817 GetYUVRec709fromRGB(pattern23R[index], pattern23G[index], pattern23B[index], dY, dU, dV);
818
819 for (; currentX < endX && currentX < w; ++currentX) {
820 if constexpr(std::is_same<pixel_t, float>::value) {
821 pY[currentX] = (pixel_t)(dY * float_scale + float_offset);
822 pU[currentX] = (pixel_t)(dU * float_uv_scale);
823 pV[currentX] = (pixel_t)(dV * float_uv_scale);
824 }
825 else {
826 pY[currentX] = (pixel_t)(((dY * 219.0 + 16.0) * (1 << shift)) + 0.5);
827 pU[currentX] = (pixel_t)(((dU * 224.0 + 128.0) * (1 << shift)) + 0.5);
828 pV[currentX] = (pixel_t)(((dV * 224.0 + 128.0) * (1 << shift)) + 0.5);
829 }
830 }
831 };
832
833 for (; y < p1 + p23; ++y) {
834 int x = 0;
835
836 // 1. Left Padding (100% Cyan) - Index 0
837 ProcessBar(0, d, x);
838 // 2. The +I Bar - Index 4 (+I or Grey75 or White)
839 ProcessBar(4, c + d, x);
840 // 3. 75% White (Grey75) - Index 5
841 ProcessBar(5, c * 7 + d, x);
842 // 4. Remaining width (100% Blue) - Index 1
843 ProcessBar(1, w, x);
844
845 pY += pitchY; pU += pitchUV; pV += pitchUV;
846 }
847
848 // Pattern 3
849
850 for (; y < p1 + p23 * 2; ++y) {
851 int x = 0;
852
853 ProcessBar(2, d, x); // 100% Yellow
854
855 // Y ramp section: 0% Black, Ramp, 100% White
856 // FIXED in 3.7.6: Y-ramp to conform SMPTE RP 219-1:2014, put 0% Black before and 100% White after
857 // Divide the c * 7 area:
858 // - 1x - 0% Black (or optional +Q)
859 // - 5x - Ramp
860 // - 1x - 100% White
861
862 int rampStartX = x + c; // End of Black (+Q) step
863 int rampEndX = x + (c * 6); // Start of White step
864 int sectionEndX = x + (c * 7); // End of this whole middle section
865
866 // A. 0% black (or optional +Q) step
867 for (; x < rampStartX; ++x) {
868 ProcessPixel(0.0, 0.0, 0.0, x);
869 // ProcessPixel(PLUS_Q_R_YUV, PLUS_Q_G_YUV, PLUS_Q_B_YUV, x); // For +Q instead of 0% Black
870 }
871
872 // B. Y-ramp (0.0 to 1.0) - 5 units wide
873 int rampWidth = rampEndX - rampStartX;
874 for (int j = 0; x < rampEndX; ++x, ++j) {
875 double v = (double)j / (rampWidth - 1);
876 ProcessPixel(v, v, v, x); // For a grayscale ramp, R=G=B
877 }
878
879 // C. 100% White
880 for (; x < sectionEndX; ++x) {
881 ProcessPixel(1.0, 1.0, 1.0, x);
882 }
883 // end of Ramp section
884
885 ProcessBar(3, w, x); // 100% Red
886 pY += pitchY; pU += pitchUV; pV += pitchUV;
887 }
888
889 // Pattern 4
890
891 // Normalized RGB for Pattern 4: 15% Grey, Black, White, Black, -2%, Black, +2%, Black, +4%, Black
892 /* old table, precalculated 8 bit values
893 // Grey15 Black White Black -2% Black +2% Black +4% Black
894 static const BYTE pattern4Y[] = { 49, 16, 235, 16, 12, 16, 20, 16, 25, 16 };
895 U and V are 128
896 */
897
898 static const double pattern4RGB[] = { 0.15, 0.0, 1.0, 0.0, -0.02, 0.0, 0.02, 0.0, 0.04, 0.0 };
899 static const BYTE pattern4W[] = { 0, 9, 21, 26, 28, 30, 32, 34, 36, 42 }; // in 6th's
900 for (; y < h; ++y) {
901 int x = 0;
902
903 // 1. Left Padding (15% Grey)
904 for (; x < d; ++x) {
905 ProcessPixel(pattern4RGB[0], pattern4RGB[0], pattern4RGB[0], x);
906 }
907
908 // 2. PLUGE and Bars (Indices 1 through 9)
909 for (int i = 1; i <= 9; i++) {
910 int endX = d + (pattern4W[i] * c + 3) / 6;
911 for (; x < endX && x < w; ++x) {
912 ProcessPixel(pattern4RGB[i], pattern4RGB[i], pattern4RGB[i], x);
913 }
914 }
915
916 // 3. Right Padding (15% Grey)
917 for (; x < w; ++x) {
918 ProcessPixel(pattern4RGB[0], pattern4RGB[0], pattern4RGB[0], x);
919 }
920
921 pY += pitchY; pU += pitchUV; pV += pitchUV;
922 }
923 } // ColorBarsHD
924
925 /*******************************************************************
926 *
927 * ColorBars for YUV formats (BT.601) and and RGB
928 *
929 *********************************************************************/
930 // See also: http://avisynth.nl/index.php/ColorBars_theory
931 // and https://avisynthplus.readthedocs.io/en/latest/avisynthdoc/corefilters/colorbars.html
932
933 /*
934 * Integer 8 bit tables not used anymore, replaced by double-precision RGB tables with
935 * accurate RGB and RGB->YUV conversion for better precision and correctness, especially
936 * for higher bitdepths.
937
938 // Studio RGB constants for ColorBars
939 static const uint32_t bottom_quarter[] =
940 // RGB[16..235] -I white +Q Black -4% Black Black +4% Black Black
941 { 0x10466a, 0xebebeb, 0x481076, 0x101010, 0x070707, 0x101010, 0x191919, 0x101010 }; // Qlum=Ilum=13.4%
942 static const int two_thirds_to_three_quarters[] =
943 // RGB[16..235] Blue Black Magenta Black Cyan Black LtGrey
944 { 0x1010b4, 0x101010, 0xb410b4, 0x101010, 0x10b4b4, 0x101010, 0xb4b4b4 };
945 static const int top_two_thirds[] =
946 // RGB[16..235] LtGrey Yellow Cyan Green Magenta Red Blue
947 { 0xb4b4b4, 0xb4b410, 0x10b4b4, 0x10b410, 0xb410b4, 0xb41010, 0x1010b4 };
948 */
949
950 // Ground truth gamma-encoded RGB for ColorBars (Rec. ITU-R BT.801-1).
951 // Normalised limited-range signal levels [0.0..1.0]: 0.0 = code 16, 1.0 = code 235.
952 // 0.75 = 75% encoded signal swing (E'R/G'B, not linear light).
953 // 8-bit studio value: (int)(R * 219.0 + 16.0 + 0.5)
954
955 // Top 2/3: LtGrey, Yellow, Cyan, Green, Magenta, Red, Blue
956 static const double top_two_thirdsR[] = { 0.75, 0.75, 0.0, 0.0, 0.75, 0.75, 0.0 };
957 static const double top_two_thirdsG[] = { 0.75, 0.75, 0.75, 0.75, 0.0, 0.0, 0.0 };
958 static const double top_two_thirdsB[] = { 0.75, 0.0, 0.75, 0.0, 0.75, 0.0, 0.75 };
959
960 // 2/3 to 3/4: Blue, Black, Magenta, Black, Cyan, Black, LtGrey
961 static const double two_thirds_to_three_quartersR[] = { 0.0, 0.0, 0.75, 0.0, 0.0, 0.0, 0.75 };
962 static const double two_thirds_to_three_quartersG[] = { 0.0, 0.0, 0.0, 0.0, 0.75, 0.0, 0.75 };
963 static const double two_thirds_to_three_quartersB[] = { 0.75, 0.0, 0.75, 0.0, 0.75, 0.0, 0.75 };
964
965 /*
966 BOTTOM QUARTER BAR DEFINITIONS (-I, White, +Q, Black, -4%, Black, +4%, Black)
967
968 ============================================================================
969 DERIVATION OF -I AND +Q VALUES
970 ============================================================================
971
972 The -I and +Q signals are defined in the YIQ colorspace as pure chroma-axis
973 signals with zero luma and 20 IRE saturation (0.2162 normalized):
974
975 -I: I = -0.2162, Q = 0
976 +Q: I = 0, Q = +0.2162
977
978 Converting via the BT.601 UV rotation (Poynton eq. 33, with UV swap):
979
980 -I raw RGB (Y=0): R = -0.2067, G = +0.0588, B = +0.2394
981 +Q raw RGB (Y=0): R = +0.1343, G = -0.1400, B = +0.3685
982
983 Both signals contain out-of-range (negative) RGB components. Three
984 interpretations exist in the literature:
985
986 ---------------------------------------------------------------------------
987 OPTION 1 — Zero-luma, lift to studio black (legacy YUV implementation)
988 ---------------------------------------------------------------------------
989 Y = 16 (studio black). The most negative RGB component is left negative
990 and will encode to a super-black code (0-15 range), or must be clipped
991 when rendering as RGB, distorting the color.
992
993 This is a HACK that had to be applied differently for RGB vs YUV:
994
995 For YUV output (bottom_quarterR/G/B_for_YUV):
996 No lift applied - use raw zero-luma RGB values
997 Result: Y = 16, perfectly preserved chroma-axis definition
998 Consequence: RGB contains super-black components (codes < 16)
999
1000 For RGB output (legacy AviSynth approach, NOT current implementation):
1001 Each component individually clamped/adjusted to avoid codes < 16
1002 Result: RGB codes all ≥ 16, but YUV back-conversion doesn't match
1003 Consequence: Loss of theoretical purity, inconsistent RGB↔YUV round-trip
1004
1005 This was the legacy AviSynth ColorBars implementation:
1006 RGB path used bitmap-derived values: -I = RGB(16, 70, 106)
1007 YUV path used zero-luma calculation: -I = Y16 Cb158 Cr95
1008 These two specifications are fundamentally incompatible.
1009
1010 ---------------------------------------------------------------------------
1011 OPTION 2 — Luma-corrected to studio black (CURRENT RGB IMPLEMENTATION)
1012 ---------------------------------------------------------------------------
1013 Luma is raised until the most negative component reaches code 16
1014 (studio black). The lift calculation:
1015
1016 For -I: Y_lift = 0.2067 - 16/219 = 0.13364
1017 For +Q: Y_lift = 0.1400 - 16/219 = 0.06694
1018
1019 After lifting (bottom_quarterR/G/B arrays, RGB-native):
1020 -I: R = 16 (studio black), G = 90, B = 130, Y ≈ 77
1021 +Q: R = 92, G = 16 (studio black), B = 143, Y ≈ 63
1022
1023 This gives a consistent, broadcast-safe signal for RGB output with all
1024 components within valid studio range (16-235), but produces different
1025 YUV values than the legacy zero-luma specification (Y=77/63 vs Y=16).
1026
1027 We maintain TWO separate ground truth tables to preserve legacy compatibility:
1028
1029 1. bottom_quarterR/G/B (Option 2):
1030 - Used for RGB output formats
1031 - all I and Q codes >= 16
1032 - Colorimetrically consistent RGB↔YUV conversion
1033
1034 2. bottom_quarterR/G/B_for_YUV (Option 1 YUV side):
1035 - Used for YUV output formats
1036 - Produces exact legacy values: -I Y=16, +Q Y=16
1037 - Contains out-of-range RGB components (will clip if rendered as RGB)
1038
1039 This dual-table approach acknowledges the historical reality: the original
1040 AviSynth ColorBars had two independent specifications that don't convert
1041 to each other via standard matrix math. The -I and +Q signals were analog
1042 broadcast test signals (voltage levels), not digital RGB/YUV values, and
1043 their digital representation requires compromises.
1044
1045 So we use different linear RGB tables for -I and +Q bars, depending on whether
1046 the target is RGB or YUV, to best match legacy values in each domain.
1047 Note that moving I and Q values to provide legal RGB and YUV values is a "hack".
1048 These values match the legacy Avisynth.
1049 */
1050
1051 // ===== RGB-NATIVE GROUND TRUTH =====
1052 // For RGB output formats only.
1053 // Luma-corrected: most negative component lifted to studio black (code 16).
1054 // -I: R lifted to code 16 -> RGB(16, 90, 130) at 8-bit
1055 // +Q: G lifted to code 16 -> RGB(92, 16, 143) at 8-bit
1056 // Convention: 0.0 = code 16 (studio black), 1.0 = code 235 (studio white)
1057 static const double bottom_quarterR[] = { MINUS_I_R, 1.0, PLUS_Q_R, 0.0, -0.04, 0.0, 0.04, 0.0 };
1058 static const double bottom_quarterG[] = { MINUS_I_G, 1.0, PLUS_Q_G, 0.0, -0.04, 0.0, 0.04, 0.0 };
1059 static const double bottom_quarterB[] = { MINUS_I_B, 1.0, PLUS_Q_B, 0.0, -0.04, 0.0, 0.04, 0.0 };
1060
1061 // ===== YUV-TARGETED RGB GROUND TRUTH =====
1062 // For YUV output formats only.
1063 // When converted via GetYUVBT601fromRGB, produces legacy YUV values:
1064 // -I: Y=16, Cb=158, Cr=95 (zero-luma, pure chroma definition)
1065 // +Q: Y=16, Cb=174, Cr=149 (zero-luma, pure chroma definition)
1066 // Note: Contains out-of-range values (R < 0 for -I, G < 0 for +Q)
1067 // which will be clamped when rendering RGB formats.
1068 // Convention: 0.0 = code 16 (studio black), 1.0 = code 235 (studio white)
1069 static const double bottom_quarterR_for_YUV[] = { MINUS_I_R_YUV, 1.0, PLUS_Q_R_YUV, 0.0, -0.04, 0.0, 0.04, 0.0 };
1070 static const double bottom_quarterG_for_YUV[] = { MINUS_I_G_YUV, 1.0, PLUS_Q_G_YUV, 0.0, -0.04, 0.0, 0.04, 0.0 };
1071 static const double bottom_quarterB_for_YUV[] = { MINUS_I_B_YUV, 1.0, PLUS_Q_B_YUV, 0.0, -0.04, 0.0, 0.04, 0.0 };
1072
1073 /*******************************************************************
1074 * ColorBars for YUV
1075 *********************************************************************/
1076
1077 static void GetYUVBT601fromRGB(double R, double G, double B, double& dY, double& dU, double& dV)
1078 {
1079 // See https://www.itu.int/rec/R-REC-BT.601/en
1080 double Kr, Kb;
1081 GetKrKb(AVS_MATRIX_ST170_M, Kr, Kb); // BT601: Kr=0.299, Kb=0.114
1082 dY = Kr * R + (1.0 - Kr - Kb) * G + Kb * B;
1083 dU = (B - dY) / (2.0 * (1.0 - Kb));
1084 dV = (R - dY) / (2.0 * (1.0 - Kr));
1085 }
1086
1087 // BT.601 YUV conversion constants for ColorBars (Rec. ITU-R BT.801-1)
1088 // Ground truth linear RGB -> BT.601 YUV, integer and float limited range output.
1089 // Replaces the old hardcoded 8-bit tables.
1090
1091 // Bar boundaries are computed in chroma coordinates so that color transitions
1092 // always fall on chroma-aligned luma positions (a multiple of the horizontal
1093 // subsampling factor). This satisfies the requirement from Rec. ITU-R BT.801-1
1094 // that transitions occur on chroma-aligned boundaries.
1095 // Note: due to integer rounding, boundary positions may differ by +/-1 luma pixel
1096 // compared to a 4:4:4 or RGB rendering of the same width, which is unavoidable
1097 // when 7 bars do not divide evenly into the frame width.
1098 template<typename pixel_t, bool is420, bool is422, bool is411>
1099 static void draw_colorbars_yuv(uint8_t* pY8, uint8_t* pU8, uint8_t* pV8, int pitchY, int pitchUV, int w, int h, int bits_per_pixel)
1100 {
1101 pixel_t* pY = reinterpret_cast<pixel_t*>(pY8);
1102 pixel_t* pU = reinterpret_cast<pixel_t*>(pU8);
1103 pixel_t* pV = reinterpret_cast<pixel_t*>(pV8);
1104 pitchY /= sizeof(pixel_t);
1105 pitchUV /= sizeof(pixel_t);
1106
1107 const int shift = sizeof(pixel_t) == 4 ? 0 : (bits_per_pixel - 8);
1108
1109 // Pre-compute conversion constants for float limited range,
1110 // using the same centralized function as ColorbarsHD.
1111
1112 // Also for float target we make "limited" range
1113 bits_conv_constants luma, chroma;
1114 // RGB is source, YUV is destination
1115 // For RGB source / Y destination (both luma-like):
1116 const bool full_scale_s = true; // full scale reference
1117 const bool full_scale_d = false; // narrow range reference
1118 get_bits_conv_constants(luma, false, full_scale_s, full_scale_d, 32, 32);
1119 // For UV destination (chroma behavior):
1120 // Note: we only need dst_span for UV, so we use full_scale_d for both params
1121 get_bits_conv_constants(chroma, true, full_scale_s, full_scale_d, 32, 32);
1122
1123 double float_offset = luma.dst_offset;
1124 double float_scale = luma.mul_factor; // 219.0 / 255.0;
1125 double float_uv_scale = chroma.mul_factor;
1126
1127 // Convert one RGB triplet to a YUV pixel triplet at target bit depth.
1128 struct YUV3 { pixel_t y, u, v; };
1129
1130 // Helper to process and write a pixel based on RGB input
1131 // Convention: encoded signal levels; 0.0 = code 16 (studio black), 1.0 = code 235 (studio white)
1132
1133 auto make_yuv = [&](double r, double g, double b) -> YUV3 {
1134 XP_LAMBDA_CAPTURE_FIX(float_scale);
1135 XP_LAMBDA_CAPTURE_FIX(float_offset);
1136 XP_LAMBDA_CAPTURE_FIX(float_uv_scale);
1137 XP_LAMBDA_CAPTURE_FIX(shift);
1138 double dY, dU, dV;
1139 GetYUVBT601fromRGB(r, g, b, dY, dU, dV);
1140
1141 if constexpr (std::is_same<pixel_t, float>::value) {
1142 return {
1143 (pixel_t)(dY * float_scale + float_offset),
1144 (pixel_t)(dU * float_uv_scale),
1145 (pixel_t)(dV * float_uv_scale)
1146 };
1147 }
1148 else {
1149 // High-precision calculation for 10/12/16-bit
1150 return {
1151 (pixel_t)(((dY * 219.0 + 16.0) * (1 << shift)) + 0.5),
1152 (pixel_t)(((dU * 224.0 + 128.0) * (1 << shift)) + 0.5),
1153 (pixel_t)(((dV * 224.0 + 128.0) * (1 << shift)) + 0.5)
1154 };
1155 }
1156 };
1157
1158 // Pre-compute all bar entries from ground truth RGB tables.
1159 YUV3 bq[8], ttq[7], ttt[7];
1160 for (int i = 0; i < 8; ++i)
1161 bq[i] = make_yuv(bottom_quarterR_for_YUV[i], bottom_quarterG_for_YUV[i], bottom_quarterB_for_YUV[i]);
1162 for (int i = 0; i < 7; ++i)
1163 ttq[i] = make_yuv(two_thirds_to_three_quartersR[i], two_thirds_to_three_quartersG[i], two_thirds_to_three_quartersB[i]);
1164 for (int i = 0; i < 7; ++i)
1165 ttt[i] = make_yuv(top_two_thirdsR[i], top_two_thirdsG[i], top_two_thirdsB[i]);
1166
1167 // Write luma for one chroma-sample position x.
1168 // For subsampled formats, each chroma x covers multiple luma pixels.
1169 // For 444, chromaX == lumaX directly.
1170 auto write_luma = [&](int x, pixel_t yval) {
1171 XP_LAMBDA_CAPTURE_FIX(pitchY);
1172 if constexpr (is420)
1173 pY[x * 2 + 0] = pY[x * 2 + 1] = pY[x * 2 + pitchY] = pY[x * 2 + 1 + pitchY] = yval;
1174 else if constexpr (is422)
1175 pY[x * 2 + 0] = pY[x * 2 + 1] = yval;
1176 else if constexpr (is411)
1177 pY[x * 4 + 0] = pY[x * 4 + 1] = pY[x * 4 + 2] = pY[x * 4 + 3] = yval;
1178 else // 444
1179 pY[x] = yval;
1180 };
1181
1182 auto write_yuv = [&](int x, const YUV3& c) {
1183 write_luma(x, c.y);
1184 pU[x] = c.u;
1185 pV[x] = c.v;
1186 };
1187
1188 // For subsampled formats the chroma plane is narrower/shorter.
1189 // We iterate in chroma coordinates; write_luma expands to luma coordinates.
1190 int wUV = w;
1191 int hUV = h;
1192 if constexpr (is420 || is422) wUV >>= 1;
1193 if constexpr (is411) wUV >>= 2;
1194 if constexpr (is420) hUV >>= 1;
1195
1196 int y = 0;
1197
1198 // Top 2/3
1199 for (; y * 3 < hUV * 2; ++y) {
1200 int x = 0;
1201 for (int i = 0; i < 7; ++i)
1202 for (; x < (wUV * (i + 1) + 3) / 7; ++x)
1203 write_yuv(x, ttt[i]);
1204 if constexpr (is420)
1205 pY += pitchY * 2;
1206 else
1207 pY += pitchY;
1208 pU += pitchUV; pV += pitchUV;
1209 }
1210
1211 // Middle band (2/3 to 3/4)
1212 for (; y * 4 < hUV * 3; ++y) {
1213 int x = 0;
1214 for (int i = 0; i < 7; ++i)
1215 for (; x < (wUV * (i + 1) + 3) / 7; ++x)
1216 write_yuv(x, ttq[i]);
1217 if constexpr (is420)
1218 pY += pitchY * 2;
1219 else
1220 pY += pitchY;
1221 pU += pitchUV; pV += pitchUV;
1222 }
1223
1224 // Bottom quarter
1225 for (; y < hUV; ++y) {
1226 int x = 0;
1227 for (int i = 0; i < 4; ++i)
1228 for (; x < (wUV * (i + 1) * 5 + 14) / 28; ++x)
1229 write_yuv(x, bq[i]);
1230 for (int j = 4; j < 7; ++j)
1231 for (; x < (wUV * (j + 12) + 10) / 21; ++x)
1232 write_yuv(x, bq[j]);
1233 for (; x < wUV; ++x)
1234 write_yuv(x, bq[7]);
1235 if constexpr (is420)
1236 pY += pitchY * 2;
1237 else
1238 pY += pitchY;
1239 pU += pitchUV; pV += pitchUV;
1240 }
1241 }
1242
1243 /*******************************************************************
1244 * ColorBars for RGB (packed 32/64, 24/48, planar RGB)
1245 *********************************************************************/
1246
1247 // Convert normalised linear RGB [0.0..1.0] to integer studio/limited RGB at any bit depth.
1248 // Limited range: black = 16 << (bpp-8), white = 235 << (bpp-8)
1249 // Values outside [0.0..1.0] (e.g. PLUGE -4%, +4%) are handled naturally.
1250 static int studio_rgb_to_integer(double value, int bits_per_pixel)
1251 {
1252 const int offset = 16 << (bits_per_pixel - 8);
1253 const int range = 219 << (bits_per_pixel - 8);
1254 return (int)(value * range + offset + 0.5);
1255 }
1256
1257 template<typename pixel_t>
1258 static void draw_colorbars_rgb3264(uint8_t* p8, int pitch, int w, int h)
1259 {
1260 typedef typename std::conditional<sizeof(pixel_t) == 2, uint64_t, uint32_t>::type internal_pixel_t;
1261 internal_pixel_t* p = reinterpret_cast<internal_pixel_t*>(p8);
1262 pitch /= sizeof(pixel_t);
1263
1264 // Pre-compute packed pixel values from ground truth double tables at target bit depth.
1265 // Pack order in uint32: 0x00RRGGBB, in uint64: RR(16)GG(16)BB(16) (alpha/padding zero)
1266 // RGB32/64 pixel layout (bottom byte = B, then G, then R, then pad/alpha)
1267 auto make_pixel = [&](double r, double g, double b) -> internal_pixel_t {
1268 if constexpr (sizeof(pixel_t) == 1) {
1269 // RGB32: 8-bit per channel, packed as 0x00RRGGBB
1270 uint32_t ri = (uint32_t)studio_rgb_to_integer(r, 8);
1271 uint32_t gi = (uint32_t)studio_rgb_to_integer(g, 8);
1272 uint32_t bi = (uint32_t)studio_rgb_to_integer(b, 8);
1273 return (internal_pixel_t)((ri << 16) | (gi << 8) | bi);
1274 }
1275 else {
1276 // RGB64: 16-bit per channel
1277 uint64_t ri = (uint64_t)studio_rgb_to_integer(r, 16);
1278 uint64_t gi = (uint64_t)studio_rgb_to_integer(g, 16);
1279 uint64_t bi = (uint64_t)studio_rgb_to_integer(b, 16);
1280 return (internal_pixel_t)((ri << 32) | (gi << 16) | bi);
1281 }
1282 };
1283
1284 // Pre-compute all entries (bottom->top scan order matches original)
1285 internal_pixel_t bq[8], ttq[7], ttt[7];
1286 for (int i = 0; i < 8; ++i)
1287 bq[i] = make_pixel(bottom_quarterR[i], bottom_quarterG[i], bottom_quarterB[i]);
1288 for (int i = 0; i < 7; ++i)
1289 ttq[i] = make_pixel(two_thirds_to_three_quartersR[i], two_thirds_to_three_quartersG[i], two_thirds_to_three_quartersB[i]);
1290 for (int i = 0; i < 7; ++i)
1291 ttt[i] = make_pixel(top_two_thirdsR[i], top_two_thirdsG[i], top_two_thirdsB[i]);
1292
1293 // note we go bottom->top
1294 int y = 0;
1295 for (; y < h / 4; ++y) {
1296 int x = 0;
1297 for (int i = 0; i < 4; ++i)
1298 for (; x < (w * (i + 1) * 5 + 14) / 28; ++x)
1299 p[x] = bq[i];
1300 for (int j = 4; j < 7; ++j)
1301 for (; x < (w * (j + 12) + 10) / 21; ++x)
1302 p[x] = bq[j];
1303 for (; x < w; ++x)
1304 p[x] = bq[7];
1305 p += pitch;
1306 }
1307 for (; y < h / 3; ++y) {
1308 int x = 0;
1309 for (int i = 0; i < 7; ++i)
1310 for (; x < (w * (i + 1) + 3) / 7; ++x)
1311 p[x] = ttq[i];
1312 p += pitch;
1313 }
1314 for (; y < h; ++y) {
1315 int x = 0;
1316 for (int i = 0; i < 7; ++i)
1317 for (; x < (w * (i + 1) + 3) / 7; ++x)
1318 p[x] = ttt[i];
1319 p += pitch;
1320 }
1321 }
1322
1323
1324 template<typename pixel_t>
1325 static void draw_colorbars_rgb2448(uint8_t* p8, int pitch, int w, int h)
1326 {
1327 pixel_t* p = reinterpret_cast<pixel_t*>(p8);
1328 pitch /= sizeof(pixel_t);
1329
1330 // Pre-computed triplets from ground truth double tables
1331 struct RGB3 { pixel_t r, g, b; };
1332
1333 auto make_rgb3 = [&](double r, double g, double b) -> RGB3 {
1334 return {
1335 (pixel_t)studio_rgb_to_integer(r, sizeof(pixel_t) == 1 ? 8 : 16),
1336 (pixel_t)studio_rgb_to_integer(g, sizeof(pixel_t) == 1 ? 8 : 16),
1337 (pixel_t)studio_rgb_to_integer(b, sizeof(pixel_t) == 1 ? 8 : 16)
1338 };
1339 };
1340
1341 // Pre-compute all entries (bottom->top scan order matches original)
1342 RGB3 bq[8], ttq[7], ttt[7];
1343 for (int i = 0; i < 8; ++i)
1344 bq[i] = make_rgb3(bottom_quarterR[i], bottom_quarterG[i], bottom_quarterB[i]);
1345 for (int i = 0; i < 7; ++i)
1346 ttq[i] = make_rgb3(two_thirds_to_three_quartersR[i], two_thirds_to_three_quartersG[i], two_thirds_to_three_quartersB[i]);
1347 for (int i = 0; i < 7; ++i)
1348 ttt[i] = make_rgb3(top_two_thirdsR[i], top_two_thirdsG[i], top_two_thirdsB[i]);
1349
1350 auto write_pixel = [&](int x, const RGB3& c) {
1351 p[x * 3 + 0] = c.b; // RGB24/48 memory layout: B, G, R
1352 p[x * 3 + 1] = c.g;
1353 p[x * 3 + 2] = c.r;
1354 };
1355
1356 // note we go bottom->top
1357 int y = 0;
1358 for (; y < h / 4; ++y) {
1359 int x = 0;
1360 for (int i = 0; i < 4; ++i)
1361 for (; x < (w * (i + 1) * 5 + 14) / 28; ++x)
1362 write_pixel(x, bq[i]);
1363 for (int j = 4; j < 7; ++j)
1364 for (; x < (w * (j + 12) + 10) / 21; ++x)
1365 write_pixel(x, bq[j]);
1366 for (; x < w; ++x)
1367 write_pixel(x, bq[7]);
1368 p += pitch;
1369 }
1370 for (; y < h / 3; ++y) {
1371 int x = 0;
1372 for (int i = 0; i < 7; ++i)
1373 for (; x < (w * (i + 1) + 3) / 7; ++x)
1374 write_pixel(x, ttq[i]);
1375 p += pitch;
1376 }
1377 for (; y < h; ++y) {
1378 int x = 0;
1379 for (int i = 0; i < 7; ++i)
1380 for (; x < (w * (i + 1) + 3) / 7; ++x)
1381 write_pixel(x, ttt[i]);
1382 p += pitch;
1383 }
1384 }
1385
1386 template<typename pixel_t>
1387 static void draw_colorbars_rgbp(uint8_t* pR8, uint8_t* pG8, uint8_t* pB8, int pitch, int w, int h, int bits_per_pixel)
1388 {
1389 pixel_t* pR = reinterpret_cast<pixel_t*>(pR8);
1390 pixel_t* pG = reinterpret_cast<pixel_t*>(pG8);
1391 pixel_t* pB = reinterpret_cast<pixel_t*>(pB8);
1392 pitch /= sizeof(pixel_t);
1393
1394 struct RGB3 { pixel_t r, g, b; };
1395
1396 bits_conv_constants rgb_luma_f;
1397 // source: full range [0..1] RGB, destination: limited range float
1398 // rgb_luma_f.mul_factor = 219.0/255.0
1399 // rgb_luma_f.dst_offset = 16.0/255.0
1400 get_bits_conv_constants(rgb_luma_f, false /*use_chroma*/, true /*fulls*/, false/*fulld*/, 32, 32);
1401
1402 auto make_rgb3 = [&](double r, double g, double b) -> RGB3 {
1403
1404 if constexpr(std::is_same<pixel_t, float>::value) {
1405 return {
1406 (float)(r * rgb_luma_f.mul_factor + rgb_luma_f.dst_offset),
1407 (float)(g * rgb_luma_f.mul_factor + rgb_luma_f.dst_offset),
1408 (float)(b * rgb_luma_f.mul_factor + rgb_luma_f.dst_offset)
1409 };
1410 }
1411 else {
1412 return {
1413 (pixel_t)studio_rgb_to_integer(r, bits_per_pixel),
1414 (pixel_t)studio_rgb_to_integer(g, bits_per_pixel),
1415 (pixel_t)studio_rgb_to_integer(b, bits_per_pixel)
1416 };
1417 }
1418 };
1419
1420 RGB3 bq[8], ttq[7], ttt[7];
1421 for (int i = 0; i < 8; ++i)
1422 bq[i] = make_rgb3(bottom_quarterR[i], bottom_quarterG[i], bottom_quarterB[i]);
1423 for (int i = 0; i < 7; ++i)
1424 ttq[i] = make_rgb3(two_thirds_to_three_quartersR[i], two_thirds_to_three_quartersG[i], two_thirds_to_three_quartersB[i]);
1425 for (int i = 0; i < 7; ++i)
1426 ttt[i] = make_rgb3(top_two_thirdsR[i], top_two_thirdsG[i], top_two_thirdsB[i]);
1427
1428 auto write_pixel = [&](int x, const RGB3& c) {
1429 pR[x] = c.r;
1430 pG[x] = c.g;
1431 pB[x] = c.b;
1432 };
1433
1434 // Planar RGB is top-to-bottom natively, no bottom-up workaround needed.
1435 // layout top->bottom: top_two_thirds, two_thirds_to_three_quarters, bottom_quarter
1436 int y = 0;
1437
1438 // Top 2/3
1439 for (; y * 3 < h * 2; ++y) {
1440 int x = 0;
1441 for (int i = 0; i < 7; ++i)
1442 for (; x < (w * (i + 1) + 3) / 7; ++x)
1443 write_pixel(x, ttt[i]);
1444 pR += pitch; pG += pitch; pB += pitch;
1445 }
1446
1447 // Middle band (2/3 to 3/4)
1448 for (; y * 4 < h * 3; ++y) {
1449 int x = 0;
1450 for (int i = 0; i < 7; ++i)
1451 for (; x < (w * (i + 1) + 3) / 7; ++x)
1452 write_pixel(x, ttq[i]);
1453 pR += pitch; pG += pitch; pB += pitch;
1454 }
1455
1456 // Bottom quarter
1457 for (; y < h; ++y) {
1458 int x = 0;
1459 for (int i = 0; i < 4; ++i)
1460 for (; x < (w * (i + 1) * 5 + 14) / 28; ++x)
1461 write_pixel(x, bq[i]);
1462 for (int j = 4; j < 7; ++j)
1463 for (; x < (w * (j + 12) + 10) / 21; ++x)
1464 write_pixel(x, bq[j]);
1465 for (; x < w; ++x)
1466 write_pixel(x, bq[7]);
1467 pR += pitch; pG += pitch; pB += pitch;
1468 }
1469 }
1470
1471 /*******************************************************************
1472 *
1473 * ColorBarsUHD for YUV 444 formats (BT.2100 / BT.2111-3)
1474 *
1475 * Implements Recommendation ITU-R BT.2111-3 (05/2025):
1476 * "Specification of color bar test pattern for HDR television systems"
1477 *
1478 * Three subtypes (BT.2111-3 Annex 1, §4):
1479 * subtype 0: HLG narrow range (Fig. 1, Table 2)
1480 * subtype 1: PQ narrow range (Fig. 2, Table 3)
1481 * subtype 2: PQ full range (Fig. 3, Table 4)
1482 *
1483 * ColorBarsUHD approach: BT.2111-3 hands you the exact 10-bit R'G'B' integer code values directly in Tables 2/3/4. You do not derive them from floating-point primaries. The flow is:
1484 * - Look up 10-bit R'G'B' from the table
1485 * - Scale to target bit depth (10->12 is << 2; 10->16 is << 6; 10->8 is >> 2 with rounding)
1486 * - Convert R'G'B' -> YCbCr using BT.2020 NCL matrix (Kr=0.2627, Kb=0.0593) for YUV output
1487 * - For float: normalise to limited-range float
1488 * Source code values are 10-bit integers taken directly from BT.2111-3 Tables 2/3/4.
1489 * 12-bit values are derived by left-shifting 10-bit values by 2 (BT.2111-3 §5).
1490 * Other bit depths are scaled proportionally from the 10-bit reference.
1491 *
1492 * Unlike ColorBars/ColorBarsHD, no floating-point RGB primaries are stored here.
1493 * BT.2111-3 specifies the signal levels directly as R'G'B' code values in the
1494 * transfer-function-encoded (non-linear) domain. There is no Kr/Kb matrix
1495 * derivation step — the standard pre-computes everything including the
1496 * BT.709-equivalent bars (via the proper linear-light BT.2407 matrix path).
1497 *
1498 * YCbCr output uses BT.2100 NCL matrix: Kr=0.2627, Kb=0.0593 (same as BT.2020).
1499 *
1500 * Layout dimensions (Table 1):
1501 * a = frame width (1920 / 3840 / 7680 for 2K/4K/8K)
1502 * b = frame height (1080 / 2160 / 4320)
1503 * c = a/8 = color bar unit width
1504 * d = left/right optional grey pad (from Table 1)
1505 * e,f,g,h,i,j,k = row heights and ramp geometry (from Table 1)
1506 *
1507 * Ramp (Tables 5/6): full sweep from -7%(min) to 109%(max) of the encoded range.
1508 * Positioned so that 0% aligns with the left edge of the Green color bar.
1509 * Stair: 12 steps (0%,10%,...,100%,109%), each c/2 wide.
1510 * Left edge of 0% step aligns with left edge of Yellow bar.
1511 * PLUGE: 0%, -2%, 0%, +2%, 0%, +4%, 0% black levels (bottom row).
1512 *
1513 * The critical signal level difference between subtypes is:
1514 *
1515 * HLG: primary bars are 75% (code 721 at 10-bit narrow)
1516 * PQ narrow/full: primary bars are 100% top row + 58% second row (58% = 203 cd/m² = equivalent to 75% HLG)
1517 * Full range: black = code 0, white = 1023; narrow range: black = 64, white = 940
1518 *
1519 *********************************************************************/
1520
1521 // BT.2100 NCL (= BT.2020) matrix coefficients
1522 static void GetYUVBT2020fromRGB(double R, double G, double B, double& dY, double& dU, double& dV)
1523 {
1524 // BT.2100 / BT.2020 NCL: Kr=0.2627, Kb=0.0593
1525 // See https://www.itu.int/rec/R-REC-BT.2100/en
1526 double Kr, Kb;
1527 GetKrKb(AVS_MATRIX_BT2020_NCL, Kr, Kb);
1528 dY = Kr * R + (1.0 - Kr - Kb) * G + Kb * B;
1529 dU = (B - dY) / (2.0 * (1.0 - Kb));
1530 dV = (R - dY) / (2.0 * (1.0 - Kr));
1531 }
1532
1533 /*
1534 BT.2111-3 Layout (same spatial structure for HLG, PQ narrow, PQ full range):
1535 2K 4K 8K
1536 a 1920 3840 7680 full width, a = 2c + 6d + e
1537 b 1080 2160 4320 full height
1538 c 240 480 960 left/right grey pad width (a/8)
1539 d 206 412 824 standard color bar width (most bars)
1540 e 204 408 816 green bar width (wider than d)
1541 f 136 272 544
1542 g 70 140 280
1543 h 68 136 272
1544 i 238 476 952
1545 j 438 876 1752
1546 k 282 564 1128
1547
1548 NOTE: d > e at 2K (206 > 204). The green bar is narrower than the others, absorbs the rounding differences.
1549
1550 ←—————————————————————————— a ———-—————————————————————→
1551 +--d--+--c---+--c--+--c--+--e--+--c--+--c--+--c--+--d--+
1552 | |100% |100% |100% |100% |100% |100% |100% | | height = b/12 (100% bars)
1553 | |White |Yell |Cyan |Green|Mag |Red |Blue | |
1554 + Grey+------+-----+-----+-----+-----+-----+-----+ Grey+
1555 | 40% | | | | | | | | 40% |
1556 | |75% |75% |75% |75% |75% |75% |75% | | height = b/2 (HLG: 75%; PQ: 58% bars)
1557 | |White |Yell |Cyan |Green|Mag |Red |Blue | |
1558 +-----+------+-----+-----+-----+-----+-----+-----+-----+
1559 | | | | | | | | | | | | | 1|1 | | Stair
1560 | 75% |-7% |0%|10|20|30|40|50|60|70|80|90| 0|0 | 75% | height: b/12
1561 |White|step |step |step |step |step |step | 0|9 |White| PQ Full: 0%/100% instead of -7/109%
1562 +-----+------+-----+-----+-----+-----+-----+-----+-----+
1563 | 0% |←—————————————————— ramp -—————————————————————→| Black, lead-in, ramp, lead-out
1564 | Blk | HLG/PQ-narrow:-7 to 109% PQ-full:0 to 100% | height = b/12
1565 +-+-+-+----+-+-+-+-+-+------+-----------+--------+-+-+-+
1566 | | | | 0% | | | | | | 0% | 75% | 0% | | | | BT.709 bars, Black, PLUGE, Black, White, Black, BT.709 bars
1567 | | | |Blk | | | | | |Black | White | Black | | | | height = b/4
1568 |BT709| | pluge | | | |BT709| Bars: Yellow,Cyan, Green (left), Magenta, Red, Blue (right)
1569 +-+-+-+----+-+-+-+-+-+------+-----------+--------+-+-+-+
1570 c c c f g h g h g i j k c c c
1571 / / / / / /
1572 3 3 3 3 3 3
1573
1574 NOTE: "It is desirable that implementers should include in this test signal some
1575 visual identification of the signal format (HLG narrow range, PQ narrow range, or
1576 PQ full range). The test pattern includes grey bars (top right and top left) that
1577 may optionally be used for this and/or other purposes."
1578 We fill them with 40% Grey.
1579
1580 Ramp and stair are placed so they do NOT overlap on a waveform monitor:
1581 - Ramp: positioned so 0% aligns with left edge of Green bar (Table 5/6 column B/C/D)
1582 - Stair: left edge of 0% step aligns with left edge of Yellow bar
1583 - Each stair step is half a color bar (c/2) wide, 11 steps: 0%,10%,...,100%.
1584 */
1585 template<typename pixel_t, bool is_rgb>
1586 static void draw_colorbarsUHD_444_rgb(uint8_t* pY8, uint8_t* pU8, uint8_t* pV8,
1587 int pitchY, int pitchUV,
1588 int w, int h, int bits_per_pixel, int subtype)
1589 {
1590 pixel_t* pYG = reinterpret_cast<pixel_t*>(pY8); // Y plane, or G plane for RGB (GBR order)
1591 pixel_t* pUB = reinterpret_cast<pixel_t*>(pU8); // U plane, or B plane for RGB
1592 pixel_t* pVR = reinterpret_cast<pixel_t*>(pV8); // V plane, or R plane for RGB
1593 pitchY /= sizeof(pixel_t);
1594 pitchUV /= sizeof(pixel_t);
1595
1596 // -- Bit-depth scaling via get_bits_conv_constants -------------------------
1597 //
1598 // BT.2111-3 §5: 10-bit is the primary reference for narrow range.
1599 // YUV is derived via BT.2020 NCL matrix, then scaled to target bit depth.
1600 // Narrow range (subtypes 0/1): black=64, white=940, limited swing.
1601 // Integer scaling: pure bit-shift (limited->limited is exact per §5).
1602 // Full range (subtype 2): black=0, white=1023/4095, full swing.
1603 // 10-bit AND 12-bit are INDEPENDENTLY specified by ITU (Table 4).
1604 // Neither is derivable from the other by simple bit-shift.
1605 // Other depths: scale from 12-bit ITU reference via round(code12/4095*(vmax-1)).
1606 // Float (any subtype): normalised via get_bits_conv_constants to Avisynth's
1607 // float convention (luma 0.0..1.0 maps to black..white).
1608 // Float always uses full-range normalisation; external tools use this convention.
1609 //
1610 // Pre-compute conversion constants for luma and chroma.
1611 // Source: 10-bit reference. Destination: target depth and range.
1612 constexpr bool is_float_output = std::is_same<pixel_t, float>::value;
1613 const bool is_full_range = subtype == 2;
1614 // Float output always uses full-range normalisation regardless of subtype.
1615 const bool is_full_range_target = is_full_range || is_float_output;
1616 constexpr int src_bits = 10;
1617
1618 bits_conv_constants luma_c, chroma_c;
1619 bits_conv_constants luma_c_from_float, chroma_c_from_float;
1620
1621 // Used by make_yuv to normalise native pixel_t RGB before BT.2020 matrix.
1622 // src: target bit depth and range. dst: float full range.
1623 bits_conv_constants luma_c_to_float;
1624 if constexpr (!is_rgb) {
1625 }
1626
1627 get_bits_conv_constants(luma_c, false, is_full_range, is_full_range_target, src_bits, bits_per_pixel);
1628 if constexpr (!is_rgb) {
1629 // YUV output helpers
1630 get_bits_conv_constants(luma_c_to_float, false,
1631 is_full_range_target, // src range matches target
1632 true, // dst is float full range [0..1]
1633 bits_per_pixel, 32);
1634
1635 // luma_c_from_float: float BT.2020 matrix luma output [0..1] → target depth
1636 get_bits_conv_constants(luma_c_from_float, false,
1637 true,
1638 is_full_range_target, 32, bits_per_pixel);
1639 // chroma_c_from_float: float BT.2020 matrix chroma output [-0.5..+0.5] → target depth
1640 get_bits_conv_constants(chroma_c_from_float, true,
1641 true, // src is always full-range float
1642 is_full_range_target, // dst matches target signal range
1643 32, bits_per_pixel);
1644 get_bits_conv_constants(chroma_c, true, is_full_range, is_full_range_target, src_bits, bits_per_pixel);
1645 }
1646
1647 // -- Bar geometry from BT.2111-3 Table 1 ----------------------------------
1648 // All values scaled from 2K reference (a=1920, c=240, d=206).
1649 const int c_bar = (240 * w + 960) / 1920; // left/right grey pad
1650 const int d_bar = (206 * w + 960) / 1920; // standard bar width
1651 const int e_bar = w - 2 * c_bar - 6 * d_bar; // green bar (remainder, absorbs rounding)
1652 const int bar_White = c_bar;
1653 const int bar_Yellow = c_bar + d_bar;
1654 const int bar_Cyan = c_bar + 2 * d_bar;
1655 const int bar_Green = c_bar + 3 * d_bar;
1656 const int bar_Magenta = c_bar + 3 * d_bar + e_bar;
1657 const int bar_Red = c_bar + 4 * d_bar + e_bar;
1658 const int bar_Blue = c_bar + 5 * d_bar + e_bar;
1659 const int bar_RightC = c_bar + 6 * d_bar + e_bar; // = w - c_bar
1660 const int barend_White = bar_Yellow;
1661 const int barend_Yellow = bar_Cyan;
1662 const int barend_Cyan = bar_Green;
1663 const int barend_Green = bar_Magenta;
1664 const int barend_Magenta = bar_Red;
1665 const int barend_Red = bar_Blue;
1666 const int barend_Blue = bar_RightC;
1667
1668 // -- Vertical row heights --------------------------------------------------
1669 const int row1 = (h + 6) / 12; // b/12: 100% bars
1670 const int row2 = (h * 6 + 6) / 12; // b/2: 75%/58% bars
1671 const int row3 = (h + 6) / 12; // b/12: stair
1672 const int row4 = (h + 6) / 12; // b/12: ramp
1673 const int y1 = row1;
1674 const int y2 = y1 + row2;
1675 const int y3 = y2 + row3;
1676 const int y4 = y3 + row4;
1677
1678 // -- Bottom row geometry --------------------------------------------------
1679 const int bt709_bar_w = c_bar / 3;
1680 const int f_pluge = (136 * w + 960) / 1920;
1681 const int g_pluge = (70 * w + 960) / 1920;
1682 const int h_pluge = (68 * w + 960) / 1920;
1683 const int i_span = (238 * w + 960) / 1920;
1684 const int j_span = (438 * w + 960) / 1920;
1685 const int k_span = (282 * w + 960) / 1920;
1686
1687 // =========================================================================
1688 // -- Signal level tables (BT.2111-3) — ITU source values ------------------
1689 // =========================================================================
1690 // color bar order in Tables 2/3/4:
1691 // [0]=White [1]=Yellow [2]=Cyan [3]=Green [4]=Magenta [5]=Red [6]=Blue
1692 //
1693 // 100% bars — narrow range 10-bit (Tables 2/3):
1694 static const int bars100_R_narrow_10[] = { 940, 940, 64, 64, 940, 940, 64 };
1695 static const int bars100_G_narrow_10[] = { 940, 940, 940, 940, 64, 64, 64 };
1696 static const int bars100_B_narrow_10[] = { 940, 64, 940, 64, 940, 64, 940 };
1697 // 100% bars — full range 10-bit (Table 4): 0=black, 1023=white
1698 static const int bars100_R_fr10[] = { 1023, 1023, 0, 0, 1023, 1023, 0 };
1699 static const int bars100_G_fr10[] = { 1023, 1023, 1023, 1023, 0, 0, 0 };
1700 static const int bars100_B_fr10[] = { 1023, 0, 1023, 0, 1023, 0, 1023 };
1701 // 100% bars — full range 12-bit (Table 4): 0=black, 4095=white
1702 static const int bars100_R_fr12[] = { 4095, 4095, 0, 0, 4095, 4095, 0 };
1703 static const int bars100_G_fr12[] = { 4095, 4095, 4095, 4095, 0, 0, 0 };
1704 static const int bars100_B_fr12[] = { 4095, 0, 4095, 0, 4095, 0, 4095 };
1705
1706 // 75% HLG primary bars — narrow range 10-bit (Table 2):
1707 static const int bars75hlg_R_10[] = { 721, 721, 64, 64, 721, 721, 64 };
1708 static const int bars75hlg_G_10[] = { 721, 721, 721, 721, 64, 64, 64 };
1709 static const int bars75hlg_B_10[] = { 721, 64, 721, 64, 721, 64, 721 };
1710
1711 // 58% PQ primary bars — narrow range 10-bit (Table 3):
1712 static const int bars58pq_R_10[] = { 573, 573, 64, 64, 573, 573, 64 };
1713 static const int bars58pq_G_10[] = { 573, 573, 573, 573, 64, 64, 64 };
1714 static const int bars58pq_B_10[] = { 573, 64, 573, 64, 573, 64, 573 };
1715 // 58% PQ primary bars — full range 10-bit (Table 4):
1716 static const int bars58pq_R_fr10[] = { 594, 594, 0, 0, 594, 594, 0 };
1717 static const int bars58pq_G_fr10[] = { 594, 594, 594, 594, 0, 0, 0 };
1718 static const int bars58pq_B_fr10[] = { 594, 0, 594, 0, 594, 0, 594 };
1719 // 58% PQ primary bars — full range 12-bit (Table 4):
1720 static const int bars58pq_R_fr12[] = { 2378, 2378, 0, 0, 2378, 2378, 0 };
1721 static const int bars58pq_G_fr12[] = { 2378, 2378, 2378, 2378, 0, 0, 0 };
1722 static const int bars58pq_B_fr12[] = { 2378, 0, 2378, 0, 2378, 0, 2378 };
1723
1724 // BT.709-equivalent bars — order: [0]=Yellow [1]=Cyan [2]=Green [3]=Magenta [4]=Red [5]=Blue
1725 // Pre-computed via BT.2111-3 Attachment 1 / BT.2407 colorimetric conversion.
1726 // Full range 10-bit and 12-bit are independently rounded from the underlying
1727 // colorimetric float — neither reproduces the other by simple bit-scaling.
1728 // Calculations done by ITU, we just using their tables.
1729 // Subtype 0 — HLG narrow 10-bit (Table 2):
1730 static const int bt709_hlg_R[] = { 713, 538, 512, 651, 639, 227 };
1731 static const int bt709_hlg_G[] = { 719, 709, 706, 286, 269, 147 };
1732 static const int bt709_hlg_B[] = { 316, 718, 296, 705, 164, 702 };
1733 // Subtype 1 — PQ narrow 10-bit (Table 3):
1734 static const int bt709_pq_narrow_R_10[] = { 569, 485, 474, 537, 531, 318 };
1735 static const int bt709_pq_narrow_G_10[] = { 572, 566, 565, 362, 351, 236 };
1736 static const int bt709_pq_narrow_B_10[] = { 381, 571, 368, 564, 257, 563 };
1737 // Subtype 2 — PQ full range 10-bit (Table 4):
1738 static const int bt709_pq_R_fr10[] = { 589, 491, 479, 552, 545, 296 };
1739 static const int bt709_pq_G_fr10[] = { 593, 586, 585, 348, 335, 201 };
1740 static const int bt709_pq_B_fr10[] = { 370, 592, 355, 584, 225, 582 };
1741 // Subtype 2 — PQ full range 12-bit (Table 4): independently specified,
1742 // e.g. bt709_pq_G_fr10[1] 586 and 2348 cannot be calculated from each other by scaling
1743 static const int bt709_pq_R_fr12[] = { 2359, 1967, 1918, 2209, 2181, 1186 };
1744 static const int bt709_pq_G_fr12[] = { 2373, 2348, 2342, 1391, 1339, 806 };
1745 static const int bt709_pq_B_fr12[] = { 1483, 2371, 1423, 2339, 901, 2331 };
1746
1747 // non-RGB, simply grey levels:
1748
1749 // 40% Grey:
1750 // Narrow (subtypes 0/1): 414 at 10-bit (primary). 12-bit = 414<<2 = 1656 (exact shift).
1751 // Full (subtype 2): 409 at 10-bit, 1638 at 12-bit — independently rounded from ~0.4.
1752 static const int grey40_narrow = 414;
1753 static const int grey40_fr10 = 409;
1754 static const int grey40_fr12 = 1638;
1755
1756 // PLUGE levels — derived from BT.814-4 Table 3 "Slightly lighter/darker level".
1757 // BT.814-4 defines: Slightly lighter=80, Slightly darker=48 at 10-bit narrow.
1758 // The naming of 2% and 4% levels are just approximations.
1759 // Full range values were probably independently derived from narrow float.
1760 // lighter_float = (80-64)/876 ≈ 0.01826 → 10-bit (*1023): 19, 12-bit (*4096): 75
1761 // +4%_float = (99-64)/876 ≈ 0.03995 → 10-bit: 41, 12-bit: 164
1762 // Narrow: { 0%, -2%, 0%, +2%, 0%, +4%, 0% }
1763 static const int pluge_narrow[] = { 64, 48, 64, 80, 64, 99, 64 };
1764 // Full range 10-bit (Table 4): no -2% (no sub-black in full range per Table 4 footnote)
1765 static const int pluge_fr10[] = { 0, 0, 0, 19, 0, 41, 0 };
1766 // Full range 12-bit (Table 4)
1767 static const int pluge_fr12[] = { 0, 0, 0, 75, 0, 164, 0 };
1768
1769 // Stair step values (10-bit), BT.2111-3 Tables 2/3/4:
1770 // 13 steps: -7%, 0%, 10%..100%, 109%
1771 // Narrow (subtypes 0/1):
1772 static const int steps_narrow[] = { 4, 64, 152, 239, 327, 414, 502, 590, 677, 765, 852, 940, 1019 };
1773 // Full range (subtype 2): no -7% (clamped to 0) and no 109% (clamped to 1023)
1774 static const int steps_fr10[] = { 0, 0, 102, 205, 307, 409, 512, 614, 716, 818, 921, 1023, 1023 };
1775 static const int steps_fr12[] = { 0, 0, 410, 819, 1229, 1638, 2048, 2457, 2867, 3276, 3686, 4095, 4095 };
1776
1777 // narrow range is always 10-bit reference
1778 // Reference anchor points at 10/12-bits (used for stair/ramp generation):
1779 const int ref_black_narrow = 64; // at 10 bits
1780 const int ref_black_fr10 = 0;
1781 const int ref_black_fr12 = 0;
1782 const int ref_white_narrow = 940; // at 10 bits
1783 const int ref_white_fr10 = 1023;
1784 const int ref_white_fr12 = 4095;
1785 const int ref_min_narrow = 4; // -7% step
1786 const int ref_min_fr10 = 0; // 0% step
1787 const int ref_min_fr12 = 0; // 0% step
1788 const int ref_max_narrow = 1019; // at 10 bits, +109% step
1789 const int ref_max_fr10 = 1023; // at 10 bits, +100% step (full range)
1790 const int ref_max_fr12 = 4095; // at 12 bits, +100% step (full range)
1791
1792 // Code value 4 = approx −7% = minimum permitted narrow range value (BT.2100).
1793 // Code value 1019 = approx +109% = maximum permitted narrow range value (BT.2100).
1794
1795 // =========================================================================
1796 // -- Native pixel_t table pre-computation ---------------------------------
1797 // =========================================================================
1798 // All ITU source values (int) are converted once to pixel_t here.
1799 // Drawing code then reads pre-computed pixel_t values directly with no
1800 // per-pixel scaling. This replaces the mixed fill_span/fill_span_native/
1801 // fill_bar dispatch logic.
1802 //
1803 // pixel_t: the element type of all pre-computed tables.
1804 // float output → float (normalised via scale_y)
1805 // integer output → pixel_t (= uint8_t, uint16_t)
1806 // All tables are indexed exactly as their ITU int counterparts.
1807
1808 // -- scale_y: convert one 10-bit source code to pixel_t -------------------
1809 // Narrow range: limited->limited pure bit-shift (exact per BT.2111-3 §5).
1810 // Full range: full->full rational rescale via luma_c constants.
1811 // Float: normalised via luma_c.mul_factor + luma_c.dst_offset.
1812 auto scale_y = [&](int code10) -> pixel_t {
1813 XP_LAMBDA_CAPTURE_FIX(is_float_output);
1814 XP_LAMBDA_CAPTURE_FIX(is_full_range);
1815 #ifdef XP_TLS
1816 if (is_float_output)
1817 #else
1818 if constexpr (is_float_output)
1819 #endif
1820 {
1821 return (pixel_t)((code10 - luma_c.src_offset_i) * luma_c.mul_factor + luma_c.dst_offset);
1822 }
1823 else {
1824 if (!is_full_range) {
1825 const int shift = bits_per_pixel - src_bits;
1826 if (shift >= 0) return (pixel_t)(code10 << shift);
1827 else return (pixel_t)((code10 + (1 << (-shift - 1))) >> (-shift));
1828 }
1829 else {
1830 const int dst_max = (1 << bits_per_pixel) - 1;
1831 const int result = (int)((code10 - luma_c.src_offset_i) * luma_c.mul_factor
1832 + luma_c.dst_offset + 0.5f);
1833 return (pixel_t)(std::max(0, std::min(dst_max, result)));
1834 }
1835 }
1836 };
1837
1838 // -- fr_native12: convert full-range value to pixel_t from 12-bit reference
1839 // For full range non-10/12-bit integer depths: scale from 12-bit ITU value.
1840 // For 10-bit: use code10 directly.
1841 // For 12-bit: use code12 directly.
1842 // For float: use code10 via scale_y (same as narrow — scale_y handles float normalisation).
1843 // This ensures ITU-specified 10 and 12-bit values are reproduced exactly,
1844 // while other depths get the best approximation from the 12-bit reference.
1845 auto fr_native12 = [&](int code10, int code12) -> pixel_t {
1846 #ifdef XP_TLS
1847 if (is_float_output)
1848 #else
1849 if constexpr (is_float_output)
1850 #endif
1851 {
1852 return scale_y(code10); // float always uses 10-bit ref via scale_y
1853 }
1854 else {
1855 if (bits_per_pixel == 10) return (pixel_t)code10;
1856 if (bits_per_pixel == 12) return (pixel_t)code12;
1857 const int vmax = (1 << bits_per_pixel) - 1;
1858 return (pixel_t)(int)round((double)code12 / 4095.0 * vmax);
1859 }
1860 };
1861
1862 // Helper: build a pixel_t value from a 10-bit code via scale_y.
1863 // For narrow range: bit-shift. For full range: rational rescale. For float: normalise.
1864 // Used for all grey/luma-only values (grey40, PLUGE, stair, black, white).
1865 auto make_luma = [&](int code10) -> pixel_t { return scale_y(code10); };
1866
1867 // Helper: build a pixel_t value from paired 10/12-bit full-range codes.
1868 // Selects ITU exact value at 10/12-bit, scales from 12-bit for other depths.
1869 // Float always uses 10-bit reference via scale_y.
1870 auto make_luma_fr = [&](int code10, int code12) -> pixel_t {
1871 return fr_native12(code10, code12);
1872 };
1873
1874 // -- Pre-compute black and white pixel_t values ---------------------------
1875 // Used for i/j/k blocks in row 5, left pad, ramp B/D sections.
1876 const pixel_t t_black = is_full_range
1877 ? make_luma_fr(ref_black_fr10, ref_black_fr12) // 0 for full range
1878 : make_luma(ref_black_narrow);
1879 const pixel_t t_white = is_full_range
1880 ? make_luma_fr(ref_white_fr10, ref_white_fr12) // 100% for full range
1881 : make_luma(ref_white_narrow);
1882 const pixel_t t_ramp_min = is_full_range
1883 ? make_luma_fr(ref_min_fr10, ref_min_fr12) // -7% for ramp/stair B section
1884 : make_luma(ref_min_narrow);
1885 const pixel_t t_ramp_max = is_full_range
1886 ? make_luma_fr(ref_max_fr10, ref_max_fr12) // +109% for ramp D section
1887 : make_luma(ref_max_narrow);
1888
1889
1890 // =========================================================================
1891 // -- Pre-compute all native pixel_t bar tables ----------------------------
1892 // =========================================================================
1893 // All tables are pre-computed once here. Drawing loops read pixel_t values
1894 // directly — no per-pixel scaling, no dispatch on range/depth at draw time.
1895 //
1896 // Naming: t_* = pre-computed pixel_t table.
1897 //
1898 // RGB tables: 3 planes × N bars, stored as separate arrays.
1899 // t_R100[i]/t_G100[i]/t_B100[i] = 100% bar i, target pixel_t for R/G/B plane.
1900 // YUV tables: stored as YCbCr triplets via pre_yuv helper below.
1901 //
1902 // Table sizes:
1903 // 100% and primary bars: 7 entries [0..6]
1904 // BT.709-equivalent bars: 6 entries [0..5]
1905 // PLUGE: 7 entries [0..6]
1906 // Stair steps: 13 entries [0..12]
1907 // Grey40: scalar
1908 // Black/white: scalar
1909
1910 // -- Pre-compute grey40 ---------------------------------------------------
1911 // Narrow: 414 (10-bit primary, scale_y handles all depths via bit-shift).
1912 // Full: 409 (10-bit ITU), 1638 (12-bit ITU) — independently rounded from 0.4.
1913 // 409<<2=1636≠1638: confirms independent specification.
1914 // fr_native12 selects exact ITU value at 10/12-bit, scales from 12-bit otherwise.
1915 const pixel_t t_grey40 = is_full_range
1916 ? make_luma_fr(grey40_fr10, grey40_fr12)
1917 : make_luma(grey40_narrow);
1918
1919 // Neutral chroma value (mid-point for YUV, unused for RGB).
1920 const pixel_t t_chroma_mid = is_rgb ? pixel_t{} : (pixel_t)chroma_c.dst_offset;
1921
1922 // -- Helper: write a solid luma+neutral-chroma span (grey) ----------------
1923 // All grey fills use pre-computed pixel_t values — no per-pixel scaling.
1924 auto fill_grey_px = [&](int x0, int x1, pixel_t luma) {
1925 XP_LAMBDA_CAPTURE_FIX(t_chroma_mid);
1926 for (int x = x0; x < x1 && x < w; ++x) {
1927 if constexpr (is_rgb) { pYG[x] = luma; pUB[x] = luma; pVR[x] = luma; }
1928 else { pYG[x] = luma; pUB[x] = t_chroma_mid; pVR[x] = t_chroma_mid; }
1929 }
1930 };
1931
1932 // -- Pre-compute PLUGE pixel_t values -------------------------------------
1933 // pluge_px[0..6] correspond to pluge_narrow[] / pluge_fr10[] / pluge_fr12[].
1934 // Narrow: scale_y(pluge_narrow[s]) — bit-shift from 10-bit.
1935 // Full: fr_native12(pluge_fr10[s], pluge_fr12[s]) — ITU-exact at 10/12-bit.
1936 pixel_t t_pluge[7];
1937 for (int s = 0; s < 7; ++s) {
1938 t_pluge[s] = is_full_range
1939 ? make_luma_fr(pluge_fr10[s], pluge_fr12[s])
1940 : make_luma(pluge_narrow[s]);
1941 }
1942
1943 // -- Pre-compute stair step pixel_t values --------------------------------
1944 // steps_narrow/steps_fr10/steps_fr12 are 10-bit source values.
1945 // Narrow: scale_y(steps_narrow[s]) — bit-shift from 10-bit.
1946 // Full: fr_native12(steps_fr10[s], steps_fr12[s]) — ITU-exact at 10/12-bit.
1947 pixel_t t_steps[13];
1948 for (int s = 0; s < 13; ++s)
1949 t_steps[s] = is_full_range
1950 ? make_luma_fr(steps_fr10[s], steps_fr12[s])
1951 : make_luma(steps_narrow[s]);
1952
1953 // For YUV output, each bar is stored as a pre-computed YCbCr triplet
1954 // in three parallel arrays.
1955 struct YUVPixel { pixel_t y, cb, cr; };
1956
1957 // make_yuv: convert native pixel_t R/G/B → YCbCr pixel_t.
1958 // Input is already at target bit depth and range — no further scaling needed.
1959 // Normalises to [0..1] float using the native range (full or limited),
1960 // applies BT.2020 NCL matrix, then encodes to target depth.
1961 // luma_c_to_float converts native pixel_t luma → [0..1] float.
1962 auto make_yuv = [&](pixel_t r_px, pixel_t g_px, pixel_t b_px) -> YUVPixel {
1963 // Convert native pixel_t to normalised float [0..1].
1964 // luma_c_to_float: native target depth → float [0..1] over signal range.
1965 // (src=bits_per_pixel full/limited, dst=float full range)
1966 const double R = ((double)r_px - luma_c_to_float.src_offset_i) * luma_c_to_float.mul_factor;
1967 const double G = ((double)g_px - luma_c_to_float.src_offset_i) * luma_c_to_float.mul_factor;
1968 const double B = ((double)b_px - luma_c_to_float.src_offset_i) * luma_c_to_float.mul_factor;
1969 double dY, dU, dV;
1970 GetYUVBT2020fromRGB(R, G, B, dY, dU, dV);
1971 pixel_t sy, scb, scr;
1972 #ifdef XP_TLS
1973 if (is_float_output) {
1974 #else
1975 if constexpr (is_float_output) {
1976 #endif
1977 sy = (pixel_t)(dY * luma_c_from_float.mul_factor + luma_c_from_float.dst_offset);
1978 scb = (pixel_t)(dU * chroma_c_from_float.mul_factor);
1979 scr = (pixel_t)(dV * chroma_c_from_float.mul_factor);
1980 }
1981 else {
1982 const int dst_max = (1 << bits_per_pixel) - 1;
1983 sy = (pixel_t)std::max(0, std::min(dst_max,
1984 (int)(dY * luma_c_from_float.mul_factor + luma_c_from_float.dst_offset + 0.5)));
1985 scb = (pixel_t)std::max(0, std::min(dst_max,
1986 (int)(dU * chroma_c_from_float.mul_factor + chroma_c_from_float.dst_offset + 0.5)));
1987 scr = (pixel_t)std::max(0, std::min(dst_max,
1988 (int)(dV * chroma_c_from_float.mul_factor + chroma_c_from_float.dst_offset + 0.5)));
1989 }
1990 return { sy, scb, scr };
1991 };
1992
1993 // BarEntry: always store r/g/b at native pixel_t precision.
1994 // For RGB output: r/g/b are written directly to planes.
1995 // For YUV output: y/cb/cr are derived from r/g/b via make_yuv.
1996 // Having r/g/b always present unifies make_bar and make_bar_fr —
1997 // no if constexpr(is_rgb) split needed.
1998 struct BarEntry {
1999 pixel_t r, g, b; // native pixel_t — always computed
2000 pixel_t y, cb, cr; // YUV planes — computed from r/g/b via make_yuv
2001 };
2002
2003 // make_bar_from_px: build BarEntry from native pixel_t R/G/B values.
2004 // Always computes r/g/b. Derives y/cb/cr via make_yuv if !is_rgb.
2005 auto make_bar_from_px = [&](pixel_t r_px, pixel_t g_px, pixel_t b_px) -> BarEntry {
2006 XP_LAMBDA_CAPTURE_FIX(make_yuv);
2007 BarEntry e{};
2008 e.r = r_px; e.g = g_px; e.b = b_px;
2009 if constexpr (!is_rgb) {
2010 auto yuv = make_yuv(r_px, g_px, b_px);
2011 e.y = yuv.y; e.cb = yuv.cb; e.cr = yuv.cr;
2012 }
2013 return e;
2014 };
2015
2016 // make_bar: build BarEntry from 10-bit source codes (narrow range or full range 10-bit).
2017 // Converts via scale_y to native pixel_t, then derives YUV from native values.
2018 auto make_bar = [&](int r10, int g10, int b10) -> BarEntry {
2019 return make_bar_from_px(scale_y(r10), scale_y(g10), scale_y(b10));
2020 };
2021
2022 // make_bar_fr: build BarEntry for full range using paired 10/12-bit ITU values.
2023 // RGB path: fr_native12 gives ITU-exact native pixel_t at 10/12-bit, scaled otherwise.
2024 // YUV path: make_yuv receives the same native pixel_t values — more accurate than
2025 // passing code10 directly since fr_native12 may differ from code10 at 12/other depths.
2026 // Previously YUV used code10 only; now it uses the fr_native12-derived pixel_t,
2027 // giving correct matrix input at all bit depths.
2028 auto make_bar_fr = [&](int r10, int g10, int b10,
2029 int r12, int g12, int b12) -> BarEntry {
2030 return make_bar_from_px(
2031 fr_native12(r10, r12),
2032 fr_native12(g10, g12),
2033 fr_native12(b10, b12));
2034 };
2035
2036 // -- 100% bars (7 entries) ------------------------------------------------
2037 BarEntry t_bars100[7];
2038 for (int i = 0; i < 7; ++i) {
2039 if (is_full_range)
2040 t_bars100[i] = make_bar_fr(bars100_R_fr10[i], bars100_G_fr10[i], bars100_B_fr10[i],
2041 bars100_R_fr12[i], bars100_G_fr12[i], bars100_B_fr12[i]);
2042 else
2043 t_bars100[i] = make_bar(bars100_R_narrow_10[i], bars100_G_narrow_10[i], bars100_B_narrow_10[i]);
2044 }
2045
2046 // -- Primary bars (7 entries): 75% HLG or 58% PQ -------------------------
2047 BarEntry t_bpri[7];
2048 for (int i = 0; i < 7; ++i) {
2049 if (subtype == 0) // HLG narrow
2050 t_bpri[i] = make_bar(bars75hlg_R_10[i], bars75hlg_G_10[i], bars75hlg_B_10[i]);
2051 else if (subtype == 1) // PQ narrow
2052 t_bpri[i] = make_bar(bars58pq_R_10[i], bars58pq_G_10[i], bars58pq_B_10[i]);
2053 else // PQ full range
2054 t_bpri[i] = make_bar_fr(bars58pq_R_fr10[i], bars58pq_G_fr10[i], bars58pq_B_fr10[i],
2055 bars58pq_R_fr12[i], bars58pq_G_fr12[i], bars58pq_B_fr12[i]);
2056 }
2057
2058 // -- BT.709-equivalent bars (6 entries) -----------------------------------
2059 BarEntry t_b709[6];
2060 for (int i = 0; i < 6; ++i) {
2061 if (subtype == 0) // HLG narrow
2062 t_b709[i] = make_bar(bt709_hlg_R[i], bt709_hlg_G[i], bt709_hlg_B[i]);
2063 else if (subtype == 1) // PQ narrow
2064 t_b709[i] = make_bar(bt709_pq_narrow_R_10[i], bt709_pq_narrow_G_10[i], bt709_pq_narrow_B_10[i]);
2065 else // PQ full range
2066 t_b709[i] = make_bar_fr(bt709_pq_R_fr10[i], bt709_pq_G_fr10[i], bt709_pq_B_fr10[i],
2067 bt709_pq_R_fr12[i], bt709_pq_G_fr12[i], bt709_pq_B_fr12[i]);
2068 }
2069
2070 // =========================================================================
2071 // -- Drawing helpers (all operate on pre-computed pixel_t values) ---------
2072 // =========================================================================
2073
2074 // fill_grey_px: fill span with a pre-computed luma + neutral chroma.
2075 // (defined above)
2076
2077 // fill_bar_px: fill span with a pre-computed BarEntry.
2078 // Direct pixel_t write — no scaling at draw time.
2079 auto fill_bar_px = [&](int x0, int x1, const BarEntry& e) {
2080 for (int x = x0; x < x1 && x < w; ++x) {
2081 if constexpr (is_rgb) {
2082 pYG[x] = e.g; pUB[x] = e.b; pVR[x] = e.r; // Avisynth GBR order
2083 }
2084 else {
2085 pYG[x] = e.y; pUB[x] = e.cb; pVR[x] = e.cr;
2086 }
2087 }
2088 };
2089
2090 // =========================================================================
2091 // -- Draw rows ------------------------------------------------------------
2092 // =========================================================================
2093 int y = 0;
2094
2095 // -- Row 1: b/12 — 100% colour bars ---------------------------------------
2096 for (; y < y1; ++y) {
2097 fill_grey_px(0, c_bar, t_grey40);
2098 fill_bar_px(bar_White, barend_White, t_bars100[0]); // White
2099 fill_bar_px(bar_Yellow, barend_Yellow, t_bars100[1]); // Yellow
2100 fill_bar_px(bar_Cyan, barend_Cyan, t_bars100[2]); // Cyan
2101 fill_bar_px(bar_Green, barend_Green, t_bars100[3]); // Green
2102 fill_bar_px(bar_Magenta, barend_Magenta, t_bars100[4]); // Magenta
2103 fill_bar_px(bar_Red, barend_Red, t_bars100[5]); // Red
2104 fill_bar_px(bar_Blue, barend_Blue, t_bars100[6]); // Blue
2105 fill_grey_px(bar_RightC, w, t_grey40);
2106 pYG += pitchY; pUB += pitchUV; pVR += pitchUV;
2107 }
2108
2109 // -- Row 2: b/2 — 75% HLG or 58% PQ primary colour bars ------------------
2110 for (; y < y2; ++y) {
2111 fill_grey_px(0, c_bar, t_grey40);
2112 fill_bar_px(bar_White, barend_White, t_bpri[0]);
2113 fill_bar_px(bar_Yellow, barend_Yellow, t_bpri[1]);
2114 fill_bar_px(bar_Cyan, barend_Cyan, t_bpri[2]);
2115 fill_bar_px(bar_Green, barend_Green, t_bpri[3]);
2116 fill_bar_px(bar_Magenta, barend_Magenta, t_bpri[4]);
2117 fill_bar_px(bar_Red, barend_Red, t_bpri[5]);
2118 fill_bar_px(bar_Blue, barend_Blue, t_bpri[6]);
2119 fill_grey_px(bar_RightC, w, t_grey40);
2120 pYG += pitchY; pUB += pitchUV; pVR += pitchUV;
2121 }
2122
2123 // -- Row 3: b/12 — stair --------------------------------------------------
2124 // Layout: primary-white(c) | -7%or0%(d) | 12 steps | primary-white(c)
2125 // Steps: 0%,10%..100%,109% (narrow) or 0%,10%..100%,100% (full range)
2126 // Under each d-width bar: 2 steps of d/2. Under green (e_bar): 2 steps of e/2.
2127 // All steps pre-computed in t_steps[0..12].
2128 for (; y < y3; ++y) {
2129 fill_bar_px(0, bar_White, t_bpri[0]); // primary white left pad
2130 fill_grey_px(bar_White, bar_Yellow, t_steps[0]); // pre-step: -7% or 0%
2131 // 6 bars × 2 half-steps each = steps[1..12]
2132 const int bar_starts[6] = { bar_Yellow, bar_Cyan, bar_Green, bar_Magenta, bar_Red, bar_Blue };
2133 const int bar_widths[6] = { d_bar, d_bar, e_bar, d_bar, d_bar, d_bar };
2134 for (int b = 0; b < 6; ++b) {
2135 const int half = bar_widths[b] / 2;
2136 fill_grey_px(bar_starts[b], bar_starts[b] + half, t_steps[1 + b * 2]);
2137 fill_grey_px(bar_starts[b] + half, bar_starts[b] + bar_widths[b], t_steps[2 + b * 2]);
2138 }
2139 fill_bar_px(bar_RightC, w, t_bpri[0]); // primary white right pad
2140 pYG += pitchY; pUB += pitchUV; pVR += pitchUV;
2141 }
2142
2143 // -- Row 4: b/12 — ramp ---------------------------------------------------
2144 // Ramp is NOT pre-computed (gradient changes per pixel).
2145 // Uses the existing real-valued step computation for correctness at all depths.
2146 // Section layout:
2147 // left pad (black) | B (constant min) | C (linear ramp) | D (constant max)
2148 // Narrow: ramp -7% → +109% (B=ref_min, D=ref_max)
2149 // Full: ramp 0% → 100% (B=black, D=white)
2150 {
2151 const int A_px = (1680 * w + 960) / 1920;
2152 const int left_pad_end = w - A_px;
2153 int B_px, C_px, D_px;
2154 if (!is_full_range) {
2155 B_px = (559 * w + 960) / 1920;
2156 D_px = (107 * w + 960) / 1920;
2157 }
2158 else {
2159 B_px = (618 * w + 960) / 1920;
2160 D_px = (40 * w + 960) / 1920;
2161 }
2162 C_px = A_px - B_px - D_px;
2163 const int B_start = left_pad_end;
2164 const int C_start = B_start + B_px;
2165 const int D_start = C_start + C_px;
2166
2167 if constexpr (is_float_output) {
2168 // Float: compute B/D levels in float via scale_y, then step linearly.
2169 const int B_code = !is_full_range ? ref_min_narrow : ref_black_fr10;
2170 const int D_code = !is_full_range ? ref_max_narrow : ref_white_fr10;
2171 const double B_lf = (double)((B_code - luma_c.src_offset_i) * luma_c.mul_factor + luma_c.dst_offset);
2172 const double D_lf = (double)((D_code - luma_c.src_offset_i) * luma_c.mul_factor + luma_c.dst_offset);
2173 const double step_f = C_px > 0 ? (D_lf - B_lf) / C_px : luma_c.mul_factor;
2174 const pixel_t chroma_val = (pixel_t)chroma_c.dst_offset;
2175 for (; y < y4; ++y) {
2176 fill_grey_px(0, B_start, t_black);
2177 fill_grey_px(B_start, C_start, is_full_range ? t_black : t_ramp_min);
2178 for (int x = C_start; x < D_start; ++x) {
2179 const pixel_t val = (pixel_t)(B_lf + step_f * (x - C_start + 1));
2180 if constexpr (is_rgb) { pYG[x] = val; pUB[x] = val; pVR[x] = val; }
2181 else { pYG[x] = val; pUB[x] = chroma_val; pVR[x] = chroma_val; }
2182 }
2183 fill_grey_px(D_start, w, is_full_range ? t_white : t_ramp_max);
2184 pYG += pitchY; pUB += pitchUV; pVR += pitchUV;
2185 }
2186 }
2187 else {
2188 // Integer: compute native B/D levels, then step with real-valued step.
2189 // similar to make_luma_fr but with separate narrow/full logic since we need the 10-bit code for the step calculation below.
2190 const int B_level = t_black;
2191 const int D_level = t_white;
2192 const double real_step = C_px > 0 ? (double)(D_level - B_level) / C_px : 1.0;
2193 // Verify against ITU table notes (see Table 5/6 comments above):
2194 // 10-bit narrow 2K: B=4, D=1019, step≈1.001 → first=5, last=1019
2195 // 12-bit narrow 2K: B=16, D=4076, step=4.000 → first=20, last=4076
2196 // 10-bit full 2K: B=0, D=1023, step≈1.001 → first=1, last=1023
2197 // 12-bit full 2K: B=0, D=4092, step=4.000 → first=4, last=4092
2198 for (; y < y4; ++y) {
2199 fill_grey_px(0, B_start, t_black);
2200 fill_grey_px(B_start, C_start, is_full_range ? t_black : t_ramp_min);
2201 for (int x = C_start; x < D_start; ++x) {
2202 const pixel_t val = (pixel_t)(int)(B_level + real_step * (x - C_start + 1) + 0.5);
2203 if constexpr (is_rgb) { pYG[x] = val; pUB[x] = val; pVR[x] = val; }
2204 else { pYG[x] = val; pUB[x] = t_chroma_mid; pVR[x] = t_chroma_mid; }
2205 }
2206 fill_grey_px(D_start, w, is_full_range ? t_white : t_ramp_max);
2207 pYG += pitchY; pUB += pitchUV; pVR += pitchUV;
2208 }
2209 }
2210 }
2211
2212 // -- Row 5: b/4 — bottom section ------------------------------------------
2213 // Left: BT.709 Yellow, Cyan, Green (each c/3 wide)
2214 // PLUGE: f(0%) | g(-2%) | h(0%) | g(+2%) | h(0%) | g(+4%) | i(0%) | j(white) | k(0%)
2215 // Right: BT.709 Magenta, Red, Blue (each c/3 wide)
2216 // Full range PQ: no -2% sub-black level (clamped to 0% per Table 4 footnote).
2217 // All PLUGE values pre-computed in t_pluge[0..6].
2218 {
2219 const int pluge_x0 = 3 * bt709_bar_w;
2220 const int pluge_f_end = pluge_x0 + f_pluge;
2221 const int seg[5] = { g_pluge, h_pluge, g_pluge, h_pluge, g_pluge };
2222 // pluge indices: 0=f(0%), 1=-2%, 2=0%, 3=+2%, 4=0%, 5=+4%, 6=0%
2223 int pluge_x = pluge_f_end;
2224 int seg_ends[5];
2225 for (int s = 0; s < 5; ++s) { pluge_x += seg[s]; seg_ends[s] = pluge_x; }
2226 const int i_start = pluge_x;
2227 const int j_start = i_start + i_span;
2228 const int k_start = j_start + j_span;
2229 const int k_end = k_start + k_span;
2230
2231 for (; y < h; ++y) {
2232 fill_bar_px(0, bt709_bar_w, t_b709[0]); // BT.709 Yellow
2233 fill_bar_px(bt709_bar_w, 2 * bt709_bar_w, t_b709[1]); // BT.709 Cyan
2234 fill_bar_px(2 * bt709_bar_w, 3 * bt709_bar_w, t_b709[2]); // BT.709 Green
2235 // f block: 0% black
2236 fill_grey_px(pluge_x0, pluge_f_end, t_pluge[0]);
2237 // 5 PLUGE segments: -2%(g), 0%(h), +2%(g), 0%(h), +4%(g)
2238 {
2239 int px = pluge_f_end;
2240 for (int s = 0; s < 5; ++s) { fill_grey_px(px, seg_ends[s], t_pluge[1 + s]); px = seg_ends[s]; }
2241 }
2242 // i: 0% black j: 75%/58% white (primary white = t_bpri[0]) k: 0% black
2243 fill_grey_px(i_start, j_start, t_black);
2244 fill_bar_px(j_start, k_start, t_bpri[0]); // 75%/58% white patch
2245 fill_grey_px(k_start, k_end, t_black);
2246 fill_bar_px(k_end, k_end + bt709_bar_w, t_b709[3]); // BT.709 Magenta
2247 fill_bar_px(k_end + bt709_bar_w, k_end + 2 * bt709_bar_w, t_b709[4]); // BT.709 Red
2248 fill_bar_px(k_end + 2 * bt709_bar_w, w, t_b709[5]); // BT.709 Blue
2249 pYG += pitchY; pUB += pitchUV; pVR += pitchUV;
2250 }
2251 }
2252 } // draw_colorbarsUHD_444_rgb
2253
2254
2255 class ColorBars : public IClip {
2256 VideoInfo vi;
2257 PVideoFrame frame;
2258 SFLOAT *audio;
2259 unsigned nsamples;
2260 bool staticframes; // false: re-draw each frame. Defaults to true (one pre-computed static frame is served).
2261 int subtype; // for UHD
2262
2263 enum { Hz = 440 } ;
2264
2265 public:
2266
2267 ~ColorBars() {
2268 delete audio;
2269 }
2270
2271 ColorBars(int w, int h, const char* pixel_type, bool _staticframes, int type, int _subtype, IScriptEnvironment* env) : subtype(_subtype) {
2272 memset(&vi, 0, sizeof(VideoInfo));
2273 staticframes = _staticframes;
2274 vi.width = w;
2275 vi.height = h;
2276 vi.fps_numerator = 30000;
2277 vi.fps_denominator = 1001;
2278 vi.num_frames = 107892; // 1 hour
2279 int i_pixel_type = GetPixelTypeFromName(pixel_type);
2280 vi.pixel_type = i_pixel_type;
2281 int bits_per_pixel = vi.BitsPerComponent();
2282
2283 const bool IsColorbars = (type == 0);
2284 const bool IsColorbarsHD = (type == 1);
2285 const bool IsColorbarsUHD = (type == 2);
2286
2287 if (IsColorbarsUHD) {
2288 if (!vi.Is444() && !vi.IsPlanarRGB())
2289 env->ThrowError("ColorBarsUHD: pixel_type must be a planar RGB or 4:4:4 video format");
2290 }
2291 else if (IsColorbarsHD) { // ColorbarsHD
2292 if (!vi.Is444())
2293 env->ThrowError("ColorBarsHD: pixel_type must be \"YV24\" or other 4:4:4 video format");
2294 }
2295 else if (vi.IsRGB32() || vi.IsRGB64() || vi.IsRGB24() || vi.IsRGB48()) {
2296 // no special check
2297 }
2298 else if (vi.IsRGB() && vi.IsPlanar()) { // planar RGB
2299 // no special check
2300 }
2301 else if (vi.IsYUY2()) { // YUY2
2302 if (w & 1)
2303 env->ThrowError("ColorBars: YUY2 width must be even!");
2304 }
2305 else if (vi.Is420()) { // 4:2:0
2306 if ((w & 1) || (h & 1))
2307 env->ThrowError("ColorBars: for 4:2:0 both height and width must be even!");
2308 }
2309 else if (vi.Is422()) { // 4:2:2
2310 if (w & 1)
2311 env->ThrowError("ColorBars: for 4:2:2 width must be even!");
2312 }
2313 else if (vi.IsYV411()) { // 4:1:1
2314 if (w & 3)
2315 env->ThrowError("ColorBars: for 4:1:1 width must be divisible by 4!");
2316 }
2317 else if (vi.Is444()) { // 4:4:4
2318 // no special check
2319 }
2320 else {
2321 env->ThrowError("ColorBars: this pixel_type not supported");
2322 }
2323 vi.sample_type = SAMPLE_FLOAT;
2324 vi.nchannels = 2;
2325 vi.audio_samples_per_second = 48000;
2326 vi.num_audio_samples = vi.AudioSamplesFromFrames(vi.num_frames);
2327
2328 frame = env->NewVideoFrame(vi);
2329
2330 uint32_t* p = (uint32_t*)frame->GetWritePtr();
2331
2332 // set basic frame properties
2333 auto props = env->getFramePropsRW(frame);
2334 int theMatrix;
2335 int theColorRange;
2336 int theTransfer;
2337 int thePrimaries;
2338 if (IsColorbarsUHD) {
2339 // subtypes 0,1,2: HLG narrow, PQ narrow, PQ full range (BT.2111-3)
2340
2341 // ColorBarsUHD RGB or YUV444
2342 // For YUV BT.2100 uses Y'CbCr NCL (identical Kr/Kb to BT.2020 NCL).
2343 theMatrix = vi.IsRGB() ? Matrix_e::AVS_MATRIX_RGB : Matrix_e::AVS_MATRIX_BT2020_NCL; // = 0/9
2344 // HLG narrow (0) and PQ narrow (1) use limited swing (64–940 at 10-bit).
2345 // PQ full range (subtype 2) uses full swing (0–1023 at 10-bit).
2346 // Note: AVS_COLORRANGE_LIMITED = 1, AVS_COLORRANGE_FULL = 0 in the Compat enum
2347 // (inverted from the ITU-T H.265 / AVS_RANGE_* convention).
2348 // Float output is always full range, tools usually do not support Avisynth's style "limited float"
2349 theColorRange = (subtype == 2 || bits_per_pixel == 32)
2350 ? ColorRange_Compat_e::AVS_COLORRANGE_FULL
2351 : ColorRange_Compat_e::AVS_COLORRANGE_LIMITED;
2352 // Transfer: HLG (subtype 0) = ARIB B67 (= 18), PQ (subtypes 1/2) = ST2084 (= 16).
2353 theTransfer = (subtype == 0) ? Transfer_e::AVS_TRANSFER_ARIB_B67 : Transfer_e::AVS_TRANSFER_ST2084;
2354 thePrimaries = Primaries_e::AVS_PRIMARIES_BT2020;
2355 }
2356 else if (IsColorbarsHD) {
2357 // ColorBarsHD 444 only
2358 theMatrix = Matrix_e::AVS_MATRIX_BT709;
2359 theColorRange = ColorRange_Compat_e::AVS_COLORRANGE_LIMITED;
2360 theTransfer = Transfer_e::AVS_TRANSFER_BT709; // = 1
2361 thePrimaries = Primaries_e::AVS_PRIMARIES_BT709; // = 1
2362 }
2363 else {
2364 // ColorBars RGB or YUV
2365 // RGB output has no YCbCr matrix, but still carries BT.601 (ST170-M) primaries and transfer.
2366 theMatrix = vi.IsRGB() ? Matrix_e::AVS_MATRIX_RGB : Matrix_e::AVS_MATRIX_ST170_M;
2367 // Studio RGB: limited!
2368 // Unlike UHD, float is Avisynth's "limited float"
2369 theColorRange = vi.IsRGB() ? ColorRange_Compat_e::AVS_COLORRANGE_LIMITED : ColorRange_Compat_e::AVS_COLORRANGE_LIMITED;
2370 theTransfer = Transfer_e::AVS_TRANSFER_BT601; // = 6, same gamma curve as BT.709
2371 thePrimaries = Primaries_e::AVS_PRIMARIES_ST170_M; // = 6, BT.601-525 (NTSC)
2372 }
2373 update_Matrix_and_ColorRange(props, theMatrix, theColorRange, env);
2374 update_Transfer_and_Primaries(props, theTransfer, thePrimaries, env);
2375
2376 if (IsColorbarsUHD) { // ColorbarsUHD
2377 // UHD colorbars BT.2111-3 (05/2025)
2378 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2111-3-202505-I!!PDF-E.pdf
2379 // RECOMMENDATION ITU-R BT.2111-3
2380 // Specification of color bar test pattern for high dynamic range television systems (2017-2019-2020-2025)
2381 // subtype=0: hybrid log-gamma (HLG) system with narrow range coding
2382 // subtype=1: perceptual quantization (PQ) system with narrow range coding
2383 // subtype=2: PQ system with full range coding
2384 const bool isRGB = vi.IsRGB();
2385 BYTE* p1 = (BYTE*)frame->GetWritePtr(isRGB ? PLANAR_G : PLANAR_Y);
2386 BYTE* p2 = (BYTE*)frame->GetWritePtr(isRGB ? PLANAR_B : PLANAR_U);
2387 BYTE* p3 = (BYTE*)frame->GetWritePtr(isRGB ? PLANAR_R : PLANAR_V);
2388 const int pitch1 = frame->GetPitch(isRGB ? PLANAR_G : PLANAR_Y);
2389 const int pitch23 = frame->GetPitch(isRGB ? PLANAR_G : PLANAR_U); // all pitches equal in planar RGB
2390
2391 if (bits_per_pixel == 8) {
2392 if (isRGB)
2393 draw_colorbarsUHD_444_rgb<uint8_t, true>(p1, p2, p3, pitch1, pitch23, w, h, 8, subtype);
2394 else
2395 draw_colorbarsUHD_444_rgb<uint8_t, false>(p1, p2, p3, pitch1, pitch23, w, h, 8, subtype);
2396 }
2397 else if (bits_per_pixel <= 16) {
2398 if (isRGB)
2399 draw_colorbarsUHD_444_rgb<uint16_t, true>(p1, p2, p3, pitch1, pitch23, w, h, bits_per_pixel, subtype);
2400 else
2401 draw_colorbarsUHD_444_rgb<uint16_t, false>(p1, p2, p3, pitch1, pitch23, w, h, bits_per_pixel, subtype);
2402 }
2403 else if (bits_per_pixel == 32) {
2404 if (isRGB)
2405 draw_colorbarsUHD_444_rgb<float, true>(p1, p2, p3, pitch1, pitch23, w, h, 32, subtype);
2406 else
2407 draw_colorbarsUHD_444_rgb<float, false>(p1, p2, p3, pitch1, pitch23, w, h, 32, subtype);
2408 }
2409 }
2410 else if (IsColorbarsHD) { // ColorbarsHD
2411 // HD colorbars arib_std_b28
2412 // Rec709 yuv values
2413 BYTE* pY = (BYTE*)frame->GetWritePtr(PLANAR_Y);
2414 BYTE* pU = (BYTE*)frame->GetWritePtr(PLANAR_U);
2415 BYTE* pV = (BYTE*)frame->GetWritePtr(PLANAR_V);
2416 const int pitchY = frame->GetPitch(PLANAR_Y);
2417 const int pitchUV = frame->GetPitch(PLANAR_U);
2418
2419 if (bits_per_pixel == 8)
2420 draw_colorbarsHD_444<uint8_t>(pY, pU, pV, pitchY, pitchUV, w, h, 8);
2421 else if (bits_per_pixel <= 16)
2422 draw_colorbarsHD_444<uint16_t>(pY, pU, pV, pitchY, pitchUV, w, h, bits_per_pixel);
2423 else if (bits_per_pixel == 32)
2424 draw_colorbarsHD_444<float>(pY, pU, pV, pitchY, pitchUV, w, h, 32);
2425 }
2426 else if (IsColorbars) {
2427 // Rec. ITU-R BT.801-1
2428 // "ColorBars" pattern is defined in Rec. ITU-R BT.801-1, with studio RGB values that are then converted to YUV for YUV formats.
2429 // Optional YUV output calculation uses 170_ST (601) independent from the actual frame dimensions.
2430 if (vi.IsRGB() && vi.IsPlanar()) {
2431 BYTE* pG = (BYTE*)frame->GetWritePtr(PLANAR_G);
2432 BYTE* pB = (BYTE*)frame->GetWritePtr(PLANAR_B);
2433 BYTE* pR = (BYTE*)frame->GetWritePtr(PLANAR_R);
2434 const int pitch = frame->GetPitch(PLANAR_G);
2435
2436 if (bits_per_pixel == 8)
2437 draw_colorbars_rgbp<uint8_t>(pR, pG, pB, pitch, w, h, 8);
2438 else if (bits_per_pixel <= 16)
2439 draw_colorbars_rgbp<uint16_t>(pR, pG, pB, pitch, w, h, bits_per_pixel);
2440 else if (bits_per_pixel == 32)
2441 draw_colorbars_rgbp<float>(pR, pG, pB, pitch, w, h, 32);
2442 }
2443 else if (vi.IsRGB32() || vi.IsRGB64()) {
2444 const int pitch = frame->GetPitch() / 4;
2445 switch (bits_per_pixel) {
2446 case 8: draw_colorbars_rgb3264<uint8_t>((uint8_t*)p, pitch, w, h); break;
2447 case 16: draw_colorbars_rgb3264<uint16_t>((uint8_t*)p, pitch, w, h); break;
2448 }
2449 }
2450 else if (vi.IsRGB24() || vi.IsRGB48()) {
2451 const int pitch = frame->GetPitch();
2452 switch (bits_per_pixel) {
2453 case 8: draw_colorbars_rgb2448<uint8_t>((uint8_t*)p, pitch, w, h); break;
2454 case 16: draw_colorbars_rgb2448<uint16_t>((uint8_t*)p, pitch, w, h); break;
2455 }
2456 }
2457 else if (vi.IsYUY2()) {
2458 // YUY2 is a packed 4:2:2 format: Y0 U0 Y1 V0 (alternating luma/chroma samples)
2459 // We treat it as planar internally, then pack the results
2460 const int pitch = frame->GetPitch();
2461 uint8_t* dst = frame->GetWritePtr();
2462
2463 // Allocate temporary planar buffers for 8-bit YUV
2464 std::vector<uint8_t> tempY(w * h);
2465 std::vector<uint8_t> tempU((w >> 1) * h);
2466 std::vector<uint8_t> tempV((w >> 1) * h);
2467
2468 // Use the unified YUV drawing function to generate planar data
2469 draw_colorbars_yuv<uint8_t, false, true, false>(
2470 tempY.data(), tempU.data(), tempV.data(),
2471 w, w >> 1, w, h, 8
2472 );
2473
2474 // Pack planar YUV 4:2:2 into YUY2 format (Y0 U0 Y1 V0)
2475 for (int y = 0; y < h; ++y) {
2476 const uint8_t* srcY = tempY.data() + y * w;
2477 const uint8_t* srcU = tempU.data() + y * (w >> 1);
2478 const uint8_t* srcV = tempV.data() + y * (w >> 1);
2479 uint8_t* dstRow = dst + y * pitch;
2480
2481 for (int x = 0; x < w >> 1; ++x) {
2482 dstRow[x * 4 + 0] = srcY[x * 2 + 0]; // Y0
2483 dstRow[x * 4 + 1] = srcU[x]; // U0
2484 dstRow[x * 4 + 2] = srcY[x * 2 + 1]; // Y1
2485 dstRow[x * 4 + 3] = srcV[x]; // V0
2486 }
2487 }
2488 }
2489 if (vi.Is444()) {
2490 BYTE* pY = (BYTE*)frame->GetWritePtr(PLANAR_Y);
2491 BYTE* pU = (BYTE*)frame->GetWritePtr(PLANAR_U);
2492 BYTE* pV = (BYTE*)frame->GetWritePtr(PLANAR_V);
2493 const int pitchY = frame->GetPitch(PLANAR_Y);
2494 const int pitchUV = frame->GetPitch(PLANAR_U);
2495 if (bits_per_pixel == 8)
2496 draw_colorbars_yuv<uint8_t, false, false, false>(pY, pU, pV, pitchY, pitchUV, w, h, 8);
2497 else if (bits_per_pixel <= 16)
2498 draw_colorbars_yuv<uint16_t, false, false, false>(pY, pU, pV, pitchY, pitchUV, w, h, bits_per_pixel);
2499 else
2500 draw_colorbars_yuv<float, false, false, false>(pY, pU, pV, pitchY, pitchUV, w, h, 32);
2501 }
2502 else if (vi.Is420()) {
2503 BYTE* pY = (BYTE*)frame->GetWritePtr(PLANAR_Y);
2504 BYTE* pU = (BYTE*)frame->GetWritePtr(PLANAR_U);
2505 BYTE* pV = (BYTE*)frame->GetWritePtr(PLANAR_V);
2506 const int pitchY = frame->GetPitch(PLANAR_Y);
2507 const int pitchUV = frame->GetPitch(PLANAR_U);
2508 if (bits_per_pixel == 8)
2509 draw_colorbars_yuv<uint8_t, true, false, false>(pY, pU, pV, pitchY, pitchUV, w, h, 8);
2510 else if (bits_per_pixel <= 16)
2511 draw_colorbars_yuv<uint16_t, true, false, false>(pY, pU, pV, pitchY, pitchUV, w, h, bits_per_pixel);
2512 else
2513 draw_colorbars_yuv<float, true, false, false>(pY, pU, pV, pitchY, pitchUV, w, h, 32);
2514 }
2515 else if (vi.Is422()) {
2516 BYTE* pY = (BYTE*)frame->GetWritePtr(PLANAR_Y);
2517 BYTE* pU = (BYTE*)frame->GetWritePtr(PLANAR_U);
2518 BYTE* pV = (BYTE*)frame->GetWritePtr(PLANAR_V);
2519 const int pitchY = frame->GetPitch(PLANAR_Y);
2520 const int pitchUV = frame->GetPitch(PLANAR_U);
2521 if (bits_per_pixel == 8)
2522 draw_colorbars_yuv<uint8_t, false, true, false>(pY, pU, pV, pitchY, pitchUV, w, h, 8);
2523 else if (bits_per_pixel <= 16)
2524 draw_colorbars_yuv<uint16_t, false, true, false>(pY, pU, pV, pitchY, pitchUV, w, h, bits_per_pixel);
2525 else
2526 draw_colorbars_yuv<float, false, true, false>(pY, pU, pV, pitchY, pitchUV, w, h, 32);
2527 }
2528 else if (vi.IsYV411()) {
2529 BYTE* pY = (BYTE*)frame->GetWritePtr(PLANAR_Y);
2530 BYTE* pU = (BYTE*)frame->GetWritePtr(PLANAR_U);
2531 BYTE* pV = (BYTE*)frame->GetWritePtr(PLANAR_V);
2532 const int pitchY = frame->GetPitch(PLANAR_Y);
2533 const int pitchUV = frame->GetPitch(PLANAR_U);
2534 // YV411 is 8-bit only
2535 draw_colorbars_yuv<uint8_t, false, false, true>(pY, pU, pV, pitchY, pitchUV, w, h, 8);
2536 }
2537 } // "ColorBars" pattern generation
2538 else {
2539 // future other ColorBars types can be added here
2540 }
2541
2542 // Alpha cnannel - if any - is common. RGB32/64 has already filled alpha.
2543 if (vi.IsYUVA() || vi.IsPlanarRGBA()) {
2544 // initialize planar alpha planes with zero (no transparency), like RGB32 does
2545 BYTE* dstp = frame->GetWritePtr(PLANAR_A);
2546 int rowsize = frame->GetRowSize(PLANAR_A);
2547 int pitch = frame->GetPitch(PLANAR_A);
2548 int height = frame->GetHeight(PLANAR_A);
2549 switch (bits_per_pixel) {
2550 case 8: fill_plane<uint8_t>(dstp, height, rowsize, pitch, 0); break;
2551 case 10: case 12: case 14: case 16: fill_plane<uint16_t>(dstp, height, rowsize, pitch, 0); break;
2552 case 32: fill_plane<float>(dstp, height, rowsize, pitch, 0.0f); break;
2553 }
2554 }
2555
2556 // Generate Audio buffer
2557 {
2558 unsigned x = vi.audio_samples_per_second, y = Hz;
2559 while (y) { // find gcd
2560 unsigned t = x % y; x = y; y = t;
2561 }
2562 nsamples = vi.audio_samples_per_second / x; // 1200
2563 const unsigned ncycles = Hz / x; // 11
2564
2565 audio = new(std::nothrow) SFLOAT[nsamples];
2566 if (!audio)
2567 env->ThrowError("ColorBars: insufficient memory");
2568
2569 const double add_per_sample = ncycles / (double)nsamples;
2570 double second_offset = 0.0;
2571 for (unsigned i = 0; i < nsamples; i++) {
2572 audio[i] = (SFLOAT)sin(PI * 2.0 * second_offset);
2573 second_offset += add_per_sample;
2574 }
2575 }
2576 }
2577
2578 // By the new "staticframes" parameter: colorbars we generate (copy) real new frames instead of a ready-to-use static one
2579 PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env)
2580 {
2581 AVS_UNUSED(n);
2582 if (staticframes)
2583 return frame; // original default method returns precomputed static frame.
2584 else {
2585 PVideoFrame result = env->NewVideoFrameP(vi, &frame);
2586 // BitBlts are safe to call on all planes, planes with zero-size height or row-size are ignored.
2587 env->BitBlt(result->GetWritePtr(), result->GetPitch(), frame->GetReadPtr(), frame->GetPitch(), frame->GetRowSize(), frame->GetHeight());
2588 env->BitBlt(result->GetWritePtr(PLANAR_V), result->GetPitch(PLANAR_V), frame->GetReadPtr(PLANAR_V), frame->GetPitch(PLANAR_V), frame->GetRowSize(PLANAR_V), frame->GetHeight(PLANAR_V));
2589 env->BitBlt(result->GetWritePtr(PLANAR_U), result->GetPitch(PLANAR_U), frame->GetReadPtr(PLANAR_U), frame->GetPitch(PLANAR_U), frame->GetRowSize(PLANAR_U), frame->GetHeight(PLANAR_U));
2590 env->BitBlt(result->GetWritePtr(PLANAR_A), result->GetPitch(PLANAR_A), frame->GetReadPtr(PLANAR_A), frame->GetPitch(PLANAR_A), frame->GetRowSize(PLANAR_A), frame->GetHeight(PLANAR_A));
2591 return result;
2592 }
2593 }
2594
2595 bool __stdcall GetParity(int n) {
2596 AVS_UNUSED(n);
2597 return false;
2598 }
2599 const VideoInfo& __stdcall GetVideoInfo() { return vi; }
2600 int __stdcall SetCacheHints(int cachehints,int frame_range)
2601 {
2602 AVS_UNUSED(frame_range);
2603 switch (cachehints)
2604 {
2605 case CACHE_GET_MTMODE:
2606 return MT_NICE_FILTER;
2607 case CACHE_DONT_CACHE_ME:
2608 return 1;
2609 default:
2610 return 0;
2611 }
2612 };
2613
2614 void FillAudioZeros(void* buf, int start_offset, int count) {
2615 const int bps = vi.BytesPerAudioSample();
2616 unsigned char* byte_buf = (unsigned char*)buf;
2617 memset(byte_buf + start_offset * bps, 0, count * bps);
2618 }
2619
2620 void __stdcall GetAudio(void* buf, int64_t start, int64_t count, IScriptEnvironment* env) {
2621 AVS_UNUSED(env);
2622 #if 1
2623 // This filter is non-cached so we guard against negative start and overread, like in Cache::GetAudio
2624 if ((start + count <= 0) || (start >= vi.num_audio_samples)) {
2625 // Completely skip.
2626 FillAudioZeros(buf, 0, (int)count);
2627 count = 0;
2628 return;
2629 }
2630
2631 if (start < 0) { // Partial initial skip
2632 FillAudioZeros(buf, 0, (int)-start); // Fill all samples before 0 with silence.
2633 count += start; // Subtract start bytes from count.
2634 buf = ((BYTE*)buf) - (int)(start * vi.BytesPerAudioSample());
2635 start = 0;
2636 }
2637
2638 if (start + count > vi.num_audio_samples) { // Partial ending skip
2639 FillAudioZeros(buf, (int)(vi.num_audio_samples - start), (int)(count - (vi.num_audio_samples - start))); // Fill end samples
2640 count = (vi.num_audio_samples - start);
2641 }
2642
2643 const int d_mod = vi.audio_samples_per_second*2;
2644 float* samples = (float*)buf;
2645
2646 unsigned j = (unsigned)(start % nsamples);
2647 for (int i=0;i<count;i++) {
2648 samples[i*2]=audio[j];
2649 if (((start+i)%d_mod)>vi.audio_samples_per_second) {
2650 samples[i*2+1]=audio[j];
2651 } else {
2652 samples[i*2+1]=0;
2653 }
2654 if (++j >= nsamples) j = 0;
2655 }
2656 #else
2657 int64_t Hz=440;
2658 // Calculate what start equates in cycles.
2659 // This is the number of cycles (rounded down) that has already been taken.
2660 int64_t startcycle = (start*Hz) / vi.audio_samples_per_second;
2661
2662 // Move offset down - this is to avoid float rounding errors
2663 int start_offset = (int)(start - ((startcycle * vi.audio_samples_per_second) / Hz));
2664
2665 double add_per_sample=Hz/(double)vi.audio_samples_per_second;
2666 double second_offset=((double)start_offset*add_per_sample);
2667 int d_mod=vi.audio_samples_per_second*2;
2668 float* samples = (float*)buf;
2669
2670 for (int i=0;i<count;i++) {
2671 samples[i*2]=(SFLOAT)sin(PI * 2.0 * second_offset);
2672 if (((start+i)%d_mod)>vi.audio_samples_per_second) {
2673 samples[i*2+1]=samples[i*2];
2674 } else {
2675 samples[i*2+1]=0;
2676 }
2677 second_offset+=add_per_sample;
2678 }
2679 #endif
2680 }
2681
2682 static AVSValue __cdecl Create(AVSValue args, void* _type, IScriptEnvironment* env) {
2683 const int type = (int)(size_t)_type;
2684 enum { typeColorBars = 0, typeColorBarsHD = 1, typeColorBarsUHD = 2 };
2685 // 0: ColorBars, 1: ColorBarsHD, 2: ColorBarsUHD
2686
2687 // { "ColorBars", BUILTIN_FUNC_PREFIX, "[width]i[height]i[pixel_type]s[staticframes]b", ColorBars::Create, (void*)0 },
2688 // { "ColorBarsHD", BUILTIN_FUNC_PREFIX, "[width]i[height]i[pixel_type]s[staticframes]b", ColorBars::Create, (void*)1 },
2689 // { "ColorBarsUHD", BUILTIN_FUNC_PREFIX, "[width]i[height]i[pixel_type]s[staticframes]b[mode]i", ColorBars::Create, (void*)2 }, // BT-2111-3
2690
2691 bool staticframes = args[3].AsBool(true);
2692
2693 const int default_width =
2694 type == typeColorBarsUHD ? 3840 :
2695 type == typeColorBarsHD ? 1288 :
2696 640; // typeColorBars
2697 int width = args[0].AsInt(default_width);
2698 // for UHD the default height is adaptive
2699 // ColorBarsUHD width may imply the height:
2700 // 1920x1080, 3840x2160 or 7680x4320, but user can override it. For non-UHD types the default height is fixed.
2701 const int default_height =
2702 type == typeColorBarsUHD ? (width * 2160) / 3840 :
2703 type == typeColorBarsHD ? 720 :
2704 480; // typeColorBars
2705
2706 const char* default_pixel_type =
2707 type == typeColorBarsUHD ? "RGBP10" :
2708 type == typeColorBarsHD ? "YV24" :
2709 "RGB32"; // typeColorBars
2710
2711 int UHD_subType = 0;
2712 if (type == typeColorBarsUHD) {
2713 UHD_subType = args[4].AsInt(0);
2714 }
2715
2716 PClip clip = new ColorBars(width,
2717 args[1].AsInt(default_height),
2718 args[2].AsString(default_pixel_type),
2719 staticframes,
2720 type,
2721 UHD_subType,
2722 env);
2723 // wrap in OnCPU to support multi devices
2724 AVSValue arg[2]{ clip, 1 }; // prefetch=1: enable cache but not thread
2725 AVSValue ret = env->Invoke("OnCPU", AVSValue(arg, 2));
2726 if (staticframes) {
2727 return new SingleFrame(ret.AsClip());
2728 }
2729 return ret;
2730 }
2731 };
2732
2733 /********************************************************************
2734 ********************************************************************/
2735
2736 #ifdef AVS_WINDOWS
2737 // AviSource is Windows-only, because it explicitly relies on Video for Windows
2738 AVSValue __cdecl Create_SegmentedSource(AVSValue args, void* use_directshow, IScriptEnvironment* env) {
2739 bool bAudio = !use_directshow && args[1].AsBool(true);
2740 const char* pixel_type = 0;
2741 const char* fourCC = 0;
2742 int vtrack = 0;
2743 int atrack = 0;
2744 bool utf8;
2745 const int inv_args_count = args.ArraySize();
2746 AVSValue inv_args[9];
2747 if (!use_directshow) {
2748 pixel_type = args[2].AsString("");
2749 fourCC = args[3].AsString("");
2750 vtrack = args[4].AsInt(0);
2751 atrack = args[5].AsInt(0);
2752 utf8 = args[6].AsBool(false);
2753 }
2754 else {
2755 for (int i=1; i<inv_args_count ;i++)
2756 inv_args[i] = args[i];
2757 }
2758 args = args[0];
2759 PClip result = 0;
2760 const char* error_msg=0;
2761 for (int i = 0; i < args.ArraySize(); ++i) {
2762 char basename[260];
2763 strcpy(basename, args[i].AsString());
2764 char* extension = strrchr(basename, '.');
2765 if (extension)
2766 *extension++ = 0;
2767 else
2768 extension[0] = 0;
2769 for (int j = 0; j < 100; ++j) {
2770 char filename[260];
2771 wsprintf(filename, "%s.%02d.%s", basename, j, extension);
2772 if (GetFileAttributes(filename) != (DWORD)-1) { // check if file exists
2773 PClip clip;
2774 try {
2775 if (use_directshow) {
2776 inv_args[0] = filename;
2777 clip = env->Invoke("DirectShowSource",AVSValue(inv_args, inv_args_count)).AsClip(); // no utf8 yet
2778 } else {
2779 clip = (IClip*)(new AVISource(filename, bAudio, pixel_type, fourCC, vtrack, atrack, AVISource::MODE_NORMAL, utf8, env));
2780 }
2781 AVSValue arg[3] = { result, clip, 0 };
2782 result = !result ? clip : env->Invoke("UnalignedSplice", AVSValue(arg, 3)).AsClip();
2783 } catch (const AvisynthError &e) {
2784 error_msg=e.msg;
2785 }
2786 }
2787 }
2788 }
2789 if (!result) {
2790 if (!error_msg) {
2791 env->ThrowError("Segmented%sSource: no files found!", use_directshow ? "DirectShow" : "AVI");
2792 } else {
2793 env->ThrowError("Segmented%sSource: decompressor returned error:\n%s!", use_directshow ? "DirectShow" : "AVI",error_msg);
2794 }
2795 }
2796 return result;
2797 }
2798 #endif
2799
2800 /**********************************************************
2801 * TONE *
2802 **********************************************************/
2803 class SampleGenerator {
2804 public:
2805 SampleGenerator() {}
2806 virtual SFLOAT getValueAt(double where) {
2807 AVS_UNUSED(where);
2808 return 0.0f;}
2809 };
2810
2811 class SineGenerator : public SampleGenerator {
2812 public:
2813 SineGenerator() {}
2814 SFLOAT getValueAt(double where) {return (SFLOAT)sin(PI * where * 2.0);}
2815 };
2816
2817
2818 class NoiseGenerator : public SampleGenerator {
2819 public:
2820 NoiseGenerator() {
2821 srand( (unsigned)time( NULL ) );
2822 }
2823
2824 SFLOAT getValueAt(double where) {
2825 AVS_UNUSED(where);
2826 return (float) rand()*(2.0f/RAND_MAX) -1.0f;}
2827 };
2828
2829 class SquareGenerator : public SampleGenerator {
2830 public:
2831 SquareGenerator() {}
2832
2833 SFLOAT getValueAt(double where) {
2834 if (where<=0.5) {
2835 return 1.0f;
2836 } else {
2837 return -1.0f;
2838 }
2839 }
2840 };
2841
2842 class TriangleGenerator : public SampleGenerator {
2843 public:
2844 TriangleGenerator() {}
2845
2846 SFLOAT getValueAt(double where) {
2847 if (where<=0.25) {
2848 return (SFLOAT)(where*4.0);
2849 } else if (where<=0.75) {
2850 return (SFLOAT)((-4.0*(where-0.50)));
2851 } else {
2852 return (SFLOAT)((4.0*(where-1.00)));
2853 }
2854 }
2855 };
2856
2857 class SawtoothGenerator : public SampleGenerator {
2858 public:
2859 SawtoothGenerator() {}
2860
2861 SFLOAT getValueAt(double where) {
2862 return (SFLOAT)(2.0*(where-0.5));
2863 }
2864 };
2865
2866
2867 class Tone : public IClip {
2868 VideoInfo vi;
2869 SampleGenerator *s;
2870 const double freq; // Frequency in Hz
2871 const double samplerate; // Samples per second
2872 const int ch; // Number of channels
2873 const double add_per_sample; // How much should we add per sample in seconds
2874 const float level;
2875
2876 public:
2877
2878 Tone(double _length, double _freq, int _samplerate, int _ch, const char* _type, float _level, IScriptEnvironment* env):
2879 freq(_freq), samplerate(_samplerate), ch(_ch), add_per_sample(_freq/_samplerate), level(_level) {
2880 memset(&vi, 0, sizeof(VideoInfo));
2881 vi.sample_type = SAMPLE_FLOAT;
2882 vi.nchannels = _ch;
2883 vi.audio_samples_per_second = _samplerate;
2884 vi.num_audio_samples=(int64_t)(_length*vi.audio_samples_per_second+0.5);
2885
2886 if (!lstrcmpi(_type, "Sine"))
2887 s = new SineGenerator();
2888 else if (!lstrcmpi(_type, "Noise"))
2889 s = new NoiseGenerator();
2890 else if (!lstrcmpi(_type, "Square"))
2891 s = new SquareGenerator();
2892 else if (!lstrcmpi(_type, "Triangle"))
2893 s = new TriangleGenerator();
2894 else if (!lstrcmpi(_type, "Sawtooth"))
2895 s = new SawtoothGenerator();
2896 else if (!lstrcmpi(_type, "Silence"))
2897 s = new SampleGenerator();
2898 else
2899 env->ThrowError("Tone: Type was not recognized!");
2900 }
2901
2902 void __stdcall GetAudio(void* buf, int64_t start, int64_t count, IScriptEnvironment* env) {
2903 AVS_UNUSED(env);
2904 // Where in the cycle are we in?
2905 const double cycle = (freq * start) / samplerate;
2906 double period_place = cycle - floor(cycle);
2907
2908 SFLOAT* samples = (SFLOAT*)buf;
2909
2910 for (int i=0;i<count;i++) {
2911 SFLOAT v = s->getValueAt(period_place) * level;
2912 for (int o=0;o<ch;o++) {
2913 samples[o+i*ch] = v;
2914 }
2915 period_place += add_per_sample;
2916 if (period_place >= 1.0)
2917 period_place -= floor(period_place);
2918 }
2919 }
2920
2921 static AVSValue __cdecl Create(AVSValue args, void*, IScriptEnvironment* env)
2922 {
2923 return new Tone(args[0].AsFloat(10.0f), args[1].AsFloat(440.0f), args[2].AsInt(48000),
2924 args[3].AsInt(2), args[4].AsString("Sine"), args[5].AsFloatf(1.0f), env);
2925 }
2926
2927 PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) {
2928 AVS_UNUSED(n);
2929 AVS_UNUSED(env);
2930 return NULL; }
2931 const VideoInfo& __stdcall GetVideoInfo() { return vi; }
2932 bool __stdcall GetParity(int n) {
2933 AVS_UNUSED(n);
2934 return false; }
2935 int __stdcall SetCacheHints(int cachehints,int frame_range) {
2936 AVS_UNUSED(cachehints);
2937 AVS_UNUSED(frame_range);
2938 return 0; };
2939
2940 };
2941
2942
2943 AVSValue __cdecl Create_Version(AVSValue args, void*, IScriptEnvironment* env) {
2944 // 0 1 2 3 4
2945 // [length]i[width]i[height]i[pixel_type]s[clip]c
2946 VideoInfo vi_default;
2947
2948 int i_pixel_type = VideoInfo::CS_BGR24;
2949
2950 const bool has_clip = args[4].Defined();
2951 if (has_clip) {
2952 // clip overrides
2953 vi_default = args[4].AsClip()->GetVideoInfo();
2954 i_pixel_type = vi_default.pixel_type;
2955 }
2956
2957 if (args[3].Defined()) {
2958 i_pixel_type = GetPixelTypeFromName(args[3].AsString());
2959 if (i_pixel_type == VideoInfo::CS_UNKNOWN)
2960 env->ThrowError("Version: invalid 'pixel_type'");
2961 }
2962
2963 int num_frames = args[0].AsInt(has_clip ? vi_default.num_frames : -1); // auto (240)
2964 int w = args[1].AsInt(has_clip ? vi_default.width : -1); // auto
2965 int h = args[2].AsInt(has_clip ? vi_default.height : -1); // auto
2966 const bool shrink = false;
2967 const int textcolor = 0xECF2BF;
2968 const int halocolor = 0;
2969 const int bgcolor = 0x404040;
2970
2971 const int fps_numerator = has_clip ? vi_default.fps_numerator :-1; // auto
2972 const int fps_denominator = has_clip ? vi_default.fps_denominator : -1; // auto
2973
2974 return Create_MessageClip(
2975 AVS_FULLVERSION AVS_DEVELOPMENT_BUILD AVS_DEVELOPMENT_BUILD_GIT AVS_COPYRIGHT_UTF8,
2976 w, h, i_pixel_type, shrink, textcolor, halocolor, bgcolor, fps_numerator, fps_denominator, num_frames,
2977 true, // utf8
2978 env);
2979 }
2980
2981
2982 extern const AVSFunction Source_filters[] = {
2983 #ifdef AVS_WINDOWS
2984 { "AVISource", BUILTIN_FUNC_PREFIX, "s+[audio]b[pixel_type]s[fourCC]s[vtrack]i[atrack]i[utf8]b", AVISource::Create, (void*) AVISource::MODE_NORMAL },
2985 { "AVIFileSource", BUILTIN_FUNC_PREFIX, "s+[audio]b[pixel_type]s[fourCC]s[vtrack]i[atrack]i[utf8]b", AVISource::Create, (void*) AVISource::MODE_AVIFILE },
2986 { "WAVSource", BUILTIN_FUNC_PREFIX, "s+[utf8]b", AVISource::Create, (void*) AVISource::MODE_WAV },
2987 { "OpenDMLSource", BUILTIN_FUNC_PREFIX, "s+[audio]b[pixel_type]s[fourCC]s[vtrack]i[atrack]i[utf8]b", AVISource::Create, (void*) AVISource::MODE_OPENDML },
2988 { "SegmentedAVISource", BUILTIN_FUNC_PREFIX, "s+[audio]b[pixel_type]s[fourCC]s[vtrack]i[atrack]i[utf8]b", Create_SegmentedSource, (void*)0 },
2989 { "SegmentedDirectShowSource", BUILTIN_FUNC_PREFIX,
2990 // args 0 1 2 3 4 5 6 7 8
2991 "s+[fps]f[seek]b[audio]b[video]b[convertfps]b[seekzero]b[timeout]i[pixel_type]s",
2992 Create_SegmentedSource, (void*)1 },
2993 // args 0 1 2 3 4 5 6 7 8 9 10 11 12
2994 #endif
2995 { "BlankClip", BUILTIN_FUNC_PREFIX, "[]c*[length]i[width]i[height]i[pixel_type]s[fps]f[fps_denominator]i[audio_rate]i[stereo]b[sixteen_bit]b[color]i[color_yuv]i[clip]c", Create_BlankClip },
2996 { "BlankClip", BUILTIN_FUNC_PREFIX, "[]c*[length]i[width]i[height]i[pixel_type]s[fps]f[fps_denominator]i[audio_rate]i[channels]i[sample_type]s[color]i[color_yuv]i[clip]c", Create_BlankClip },
2997 { "BlankClip", BUILTIN_FUNC_PREFIX, "[]c*[length]i[width]i[height]i[pixel_type]s[fps]f[fps_denominator]i[audio_rate]i[stereo]b[sixteen_bit]b[color]i[color_yuv]i[clip]c[colors]f+", Create_BlankClip },
2998 { "BlankClip", BUILTIN_FUNC_PREFIX, "[]c*[length]i[width]i[height]i[pixel_type]s[fps]f[fps_denominator]i[audio_rate]i[channels]i[sample_type]s[color]i[color_yuv]i[clip]c[colors]f+", Create_BlankClip },
2999 { "Blackness", BUILTIN_FUNC_PREFIX, "[]c*[length]i[width]i[height]i[pixel_type]s[fps]f[fps_denominator]i[audio_rate]i[stereo]b[sixteen_bit]b[color]i[color_yuv]i[clip]c", Create_BlankClip },
3000 { "Blackness", BUILTIN_FUNC_PREFIX, "[]c*[length]i[width]i[height]i[pixel_type]s[fps]f[fps_denominator]i[audio_rate]i[channels]i[sample_type]s[color]i[color_yuv]i[clip]c", Create_BlankClip },
3001 { "MessageClip", BUILTIN_FUNC_PREFIX, "s[width]i[height]i[shrink]b[text_color]i[halo_color]i[bg_color]i[utf8]b", Create_MessageClip },
3002 { "ColorBars", BUILTIN_FUNC_PREFIX, "[width]i[height]i[pixel_type]s[staticframes]b", ColorBars::Create, (void*)0 },
3003 { "ColorBarsHD", BUILTIN_FUNC_PREFIX, "[width]i[height]i[pixel_type]s[staticframes]b", ColorBars::Create, (void*)1 },
3004 { "ColorBarsUHD", BUILTIN_FUNC_PREFIX, "[width]i[height]i[pixel_type]s[staticframes]b[mode]i", ColorBars::Create, (void*)2 }, // BT-2111-3
3005 { "Tone", BUILTIN_FUNC_PREFIX, "[length]f[frequency]f[samplerate]i[channels]i[type]s[level]f", Tone::Create },
3006
3007 { "Version", BUILTIN_FUNC_PREFIX, "[length]i[width]i[height]i[pixel_type]s[clip]c", Create_Version },
3008
3009 { NULL }
3010 };
3011
3012 #undef XP_LAMBDA_CAPTURE_FIX
3013