UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
video_pattern_generator.cpp
Go to the documentation of this file.
1
14/*
15 * Copyright (c) 2005-2006 University of Glasgow
16 * Copyright (c) 2005-2024 CESNET
17 * All rights reserved.
18 *
19 * Redistribution and use in source and binary forms, with or without
20 * modification, is permitted provided that the following conditions
21 * are met:
22 *
23 * 1. Redistributions of source code must retain the above copyright
24 * notice, this list of conditions and the following disclaimer.
25 *
26 * 2. Redistributions in binary form must reproduce the above copyright
27 * notice, this list of conditions and the following disclaimer in the
28 * documentation and/or other materials provided with the distribution.
29 *
30 * 3. Neither the name of CESNET nor the names of its contributors may be
31 * used to endorse or promote products derived from this software without
32 * specific prior written permission.
33 *
34 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
35 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
36 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
37 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
38 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
39 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
40 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
41 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
42 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
43 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
44 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
45 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46 */
53#include <algorithm>
54#include <cassert>
55#include <cstdint>
56#include <cstring>
57#include <iostream>
58#include <memory>
59#include <random>
60#include <utility>
61#include <vector>
62
63#include "color.h"
64#include "debug.h"
65#include "pixfmt_conv.h"
66#include "ug_runtime_error.hpp"
67#include "utils/color_out.h"
68#include "utils/macros.h"
70#include "utils/text.h"
71#include "video.h"
74
75#define BLANK_USAGE "blank[=0x<AABBGGRR>]"
76#define MOD_NAME "[vid. patt. generator] "
77constexpr size_t headroom = 128; // headroom for cases when dst color_spec has wider block size
78constexpr int rg48_bpp = 6;
79
80
81using namespace std::string_literals;
82using std::copy;
83using std::cout;
84using std::default_random_engine;
85using std::exception;
86using std::for_each;
87using std::make_unique;
88using std::min;
89using std::stoi;
90using std::stoll;
91using std::string;
92using std::string_view;
93using std::to_string;
94using std::unique_ptr;
95using std::uniform_int_distribution;
96using std::vector;
97
98enum class generator_depth {
99 bits8,
100 bits16
101};
102
104 public:
105 static unique_ptr<image_pattern> create(string const &pattern, string const &params);
106 auto init(int width, int height, enum generator_depth depth) noexcept {
107 size_t data_len = width * height * rg48_bpp + headroom;
108 vector<unsigned char> out(data_len);
109 auto actual_bit_depth = fill(width, height, out.data());
110 if (depth == generator_depth::bits8 && actual_bit_depth == generator_depth::bits16) {
111 convert_rg48_to_rgba(width, height, out.data());
112 }
113 if (depth == generator_depth::bits16 && actual_bit_depth == generator_depth::bits8) {
114 convert_rgba_to_rg48(width, height, out.data());
115 }
116 return out;
117 }
118 virtual ~image_pattern() = default;
119 image_pattern() = default;
120 image_pattern(const image_pattern &) = delete;
124 private:
126 virtual enum generator_depth fill(int width, int height, unsigned char *data) = 0;
127
129 virtual void convert_rgba_to_rg48(int width, int height, unsigned char *data) {
130 for (int y = height - 1; y >= 0; --y) {
131 for (int x = width - 1; x >= 0; --x) {
132 unsigned char *in_pix = data + 4 * (y * width + x);
133 unsigned char *out_pix = data + 6 * (y * width + x);
134 unsigned char r = *in_pix++;
135 unsigned char g = *in_pix++;
136 unsigned char b = *in_pix++;
137 *out_pix++ = 0;
138 *out_pix++ = r;
139 *out_pix++ = 0;
140 *out_pix++ = g;
141 *out_pix++ = 0;
142 *out_pix++ = b;
143 }
144 }
145 }
147 virtual void convert_rg48_to_rgba(int width, int height, unsigned char *data) {
148 for (int y = 0; y < height; ++y) {
149 for (int x = 0; x < width; ++x) {
150 unsigned char *in_pix = data + 6 * (y * width + x);
151 unsigned char *out_pix = data + 4 * (y * width + x);
152 *out_pix++ = in_pix[1];
153 *out_pix++ = in_pix[3];
154 *out_pix++ = in_pix[5];
155 *out_pix++ = 0xFFU;
156 }
157 }
158 }
159};
160
162 public:
163 explicit image_pattern_bars(string const &init) {
164 if (init == "help"s) {
165 col() << "Testcard bar usage:\n\t" << SBOLD(SRED("-t testcard:pattern=bars") << "[=text]") << " - optionally annotate with text" << "\n";
166 throw 1;
167 }
168 annotate = init;
169 }
170 private:
171 enum generator_depth fill(int width, int height, unsigned char *data) override {
172 int col_num = 0;
173 int rect_size = COL_NUM;
174 struct testcard_rect r{};
175 struct testcard_pixmap pixmap{};
176 pixmap.w = width;
177 pixmap.h = height;
178 pixmap.data = data;
179 rect_size = (width + rect_size - 1) / rect_size;
180 for (int j = 0; j < height; j += rect_size) {
181 uint32_t grey = 0xFF010101U;
182 if (j == rect_size * 2) {
183 r.w = width;
184 r.h = rect_size / 4;
185 r.x = 0;
186 r.y = j;
187 testcard_fillRect(&pixmap, &r, 0xFFFFFFFFU);
188 r.h = rect_size - (rect_size * 3 / 4);
189 r.y = j + rect_size * 3 / 4;
190 testcard_fillRect(&pixmap, &r, 0xFF000000U);
191 }
192 for (int i = 0; i < width; i += rect_size) {
193 r.x = i;
194 r.y = j;
195 r.w = rect_size;
196 r.h = min<int>(rect_size, height - r.y);
197 LOG(LOG_LEVEL_DEBUG) << MOD_NAME << "Fill rect at " << r.x << "," << r.y << "\n";
198 if (j != rect_size * 2) {
199 testcard_fillRect(&pixmap, &r,
200 rect_colors[col_num]);
201 col_num = (col_num + 1) % COL_NUM;
202 } else {
203 r.h = rect_size / 2;
204 r.y += rect_size / 4;
205 testcard_fillRect(&pixmap, &r, grey);
206 grey += 0x00010101U * (255 / COL_NUM);
207 }
208 }
209 }
210 if (!annotate.empty()) {
211 draw_line((char *) data, width * 4, annotate.c_str(), 0xFFFFFFFF, true);
212 }
214 }
215 string annotate;
216};
217
221template<uint8_t f, int columns>
223 static constexpr uint32_t bars[] = {
224 uint32_t{0xFFU << 24U | f << 16U | f << 8U | f },
225 uint32_t{0xFFU << 24U | 0U << 16U | f << 8U | f },
226 uint32_t{0xFFU << 24U | f << 16U | f << 8U | 0U },
227 uint32_t{0xFFU << 24U | 0U << 16U | f << 8U | 0U },
228 uint32_t{0xFFU << 24U | f << 16U | 0U << 8U | f },
229 uint32_t{0xFFU << 24U | 0U << 16U | 0U << 8U | f },
230 uint32_t{0xFFU << 24U | f << 16U | 0U << 8U | 0U },
231 uint32_t{0xFFU << 24U | 0U << 16U | 0U << 8U | 0U },
232 };
233 enum generator_depth fill(int width, int height, unsigned char *data) override {
234 int col_num = 0;
235 const int rect_size = (width + columns - 1) / columns;
236 struct testcard_rect r{};
237 struct testcard_pixmap pixmap{};
238 pixmap.w = width;
239 pixmap.h = height;
240 pixmap.data = data;
241 for (int j = 0; j < height; j += rect_size) {
242 for (int i = 0; i < width; i += rect_size) {
243 r.x = i;
244 r.y = j;
245 r.w = rect_size;
246 r.h = min<int>(rect_size, height - r.y);
247 log_msg(LOG_LEVEL_DEBUG, MOD_NAME "Fill rect at %d,%d\n", r.x, r.y);
248 testcard_fillRect(&pixmap, &r,
249 bars[col_num]);
250 col_num = (col_num + 1) % columns;
251 }
252 }
254 }
256};
257
259 static constexpr uint32_t bottom_bars[] = {
260 uint32_t{0xFFU << 24U | 105 << 16U | 63 << 8U | 0U },
261 uint32_t{0xFFFFFFFFU },
262 uint32_t{0xFFU << 24U | 119U << 16U | 0U << 8U | 0U },
263 uint32_t{0xFF000000U },
264 uint32_t{0xFF000000U },
265 uint32_t{0xFF000000U },
266 };
267 enum generator_depth fill(int width, int height, unsigned char *data) override {
268 auto ret = image_pattern_ebu_smpte_bars<0xBFU, 7>::fill(width, height, data); // upper 2 3rds
269 assert(ret == generator_depth::bits8);
270 int columns = 7;
271 struct testcard_pixmap pixmap{ .w = width, .h = height, .data = data };
272 const int mid_strip_height = height / 3 - width / 6;
273 struct testcard_rect r{ .x = 0, .y = height / 3 * 2, .w = (width + columns - 1) / columns, .h = mid_strip_height};
274 for (int i = 0; i < columns; i += 1) {
275 r.x = i * r.w;
276 log_msg(LOG_LEVEL_DEBUG, MOD_NAME "Fill rect at %d,%d\n", r.x, r.y);
277 if (i % 2 == 1) {
278 testcard_fillRect(&pixmap, &r, 0);
279 } else {
281 &pixmap, &r,
283 0xBFU, 7>::bars[columns - 1 - i]);
284 }
285 }
286 columns = 6;
287 r.w = (width + columns - 1) / columns;
288 r.h = width / 6;
289 r.y += mid_strip_height;
290 for (int i = 0; i < columns; i += 1) {
291 r.x = i * r.w;
292 log_msg(LOG_LEVEL_DEBUG, MOD_NAME "Fill rect at %d,%d\n", r.x, r.y);
293 testcard_fillRect(&pixmap, &r,
294 bottom_bars[i]);
295 }
296 // pluge - skipping a "superblack" and black bar
297 r.x = 5 * (width / 7);
298 r.w = (width / 7) / 3;
299 r.x += 2 * r.w;
300 log_msg(LOG_LEVEL_DEBUG, MOD_NAME "Fill rect at %d,%d\n", r.x, r.y);
301 testcard_fillRect(&pixmap, &r,
302 0xFFU << 24 | 0x0A0A0A);
304 }
305};
306
308 public:
309 explicit image_pattern_blank(string const &init) {
310 if (init == "help"s) {
311 color_printf("Testcard " TBOLD("blank") " usage:\n");
313 "-t testcard:patt=" BLANK_USAGE)) "\n");
315 "\nLeading zeros can be omitted, eg. "
316 "`0xFF` produces a red pattern.\n");
318 "Defaults to 0xFF000000.\n");
319 throw 1;
320 }
321 if (!init.empty()) {
322 color = stoll(init, nullptr, 0);
323 }
324 }
325
326 private:
327 enum generator_depth fill(int width, int height, unsigned char *data) override {
328 for (int i = 0; i < width * height; ++i) {
329 (reinterpret_cast<uint32_t *>(data))[i] = color;
330 }
332 }
333 uint32_t color = 0xFF000000U;
334};
335
337 public:
338 explicit image_pattern_gradient(const string &config) {
339 if (!config.empty()) {
340 color = stol(config, nullptr, 0);
341 }
342 }
343 static constexpr uint32_t red = 0xFFU;
344 private:
345 enum generator_depth fill(int width, int height, unsigned char *data) override {
346 auto *ptr = reinterpret_cast<uint16_t *>(data);
347 for (int j = 0; j < height; j += 1) {
348 uint16_t r = sin(static_cast<double>(j) / height * M_PI) * (color & 0xFFU) / 0xFFU * 0xFF'FFU;
349 uint16_t g = sin(static_cast<double>(j) / height * M_PI) * ((color >> 8) & 0xFFU) / 0xFFU * 0xFF'FFU;
350 uint16_t b = sin(static_cast<double>(j) / height * M_PI) * ((color >> 16) & 0xFFU) / 0xFFU * 0xFF'FFU;
351 for (int i = 0; i < width; i += 1) {
352 *ptr++ = r;
353 *ptr++ = g;
354 *ptr++ = b;
355 }
356 }
358 }
360};
361
363 public:
364 explicit image_pattern_gradient2(string const &config) {
365 if (config.empty()) {
366 return;
367 }
368 if (config == "help"s) {
369 cout << "Testcard gradient2 usage:\n\t-t testcard:gradient2[=maxval] - maxval is 16-bit number\n";
370 throw 1;
371 }
372 val_max = stol(config, nullptr, 0);
373 }
374 private:
375 unsigned int val_max = 0XFFFFU;
376 enum generator_depth fill(int width, int height, unsigned char *data) override {
377 assert(width > 1); // avoid division by zero
378 auto *ptr = reinterpret_cast<uint16_t *>(data);
379 for (int j = 0; j < height; j += 1) {
380 for (int i = 0; i < width; i += 1) {
381 unsigned int gray = i * val_max / (width - 1);
382 *ptr++ = gray;
383 *ptr++ = gray;
384 *ptr++ = gray;
385 }
386 }
388 }
389};
390
392 public:
393 explicit image_pattern_uv_plane(string const &y_lvl) {
394 if (!y_lvl.empty()) {
395 y_level = LIMIT_LO(16) + stof(y_lvl) * (LIMIT_HI_Y(16) - LIMIT_LO(16));
396 }
397 }
398 private:
399 int y_level = (LIMIT_HI_Y(16) + LIMIT_LO(16)) / 2;
400 enum generator_depth fill(int width, int height, unsigned char *data) override {
401 assert(width > 1 && height > 1); // avoid division by zero
402 auto *ptr = reinterpret_cast<uint16_t *>(data);
403 auto *conv = get_decoder_from_to(Y416, RG48);
404 int scale_cbcr = LIMIT_HI_CBCR(16) - LIMIT_LO(16);
405 for (int j = 0; j < height; j += 1) {
406 for (int i = 0; i < width; i += 1) {
407 uint16_t uyva[4];
408 uyva[0] = LIMIT_LO(16) + i * scale_cbcr / (width - 1);
409 uyva[1] = y_level;
410 uyva[2] = LIMIT_LO(16) + j * scale_cbcr / (height - 1);
411 uyva[3] = 0xFF'FF;
412 conv((unsigned char *) ptr, (unsigned char *) uyva, 6, DEFAULT_R_SHIFT, DEFAULT_G_SHIFT, DEFAULT_B_SHIFT);
413 ptr += 3;
414 }
415 }
417 }
418};
419
421 default_random_engine rand_gen;
422 enum generator_depth fill(int width, int height, unsigned char *data) override {
423 uniform_int_distribution<> dist(0, 0xFFFF);
424 for_each(reinterpret_cast<uint16_t *>(data), reinterpret_cast<uint16_t *>(data) + 3 * width * height, [&](uint16_t & c) { c = dist(rand_gen); });
426 }
427};
428
430{
431 explicit image_pattern_strips(string const &config)
432 {
433 for (int i = 0; i < COL_NUM; ++i) {
434 pattern[3 + i] = rect_colors[i];
435 }
436 parse_fmt(config);
437 }
438
439 private:
440 void parse_fmt(string const &config)
441 {
442 if (config.empty()) {
443 return;
444 }
445 if (config == "help"s) {
447 "\t" TBOLD("-t "
448 "testcard:patt=strips[=[vert|hor|"
449 "diag][,w[idth]=W]]") "\n");
450 throw 1;
451 }
452 char conf[STR_LEN];
453 snprintf_ch(conf, "%s", config.c_str());
454 char *tmp = conf;
455 char *endptr = nullptr;
456 while (char *item = strtok_r(tmp, ",", &endptr)) {
457 tmp = nullptr;
458 if (strncmp(item, "hor", 3) == 0) {
459 type = COLS;
460 } else if (strncmp(item, "ver", 3) == 0) {
461 type = ROWS;
462 } else if (strncmp(item, "dia", 3) == 0) {
463 type = DIAG;
464 } else if (IS_KEY_PREFIX(item, "width")) {
465 fill_w = stoi(strchr(item, '=') + 1);
466 } else {
467 throw ug_runtime_error(
468 string("Wrong option: ") + config);
469 }
470 }
471 }
472
473 enum { ROWS, COLS, DIAG } type = DIAG;
475 int fill_w = 10;
476 enum generator_depth fill(int width, int height,
477 unsigned char *data) override
478 {
479 auto *ptr = reinterpret_cast<uint32_t *>(data);
480 for (int j = 0; j < height; j += 1) {
481 for (int i = 0; i < width; i += 1) {
482 const int col_idx = type == DIAG ? i + j
483 : type == ROWS ? i
484 : j;
485 *ptr++ = pattern[(col_idx / fill_w) %
487 }
488 }
490 }
491};
492
494 public:
495 explicit image_pattern_raw(string config) {
496 if (config.empty()) {
497 throw ug_runtime_error("Empty raw pattern is not allowed!");
498 }
499 if (config.substr(0, "0x"s.length()) == "0x") { // strip optional "0x"
500 config = config.substr(2);
501 }
502 while (!config.empty()) {
503 unsigned char byte = 0;
504 if (sscanf(config.c_str(), "%2hhx", &byte) == 1) {
505 m_pattern.push_back(byte);
506 }
507 config = config.substr(min<size_t>(config.size(), 2));
508 }
509 }
510
511 void raw_fill(unsigned char *data, size_t data_len) {
512 while (data_len >= m_pattern.size()) {
513 copy(m_pattern.begin(), m_pattern.end(), data);
514 data += m_pattern.size();
515 data_len -= m_pattern.size();
516 }
517 }
518 private:
519 enum generator_depth fill(int width, int height, unsigned char *data) override {
520 memset(data, 0, width * height * 3); // placeholder only
522 }
523 vector<unsigned char> m_pattern;
524};
525
527 public:
528 explicit image_pattern_text(const string & config) {
529 if (config == "help"s) {
530 col() << "Testcard text usage:\n\t" << SBOLD(SRED("-t testcard:pattern=text") << "[=pattern][,bg=0x<AABBGGRR<][,fg=0x<AABBGGRR>]") << "\n";
531 throw 1;
532 }
533 if (!config.empty()) {
534 string_view sv = config;
535 bool text_set = false;
536 while (!sv.empty()) {
537 auto tok = tokenize(sv, ',');
538 if (tok.substr(0,3) == "bg=") {
539 bg = stol((string) tok.substr(3), nullptr, 0);
540 } else if (tok.substr(0,3) == "fg=") {
541 fg = stol((string) tok.substr(3), nullptr, 0);
542 } else if (!text_set) {
543 text = tok;
544 text_set = true;
545 } else {
546 throw ug_runtime_error("Testcard text - wrong option: " + string(tok));
547 }
548 }
549 }
550 }
551
552 private:
553 enum generator_depth fill(int width, int height, unsigned char *data) override {
554 std::fill((uint32_t *)(void *) data, (uint32_t *)(void *) (data + width * height * 4), bg);
555 string line;
556 for (int i = 0; i < width; i += 8 * (text.size() + 1)) {
557 line += " " + text;
558 }
559 for (int i = 0; i < height / 16; ++i) {
560 draw_line((char *) data + (i * 16 * width * 4), width * 4, line.c_str(), fg, false);
561 }
563 }
564 string text = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
565 uint32_t bg = 0xFFCC00CCU;
566 uint32_t fg = 0xFFFFFFFFU;
567};
568
570 public:
571 explicit image_pattern_diagonal(const string & config) {
572 if (config == "help"s) {
573 col() << "Testcard diagonal usage:\n\t" << SBOLD(SRED("-t testcard:pattern=diagonal") << "[,bg=0x<AABBGGRR<][,fg=0x<AABBGGRR>][,stride=<stride>][,line_width=<width>]") << "\n";
574 throw 1;
575 }
576 if (!config.empty()) {
577 string_view sv = config;
578 while (!sv.empty()) {
579 auto tok = tokenize(sv, ',');
580 auto key = tokenize(tok, '=');
581 auto val = tokenize(tok, '=');
582 if (key == "bg") {
583 bg = stol((string) val, nullptr, 0);
584 } else if (key == "fg") {
585 fg = stol((string) val, nullptr, 0);
586 } else if (key == "stride") {
587 stride = stol((string) val, nullptr, 0);
588 } else if (key == "line_width") {
589 line_width = stol((string) val, nullptr, 0);
590 } else {
591 throw ug_runtime_error("Testcard diagonal - wrong option: " + string(tok));
592 }
593 }
594 }
595 }
596
597 private:
598 enum generator_depth fill(int width, int height, unsigned char *data) override {
599 std::fill((uint32_t *)(void *) data, (uint32_t *)(void *) (data + width * height * 4), bg);
600 for(int y = 0; y < height; y++){
601 for(int x = y % stride; x < width - line_width; x += stride){
602 for(int i = 0; i < line_width; i++){
603 *((uint32_t *) (void *) (data + (y*width + x + i) * 4)) = fg;
604 }
605 }
606 for(int x = stride - (y % stride); x < width - line_width; x += stride){
607 for(int i = 0; i < line_width; i++){
608 *((uint32_t *) (void *) (data + (y*width + x + i) * 4)) = fg;
609 }
610 }
611 }
612
614 }
615 uint32_t bg = 0xFF000000U;
616 uint32_t fg = 0xFFFFFFFFU;
617 int stride = 80;
618 int line_width = 4;
619};
620
621unique_ptr<image_pattern> image_pattern::create(string const &pattern, string const &params) {
622 if (pattern == "bars") {
623 return make_unique<image_pattern_bars>(params);
624 }
625 if (pattern == "blank") {
626 return make_unique<image_pattern_blank>(params);
627 }
628 if (pattern == "ebu_bars") {
629 return make_unique<image_pattern_ebu_smpte_bars<0xFFU, 8>>();
630 }
631 if (pattern == "gradient") {
632 return make_unique<image_pattern_gradient>(params);
633 }
634 if (pattern == "gradient2") {
635 return make_unique<image_pattern_gradient2>(params);
636 }
637 if (pattern == "noise") {
638 return make_unique<image_pattern_noise>();
639 }
640 if (pattern == "raw") {
641 return make_unique<image_pattern_raw>(params);
642 }
643 if (pattern == "smpte_bars") {
644 return make_unique<image_pattern_smpte_bars>();
645 }
646 if (pattern == "strips") {
647 return make_unique<image_pattern_strips>(params);
648 }
649 if (pattern == "text") {
650 return make_unique<image_pattern_text>(params);
651 }
652 if (pattern == "uv_plane") {
653 return make_unique<image_pattern_uv_plane>(params);
654 }
655 if (pattern == "diagonal") {
656 return make_unique<image_pattern_diagonal>(params);
657 }
658 throw ug_runtime_error("Unknown pattern: "s + pattern + "!"s);
659}
660
662 virtual char *get_next() = 0;
664};
665
667 still_image_video_pattern_generator(string const &pattern, string const &params, int w, int h, codec_t c, int o)
668 : width(w), height(h), color_spec(c), offset(o)
669 {
670 unique_ptr<image_pattern> generator;
671 try {
673 } catch (exception const &e) {
674 LOG(LOG_LEVEL_ERROR) << MOD_NAME << e.what() << "\n";
675 throw 1;
676 }
677 if (!generator) {
678 throw 2;
679 }
680
681 data = generator->init(width, height, generator_depth::bits8);
682 codec_t codec_src = RGBA;
683 if (get_decoder_from_to(RG48, color_spec) != NULL) {
684 data = generator->init(width, height, generator_depth::bits16);
685 codec_src = RG48;
686 }
687
688 vector<unsigned char> src;
689 data.swap(src);
691 data.resize(data_len * 2);
692 testcard_convert_buffer(codec_src, color_spec, data.data(), src.data(), width, height);
693
694 if (auto *raw_generator = dynamic_cast<image_pattern_raw *>(generator.get())) {
695 raw_generator->raw_fill(data.data(), data_len);
696 }
697
698 memcpy(data.data() + data_len, data.data(), data_len);
699 }
700 int width;
703 vector<unsigned char> data;
705 long cur_pos = 0;
708
709 char *get_next() override {
710 auto ret = (char *) data.data() + cur_pos;
711 cur_pos += offset;
712 if (cur_pos >= data_len) {
713 cur_pos = 0;
714 }
715 return ret;
716 }
717};
718
720 gray_video_pattern_generator(int w, int h, codec_t c, string const& opts)
721 : width(w), height(h), color_spec(c)
722 {
723 if (!opts.empty()) {
724 if (opts == "help") {
725 col() << "Usage:\n\t" SBOLD("gray[:step]") << " - interframe color increment (default " << DEFAULT_STEP << ")\n";
726 throw 1;
727 }
728 step = stoi(opts);
729 }
730 int col = 0;
731 while (col < 0xFF) {
733 unsigned char rgba[MAX_PFB_SIZE * 4];
734 for (int i = 0; i < pixels * 4; ++i) {
735 rgba[i] = (i + 1) % 4 != 0 ? col : 0xFFU; // handle alpha
736 }
737 int dst_bs = get_pf_block_bytes(color_spec);
738 unsigned char dst[MAX_PFB_SIZE];
740
741 auto next_frame = vector<unsigned char>(data_len);
742 for (int y = 0; y < height; ++y) {
743 for (int x = 0; x < width / pixels; x += 1) {
744 memcpy(next_frame.data() + y * vc_get_linesize(width, color_spec) + x * dst_bs, dst, dst_bs);
745 }
746 }
747 data.push_back(std::move(next_frame));
748 col += step;
749 }
750 }
751 char *get_next() override {
752 auto *out = (char *) data[cur_idx++].data();
753 if (cur_idx * step >= 0xFF) {
754 cur_idx = 0;
755 }
756
757 return out;
758 }
759private:
760 constexpr static int DEFAULT_STEP = 1;
761 int step = DEFAULT_STEP;
762 int width;
765 int cur_idx = 0;
766 long data_len = vc_get_datalen(width, height, color_spec);
767 vector<vector<unsigned char>> data;
768};
769
772 : width(w), height(h), linesize(vc_get_linesize(width, color_spec))
773 {
774 if (width % step != 0) {
775 throw ug_runtime_error(
776 string("[interlaced] width must be divisible by ") +
777 std::to_string(step) + "!");
778 }
779 size_t rgb_linesize = vc_get_linesize(width, RGB);
780 vector<char> rgb(3 * h * rgb_linesize + 4 * (width / step) * rgb_linesize);
781 memset(rgb.data(), 255, h * rgb_linesize);
782 char *ptr = rgb.data() + h * rgb_linesize;
783 auto fill = [&](int col1, int col2) {
784 for (int i = 0; i < width; i += step) {
785 size_t fill_len = rgb_linesize - i * 3;
786 memset(ptr, col1, fill_len);
787 memset(ptr + fill_len, col2, rgb_linesize - fill_len);
788 ptr += rgb_linesize;
789 fill_len = MAX(0, (int) rgb_linesize - (i + 2 * step) * 3);
790 memset(ptr, col1, fill_len);
791 memset(ptr + fill_len, col2, rgb_linesize - fill_len);
792 ptr += rgb_linesize;
793 }
794 };
795 fill(255, 0);
796 memset(ptr, 0, h * rgb_linesize);
797 ptr += h * rgb_linesize;
798 fill(0, 255);
799 memset(ptr, 255, h * rgb_linesize);
800 vector<char> rgba(rgb.size() / 3 * 4);
801 for (unsigned i = 0; i < rgb.size(); i += 3) {
802 rgba[i / 3 * 4] = rgb[i];
803 rgba[i / 3 * 4 + 1] = rgb[i + 1];
804 rgba[i / 3 * 4 + 2] = rgb[i + 2];
805 rgba[i / 3 * 4 + 3] = 0xff;
806 }
807 data.resize(3 * h * linesize + 4 * linesize * (w / step) +
809 testcard_convert_buffer(RGBA, color_spec, (unsigned char *) data.data(), (unsigned char *) rgba.data(), width, 3 * height + 4 * (width / step));
810 }
811 char *get_next() override {
812 auto *out = (char *) data.data() + cur_idx * linesize;
813 cur_idx += 8;
814 if (cur_idx >= 2 * height + 4 * width / step) {
815 cur_idx = 0;
816 }
817
818 return out;
819 }
820private:
821 constexpr static int step = 3;
822 int width;
824 size_t linesize;
825 int cur_idx = 0;
826 vector<char> data;
827};
828
830video_pattern_generator_create(const char *config, int width, int height, codec_t color_spec, int offset)
831{
832
833 if (string(config) == "help") {
834 col() << "Pattern to use, one of: \n";
835 for (const auto *p :
836 {
837 "bars",
839 "diagonal*",
840 "ebu_bars",
841 "gradient[=0x<AABBGGRR>]",
842 "gradient2*",
843 "gray",
844 "interlaced",
845 "noise",
846 "raw=0xXX[YYZZ..]",
847 "smpte_bars",
848 "strips*",
849 "uv_plane[=<y_lvl>]",
850 }) {
851 col() << "\t- " << SBOLD(p) << "\n";
852 }
853
854 col() << "\nNotes:\n";
855
856 col() << "\t- patterns " SBOLD("'gradient'") ", "
857 SBOLD("'gradient2'") ", " SBOLD("'noise'") " and "
858 SBOLD("'uv_plane'") " generate higher bit-depth "
859 "patterns with";
861 c = static_cast<codec_t>(static_cast<int>(c) + 1)) {
862 if (get_decoder_from_to(RG48, c) != NULL && get_bits_per_component(c) > 8) {
863 col() << " " << SBOLD(get_codec_name(c));
864 }
865 }
866 col() << "\n";
867 col() << "\t- pattern "
868 << SBOLD("'raw'") " generates repeating sequence of given "
869 "bytes without any color conversion\n";
870 col() << "\t- patterns marked with "
871 << SBOLD("'*'") " provide help as its option\n";
872 return nullptr;
873 }
874 assert(width > 0 && height > 0);
875 try {
876 string pattern = config;
877 string params;
878 if (string::size_type delim = pattern.find('='); delim != string::npos) {
879 params = pattern.substr(delim + 1);
880 pattern = pattern.substr(0, delim);
881 }
882 if (pattern == "gray" || pattern == "grey") {
883 return new gray_video_pattern_generator{width, height, color_spec, params};
884 }
885 if (pattern == "interlaced") {
886 return new interlaced_video_pattern_generator{width, height, color_spec};
887 }
888 return new still_image_video_pattern_generator{pattern, params, width, height, color_spec, offset};
889 } catch (exception const &e) {
890 LOG(LOG_LEVEL_ERROR) << MOD_NAME << e.what() << "\n";
891 return nullptr;
892 } catch (...) {
893 return nullptr;
894 }
895}
896
901
903{
904 auto &state = dynamic_cast<still_image_video_pattern_generator &>(*s);
905 memcpy(state.data.data(), data, state.data_len);
906 memcpy(state.data.data() + state.data_len, data, state.data_len);
907}
908
913
914/* vim: set expandtab sw=8: */
void ** state
int void * val
int vc_get_linesize(unsigned int width, codec_t codec)
const char * get_codec_name(codec_t codec)
pattern
Definition testcard.c:94
enum generator_depth fill(int width, int height, unsigned char *data) override
image_pattern_bars(string const &init)
image_pattern_blank(string const &init)
enum generator_depth fill(int width, int height, unsigned char *data) override
enum generator_depth fill(int width, int height, unsigned char *data) override
image_pattern_diagonal(const string &config)
enum generator_depth fill(int width, int height, unsigned char *data) override
image_pattern_gradient2(string const &config)
enum generator_depth fill(int width, int height, unsigned char *data) override
static constexpr uint32_t red
image_pattern_gradient(const string &config)
enum generator_depth fill(int width, int height, unsigned char *data) override
default_random_engine rand_gen
enum generator_depth fill(int width, int height, unsigned char *data) override
vector< unsigned char > m_pattern
enum generator_depth fill(int width, int height, unsigned char *data) override
void raw_fill(unsigned char *data, size_t data_len)
enum generator_depth fill(int width, int height, unsigned char *data) override
static constexpr uint32_t bottom_bars[]
image_pattern_text(const string &config)
enum generator_depth fill(int width, int height, unsigned char *data) override
enum generator_depth fill(int width, int height, unsigned char *data) override
image_pattern_uv_plane(string const &y_lvl)
image_pattern(const image_pattern &)=delete
image_pattern(image_pattern &&)=delete
virtual void convert_rg48_to_rgba(int width, int height, unsigned char *data)
auto init(int width, int height, enum generator_depth depth) noexcept
virtual ~image_pattern()=default
virtual enum generator_depth fill(int width, int height, unsigned char *data)=0
image_pattern & operator=(const image_pattern &)=delete
image_pattern()=default
virtual void convert_rgba_to_rg48(int width, int height, unsigned char *data)
image_pattern && operator=(image_pattern &&)=delete
static unique_ptr< image_pattern > create(string const &pattern, string const &params)
#define LIMIT_LO(depth)
Definition color.h:131
#define RGBA_WHITE
Definition color.h:199
#define LIMIT_HI_CBCR(depth)
Definition color.h:133
#define LIMIT_HI_Y(depth)
Definition color.h:132
#define RGBA_GRAY
Definition color.h:198
#define RGBA_BLACK
Definition color.h:197
int color_printf(const char *format,...)
#define SRED(x)
Definition color_out.h:85
#define SBOLD(x)
Definition color_out.h:80
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
static __device__ const float offset
Definition cuda_dxt.cu:139
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG(level)
Definition debug.h:313
static struct @19 params[100]
#define LOG_LEVEL_DEBUG
Definition kernels.cu:48
#define ARR_COUNT(arr)
Definition macros.h:60
#define snprintf_ch(str,...)
shortcut for snprintf(var, sizeof var...), var must be a char array
Definition macros.h:101
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
Definition macros.h:138
@ STR_LEN
"standard" string length placeholder
Definition macros.h:134
#define MAX(a, b)
Definition macros.h:65
decoder_t get_decoder_from_to(codec_t in, codec_t out)
#define DEFAULT_B_SHIFT
Definition pixfmt_conv.h:72
#define DEFAULT_R_SHIFT
Definition pixfmt_conv.h:70
#define DEFAULT_G_SHIFT
Definition pixfmt_conv.h:71
static unsigned char pixels[]
#define min(a, b)
Definition rtp.c:99
std::string_view tokenize(std::string_view &str, char delim, char quot)
Tokenizer for string_view.
gray_video_pattern_generator(int w, int h, codec_t c, string const &opts)
vector< vector< unsigned char > > data
enum generator_depth fill(int width, int height, unsigned char *data) override
void parse_fmt(string const &config)
image_pattern_strips(string const &config)
enum image_pattern_strips::@65 type
interlaced_video_pattern_generator(int w, int h, codec_t color_spec)
Definition hd-rum.c:27
still_image_video_pattern_generator(string const &pattern, string const &params, int w, int h, codec_t c, int o)
virtual char * get_next()=0
const uint32_t rect_colors[]
void testcard_fillRect(struct testcard_pixmap *s, struct testcard_rect *r, uint32_t color)
void testcard_convert_buffer(codec_t in_c, codec_t out_c, unsigned char *out, unsigned const char *in, int width, int height)
#define COL_NUM
depth
Definition types.h:125
codec_t
Definition types.h:65
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ VIDEO_CODEC_FIRST
Definition types.h:68
@ RG48
16-bit RGB little-endian (RlRhGl...)
Definition types.h:95
@ Y416
interleaved little-endian YCbCr 4444 16-bit - UYVA
Definition types.h:99
@ VIDEO_CODEC_COUNT
count of known video codecs (including VIDEO_CODEC_NONE)
Definition types.h:108
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
bool draw_line(char *buf, int pitch, const char *text, uint32_t color, bool solid)
Definition text.c:271
This is an umbrella header for video functions.
int get_pf_block_bytes(codec_t codec)
returns codec_info_t::block_size_bytes
int get_pf_block_pixels(codec_t codec)
returns codec_info_t::block_size_pixels
size_t vc_get_datalen(unsigned int width, unsigned int height, codec_t codec)
int get_bits_per_component(codec_t codec)
#define MAX_PADDING
Definition video_codec.h:63
#define MAX_PFB_SIZE
maximal pixfmt block size (R12L - 8 B)
Definition video_codec.h:65
const char * key
Definition cineform.cpp:125
void video_pattern_generator_fill_data(video_pattern_generator_t s, const char *data)
char * video_pattern_generator_next_frame(video_pattern_generator_t s)
#define BLANK_USAGE
constexpr int rg48_bpp
#define MOD_NAME
constexpr size_t headroom
video_pattern_generator_t video_pattern_generator_create(const char *config, int width, int height, codec_t color_spec, int offset)
void video_pattern_generator_destroy(video_pattern_generator_t s)