From: Hans Verkuil <hans.verkuil@xxxxxxxxx> The test patterns for video capture are generated by this code. All patterns are precalculated taking into account colorspace information, pixel and video aspect ratios and scaling information. Signed-off-by: Hans Verkuil <hans.verkuil@xxxxxxxxx> --- drivers/media/platform/vivid/vivid-tpg-colors.c | 310 +++++ drivers/media/platform/vivid/vivid-tpg-colors.h | 64 + drivers/media/platform/vivid/vivid-tpg.c | 1420 +++++++++++++++++++++++ drivers/media/platform/vivid/vivid-tpg.h | 437 +++++++ 4 files changed, 2231 insertions(+) create mode 100644 drivers/media/platform/vivid/vivid-tpg-colors.c create mode 100644 drivers/media/platform/vivid/vivid-tpg-colors.h create mode 100644 drivers/media/platform/vivid/vivid-tpg.c create mode 100644 drivers/media/platform/vivid/vivid-tpg.h diff --git a/drivers/media/platform/vivid/vivid-tpg-colors.c b/drivers/media/platform/vivid/vivid-tpg-colors.c new file mode 100644 index 0000000..2adddc0 --- /dev/null +++ b/drivers/media/platform/vivid/vivid-tpg-colors.c @@ -0,0 +1,310 @@ +/* + * vivid-color.c - A table that converts colors to various colorspaces + * + * The test pattern generator uses the tpg_colors for its test patterns. + * For testing colorspaces the first 8 colors of that table need to be + * converted to their equivalent in the target colorspace. + * + * The tpg_csc_colors[] table is the result of that conversion and since + * it is precalculated the colorspace conversion is just a simple table + * lookup. + * + * This source also contains the code used to generate the tpg_csc_colors + * table. Run the following command to compile it: + * + * gcc vivid-colors.c -DCOMPILE_APP -o gen-colors -lm + * + * and run the utility. + * + * Note that the converted colors are in the range 0x000-0xff0 (so times 16) + * in order to preserve precision. + * + * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <linux/videodev2.h> + +#include "vivid-tpg-colors.h" + +/* sRGB colors with range [0-255] */ +const struct color tpg_colors[TPG_COLOR_MAX] = { + /* + * Colors to test colorspace conversion: converting these colors + * to other colorspaces will never lead to out-of-gamut colors. + */ + { 191, 191, 191 }, /* TPG_COLOR_CSC_WHITE */ + { 191, 191, 50 }, /* TPG_COLOR_CSC_YELLOW */ + { 50, 191, 191 }, /* TPG_COLOR_CSC_CYAN */ + { 50, 191, 50 }, /* TPG_COLOR_CSC_GREEN */ + { 191, 50, 191 }, /* TPG_COLOR_CSC_MAGENTA */ + { 191, 50, 50 }, /* TPG_COLOR_CSC_RED */ + { 50, 50, 191 }, /* TPG_COLOR_CSC_BLUE */ + { 50, 50, 50 }, /* TPG_COLOR_CSC_BLACK */ + + /* 75% colors */ + { 191, 191, 0 }, /* TPG_COLOR_75_YELLOW */ + { 0, 191, 191 }, /* TPG_COLOR_75_CYAN */ + { 0, 191, 0 }, /* TPG_COLOR_75_GREEN */ + { 191, 0, 191 }, /* TPG_COLOR_75_MAGENTA */ + { 191, 0, 0 }, /* TPG_COLOR_75_RED */ + { 0, 0, 191 }, /* TPG_COLOR_75_BLUE */ + + /* 100% colors */ + { 255, 255, 255 }, /* TPG_COLOR_100_WHITE */ + { 255, 255, 0 }, /* TPG_COLOR_100_YELLOW */ + { 0, 255, 255 }, /* TPG_COLOR_100_CYAN */ + { 0, 255, 0 }, /* TPG_COLOR_100_GREEN */ + { 255, 0, 255 }, /* TPG_COLOR_100_MAGENTA */ + { 255, 0, 0 }, /* TPG_COLOR_100_RED */ + { 0, 0, 255 }, /* TPG_COLOR_100_BLUE */ + { 0, 0, 0 }, /* TPG_COLOR_100_BLACK */ + + { 0, 0, 0 }, /* TPG_COLOR_RANDOM placeholder */ +}; + +#ifndef COMPILE_APP + +/* Generated table */ +const struct color16 tpg_csc_colors[V4L2_COLORSPACE_SRGB + 1][TPG_COLOR_CSC_BLACK + 1] = { + [V4L2_COLORSPACE_SMPTE170M][0] = { 2953, 2939, 2939 }, + [V4L2_COLORSPACE_SMPTE170M][1] = { 2954, 2963, 585 }, + [V4L2_COLORSPACE_SMPTE170M][2] = { 84, 2967, 2937 }, + [V4L2_COLORSPACE_SMPTE170M][3] = { 93, 2990, 575 }, + [V4L2_COLORSPACE_SMPTE170M][4] = { 3030, 259, 2933 }, + [V4L2_COLORSPACE_SMPTE170M][5] = { 3031, 406, 557 }, + [V4L2_COLORSPACE_SMPTE170M][6] = { 544, 428, 2931 }, + [V4L2_COLORSPACE_SMPTE170M][7] = { 551, 547, 547 }, + [V4L2_COLORSPACE_SMPTE240M][0] = { 2926, 2926, 2926 }, + [V4L2_COLORSPACE_SMPTE240M][1] = { 2926, 2926, 857 }, + [V4L2_COLORSPACE_SMPTE240M][2] = { 1594, 2901, 2901 }, + [V4L2_COLORSPACE_SMPTE240M][3] = { 1594, 2901, 774 }, + [V4L2_COLORSPACE_SMPTE240M][4] = { 2484, 618, 2858 }, + [V4L2_COLORSPACE_SMPTE240M][5] = { 2484, 618, 617 }, + [V4L2_COLORSPACE_SMPTE240M][6] = { 507, 507, 2832 }, + [V4L2_COLORSPACE_SMPTE240M][7] = { 507, 507, 507 }, + [V4L2_COLORSPACE_REC709][0] = { 2939, 2939, 2939 }, + [V4L2_COLORSPACE_REC709][1] = { 2939, 2939, 547 }, + [V4L2_COLORSPACE_REC709][2] = { 547, 2939, 2939 }, + [V4L2_COLORSPACE_REC709][3] = { 547, 2939, 547 }, + [V4L2_COLORSPACE_REC709][4] = { 2939, 547, 2939 }, + [V4L2_COLORSPACE_REC709][5] = { 2939, 547, 547 }, + [V4L2_COLORSPACE_REC709][6] = { 547, 547, 2939 }, + [V4L2_COLORSPACE_REC709][7] = { 547, 547, 547 }, + [V4L2_COLORSPACE_470_SYSTEM_M][0] = { 2894, 2988, 2808 }, + [V4L2_COLORSPACE_470_SYSTEM_M][1] = { 2847, 3070, 843 }, + [V4L2_COLORSPACE_470_SYSTEM_M][2] = { 1656, 2962, 2783 }, + [V4L2_COLORSPACE_470_SYSTEM_M][3] = { 1572, 3045, 763 }, + [V4L2_COLORSPACE_470_SYSTEM_M][4] = { 2477, 229, 2743 }, + [V4L2_COLORSPACE_470_SYSTEM_M][5] = { 2422, 672, 614 }, + [V4L2_COLORSPACE_470_SYSTEM_M][6] = { 725, 63, 2718 }, + [V4L2_COLORSPACE_470_SYSTEM_M][7] = { 534, 561, 509 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][0] = { 2939, 2939, 2939 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][1] = { 2939, 2939, 621 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][2] = { 786, 2939, 2939 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][3] = { 786, 2939, 621 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][4] = { 2879, 547, 2923 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][5] = { 2879, 547, 547 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][6] = { 547, 547, 2923 }, + [V4L2_COLORSPACE_470_SYSTEM_BG][7] = { 547, 547, 547 }, + [V4L2_COLORSPACE_SRGB][0] = { 3056, 3056, 3056 }, + [V4L2_COLORSPACE_SRGB][1] = { 3056, 3056, 800 }, + [V4L2_COLORSPACE_SRGB][2] = { 800, 3056, 3056 }, + [V4L2_COLORSPACE_SRGB][3] = { 800, 3056, 800 }, + [V4L2_COLORSPACE_SRGB][4] = { 3056, 800, 3056 }, + [V4L2_COLORSPACE_SRGB][5] = { 3056, 800, 800 }, + [V4L2_COLORSPACE_SRGB][6] = { 800, 800, 3056 }, + [V4L2_COLORSPACE_SRGB][7] = { 800, 800, 800 }, +}; + +#else + +/* This code generates the table above */ + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +static const double rec709_to_ntsc1953[3][3] = { + { 0.6698, 0.2678, 0.0323 }, + { 0.0185, 1.0742, -0.0603 }, + { 0.0162, 0.0432, 0.8551 } +}; + +static const double rec709_to_ebu[3][3] = { + { 0.9578, 0.0422, 0 }, + { 0 , 1 , 0 }, + { 0 , 0.0118, 0.9882 } +}; + +static const double rec709_to_170m[3][3] = { + { 1.0654, -0.0554, -0.0010 }, + { -0.0196, 1.0364, -0.0167 }, + { 0.0016, 0.0044, 0.9940 } +}; + +static const double rec709_to_240m[3][3] = { + { 0.7151, 0.2849, 0 }, + { 0.0179, 0.9821, 0 }, + { 0.0177, 0.0472, 0.9350 } +}; + + +static void mult_matrix(double *r, double *g, double *b, const double m[3][3]) +{ + double ir, ig, ib; + + ir = m[0][0] * (*r) + m[0][1] * (*g) + m[0][2] * (*b); + ig = m[1][0] * (*r) + m[1][1] * (*g) + m[1][2] * (*b); + ib = m[2][0] * (*r) + m[2][1] * (*g) + m[2][2] * (*b); + *r = ir; + *g = ig; + *b = ib; +} + +static double transfer_srgb_to_rgb(double v) +{ + return (v <= 0.03928) ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4); +} + +static double transfer_rgb_to_smpte240m(double v) +{ + return (v <= 0.0228) ? v * 4.0 : 1.1115 * pow(v, 0.45) - 0.1115; +} + +static double transfer_rgb_to_rec709(double v) +{ + return (v < 0.018) ? v * 4.5 : 1.099 * pow(v, 0.45) - 0.099; +} + +static double transfer_srgb_to_rec709(double v) +{ + return transfer_rgb_to_rec709(transfer_srgb_to_rgb(v)); +} + +static void csc(enum v4l2_colorspace colorspace, double *r, double *g, double *b) +{ + /* Convert the primaries of Rec. 709 Linear RGB */ + switch (colorspace) { + case V4L2_COLORSPACE_SMPTE240M: + *r = transfer_srgb_to_rgb(*r); + *g = transfer_srgb_to_rgb(*g); + *b = transfer_srgb_to_rgb(*b); + mult_matrix(r, g, b, rec709_to_240m); + break; + case V4L2_COLORSPACE_SMPTE170M: + *r = transfer_srgb_to_rgb(*r); + *g = transfer_srgb_to_rgb(*g); + *b = transfer_srgb_to_rgb(*b); + mult_matrix(r, g, b, rec709_to_170m); + break; + case V4L2_COLORSPACE_470_SYSTEM_BG: + *r = transfer_srgb_to_rgb(*r); + *g = transfer_srgb_to_rgb(*g); + *b = transfer_srgb_to_rgb(*b); + mult_matrix(r, g, b, rec709_to_ebu); + break; + case V4L2_COLORSPACE_470_SYSTEM_M: + *r = transfer_srgb_to_rgb(*r); + *g = transfer_srgb_to_rgb(*g); + *b = transfer_srgb_to_rgb(*b); + mult_matrix(r, g, b, rec709_to_ntsc1953); + break; + case V4L2_COLORSPACE_SRGB: + case V4L2_COLORSPACE_REC709: + default: + break; + } + + *r = ((*r) < 0) ? 0 : (((*r) > 1) ? 1 : (*r)); + *g = ((*g) < 0) ? 0 : (((*g) > 1) ? 1 : (*g)); + *b = ((*b) < 0) ? 0 : (((*b) > 1) ? 1 : (*b)); + + /* Encode to gamma corrected colorspace */ + switch (colorspace) { + case V4L2_COLORSPACE_SMPTE240M: + *r = transfer_rgb_to_smpte240m(*r); + *g = transfer_rgb_to_smpte240m(*g); + *b = transfer_rgb_to_smpte240m(*b); + break; + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + *r = transfer_rgb_to_rec709(*r); + *g = transfer_rgb_to_rec709(*g); + *b = transfer_rgb_to_rec709(*b); + break; + case V4L2_COLORSPACE_SRGB: + break; + case V4L2_COLORSPACE_REC709: + default: + *r = transfer_srgb_to_rec709(*r); + *g = transfer_srgb_to_rec709(*g); + *b = transfer_srgb_to_rec709(*b); + break; + } +} + +int main(int argc, char **argv) +{ + static const unsigned colorspaces[] = { + 0, + V4L2_COLORSPACE_SMPTE170M, + V4L2_COLORSPACE_SMPTE240M, + V4L2_COLORSPACE_REC709, + 0, + V4L2_COLORSPACE_470_SYSTEM_M, + V4L2_COLORSPACE_470_SYSTEM_BG, + 0, + V4L2_COLORSPACE_SRGB, + }; + static const char * const colorspace_names[] = { + "", + "V4L2_COLORSPACE_SMPTE170M", + "V4L2_COLORSPACE_SMPTE240M", + "V4L2_COLORSPACE_REC709", + "", + "V4L2_COLORSPACE_470_SYSTEM_M", + "V4L2_COLORSPACE_470_SYSTEM_BG", + "", + "V4L2_COLORSPACE_SRGB", + }; + int i; + int c; + + printf("/* Generated table */\n"); + printf("const struct color16 tpg_csc_colors[V4L2_COLORSPACE_SRGB + 1][TPG_COLOR_CSC_BLACK + 1] = {\n"); + for (c = 0; c <= V4L2_COLORSPACE_SRGB; c++) { + for (i = 0; i <= TPG_COLOR_CSC_BLACK; i++) { + double r, g, b; + + if (colorspaces[c] == 0) + continue; + + r = tpg_colors[i].r / 255.0; + g = tpg_colors[i].g / 255.0; + b = tpg_colors[i].b / 255.0; + + csc(c, &r, &g, &b); + + printf("\t[%s][%d] = { %d, %d, %d },\n", colorspace_names[c], i, + (int)(r * 4080), (int)(g * 4080), (int)(b * 4080)); + } + } + printf("};\n\n"); + return 0; +} + +#endif diff --git a/drivers/media/platform/vivid/vivid-tpg-colors.h b/drivers/media/platform/vivid/vivid-tpg-colors.h new file mode 100644 index 0000000..a2678fb --- /dev/null +++ b/drivers/media/platform/vivid/vivid-tpg-colors.h @@ -0,0 +1,64 @@ +/* + * vivid-color.h - Color definitions for the test pattern generator + * + * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _VIVID_COLORS_H_ +#define _VIVID_COLORS_H_ + +struct color { + unsigned char r, g, b; +}; + +struct color16 { + int r, g, b; +}; + +enum tpg_color { + TPG_COLOR_CSC_WHITE, + TPG_COLOR_CSC_YELLOW, + TPG_COLOR_CSC_CYAN, + TPG_COLOR_CSC_GREEN, + TPG_COLOR_CSC_MAGENTA, + TPG_COLOR_CSC_RED, + TPG_COLOR_CSC_BLUE, + TPG_COLOR_CSC_BLACK, + TPG_COLOR_75_YELLOW, + TPG_COLOR_75_CYAN, + TPG_COLOR_75_GREEN, + TPG_COLOR_75_MAGENTA, + TPG_COLOR_75_RED, + TPG_COLOR_75_BLUE, + TPG_COLOR_100_WHITE, + TPG_COLOR_100_YELLOW, + TPG_COLOR_100_CYAN, + TPG_COLOR_100_GREEN, + TPG_COLOR_100_MAGENTA, + TPG_COLOR_100_RED, + TPG_COLOR_100_BLUE, + TPG_COLOR_100_BLACK, + TPG_COLOR_TEXTFG, + TPG_COLOR_TEXTBG, + TPG_COLOR_RANDOM, + TPG_COLOR_RAMP, + TPG_COLOR_MAX = TPG_COLOR_RAMP + 256 +}; + +extern const struct color tpg_colors[TPG_COLOR_MAX]; +extern const struct color16 tpg_csc_colors[V4L2_COLORSPACE_SRGB + 1][TPG_COLOR_CSC_BLACK + 1]; + +#endif diff --git a/drivers/media/platform/vivid/vivid-tpg.c b/drivers/media/platform/vivid/vivid-tpg.c new file mode 100644 index 0000000..2fac5d1 --- /dev/null +++ b/drivers/media/platform/vivid/vivid-tpg.c @@ -0,0 +1,1420 @@ +/* + * vivid-tpg.c - Test Pattern Generator + * + * Note: gen_twopix and tpg_gen_text are based on code from vivi.c. See the + * vivi.c source for the copyright information of those functions. + * + * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vivid-tpg.h" + +/* Must remain in sync with enum tpg_pattern */ +const char * const tpg_pattern_strings[] = { + "75% Colorbar", + "100% Colorbar", + "CSC Colorbar", + "Horizontal 100% Colorbar", + "100% Color Squares", + "100% Black", + "100% White", + "100% Red", + "100% Green", + "100% Blue", + "16x16 Checkers", + "1x1 Checkers", + "Alternating Hor Lines", + "Alternating Vert Lines", + "One Pixel Wide Cross", + "Two Pixels Wide Cross", + "Ten Pixels Wide Cross", + "Gray Ramp", + "Noise", + NULL +}; + +/* Must remain in sync with enum tpg_aspect */ +const char * const tpg_aspect_strings[] = { + "Source Width x Height", + "4x3", + "16x9", + "16x9 Anamorphic", + NULL +}; + +/* + * Sinus table: sin[0] = 127 * sin(-180 degrees) + * sin[128] = 127 * sin(0 degrees) + * sin[256] = 127 * sin(180 degrees) + */ +static const s8 sin[257] = { + 0, -4, -7, -11, -13, -18, -20, -22, -26, -29, -33, -35, -37, -41, -43, -48, + -50, -52, -56, -58, -62, -63, -65, -69, -71, -75, -76, -78, -82, -83, -87, -88, + -90, -93, -94, -97, -99, -101, -103, -104, -107, -108, -110, -111, -112, -114, -115, -117, + -118, -119, -120, -121, -122, -123, -123, -124, -125, -125, -126, -126, -127, -127, -127, -127, + -127, -127, -127, -127, -126, -126, -125, -125, -124, -124, -123, -122, -121, -120, -119, -118, + -117, -116, -114, -113, -111, -110, -109, -107, -105, -103, -101, -100, -97, -96, -93, -91, + -90, -87, -85, -82, -80, -76, -75, -73, -69, -67, -63, -62, -60, -56, -54, -50, + -48, -46, -41, -39, -35, -33, -31, -26, -24, -20, -18, -15, -11, -9, -4, -2, + 0, 2, 4, 9, 11, 15, 18, 20, 24, 26, 31, 33, 35, 39, 41, 46, + 48, 50, 54, 56, 60, 62, 64, 67, 69, 73, 75, 76, 80, 82, 85, 87, + 90, 91, 93, 96, 97, 100, 101, 103, 105, 107, 109, 110, 111, 113, 114, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 124, 125, 125, 126, 126, 127, 127, 127, + 127, 127, 127, 127, 127, 126, 126, 125, 125, 124, 123, 123, 122, 121, 120, 119, + 118, 117, 115, 114, 112, 111, 110, 108, 107, 104, 103, 101, 99, 97, 94, 93, + 90, 88, 87, 83, 82, 78, 76, 75, 71, 69, 65, 64, 62, 58, 56, 52, + 50, 48, 43, 41, 37, 35, 33, 29, 26, 22, 20, 18, 13, 11, 7, 4, + 0, +}; + +#define cos(idx) sin[((idx) + 64) % sizeof(sin)] + +/* Global font descriptor */ +static const u8 *font8x16; + +void tpg_set_font(const u8 *f) +{ + font8x16 = f; +} + +void tpg_init(struct tpg_data *tpg, unsigned w, unsigned h) +{ + memset(tpg, 0, sizeof(*tpg)); + tpg->scaled_width = tpg->src_width = w; + tpg->src_height = tpg->buf_height = h; + tpg->crop.width = tpg->compose.width = w; + tpg->crop.height = tpg->compose.height = h; + tpg->recalc_colors = true; + tpg->recalc_square_border = true; + tpg->brightness = 128; + tpg->contrast = 128; + tpg->saturation = 128; + tpg->hue = 0; + tpg->mv_hor_mode = TPG_MOVE_NONE; + tpg->mv_vert_mode = TPG_MOVE_NONE; + tpg->field = V4L2_FIELD_NONE; + tpg_s_fourcc(tpg, V4L2_PIX_FMT_RGB24); + tpg->colorspace = V4L2_COLORSPACE_SRGB; + tpg->perc_fill = 100; +} + +int tpg_alloc(struct tpg_data *tpg, unsigned max_w) +{ + unsigned pat; + unsigned plane; + + tpg->max_line_width = max_w; + for (pat = 0; pat < TPG_MAX_PAT_LINES; pat++) { + for (plane = 0; plane < TPG_MAX_PLANES; plane++) { + unsigned pixelsz = plane ? 1 : 4; + + tpg->lines[pat][plane] = vzalloc(max_w * 2 * pixelsz); + if (!tpg->lines[pat][plane]) + return -ENOMEM; + } + } + for (plane = 0; plane < TPG_MAX_PLANES; plane++) { + unsigned pixelsz = plane ? 1 : 4; + + tpg->contrast_line[plane] = vzalloc(max_w * pixelsz); + if (!tpg->contrast_line[plane]) + return -ENOMEM; + tpg->black_line[plane] = vzalloc(max_w * pixelsz); + if (!tpg->black_line[plane]) + return -ENOMEM; + tpg->random_line[plane] = vzalloc(max_w * pixelsz); + if (!tpg->random_line[plane]) + return -ENOMEM; + } + return 0; +} + +void tpg_free(struct tpg_data *tpg) +{ + unsigned pat; + unsigned plane; + + for (pat = 0; pat < TPG_MAX_PAT_LINES; pat++) + for (plane = 0; plane < TPG_MAX_PLANES; plane++) { + vfree(tpg->lines[pat][plane]); + tpg->lines[pat][plane] = NULL; + } + for (plane = 0; plane < TPG_MAX_PLANES; plane++) { + vfree(tpg->contrast_line[plane]); + vfree(tpg->black_line[plane]); + vfree(tpg->random_line[plane]); + tpg->contrast_line[plane] = NULL; + tpg->black_line[plane] = NULL; + tpg->random_line[plane] = NULL; + } +} + +bool tpg_s_fourcc(struct tpg_data *tpg, u32 fourcc) +{ + tpg->fourcc = fourcc; + tpg->planes = 1; + tpg->recalc_colors = true; + switch (fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB565X: + case V4L2_PIX_FMT_RGB555: + case V4L2_PIX_FMT_XRGB555: + case V4L2_PIX_FMT_ARGB555: + case V4L2_PIX_FMT_RGB555X: + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_XRGB32: + case V4L2_PIX_FMT_XBGR32: + case V4L2_PIX_FMT_ARGB32: + case V4L2_PIX_FMT_ABGR32: + tpg->is_yuv = 0; + break; + case V4L2_PIX_FMT_NV16M: + case V4L2_PIX_FMT_NV61M: + tpg->planes = 2; + /* fall-through */ + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + tpg->is_yuv = 1; + break; + default: + return false; + } + + switch (fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB565X: + case V4L2_PIX_FMT_RGB555: + case V4L2_PIX_FMT_XRGB555: + case V4L2_PIX_FMT_ARGB555: + case V4L2_PIX_FMT_RGB555X: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + tpg->twopixelsize[0] = 2 * 2; + break; + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + tpg->twopixelsize[0] = 2 * 3; + break; + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_XRGB32: + case V4L2_PIX_FMT_XBGR32: + case V4L2_PIX_FMT_ARGB32: + case V4L2_PIX_FMT_ABGR32: + tpg->twopixelsize[0] = 2 * 4; + break; + case V4L2_PIX_FMT_NV16M: + case V4L2_PIX_FMT_NV61M: + tpg->twopixelsize[0] = 2; + tpg->twopixelsize[1] = 2; + break; + } + return true; +} + +void tpg_s_crop_compose(struct tpg_data *tpg, const struct v4l2_rect *crop, + const struct v4l2_rect *compose) +{ + tpg->crop = *crop; + tpg->compose = *compose; + tpg->scaled_width = (tpg->src_width * tpg->compose.width + + tpg->crop.width - 1) / tpg->crop.width; + tpg->scaled_width &= ~1; + if (tpg->scaled_width > tpg->max_line_width) + tpg->scaled_width = tpg->max_line_width; + if (tpg->scaled_width < 2) + tpg->scaled_width = 2; + tpg->recalc_lines = true; +} + +void tpg_reset_source(struct tpg_data *tpg, unsigned width, unsigned height, + enum v4l2_field field) +{ + unsigned p; + + tpg->src_width = width; + tpg->src_height = height; + tpg->field = field; + tpg->buf_height = height; + if (V4L2_FIELD_HAS_T_OR_B(field)) + tpg->buf_height /= 2; + tpg->scaled_width = width; + tpg->crop.top = tpg->crop.left = 0; + tpg->crop.width = width; + tpg->crop.height = height; + tpg->compose.top = tpg->compose.left = 0; + tpg->compose.width = width; + tpg->compose.height = tpg->buf_height; + for (p = 0; p < tpg->planes; p++) + tpg->bytesperline[p] = width * tpg->twopixelsize[p] / 2; + tpg->recalc_square_border = true; +} + +static enum tpg_color tpg_get_textbg_color(struct tpg_data *tpg) +{ + switch (tpg->pattern) { + case TPG_PAT_BLACK: + return TPG_COLOR_100_WHITE; + case TPG_PAT_CSC_COLORBAR: + return TPG_COLOR_CSC_BLACK; + default: + return TPG_COLOR_100_BLACK; + } +} + +static enum tpg_color tpg_get_textfg_color(struct tpg_data *tpg) +{ + switch (tpg->pattern) { + case TPG_PAT_75_COLORBAR: + case TPG_PAT_CSC_COLORBAR: + return TPG_COLOR_CSC_WHITE; + case TPG_PAT_BLACK: + return TPG_COLOR_100_BLACK; + default: + return TPG_COLOR_100_WHITE; + } +} + +static u16 color_to_y(struct tpg_data *tpg, int r, int g, int b) +{ + switch (tpg->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + return ((16829 * r + 33039 * g + 6416 * b + 16 * 32768) >> 16) + (16 << 4); + case V4L2_COLORSPACE_SMPTE240M: + return ((11932 * r + 39455 * g + 4897 * b + 16 * 32768) >> 16) + (16 << 4); + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + default: + return ((11966 * r + 40254 * g + 4064 * b + 16 * 32768) >> 16) + (16 << 4); + } +} + +static u16 color_to_cb(struct tpg_data *tpg, int r, int g, int b) +{ + switch (tpg->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + return ((-9714 * r - 19070 * g + 28784 * b + 16 * 32768) >> 16) + (128 << 4); + case V4L2_COLORSPACE_SMPTE240M: + return ((-6684 * r - 22100 * g + 28784 * b + 16 * 32768) >> 16) + (128 << 4); + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + default: + return ((-6596 * r - 22189 * g + 28784 * b + 16 * 32768) >> 16) + (128 << 4); + } +} + +static u16 color_to_cr(struct tpg_data *tpg, int r, int g, int b) +{ + switch (tpg->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + return ((28784 * r - 24103 * g - 4681 * b + 16 * 32768) >> 16) + (128 << 4); + case V4L2_COLORSPACE_SMPTE240M: + return ((28784 * r - 25606 * g - 3178 * b + 16 * 32768) >> 16) + (128 << 4); + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + default: + return ((28784 * r - 26145 * g - 2639 * b + 16 * 32768) >> 16) + (128 << 4); + } +} + +static u16 ycbcr_to_r(struct tpg_data *tpg, int y, int cb, int cr) +{ + int r; + + y -= 16 << 4; + cb -= 128 << 4; + cr -= 128 << 4; + switch (tpg->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + r = 4769 * y + 6537 * cr; + break; + case V4L2_COLORSPACE_SMPTE240M: + r = 4769 * y + 7376 * cr; + break; + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + default: + r = 4769 * y + 7343 * cr; + break; + } + return clamp(r >> 12, 0, 0xff0); +} + +static u16 ycbcr_to_g(struct tpg_data *tpg, int y, int cb, int cr) +{ + int g; + + y -= 16 << 4; + cb -= 128 << 4; + cr -= 128 << 4; + switch (tpg->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + g = 4769 * y - 1605 * cb - 3330 * cr; + break; + case V4L2_COLORSPACE_SMPTE240M: + g = 4769 * y - 1055 * cb - 2341 * cr; + break; + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + default: + g = 4769 * y - 873 * cb - 2183 * cr; + break; + } + return clamp(g >> 12, 0, 0xff0); +} + +static u16 ycbcr_to_b(struct tpg_data *tpg, int y, int cb, int cr) +{ + int b; + + y -= 16 << 4; + cb -= 128 << 4; + cr -= 128 << 4; + switch (tpg->colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + b = 4769 * y + 7343 * cb; + break; + case V4L2_COLORSPACE_SMPTE240M: + b = 4769 * y + 8552 * cb; + break; + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SRGB: + default: + b = 4769 * y + 8652 * cb; + break; + } + return clamp(b >> 12, 0, 0xff0); +} + +/* precalculate color bar values to speed up rendering */ +static void precalculate_color(struct tpg_data *tpg, int k) +{ + int col = k; + int r = tpg_colors[col].r; + int g = tpg_colors[col].g; + int b = tpg_colors[col].b; + + if (k == TPG_COLOR_TEXTBG) { + col = tpg_get_textbg_color(tpg); + + r = tpg_colors[col].r; + g = tpg_colors[col].g; + b = tpg_colors[col].b; + } else if (k == TPG_COLOR_TEXTFG) { + col = tpg_get_textfg_color(tpg); + + r = tpg_colors[col].r; + g = tpg_colors[col].g; + b = tpg_colors[col].b; + } else if (tpg->pattern == TPG_PAT_NOISE) { + r = g = b = prandom_u32_max(256); + } else if (k == TPG_COLOR_RANDOM) { + r = g = b = tpg->qual_offset + prandom_u32_max(196); + } else if (k >= TPG_COLOR_RAMP) { + r = g = b = k - TPG_COLOR_RAMP; + } + + if (tpg->pattern == TPG_PAT_CSC_COLORBAR && col <= TPG_COLOR_CSC_BLACK) { + r = tpg_csc_colors[tpg->colorspace][col].r; + g = tpg_csc_colors[tpg->colorspace][col].g; + b = tpg_csc_colors[tpg->colorspace][col].b; + } else { + r <<= 4; + g <<= 4; + b <<= 4; + } + if (tpg->qual == TPG_QUAL_GRAY) + r = g = b = color_to_y(tpg, r, g, b); + + /* + * The assumption is that the RGB output is always full range, + * so only if the rgb_range overrides the 'real' rgb range do + * we need to convert the RGB values. + * + * Currently there is no way of signalling to userspace if you + * are actually giving it limited range RGB (or full range + * YUV for that matter). + * + * Remember that r, g and b are still in the 0 - 0xff0 range. + */ + if (tpg->real_rgb_range == V4L2_DV_RGB_RANGE_LIMITED && + tpg->rgb_range == V4L2_DV_RGB_RANGE_FULL) { + /* + * Convert from full range (which is what r, g and b are) + * to limited range (which is the 'real' RGB range), which + * is then interpreted as full range. + */ + r = (r * 219) / 255 + (16 << 4); + g = (g * 219) / 255 + (16 << 4); + b = (b * 219) / 255 + (16 << 4); + } else if (tpg->real_rgb_range != V4L2_DV_RGB_RANGE_LIMITED && + tpg->rgb_range == V4L2_DV_RGB_RANGE_LIMITED) { + /* + * Clamp r, g and b to the limited range and convert to full + * range since that's what we deliver. + */ + r = clamp(r, 16 << 4, 235 << 4); + g = clamp(g, 16 << 4, 235 << 4); + b = clamp(b, 16 << 4, 235 << 4); + r = (r - (16 << 4)) * 255 / 219; + g = (g - (16 << 4)) * 255 / 219; + b = (b - (16 << 4)) * 255 / 219; + } + + if (tpg->brightness != 128 || tpg->contrast != 128 || + tpg->saturation != 128 || tpg->hue) { + /* Implement these operations */ + + /* First convert to YCbCr */ + int y = color_to_y(tpg, r, g, b); /* Luma */ + int cb = color_to_cb(tpg, r, g, b); /* Cb */ + int cr = color_to_cr(tpg, r, g, b); /* Cr */ + int tmp_cb, tmp_cr; + + y = (16 << 4) + ((y - (16 << 4)) * tpg->contrast) / 128; + y += (tpg->brightness << 4) - (128 << 4); + + cb -= 128 << 4; + cr -= 128 << 4; + tmp_cb = (cb * cos(128 + tpg->hue)) / 127 + (cr * sin[128 + tpg->hue]) / 127; + tmp_cr = (cr * cos(128 + tpg->hue)) / 127 - (cb * sin[128 + tpg->hue]) / 127; + + cb = (128 << 4) + (tmp_cb * tpg->contrast * tpg->saturation) / (128 * 128); + cr = (128 << 4) + (tmp_cr * tpg->contrast * tpg->saturation) / (128 * 128); + if (tpg->is_yuv) { + tpg->colors[k][0] = clamp(y >> 4, 1, 254); + tpg->colors[k][1] = clamp(cb >> 4, 1, 254); + tpg->colors[k][2] = clamp(cr >> 4, 1, 254); + return; + } + r = ycbcr_to_r(tpg, y, cb, cr); + g = ycbcr_to_g(tpg, y, cb, cr); + b = ycbcr_to_b(tpg, y, cb, cr); + } + + if (tpg->is_yuv) { + /* Convert to YCbCr */ + u16 y = color_to_y(tpg, r, g, b); /* Luma */ + u16 cb = color_to_cb(tpg, r, g, b); /* Cb */ + u16 cr = color_to_cr(tpg, r, g, b); /* Cr */ + + tpg->colors[k][0] = clamp(y >> 4, 1, 254); + tpg->colors[k][1] = clamp(cb >> 4, 1, 254); + tpg->colors[k][2] = clamp(cr >> 4, 1, 254); + } else { + switch (tpg->fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB565X: + r >>= 7; + g >>= 6; + b >>= 7; + break; + case V4L2_PIX_FMT_RGB555: + case V4L2_PIX_FMT_XRGB555: + case V4L2_PIX_FMT_ARGB555: + case V4L2_PIX_FMT_RGB555X: + r >>= 7; + g >>= 7; + b >>= 7; + break; + default: + r >>= 4; + g >>= 4; + b >>= 4; + break; + } + + tpg->colors[k][0] = r; + tpg->colors[k][1] = g; + tpg->colors[k][2] = b; + } +} + +static void tpg_precalculate_colors(struct tpg_data *tpg) +{ + int k; + + for (k = 0; k < TPG_COLOR_MAX; k++) + precalculate_color(tpg, k); +} + +/* 'odd' is true for pixels 1, 3, 5, etc. and false for pixels 0, 2, 4, etc. */ +static void gen_twopix(struct tpg_data *tpg, + u8 buf[TPG_MAX_PLANES][8], int color, bool odd) +{ + unsigned offset = odd * tpg->twopixelsize[0] / 2; + u8 alpha = tpg->alpha_component; + u8 r_y, g_u, b_v; + + if (tpg->alpha_red_only && color != TPG_COLOR_CSC_RED && + color != TPG_COLOR_100_RED && + color != TPG_COLOR_75_RED) + alpha = 0; + if (color == TPG_COLOR_RANDOM) + precalculate_color(tpg, color); + r_y = tpg->colors[color][0]; /* R or precalculated Y */ + g_u = tpg->colors[color][1]; /* G or precalculated U */ + b_v = tpg->colors[color][2]; /* B or precalculated V */ + + switch (tpg->fourcc) { + case V4L2_PIX_FMT_NV16M: + buf[0][offset] = r_y; + buf[1][offset] = odd ? b_v : g_u; + break; + case V4L2_PIX_FMT_NV61M: + buf[0][offset] = r_y; + buf[1][offset] = odd ? g_u : b_v; + break; + + case V4L2_PIX_FMT_YUYV: + buf[0][offset] = r_y; + buf[0][offset + 1] = odd ? b_v : g_u; + break; + case V4L2_PIX_FMT_UYVY: + buf[0][offset] = odd ? b_v : g_u; + buf[0][offset + 1] = r_y; + break; + case V4L2_PIX_FMT_YVYU: + buf[0][offset] = r_y; + buf[0][offset + 1] = odd ? g_u : b_v; + break; + case V4L2_PIX_FMT_VYUY: + buf[0][offset] = odd ? g_u : b_v; + buf[0][offset + 1] = r_y; + break; + case V4L2_PIX_FMT_RGB565: + buf[0][offset] = (g_u << 5) | b_v; + buf[0][offset + 1] = (r_y << 3) | (g_u >> 3); + break; + case V4L2_PIX_FMT_RGB565X: + buf[0][offset] = (r_y << 3) | (g_u >> 3); + buf[0][offset + 1] = (g_u << 5) | b_v; + break; + case V4L2_PIX_FMT_RGB555: + case V4L2_PIX_FMT_XRGB555: + alpha = 0; + /* fall through */ + case V4L2_PIX_FMT_ARGB555: + buf[0][offset] = (g_u << 5) | b_v; + buf[0][offset + 1] = (alpha & 0x80) | (r_y << 2) | (g_u >> 3); + break; + case V4L2_PIX_FMT_RGB555X: + buf[0][offset] = (alpha & 0x80) | (r_y << 2) | (g_u >> 3); + buf[0][offset + 1] = (g_u << 5) | b_v; + break; + case V4L2_PIX_FMT_RGB24: + buf[0][offset] = r_y; + buf[0][offset + 1] = g_u; + buf[0][offset + 2] = b_v; + break; + case V4L2_PIX_FMT_BGR24: + buf[0][offset] = b_v; + buf[0][offset + 1] = g_u; + buf[0][offset + 2] = r_y; + break; + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_XRGB32: + alpha = 0; + /* fall through */ + case V4L2_PIX_FMT_ARGB32: + buf[0][offset] = alpha; + buf[0][offset + 1] = r_y; + buf[0][offset + 2] = g_u; + buf[0][offset + 3] = b_v; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_XBGR32: + alpha = 0; + /* fall through */ + case V4L2_PIX_FMT_ABGR32: + buf[0][offset] = b_v; + buf[0][offset + 1] = g_u; + buf[0][offset + 2] = r_y; + buf[0][offset + 3] = alpha; + break; + } +} + +/* Return how many pattern lines are used by the current pattern. */ +static unsigned tpg_get_pat_lines(struct tpg_data *tpg) +{ + switch (tpg->pattern) { + case TPG_PAT_CHECKERS_16X16: + case TPG_PAT_CHECKERS_1X1: + case TPG_PAT_ALTERNATING_HLINES: + case TPG_PAT_CROSS_1_PIXEL: + case TPG_PAT_CROSS_2_PIXELS: + case TPG_PAT_CROSS_10_PIXELS: + return 2; + case TPG_PAT_100_COLORSQUARES: + case TPG_PAT_100_HCOLORBAR: + return 8; + default: + return 1; + } +} + +/* Which pattern line should be used for the given frame line. */ +static unsigned tpg_get_pat_line(struct tpg_data *tpg, unsigned line) +{ + switch (tpg->pattern) { + case TPG_PAT_CHECKERS_16X16: + return (line >> 4) & 1; + case TPG_PAT_CHECKERS_1X1: + case TPG_PAT_ALTERNATING_HLINES: + return line & 1; + case TPG_PAT_100_COLORSQUARES: + case TPG_PAT_100_HCOLORBAR: + return (line * 8) / tpg->src_height; + case TPG_PAT_CROSS_1_PIXEL: + return line == tpg->src_height / 2; + case TPG_PAT_CROSS_2_PIXELS: + return (line + 1) / 2 == tpg->src_height / 4; + case TPG_PAT_CROSS_10_PIXELS: + return (line + 10) / 20 == tpg->src_height / 40; + default: + return 0; + } +} + +/* + * Which color should be used for the given pattern line and X coordinate. + * Note: x is in the range 0 to 2 * tpg->src_width. + */ +static enum tpg_color tpg_get_color(struct tpg_data *tpg, unsigned pat_line, unsigned x) +{ + /* Maximum number of bars are TPG_COLOR_MAX - otherwise, the input print code + should be modified */ + static const enum tpg_color bars[3][8] = { + /* Standard ITU-R 75% color bar sequence */ + { TPG_COLOR_CSC_WHITE, TPG_COLOR_75_YELLOW, + TPG_COLOR_75_CYAN, TPG_COLOR_75_GREEN, + TPG_COLOR_75_MAGENTA, TPG_COLOR_75_RED, + TPG_COLOR_75_BLUE, TPG_COLOR_100_BLACK, }, + /* Standard ITU-R 100% color bar sequence */ + { TPG_COLOR_100_WHITE, TPG_COLOR_100_YELLOW, + TPG_COLOR_100_CYAN, TPG_COLOR_100_GREEN, + TPG_COLOR_100_MAGENTA, TPG_COLOR_100_RED, + TPG_COLOR_100_BLUE, TPG_COLOR_100_BLACK, }, + /* Color bar sequence suitable to test CSC */ + { TPG_COLOR_CSC_WHITE, TPG_COLOR_CSC_YELLOW, + TPG_COLOR_CSC_CYAN, TPG_COLOR_CSC_GREEN, + TPG_COLOR_CSC_MAGENTA, TPG_COLOR_CSC_RED, + TPG_COLOR_CSC_BLUE, TPG_COLOR_CSC_BLACK, }, + }; + + switch (tpg->pattern) { + case TPG_PAT_75_COLORBAR: + case TPG_PAT_100_COLORBAR: + case TPG_PAT_CSC_COLORBAR: + return bars[tpg->pattern][((x * 8) / tpg->src_width) % 8]; + case TPG_PAT_100_COLORSQUARES: + return bars[1][(pat_line + (x * 8) / tpg->src_width) % 8]; + case TPG_PAT_100_HCOLORBAR: + return bars[1][pat_line]; + case TPG_PAT_BLACK: + return TPG_COLOR_100_BLACK; + case TPG_PAT_WHITE: + return TPG_COLOR_100_WHITE; + case TPG_PAT_RED: + return TPG_COLOR_100_RED; + case TPG_PAT_GREEN: + return TPG_COLOR_100_GREEN; + case TPG_PAT_BLUE: + return TPG_COLOR_100_BLUE; + case TPG_PAT_CHECKERS_16X16: + return (((x >> 4) & 1) ^ (pat_line & 1)) ? + TPG_COLOR_100_BLACK : TPG_COLOR_100_WHITE; + case TPG_PAT_CHECKERS_1X1: + return ((x & 1) ^ (pat_line & 1)) ? + TPG_COLOR_100_WHITE : TPG_COLOR_100_BLACK; + case TPG_PAT_ALTERNATING_HLINES: + return pat_line ? TPG_COLOR_100_WHITE : TPG_COLOR_100_BLACK; + case TPG_PAT_ALTERNATING_VLINES: + return (x & 1) ? TPG_COLOR_100_WHITE : TPG_COLOR_100_BLACK; + case TPG_PAT_CROSS_1_PIXEL: + if (pat_line || (x % tpg->src_width) == tpg->src_width / 2) + return TPG_COLOR_100_BLACK; + return TPG_COLOR_100_WHITE; + case TPG_PAT_CROSS_2_PIXELS: + if (pat_line || ((x % tpg->src_width) + 1) / 2 == tpg->src_width / 4) + return TPG_COLOR_100_BLACK; + return TPG_COLOR_100_WHITE; + case TPG_PAT_CROSS_10_PIXELS: + if (pat_line || ((x % tpg->src_width) + 10) / 20 == tpg->src_width / 40) + return TPG_COLOR_100_BLACK; + return TPG_COLOR_100_WHITE; + case TPG_PAT_GRAY_RAMP: + return TPG_COLOR_RAMP + ((x % tpg->src_width) * 256) / tpg->src_width; + default: + return TPG_COLOR_100_RED; + } +} + +/* + * Given the pixel aspect ratio and video aspect ratio calculate the + * coordinates of a centered square and the coordinates of the border of + * the active video area. The coordinates are relative to the source + * frame rectangle. + */ +static void tpg_calculate_square_border(struct tpg_data *tpg) +{ + unsigned w = tpg->src_width; + unsigned h = tpg->src_height; + unsigned sq_w, sq_h; + + sq_w = (w * 2 / 5) & ~1; + if (((w - sq_w) / 2) & 1) + sq_w += 2; + sq_h = sq_w; + tpg->square.width = sq_w; + if (tpg->vid_aspect == TPG_VIDEO_ASPECT_16X9_ANAMORPHIC) { + unsigned ana_sq_w = (sq_w / 4) * 3; + + if (((w - ana_sq_w) / 2) & 1) + ana_sq_w += 2; + tpg->square.width = ana_sq_w; + } + tpg->square.left = (w - tpg->square.width) / 2; + if (tpg->pix_aspect == TPG_PIXEL_ASPECT_NTSC) + sq_h = sq_w * 10 / 11; + else if (tpg->pix_aspect == TPG_PIXEL_ASPECT_PAL) + sq_h = sq_w * 59 / 54; + tpg->square.height = sq_h; + tpg->square.top = (h - sq_h) / 2; + tpg->border.left = 0; + tpg->border.width = w; + tpg->border.top = 0; + tpg->border.height = h; + switch (tpg->vid_aspect) { + case TPG_VIDEO_ASPECT_4X3: + if (tpg->pix_aspect) + return; + if (3 * w >= 4 * h) { + tpg->border.width = ((4 * h) / 3) & ~1; + if (((w - tpg->border.width) / 2) & ~1) + tpg->border.width -= 2; + tpg->border.left = (w - tpg->border.width) / 2; + break; + } + tpg->border.height = ((3 * w) / 4) & ~1; + tpg->border.top = (h - tpg->border.height) / 2; + break; + case TPG_VIDEO_ASPECT_16X9_CENTRE: + if (tpg->pix_aspect) { + tpg->border.height = tpg->pix_aspect == TPG_PIXEL_ASPECT_NTSC ? 400 : 430; + tpg->border.top = (h - tpg->border.height) / 2; + break; + } + if (9 * w >= 16 * h) { + tpg->border.width = ((16 * h) / 9) & ~1; + if (((w - tpg->border.width) / 2) & ~1) + tpg->border.width -= 2; + tpg->border.left = (w - tpg->border.width) / 2; + break; + } + tpg->border.height = ((9 * w) / 16) & ~1; + tpg->border.top = (h - tpg->border.height) / 2; + break; + default: + break; + } +} + +static void tpg_precalculate_line(struct tpg_data *tpg) +{ + enum tpg_color contrast; + unsigned pat; + unsigned p; + unsigned x; + + switch (tpg->pattern) { + case TPG_PAT_GREEN: + contrast = TPG_COLOR_100_RED; + break; + case TPG_PAT_CSC_COLORBAR: + contrast = TPG_COLOR_CSC_GREEN; + break; + default: + contrast = TPG_COLOR_100_GREEN; + break; + } + + for (pat = 0; pat < tpg_get_pat_lines(tpg); pat++) { + /* Coarse scaling with Bresenham */ + unsigned int_part = tpg->src_width / tpg->scaled_width; + unsigned fract_part = tpg->src_width % tpg->scaled_width; + unsigned src_x = 0; + unsigned error = 0; + + for (x = 0; x < tpg->scaled_width * 2; x += 2) { + unsigned real_x = src_x; + enum tpg_color color1, color2; + u8 pix[TPG_MAX_PLANES][8]; + + real_x = tpg->hflip ? tpg->src_width * 2 - real_x - 2 : real_x; + color1 = tpg_get_color(tpg, pat, real_x); + + src_x += int_part; + error += fract_part; + if (error >= tpg->scaled_width) { + error -= tpg->scaled_width; + src_x++; + } + + real_x = src_x; + real_x = tpg->hflip ? tpg->src_width * 2 - real_x - 2 : real_x; + color2 = tpg_get_color(tpg, pat, real_x); + + src_x += int_part; + error += fract_part; + if (error >= tpg->scaled_width) { + error -= tpg->scaled_width; + src_x++; + } + + gen_twopix(tpg, pix, tpg->hflip ? color2 : color1, 0); + gen_twopix(tpg, pix, tpg->hflip ? color1 : color2, 1); + for (p = 0; p < tpg->planes; p++) { + unsigned twopixsize = tpg->twopixelsize[p]; + u8 *pos = tpg->lines[pat][p] + x * twopixsize / 2; + + memcpy(pos, pix[p], twopixsize); + } + } + } + for (x = 0; x < tpg->scaled_width; x += 2) { + u8 pix[TPG_MAX_PLANES][8]; + + gen_twopix(tpg, pix, contrast, 0); + gen_twopix(tpg, pix, contrast, 1); + for (p = 0; p < tpg->planes; p++) { + unsigned twopixsize = tpg->twopixelsize[p]; + u8 *pos = tpg->contrast_line[p] + x * twopixsize / 2; + + memcpy(pos, pix[p], twopixsize); + } + } + for (x = 0; x < tpg->scaled_width; x += 2) { + u8 pix[TPG_MAX_PLANES][8]; + + gen_twopix(tpg, pix, TPG_COLOR_100_BLACK, 0); + gen_twopix(tpg, pix, TPG_COLOR_100_BLACK, 1); + for (p = 0; p < tpg->planes; p++) { + unsigned twopixsize = tpg->twopixelsize[p]; + u8 *pos = tpg->black_line[p] + x * twopixsize / 2; + + memcpy(pos, pix[p], twopixsize); + } + } + for (x = 0; x < tpg->scaled_width * 2; x += 2) { + u8 pix[TPG_MAX_PLANES][8]; + + gen_twopix(tpg, pix, TPG_COLOR_RANDOM, 0); + gen_twopix(tpg, pix, TPG_COLOR_RANDOM, 1); + for (p = 0; p < tpg->planes; p++) { + unsigned twopixsize = tpg->twopixelsize[p]; + u8 *pos = tpg->random_line[p] + x * twopixsize / 2; + + memcpy(pos, pix[p], twopixsize); + } + } + gen_twopix(tpg, tpg->textbg, TPG_COLOR_TEXTBG, 0); + gen_twopix(tpg, tpg->textbg, TPG_COLOR_TEXTBG, 1); + gen_twopix(tpg, tpg->textfg, TPG_COLOR_TEXTFG, 0); + gen_twopix(tpg, tpg->textfg, TPG_COLOR_TEXTFG, 1); +} + +/* need this to do rgb24 rendering */ +typedef struct { u16 __; u8 _; } __packed x24; + +void tpg_gen_text(struct tpg_data *tpg, u8 *basep[TPG_MAX_PLANES][2], + int y, int x, char *text) +{ + int line; + unsigned step = V4L2_FIELD_HAS_T_OR_B(tpg->field) ? 2 : 1; + unsigned div = step; + unsigned first = 0; + unsigned len = strlen(text); + unsigned p; + + if (font8x16 == NULL || basep == NULL) + return; + + /* Checks if it is possible to show string */ + if (y + 16 >= tpg->compose.height || x + 8 >= tpg->compose.width) + return; + + if (len > (tpg->compose.width - x) / 8) + len = (tpg->compose.width - x) / 8; + if (tpg->vflip) + y = tpg->compose.height - y - 16; + if (tpg->hflip) + x = tpg->compose.width - x - 8; + y += tpg->compose.top; + x += tpg->compose.left; + if (tpg->field == V4L2_FIELD_BOTTOM) + first = 1; + else if (tpg->field == V4L2_FIELD_SEQ_TB || tpg->field == V4L2_FIELD_SEQ_BT) + div = 2; + + for (p = 0; p < tpg->planes; p++) { + /* Print stream time */ +#define PRINTSTR(PIXTYPE) do { \ + PIXTYPE fg; \ + PIXTYPE bg; \ + memcpy(&fg, tpg->textfg[p], sizeof(PIXTYPE)); \ + memcpy(&bg, tpg->textbg[p], sizeof(PIXTYPE)); \ + \ + for (line = first; line < 16; line += step) { \ + int l = tpg->vflip ? 15 - line : line; \ + PIXTYPE *pos = (PIXTYPE *)(basep[p][line & 1] + \ + ((y * step + l) / div) * tpg->bytesperline[p] + \ + x * sizeof(PIXTYPE)); \ + unsigned s; \ + \ + for (s = 0; s < len; s++) { \ + u8 chr = font8x16[text[s] * 16 + line]; \ + \ + if (tpg->hflip) { \ + pos[7] = (chr & (0x01 << 7) ? fg : bg); \ + pos[6] = (chr & (0x01 << 6) ? fg : bg); \ + pos[5] = (chr & (0x01 << 5) ? fg : bg); \ + pos[4] = (chr & (0x01 << 4) ? fg : bg); \ + pos[3] = (chr & (0x01 << 3) ? fg : bg); \ + pos[2] = (chr & (0x01 << 2) ? fg : bg); \ + pos[1] = (chr & (0x01 << 1) ? fg : bg); \ + pos[0] = (chr & (0x01 << 0) ? fg : bg); \ + } else { \ + pos[0] = (chr & (0x01 << 7) ? fg : bg); \ + pos[1] = (chr & (0x01 << 6) ? fg : bg); \ + pos[2] = (chr & (0x01 << 5) ? fg : bg); \ + pos[3] = (chr & (0x01 << 4) ? fg : bg); \ + pos[4] = (chr & (0x01 << 3) ? fg : bg); \ + pos[5] = (chr & (0x01 << 2) ? fg : bg); \ + pos[6] = (chr & (0x01 << 1) ? fg : bg); \ + pos[7] = (chr & (0x01 << 0) ? fg : bg); \ + } \ + \ + pos += tpg->hflip ? -8 : 8; \ + } \ + } \ +} while (0) + + switch (tpg->twopixelsize[p]) { + case 2: + PRINTSTR(u8); break; + case 4: + PRINTSTR(u16); break; + case 6: + PRINTSTR(x24); break; + case 8: + PRINTSTR(u32); break; + } + } +} + +void tpg_update_mv_step(struct tpg_data *tpg) +{ + int factor = tpg->mv_hor_mode > TPG_MOVE_NONE ? -1 : 1; + + if (tpg->hflip) + factor = -factor; + switch (tpg->mv_hor_mode) { + case TPG_MOVE_NEG_FAST: + case TPG_MOVE_POS_FAST: + tpg->mv_hor_step = ((tpg->src_width + 319) / 320) * 4; + break; + case TPG_MOVE_NEG: + case TPG_MOVE_POS: + tpg->mv_hor_step = ((tpg->src_width + 639) / 640) * 4; + break; + case TPG_MOVE_NEG_SLOW: + case TPG_MOVE_POS_SLOW: + tpg->mv_hor_step = 2; + break; + case TPG_MOVE_NONE: + tpg->mv_hor_step = 0; + break; + } + tpg->mv_hor_step *= factor; + + factor = tpg->mv_vert_mode > TPG_MOVE_NONE ? -1 : 1; + switch (tpg->mv_vert_mode) { + case TPG_MOVE_NEG_FAST: + case TPG_MOVE_POS_FAST: + tpg->mv_vert_step = ((tpg->src_width + 319) / 320) * 4; + break; + case TPG_MOVE_NEG: + case TPG_MOVE_POS: + tpg->mv_vert_step = ((tpg->src_width + 639) / 640) * 4; + break; + case TPG_MOVE_NEG_SLOW: + case TPG_MOVE_POS_SLOW: + tpg->mv_vert_step = 1; + break; + case TPG_MOVE_NONE: + tpg->mv_vert_step = 0; + break; + } + tpg->mv_vert_step *= factor; +} + +/* Map the line number relative to the crop rectangle to a frame line number */ +static unsigned tpg_calc_frameline(struct tpg_data *tpg, unsigned src_y, + unsigned field) +{ + switch (field) { + case V4L2_FIELD_TOP: + return tpg->crop.top + src_y * 2; + case V4L2_FIELD_BOTTOM: + return tpg->crop.top + src_y * 2 + 1; + default: + return src_y + tpg->crop.top; + } +} + +/* + * Map the line number relative to the compose rectangle to a destination + * buffer line number. + */ +static unsigned tpg_calc_buffer_line(struct tpg_data *tpg, unsigned y, + unsigned field) +{ + y += tpg->compose.top; + switch (field) { + case V4L2_FIELD_SEQ_TB: + if (y & 1) + return tpg->buf_height / 2 + y / 2; + return y / 2; + case V4L2_FIELD_SEQ_BT: + if (y & 1) + return y / 2; + return tpg->buf_height / 2 + y / 2; + default: + return y; + } +} + +static void tpg_recalc(struct tpg_data *tpg) +{ + if (tpg->recalc_colors) { + tpg->recalc_colors = false; + tpg->recalc_lines = true; + tpg_precalculate_colors(tpg); + } + if (tpg->recalc_square_border) { + tpg->recalc_square_border = false; + tpg_calculate_square_border(tpg); + } + if (tpg->recalc_lines) { + tpg->recalc_lines = false; + tpg_precalculate_line(tpg); + } +} + +void tpg_calc_text_basep(struct tpg_data *tpg, + u8 *basep[TPG_MAX_PLANES][2], unsigned p, u8 *vbuf) +{ + unsigned stride = tpg->bytesperline[p]; + + tpg_recalc(tpg); + + basep[p][0] = vbuf; + basep[p][1] = vbuf; + if (tpg->field == V4L2_FIELD_SEQ_TB) + basep[p][1] += tpg->buf_height * stride / 2; + else if (tpg->field == V4L2_FIELD_SEQ_BT) + basep[p][0] += tpg->buf_height * stride / 2; +} + +void tpg_fillbuffer(struct tpg_data *tpg, v4l2_std_id std, unsigned p, u8 *vbuf) +{ + bool is_tv = std; + bool is_60hz = is_tv && (std & V4L2_STD_525_60); + unsigned mv_hor_old = tpg->mv_hor_count % tpg->src_width; + unsigned mv_hor_new = (tpg->mv_hor_count + tpg->mv_hor_step) % tpg->src_width; + unsigned mv_vert_old = tpg->mv_vert_count % tpg->src_height; + unsigned mv_vert_new = (tpg->mv_vert_count + tpg->mv_vert_step) % tpg->src_height; + unsigned wss_width; + unsigned f; + int hmax = (tpg->compose.height * tpg->perc_fill) / 100; + int h; + unsigned twopixsize = tpg->twopixelsize[p]; + unsigned img_width = tpg->compose.width * twopixsize / 2; + unsigned line_offset; + unsigned left_pillar_width = 0; + unsigned right_pillar_start = img_width; + unsigned stride = tpg->bytesperline[p]; + unsigned factor = V4L2_FIELD_HAS_T_OR_B(tpg->field) ? 2 : 1; + u8 *orig_vbuf = vbuf; + + /* Coarse scaling with Bresenham */ + unsigned int_part = (tpg->crop.height / factor) / tpg->compose.height; + unsigned fract_part = (tpg->crop.height / factor) % tpg->compose.height; + unsigned src_y = 0; + unsigned error = 0; + + tpg_recalc(tpg); + + mv_hor_old = (mv_hor_old * tpg->scaled_width / tpg->src_width) & ~1; + mv_hor_new = (mv_hor_new * tpg->scaled_width / tpg->src_width) & ~1; + wss_width = tpg->crop.left < tpg->src_width / 2 ? + tpg->src_width / 2 - tpg->crop.left : 0; + if (wss_width > tpg->crop.width) + wss_width = tpg->crop.width; + wss_width = wss_width * tpg->scaled_width / tpg->src_width; + + vbuf += tpg->compose.left * twopixsize / 2; + line_offset = tpg->crop.left * tpg->scaled_width / tpg->src_width; + line_offset = (line_offset & ~1) * twopixsize / 2; + if (tpg->crop.left < tpg->border.left) { + left_pillar_width = tpg->border.left - tpg->crop.left; + if (left_pillar_width > tpg->crop.width) + left_pillar_width = tpg->crop.width; + left_pillar_width = (left_pillar_width * tpg->scaled_width) / tpg->src_width; + left_pillar_width = (left_pillar_width & ~1) * twopixsize / 2; + } + if (tpg->crop.left + tpg->crop.width > tpg->border.left + tpg->border.width) { + right_pillar_start = tpg->border.left + tpg->border.width - tpg->crop.left; + right_pillar_start = (right_pillar_start * tpg->scaled_width) / tpg->src_width; + right_pillar_start = (right_pillar_start & ~1) * twopixsize / 2; + if (right_pillar_start > img_width) + right_pillar_start = img_width; + } + + f = tpg->field == (is_60hz ? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM); + + for (h = 0; h < tpg->compose.height; h++) { + bool even; + bool fill_blank = false; + unsigned frame_line; + unsigned buf_line; + unsigned pat_line_old; + unsigned pat_line_new; + u8 *linestart_older; + u8 *linestart_newer; + u8 *linestart_top; + u8 *linestart_bottom; + + frame_line = tpg_calc_frameline(tpg, src_y, tpg->field); + even = !(frame_line & 1); + buf_line = tpg_calc_buffer_line(tpg, h, tpg->field); + src_y += int_part; + error += fract_part; + if (error >= tpg->compose.height) { + error -= tpg->compose.height; + src_y++; + } + + if (h >= hmax) { + if (hmax == tpg->compose.height) + continue; + if (!tpg->perc_fill_blank) + continue; + fill_blank = true; + } + + if (tpg->vflip) + frame_line = tpg->src_height - frame_line - 1; + + if (fill_blank) { + linestart_older = tpg->contrast_line[p]; + linestart_newer = tpg->contrast_line[p]; + } else if (tpg->qual != TPG_QUAL_NOISE && + (frame_line < tpg->border.top || + frame_line >= tpg->border.top + tpg->border.height)) { + linestart_older = tpg->black_line[p]; + linestart_newer = tpg->black_line[p]; + } else if (tpg->pattern == TPG_PAT_NOISE || tpg->qual == TPG_QUAL_NOISE) { + linestart_older = tpg->random_line[p] + + twopixsize * prandom_u32_max(tpg->src_width / 2); + linestart_newer = tpg->random_line[p] + + twopixsize * prandom_u32_max(tpg->src_width / 2); + } else { + pat_line_old = tpg_get_pat_line(tpg, + (frame_line + mv_vert_old) % tpg->src_height); + pat_line_new = tpg_get_pat_line(tpg, + (frame_line + mv_vert_new) % tpg->src_height); + linestart_older = tpg->lines[pat_line_old][p] + + mv_hor_old * twopixsize / 2; + linestart_newer = tpg->lines[pat_line_new][p] + + mv_hor_new * twopixsize / 2; + linestart_older += line_offset; + linestart_newer += line_offset; + } + if (is_60hz) { + linestart_top = linestart_newer; + linestart_bottom = linestart_older; + } else { + linestart_top = linestart_older; + linestart_bottom = linestart_newer; + } + + switch (tpg->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + if (even) + memcpy(vbuf + buf_line * stride, linestart_top, img_width); + else + memcpy(vbuf + buf_line * stride, linestart_bottom, img_width); + break; + case V4L2_FIELD_INTERLACED_BT: + if (even) + memcpy(vbuf + buf_line * stride, linestart_bottom, img_width); + else + memcpy(vbuf + buf_line * stride, linestart_top, img_width); + break; + case V4L2_FIELD_TOP: + memcpy(vbuf + buf_line * stride, linestart_top, img_width); + break; + case V4L2_FIELD_BOTTOM: + memcpy(vbuf + buf_line * stride, linestart_bottom, img_width); + break; + case V4L2_FIELD_NONE: + default: + memcpy(vbuf + buf_line * stride, linestart_older, img_width); + break; + } + + if (is_tv && !is_60hz && frame_line == 0 && wss_width) { + /* + * Replace the first half of the top line of a 50 Hz frame + * with random data to simulate a WSS signal. + */ + u8 *wss = tpg->random_line[p] + + twopixsize * prandom_u32_max(tpg->src_width / 2); + + memcpy(vbuf + buf_line * stride, wss, wss_width * twopixsize / 2); + } + } + + vbuf = orig_vbuf; + vbuf += tpg->compose.left * twopixsize / 2; + src_y = 0; + error = 0; + for (h = 0; h < tpg->compose.height; h++) { + unsigned frame_line = tpg_calc_frameline(tpg, src_y, tpg->field); + unsigned buf_line = tpg_calc_buffer_line(tpg, h, tpg->field); + const struct v4l2_rect *sq = &tpg->square; + const struct v4l2_rect *b = &tpg->border; + const struct v4l2_rect *c = &tpg->crop; + + src_y += int_part; + error += fract_part; + if (error >= tpg->compose.height) { + error -= tpg->compose.height; + src_y++; + } + + if (tpg->show_border && frame_line >= b->top && + frame_line < b->top + b->height) { + unsigned bottom = b->top + b->height - 1; + unsigned left = left_pillar_width; + unsigned right = right_pillar_start; + + if (frame_line == b->top || frame_line == b->top + 1 || + frame_line == bottom || frame_line == bottom - 1) { + memcpy(vbuf + buf_line * stride + left, tpg->contrast_line[p], + right - left); + } else { + if (b->left >= c->left && + b->left < c->left + c->width) + memcpy(vbuf + buf_line * stride + left, + tpg->contrast_line[p], twopixsize); + if (b->left + b->width > c->left && + b->left + b->width <= c->left + c->width) + memcpy(vbuf + buf_line * stride + right - twopixsize, + tpg->contrast_line[p], twopixsize); + } + } + if (tpg->qual != TPG_QUAL_NOISE && frame_line >= b->top && + frame_line < b->top + b->height) { + memcpy(vbuf + buf_line * stride, tpg->black_line[p], left_pillar_width); + memcpy(vbuf + buf_line * stride + right_pillar_start, tpg->black_line[p], + img_width - right_pillar_start); + } + if (tpg->show_square && frame_line >= sq->top && + frame_line < sq->top + sq->height && + sq->left < c->left + c->width && + sq->left + sq->width >= c->left) { + unsigned left = sq->left; + unsigned width = sq->width; + + if (c->left > left) { + width -= c->left - left; + left = c->left; + } + if (c->left + c->width < left + width) + width -= left + width - c->left - c->width; + left -= c->left; + left = (left * tpg->scaled_width) / tpg->src_width; + left = (left & ~1) * twopixsize / 2; + width = (width * tpg->scaled_width) / tpg->src_width; + width = (width & ~1) * twopixsize / 2; + memcpy(vbuf + buf_line * stride + left, tpg->contrast_line[p], width); + } + if (tpg->insert_sav) { + unsigned offset = (tpg->compose.width / 6) * twopixsize; + u8 *p = vbuf + buf_line * stride + offset; + unsigned vact = 0, hact = 0; + + p[0] = 0xff; + p[1] = 0; + p[2] = 0; + p[3] = 0x80 | (f << 6) | (vact << 5) | (hact << 4) | + ((hact ^ vact) << 3) | + ((hact ^ f) << 2) | + ((f ^ vact) << 1) | + (hact ^ vact ^ f); + } + if (tpg->insert_eav) { + unsigned offset = (tpg->compose.width / 6) * 2 * twopixsize; + u8 *p = vbuf + buf_line * stride + offset; + unsigned vact = 0, hact = 1; + + p[0] = 0xff; + p[1] = 0; + p[2] = 0; + p[3] = 0x80 | (f << 6) | (vact << 5) | (hact << 4) | + ((hact ^ vact) << 3) | + ((hact ^ f) << 2) | + ((f ^ vact) << 1) | + (hact ^ vact ^ f); + } + } +} diff --git a/drivers/media/platform/vivid/vivid-tpg.h b/drivers/media/platform/vivid/vivid-tpg.h new file mode 100644 index 0000000..441c935 --- /dev/null +++ b/drivers/media/platform/vivid/vivid-tpg.h @@ -0,0 +1,437 @@ +/* + * vivid-tpg.h - Test Pattern Generator + * + * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _VIVID_TPG_H_ +#define _VIVID_TPG_H_ + +#include <linux/version.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include "vivid-tpg-colors.h" + +enum tpg_pattern { + TPG_PAT_75_COLORBAR, + TPG_PAT_100_COLORBAR, + TPG_PAT_CSC_COLORBAR, + TPG_PAT_100_HCOLORBAR, + TPG_PAT_100_COLORSQUARES, + TPG_PAT_BLACK, + TPG_PAT_WHITE, + TPG_PAT_RED, + TPG_PAT_GREEN, + TPG_PAT_BLUE, + TPG_PAT_CHECKERS_16X16, + TPG_PAT_CHECKERS_1X1, + TPG_PAT_ALTERNATING_HLINES, + TPG_PAT_ALTERNATING_VLINES, + TPG_PAT_CROSS_1_PIXEL, + TPG_PAT_CROSS_2_PIXELS, + TPG_PAT_CROSS_10_PIXELS, + TPG_PAT_GRAY_RAMP, + + /* Must be the last pattern */ + TPG_PAT_NOISE, +}; + +extern const char * const tpg_pattern_strings[]; + +enum tpg_quality { + TPG_QUAL_COLOR, + TPG_QUAL_GRAY, + TPG_QUAL_NOISE +}; + +enum tpg_video_aspect { + TPG_VIDEO_ASPECT_IMAGE, + TPG_VIDEO_ASPECT_4X3, + TPG_VIDEO_ASPECT_16X9_CENTRE, + TPG_VIDEO_ASPECT_16X9_ANAMORPHIC, +}; + +enum tpg_pixel_aspect { + TPG_PIXEL_ASPECT_SQUARE, + TPG_PIXEL_ASPECT_NTSC, + TPG_PIXEL_ASPECT_PAL, +}; + +enum tpg_move_mode { + TPG_MOVE_NEG_FAST, + TPG_MOVE_NEG, + TPG_MOVE_NEG_SLOW, + TPG_MOVE_NONE, + TPG_MOVE_POS_SLOW, + TPG_MOVE_POS, + TPG_MOVE_POS_FAST, +}; + +extern const char * const tpg_aspect_strings[]; + +#define TPG_MAX_PLANES 2 +#define TPG_MAX_PAT_LINES 8 + +struct tpg_data { + /* Source frame size */ + unsigned src_width, src_height; + /* Buffer height */ + unsigned buf_height; + /* Scaled output frame size */ + unsigned scaled_width; + u32 field; + /* crop coordinates are frame-based */ + struct v4l2_rect crop; + /* compose coordinates are format-based */ + struct v4l2_rect compose; + /* border and square coordinates are frame-based */ + struct v4l2_rect border; + struct v4l2_rect square; + + /* Color-related fields */ + enum tpg_quality qual; + unsigned qual_offset; + u8 alpha_component; + bool alpha_red_only; + u8 brightness; + u8 contrast; + u8 saturation; + s16 hue; + u32 fourcc; + bool is_yuv; + u32 colorspace; + enum tpg_video_aspect vid_aspect; + enum tpg_pixel_aspect pix_aspect; + unsigned rgb_range; + unsigned real_rgb_range; + unsigned planes; + /* Used to store the colors in native format, either RGB or YUV */ + u8 colors[TPG_COLOR_MAX][3]; + u8 textfg[TPG_MAX_PLANES][8], textbg[TPG_MAX_PLANES][8]; + /* size in bytes for two pixels in each plane */ + unsigned twopixelsize[TPG_MAX_PLANES]; + unsigned bytesperline[TPG_MAX_PLANES]; + + /* Configuration */ + enum tpg_pattern pattern; + bool hflip; + bool vflip; + unsigned perc_fill; + bool perc_fill_blank; + bool show_border; + bool show_square; + bool insert_sav; + bool insert_eav; + + /* Test pattern movement */ + enum tpg_move_mode mv_hor_mode; + int mv_hor_count; + int mv_hor_step; + enum tpg_move_mode mv_vert_mode; + int mv_vert_count; + int mv_vert_step; + + bool recalc_colors; + bool recalc_lines; + bool recalc_square_border; + + /* Used to store TPG_MAX_PAT_LINES lines, each with up to two planes */ + unsigned max_line_width; + u8 *lines[TPG_MAX_PAT_LINES][TPG_MAX_PLANES]; + u8 *random_line[TPG_MAX_PLANES]; + u8 *contrast_line[TPG_MAX_PLANES]; + u8 *black_line[TPG_MAX_PLANES]; +}; + +void tpg_init(struct tpg_data *tpg, unsigned w, unsigned h); +int tpg_alloc(struct tpg_data *tpg, unsigned max_w); +void tpg_free(struct tpg_data *tpg); +void tpg_reset_source(struct tpg_data *tpg, unsigned width, unsigned height, + u32 field); + +void tpg_set_font(const u8 *f); +void tpg_gen_text(struct tpg_data *tpg, + u8 *basep[TPG_MAX_PLANES][2], int y, int x, char *text); +void tpg_calc_text_basep(struct tpg_data *tpg, + u8 *basep[TPG_MAX_PLANES][2], unsigned p, u8 *vbuf); +void tpg_fillbuffer(struct tpg_data *tpg, v4l2_std_id std, unsigned p, u8 *vbuf); +bool tpg_s_fourcc(struct tpg_data *tpg, u32 fourcc); +void tpg_s_crop_compose(struct tpg_data *tpg, const struct v4l2_rect *crop, + const struct v4l2_rect *compose); + +static inline void tpg_s_pattern(struct tpg_data *tpg, enum tpg_pattern pattern) +{ + if (tpg->pattern == pattern) + return; + tpg->pattern = pattern; + tpg->recalc_colors = true; +} + +static inline void tpg_s_quality(struct tpg_data *tpg, + enum tpg_quality qual, unsigned qual_offset) +{ + if (tpg->qual == qual && tpg->qual_offset == qual_offset) + return; + tpg->qual = qual; + tpg->qual_offset = qual_offset; + tpg->recalc_colors = true; +} + +static inline enum tpg_quality tpg_g_quality(const struct tpg_data *tpg) +{ + return tpg->qual; +} + +static inline void tpg_s_alpha_component(struct tpg_data *tpg, + u8 alpha_component) +{ + if (tpg->alpha_component == alpha_component) + return; + tpg->alpha_component = alpha_component; + tpg->recalc_colors = true; +} + +static inline void tpg_s_alpha_mode(struct tpg_data *tpg, + bool red_only) +{ + if (tpg->alpha_red_only == red_only) + return; + tpg->alpha_red_only = red_only; + tpg->recalc_colors = true; +} + +static inline void tpg_s_brightness(struct tpg_data *tpg, + u8 brightness) +{ + if (tpg->brightness == brightness) + return; + tpg->brightness = brightness; + tpg->recalc_colors = true; +} + +static inline void tpg_s_contrast(struct tpg_data *tpg, + u8 contrast) +{ + if (tpg->contrast == contrast) + return; + tpg->contrast = contrast; + tpg->recalc_colors = true; +} + +static inline void tpg_s_saturation(struct tpg_data *tpg, + u8 saturation) +{ + if (tpg->saturation == saturation) + return; + tpg->saturation = saturation; + tpg->recalc_colors = true; +} + +static inline void tpg_s_hue(struct tpg_data *tpg, + s16 hue) +{ + if (tpg->hue == hue) + return; + tpg->hue = hue; + tpg->recalc_colors = true; +} + +static inline void tpg_s_rgb_range(struct tpg_data *tpg, + unsigned rgb_range) +{ + if (tpg->rgb_range == rgb_range) + return; + tpg->rgb_range = rgb_range; + tpg->recalc_colors = true; +} + +static inline void tpg_s_real_rgb_range(struct tpg_data *tpg, + unsigned rgb_range) +{ + if (tpg->real_rgb_range == rgb_range) + return; + tpg->real_rgb_range = rgb_range; + tpg->recalc_colors = true; +} + +static inline void tpg_s_colorspace(struct tpg_data *tpg, u32 colorspace) +{ + if (tpg->colorspace == colorspace) + return; + tpg->colorspace = colorspace; + tpg->recalc_colors = true; +} + +static inline u32 tpg_g_colorspace(const struct tpg_data *tpg) +{ + return tpg->colorspace; +} + +static inline unsigned tpg_g_planes(const struct tpg_data *tpg) +{ + return tpg->planes; +} + +static inline unsigned tpg_g_twopixelsize(const struct tpg_data *tpg, unsigned plane) +{ + return tpg->twopixelsize[plane]; +} + +static inline unsigned tpg_g_bytesperline(const struct tpg_data *tpg, unsigned plane) +{ + return tpg->bytesperline[plane]; +} + +static inline void tpg_s_bytesperline(struct tpg_data *tpg, unsigned plane, unsigned bpl) +{ + tpg->bytesperline[plane] = bpl; +} + +static inline void tpg_s_buf_height(struct tpg_data *tpg, unsigned h) +{ + tpg->buf_height = h; +} + +static inline void tpg_s_field(struct tpg_data *tpg, unsigned field) +{ + tpg->field = field; +} + +static inline void tpg_s_perc_fill(struct tpg_data *tpg, + unsigned perc_fill) +{ + tpg->perc_fill = perc_fill; +} + +static inline unsigned tpg_g_perc_fill(const struct tpg_data *tpg) +{ + return tpg->perc_fill; +} + +static inline void tpg_s_perc_fill_blank(struct tpg_data *tpg, + bool perc_fill_blank) +{ + tpg->perc_fill_blank = perc_fill_blank; +} + +static inline void tpg_s_video_aspect(struct tpg_data *tpg, + enum tpg_video_aspect vid_aspect) +{ + if (tpg->vid_aspect == vid_aspect) + return; + tpg->vid_aspect = vid_aspect; + tpg->recalc_square_border = true; +} + +static inline enum tpg_video_aspect tpg_g_video_aspect(const struct tpg_data *tpg) +{ + return tpg->vid_aspect; +} + +static inline void tpg_s_pixel_aspect(struct tpg_data *tpg, + enum tpg_pixel_aspect pix_aspect) +{ + if (tpg->pix_aspect == pix_aspect) + return; + tpg->pix_aspect = pix_aspect; + tpg->recalc_square_border = true; +} + +static inline void tpg_s_show_border(struct tpg_data *tpg, + bool show_border) +{ + tpg->show_border = show_border; +} + +static inline void tpg_s_show_square(struct tpg_data *tpg, + bool show_square) +{ + tpg->show_square = show_square; +} + +static inline void tpg_s_insert_sav(struct tpg_data *tpg, bool insert_sav) +{ + tpg->insert_sav = insert_sav; +} + +static inline void tpg_s_insert_eav(struct tpg_data *tpg, bool insert_eav) +{ + tpg->insert_eav = insert_eav; +} + +void tpg_update_mv_step(struct tpg_data *tpg); + +static inline void tpg_s_mv_hor_mode(struct tpg_data *tpg, + enum tpg_move_mode mv_hor_mode) +{ + tpg->mv_hor_mode = mv_hor_mode; + tpg_update_mv_step(tpg); +} + +static inline void tpg_s_mv_vert_mode(struct tpg_data *tpg, + enum tpg_move_mode mv_vert_mode) +{ + tpg->mv_vert_mode = mv_vert_mode; + tpg_update_mv_step(tpg); +} + +static inline void tpg_init_mv_count(struct tpg_data *tpg) +{ + tpg->mv_hor_count = tpg->mv_vert_count = 0; +} + +static inline void tpg_update_mv_count(struct tpg_data *tpg, bool frame_is_field) +{ + tpg->mv_hor_count += tpg->mv_hor_step * (frame_is_field ? 1 : 2); + tpg->mv_vert_count += tpg->mv_vert_step * (frame_is_field ? 1 : 2); +} + +static inline void tpg_s_hflip(struct tpg_data *tpg, bool hflip) +{ + if (tpg->hflip == hflip) + return; + tpg->hflip = hflip; + tpg_update_mv_step(tpg); + tpg->recalc_lines = true; +} + +static inline bool tpg_g_hflip(const struct tpg_data *tpg) +{ + return tpg->hflip; +} + +static inline void tpg_s_vflip(struct tpg_data *tpg, bool vflip) +{ + tpg->vflip = vflip; +} + +static inline bool tpg_g_vflip(const struct tpg_data *tpg) +{ + return tpg->vflip; +} + +static inline bool tpg_pattern_is_static(const struct tpg_data *tpg) +{ + return tpg->pattern != TPG_PAT_NOISE && + tpg->mv_hor_mode == TPG_MOVE_NONE && + tpg->mv_vert_mode == TPG_MOVE_NONE; +} + +#endif -- 2.0.1 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html