linux/drivers/media/common/v4l2-tpg/v4l2-tpg-colors.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * v4l2-tpg-colors.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 v4l2-tpg-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.
 */

#include <linux/videodev2.h>
#include <media/tpg/v4l2-tpg.h>

/* sRGB colors with range [0-255] */
const struct tpg_rbg_color8 tpg_colors[TPG_COLOR_MAX] =;

#ifndef COMPILE_APP

/* Generated table */
const unsigned short tpg_rec709_to_linear[255 * 16 + 1] =;

/* Generated table */
const unsigned short tpg_linear_to_rec709[255 * 16 + 1] =;

/* Generated table */
const struct tpg_rbg_color16 tpg_csc_colors[V4L2_COLORSPACE_DCI_P3 + 1][V4L2_XFER_FUNC_SMPTE2084 + 1][TPG_COLOR_CSC_BLACK + 1] =;

#else

/* This code generates the table above */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

static const double rec709_to_ntsc1953[3][3] = {
	/*
	 * This transform uses the Bradford method to compensate for
	 * the different whitepoints.
	 */
	{ 0.6785011, 0.2883441, 0.0331548 },
	{ 0.0165284, 1.0518725, -0.0684009 },
	{ 0.0179230, 0.0506096, 0.9314674 }
};

static const double rec709_to_ebu[3][3] = {
	{ 0.9578221, 0.0421779, -0.0000000 },
	{ -0.0000000, 1.0000000, 0.0000000 },
	{ -0.0000000, -0.0119367, 1.0119367 }
};

static const double rec709_to_170m[3][3] = {
	{ 1.0653640, -0.0553900, -0.0099740 },
	{ -0.0196361, 1.0363630, -0.0167269 },
	{ 0.0016327, 0.0044133, 0.9939540 },
};

static const double rec709_to_240m[3][3] = {
	{ 1.0653640, -0.0553900, -0.0099740 },
	{ -0.0196361, 1.0363630, -0.0167269 },
	{ 0.0016327, 0.0044133, 0.9939540 },
};

static const double rec709_to_oprgb[3][3] = {
	{ 0.7151627, 0.2848373, -0.0000000 },
	{ 0.0000000, 1.0000000, 0.0000000 },
	{ -0.0000000, 0.0411705, 0.9588295 },
};

static const double rec709_to_bt2020[3][3] = {
	{ 0.6274524, 0.3292485, 0.0432991 },
	{ 0.0691092, 0.9195311, 0.0113597 },
	{ 0.0163976, 0.0880301, 0.8955723 },
};

static const double rec709_to_dcip3[3][3] = {
	/*
	 * This transform uses the Bradford method to compensate for
	 * the different whitepoints.
	 */
	{ 0.8686648, 0.1288456, 0.0024896 },
	{ 0.0345479, 0.9618084, 0.0036437 },
	{ 0.0167785, 0.0710559, 0.9121655 }
};

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)
{
	if (v < -0.04045)
		return pow((-v + 0.055) / 1.055, 2.4);
	return (v <= 0.04045) ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4);
}

static double transfer_rgb_to_srgb(double v)
{
	if (v <= -0.0031308)
		return -1.055 * pow(-v, 1.0 / 2.4) + 0.055;
	if (v <= 0.0031308)
		return v * 12.92;
	return 1.055 * pow(v, 1.0 / 2.4) - 0.055;
}

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)
{
	if (v <= -0.018)
		return -1.099 * pow(-v, 0.45) + 0.099;
	return (v < 0.018) ? v * 4.5 : 1.099 * pow(v, 0.45) - 0.099;
}

static double transfer_rec709_to_rgb(double v)
{
	return (v < 0.081) ? v / 4.5 : pow((v + 0.099) / 1.099, 1.0 / 0.45);
}

static double transfer_rgb_to_oprgb(double v)
{
	return pow(v, 1.0 / 2.19921875);
}

static double transfer_rgb_to_dcip3(double v)
{
	return pow(v, 1.0 / 2.6);
}

static double transfer_rgb_to_smpte2084(double v)
{
	const double m1 = (2610.0 / 4096.0) / 4.0;
	const double m2 = 128.0 * 2523.0 / 4096.0;
	const double c1 = 3424.0 / 4096.0;
	const double c2 = 32.0 * 2413.0 / 4096.0;
	const double c3 = 32.0 * 2392.0 / 4096.0;

	/*
	 * The RGB input maps to the luminance range 0-100 cd/m^2, while
	 * SMPTE-2084 maps values to the luminance range of 0-10000 cd/m^2.
	 * Hence the factor 100.
	 */
	v /= 100.0;
	v = pow(v, m1);
	return pow((c1 + c2 * v) / (1 + c3 * v), m2);
}

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, enum v4l2_xfer_func xfer_func,
		double *r, double *g, double *b)
{
	int clamp = 1;

	*r = transfer_srgb_to_rgb(*r);
	*g = transfer_srgb_to_rgb(*g);
	*b = transfer_srgb_to_rgb(*b);

	/* Convert the primaries of Rec. 709 Linear RGB */
	switch (colorspace) {
	case V4L2_COLORSPACE_SMPTE240M:
		mult_matrix(r, g, b, rec709_to_240m);
		break;
	case V4L2_COLORSPACE_SMPTE170M:
		mult_matrix(r, g, b, rec709_to_170m);
		break;
	case V4L2_COLORSPACE_470_SYSTEM_BG:
		mult_matrix(r, g, b, rec709_to_ebu);
		break;
	case V4L2_COLORSPACE_470_SYSTEM_M:
		mult_matrix(r, g, b, rec709_to_ntsc1953);
		break;
	case V4L2_COLORSPACE_OPRGB:
		mult_matrix(r, g, b, rec709_to_oprgb);
		break;
	case V4L2_COLORSPACE_BT2020:
		mult_matrix(r, g, b, rec709_to_bt2020);
		break;
	case V4L2_COLORSPACE_DCI_P3:
		mult_matrix(r, g, b, rec709_to_dcip3);
		break;
	case V4L2_COLORSPACE_SRGB:
	case V4L2_COLORSPACE_REC709:
		break;
	default:
		break;
	}

	if (clamp) {
		*r = ((*r) < 0) ? 0 : (((*r) > 1) ? 1 : (*r));
		*g = ((*g) < 0) ? 0 : (((*g) > 1) ? 1 : (*g));
		*b = ((*b) < 0) ? 0 : (((*b) > 1) ? 1 : (*b));
	}

	switch (xfer_func) {
	case V4L2_XFER_FUNC_709:
		*r = transfer_rgb_to_rec709(*r);
		*g = transfer_rgb_to_rec709(*g);
		*b = transfer_rgb_to_rec709(*b);
		break;
	case V4L2_XFER_FUNC_SRGB:
		*r = transfer_rgb_to_srgb(*r);
		*g = transfer_rgb_to_srgb(*g);
		*b = transfer_rgb_to_srgb(*b);
		break;
	case V4L2_XFER_FUNC_OPRGB:
		*r = transfer_rgb_to_oprgb(*r);
		*g = transfer_rgb_to_oprgb(*g);
		*b = transfer_rgb_to_oprgb(*b);
		break;
	case V4L2_XFER_FUNC_DCI_P3:
		*r = transfer_rgb_to_dcip3(*r);
		*g = transfer_rgb_to_dcip3(*g);
		*b = transfer_rgb_to_dcip3(*b);
		break;
	case V4L2_XFER_FUNC_SMPTE2084:
		*r = transfer_rgb_to_smpte2084(*r);
		*g = transfer_rgb_to_smpte2084(*g);
		*b = transfer_rgb_to_smpte2084(*b);
		break;
	case V4L2_XFER_FUNC_SMPTE240M:
		*r = transfer_rgb_to_smpte240m(*r);
		*g = transfer_rgb_to_smpte240m(*g);
		*b = transfer_rgb_to_smpte240m(*b);
		break;
	case V4L2_XFER_FUNC_NONE:
		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,
		V4L2_COLORSPACE_OPRGB,
		V4L2_COLORSPACE_BT2020,
		0,
		V4L2_COLORSPACE_DCI_P3,
	};
	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",
		"V4L2_COLORSPACE_OPRGB",
		"V4L2_COLORSPACE_BT2020",
		"",
		"V4L2_COLORSPACE_DCI_P3",
	};
	static const char * const xfer_func_names[] = {
		"",
		"V4L2_XFER_FUNC_709",
		"V4L2_XFER_FUNC_SRGB",
		"V4L2_XFER_FUNC_OPRGB",
		"V4L2_XFER_FUNC_SMPTE240M",
		"V4L2_XFER_FUNC_NONE",
		"V4L2_XFER_FUNC_DCI_P3",
		"V4L2_XFER_FUNC_SMPTE2084",
	};
	int i;
	int x;
	int c;

	printf("/* Generated table */\n");
	printf("const unsigned short tpg_rec709_to_linear[255 * 16 + 1] = {");
	for (i = 0; i <= 255 * 16; i++) {
		if (i % 16 == 0)
			printf("\n\t");
		printf("%4d,%s",
			(int)(0.5 + 16.0 * 255.0 *
				transfer_rec709_to_rgb(i / (16.0 * 255.0))),
			i % 16 == 15 || i == 255 * 16 ? "" : " ");
	}
	printf("\n};\n\n");

	printf("/* Generated table */\n");
	printf("const unsigned short tpg_linear_to_rec709[255 * 16 + 1] = {");
	for (i = 0; i <= 255 * 16; i++) {
		if (i % 16 == 0)
			printf("\n\t");
		printf("%4d,%s",
			(int)(0.5 + 16.0 * 255.0 *
				transfer_rgb_to_rec709(i / (16.0 * 255.0))),
			i % 16 == 15 || i == 255 * 16 ? "" : " ");
	}
	printf("\n};\n\n");

	printf("/* Generated table */\n");
	printf("const struct tpg_rbg_color16 tpg_csc_colors[V4L2_COLORSPACE_DCI_P3 + 1][V4L2_XFER_FUNC_SMPTE2084 + 1][TPG_COLOR_CSC_BLACK + 1] = {\n");
	for (c = 0; c <= V4L2_COLORSPACE_DCI_P3; c++) {
		for (x = 1; x <= V4L2_XFER_FUNC_SMPTE2084; x++) {
			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, x, &r, &g, &b);

				printf("\t[%s][%s][%d] = { %d, %d, %d },\n",
					colorspace_names[c],
					xfer_func_names[x], i,
					(int)(r * 4080), (int)(g * 4080), (int)(b * 4080));
			}
		}
	}
	printf("};\n\n");
	return 0;
}

#endif