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 |