diff --git a/docs/reference/gtk/gtk4-image-tool.rst b/docs/reference/gtk/gtk4-image-tool.rst index f0d79d2490..911ab9d9c8 100644 --- a/docs/reference/gtk/gtk4-image-tool.rst +++ b/docs/reference/gtk/gtk4-image-tool.rst @@ -70,6 +70,11 @@ The ``convert`` command converts the image to a different format or color state. Convert to the given color state. The supported color states can be listed with ``--format=list``. +``--cicp=CICP`` + + Convert to a color state that is specified as a cicp tuple. The cicp tuple + must be specified as four numbers, separated by /, e.g. 1/13/6/0. + Relabeling ^^^^^^^^^^ @@ -78,5 +83,10 @@ This can be useful to produce wrong color renderings for diagnostics. ``--color-state=COLORSTATE`` - Convert to the given color state. The supported color states can be + Relabel to the given color state. The supported color states can be listed with ``--format=list``. + +``--cicp=CICP`` + + Relabel to a color state that is specified as a cicp tuple. The cicp tuple + must be specified as four numbers, separated by /, e.g. 1/13/6/0. diff --git a/gdk/gdkcolordefs.h b/gdk/gdkcolordefs.h index da02b7526e..c92f43053e 100644 --- a/gdk/gdkcolordefs.h +++ b/gdk/gdkcolordefs.h @@ -119,76 +119,82 @@ hlg_oetf (float v) * for how to derive the abc_to_xyz matrices from chromaticity coordinates. */ -static const float srgb_to_xyz[3][3] = { - { 0.4124564, 0.3575761, 0.1804375 }, - { 0.2126729, 0.7151522, 0.0721750 }, - { 0.0193339, 0.1191920, 0.9503041 } +static const float identity[9] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, }; -static const float xyz_to_srgb[3][3] = { - { 3.2404542, -1.5371385, -0.4985314 }, - { -0.9692660, 1.8760108, 0.0415560 }, - { 0.0556434, -0.2040259, 1.0572252 }, +static const float srgb_to_xyz[9] = { + 0.4124564, 0.3575761, 0.1804375, + 0.2126729, 0.7151522, 0.0721750, + 0.0193339, 0.1191920, 0.9503041, }; -static const float rec2020_to_xyz[3][3] = { - { 0.6369580, 0.1446169, 0.1688810 }, - { 0.2627002, 0.6779981, 0.0593017 }, - { 0.0000000, 0.0280727, 1.0609851 } +static const float xyz_to_srgb[9] = { + 3.2404542, -1.5371385, -0.4985314, + -0.9692660, 1.8760108, 0.0415560, + 0.0556434, -0.2040259, 1.0572252, }; -static const float xyz_to_rec2020[3][3] = { - { 1.7166512, -0.3556708, -0.2533663 }, - { -0.6666844, 1.6164812, 0.0157685 }, - { 0.0176399, -0.0427706, 0.9421031 }, +static const float rec2020_to_xyz[9] = { + 0.6369580, 0.1446169, 0.1688810, + 0.2627002, 0.6779981, 0.0593017, + 0.0000000, 0.0280727, 1.0609851, }; -static const float pal_to_xyz[3][3] = { - { 0.4305538, 0.3415498, 0.1783523 }, - { 0.2220043, 0.7066548, 0.0713409 }, - { 0.0201822, 0.1295534, 0.9393222 }, +static const float xyz_to_rec2020[9] = { + 1.7166512, -0.3556708, -0.2533663, + -0.6666844, 1.6164812, 0.0157685, + 0.0176399, -0.0427706, 0.9421031, }; -static const float xyz_to_pal[3][3] = { - { 3.0633611, -1.3933902, -0.4758237 }, - { -0.9692436, 1.8759675, 0.0415551 }, - { 0.0678610, -0.2287993, 1.0690896 }, +static const float pal_to_xyz[9] = { + 0.4305538, 0.3415498, 0.1783523, + 0.2220043, 0.7066548, 0.0713409, + 0.0201822, 0.1295534, 0.9393222, }; -static const float ntsc_to_xyz[3][3] = { - { 0.3935209, 0.3652581, 0.1916769 }, - { 0.2123764, 0.7010599, 0.0865638 }, - { 0.0187391, 0.1119339, 0.9583847 }, +static const float xyz_to_pal[9] = { + 3.0633611, -1.3933902, -0.4758237, + -0.9692436, 1.8759675, 0.0415551, + 0.0678610, -0.2287993, 1.0690896, }; -static const float xyz_to_ntsc[3][3] = { - { 3.5060033, -1.7397907, -0.5440583 }, - { -1.0690476, 1.9777789, 0.0351714 }, - { 0.0563066, -0.1969757, 1.0499523 }, +static const float ntsc_to_xyz[9] = { + 0.3935209, 0.3652581, 0.1916769, + 0.2123764, 0.7010599, 0.0865638, + 0.0187391, 0.1119339, 0.9583847, }; -static const float p3_to_xyz[3][3] = { - { 0.4865709, 0.2656677, 0.1982173 }, - { 0.2289746, 0.6917385, 0.0792869 }, - { 0.0000000, 0.0451134, 1.0439444 }, +static const float xyz_to_ntsc[9] = { + 3.5060033, -1.7397907, -0.5440583, + -1.0690476, 1.9777789, 0.0351714, + 0.0563066, -0.1969757, 1.0499523, }; -static const float xyz_to_p3[3][3] = { - { 2.4934969, -0.9313836, -0.4027108 }, - { -0.8294890, 1.7626641, 0.0236247 }, - { 0.0358458, -0.0761724, 0.9568845 }, +static const float p3_to_xyz[9] = { + 0.4865709, 0.2656677, 0.1982173, + 0.2289746, 0.6917385, 0.0792869, + 0.0000000, 0.0451134, 1.0439444, +}; + +static const float xyz_to_p3[9] = { + 2.4934969, -0.9313836, -0.4027108, + -0.8294890, 1.7626641, 0.0236247, + 0.0358458, -0.0761724, 0.9568845, }; /* premultiplied matrices for default conversions */ -static const float rec2020_to_srgb[3][3] = { - { 1.660227, -0.587548, -0.072838 }, - { -0.124553, 1.132926, -0.008350 }, - { -0.018155, -0.100603, 1.118998 }, +static const float rec2020_to_srgb[9] = { + 1.660227, -0.587548, -0.072838, + -0.124553, 1.132926, -0.008350, + -0.018155, -0.100603, 1.118998, }; -static const float srgb_to_rec2020[3][3] = { - { 0.627504, 0.329275, 0.043303 }, - { 0.069108, 0.919519, 0.011360 }, - { 0.016394, 0.088011, 0.895380 }, +static const float srgb_to_rec2020[9] = { + 0.627504, 0.329275, 0.043303, + 0.069108, 0.919519, 0.011360, + 0.016394, 0.088011, 0.895380, }; diff --git a/gdk/gdkcolorstate.c b/gdk/gdkcolorstate.c index 6823faff62..94e913249b 100644 --- a/gdk/gdkcolorstate.c +++ b/gdk/gdkcolorstate.c @@ -223,9 +223,9 @@ gdk_color_state_create_cicp_params (GdkColorState *self) /* {{{ Conversion functions */ typedef float (* GdkTransferFunc) (float v); -typedef const float GdkColorMatrix[3][3]; +typedef const float GdkColorMatrix[9]; -#define IDENTITY ((float**)0) +#define IDENTITY ((float*)0) #define NONE ((GdkTransferFunc)0) #define TRANSFORM(name, eotf, matrix, oetf) \ @@ -242,12 +242,12 @@ name (GdkColorState *self, \ values[i][1] = eotf (values[i][1]); \ values[i][2] = eotf (values[i][2]); \ } \ - if ((float **)matrix != IDENTITY) \ + if (matrix != IDENTITY) \ { \ float res[3]; \ - res[0] = matrix[0][0] * values[i][0] + matrix[0][1] * values[i][1] + matrix[0][2] * values[i][2]; \ - res[1] = matrix[1][0] * values[i][0] + matrix[1][1] * values[i][1] + matrix[1][2] * values[i][2]; \ - res[2] = matrix[2][0] * values[i][0] + matrix[2][1] * values[i][1] + matrix[2][2] * values[i][2]; \ + res[0] = matrix[0] * values[i][0] + matrix[1] * values[i][1] + matrix[2] * values[i][2]; \ + res[1] = matrix[3] * values[i][0] + matrix[4] * values[i][1] + matrix[5] * values[i][2]; \ + res[2] = matrix[6] * values[i][0] + matrix[7] * values[i][1] + matrix[8] * values[i][2]; \ values[i][0] = res[0]; \ values[i][1] = res[1]; \ values[i][2] = res[2]; \ @@ -426,10 +426,10 @@ struct _GdkCicpColorState GdkTransferFunc eotf; GdkTransferFunc oetf; - float to_srgb[3][3]; - float to_rec2020[3][3]; - float from_srgb[3][3]; - float from_rec2020[3][3]; + float *to_srgb; + float *to_rec2020; + float *from_srgb; + float *from_rec2020; GdkCicp cicp; }; @@ -449,7 +449,8 @@ TRANSFORM(gdk_cicp_from_rec2100_linear, NONE, cicp->from_rec2020, cicp->o #undef cicp - /* }}} */ +/* }}} */ +/* }}} */ /* {{{ Vfuncs */ static void @@ -460,6 +461,11 @@ gdk_cicp_color_state_free (GdkColorState *cs) if (self->no_srgb) gdk_color_state_unref (self->no_srgb); + g_free (self->to_srgb); + g_free (self->to_rec2020); + g_free (self->from_srgb); + g_free (self->from_rec2020); + g_free (self); } @@ -549,7 +555,7 @@ gdk_cicp_color_state_get_cicp (GdkColorState *color_state) return &self->cicp; } -/* }}} */ +/* }}} */ static const GdkColorStateClass GDK_CICP_COLOR_STATE_CLASS = { @@ -562,19 +568,19 @@ GdkColorStateClass GDK_CICP_COLOR_STATE_CLASS = { .get_cicp = gdk_cicp_color_state_get_cicp, }; -static inline void -multiply (float res[3][3], - const float m1[3][3], - const float m2[3][3]) +static inline float * +multiply (float res[9], + const float m1[9], + const float m2[9]) { - if ((float **) m1 == IDENTITY) - memcpy (res, m2, sizeof (float) * 3 * 3); - else if ((float **) m2 == IDENTITY) - memcpy (res, m1, sizeof (float) * 3 * 3); - else - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - res[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j] + m1[i][2] * m2[2][j]; +#define IDX(i,j) 3*i+j + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + res[IDX(i,j)] = m1[IDX(i,0)] * m2[IDX(0,j)] + + m1[IDX(i,1)] * m2[IDX(1,j)] + + m1[IDX(i,2)] * m2[IDX(2,j)]; + + return res; } GdkColorState * @@ -663,8 +669,8 @@ gdk_color_state_new_for_cicp (const GdkCicp *cicp, from_xyz = xyz_to_rec2020; break; case 10: - to_xyz = IDENTITY; - from_xyz = IDENTITY; + to_xyz = identity; + from_xyz = identity; break; case 12: to_xyz = p3_to_xyz; @@ -693,10 +699,10 @@ gdk_color_state_new_for_cicp (const GdkCicp *cicp, self->eotf = eotf; self->oetf = oetf; - multiply (self->to_srgb, xyz_to_srgb, to_xyz); - multiply (self->to_rec2020, xyz_to_rec2020, to_xyz); - multiply (self->from_srgb, from_xyz, srgb_to_xyz); - multiply (self->from_rec2020, from_xyz, rec2020_to_xyz); + self->to_srgb = multiply (g_new (float, 9), xyz_to_srgb, to_xyz); + self->to_rec2020 = multiply (g_new (float, 9), xyz_to_rec2020, to_xyz); + self->from_srgb = multiply (g_new (float, 9), from_xyz, srgb_to_xyz); + self->from_rec2020 = multiply (g_new (float, 9), from_xyz, rec2020_to_xyz); self->name = g_strdup_printf ("cicp-%u/%u/%u/%u", cicp->color_primaries, diff --git a/gdk/loaders/gdkjpeg.c b/gdk/loaders/gdkjpeg.c index 7af789d982..a1e7717bda 100644 --- a/gdk/loaders/gdkjpeg.c +++ b/gdk/loaders/gdkjpeg.c @@ -78,31 +78,6 @@ output_message_handler (j_common_ptr cinfo) /* }}} */ /* {{{ Format conversion */ -static void -convert_grayscale_to_rgb (guchar *data, - int width, - int height, - int stride) -{ - gsize x, y; - guchar *dest, *src; - - for (y = 0; y < height; y++) - { - src = data + width; - dest = data + 3 * width; - for (x = 0; x < width; x++) - { - dest -= 3; - src -= 1; - dest[0] = *src; - dest[1] = *src; - dest[2] = *src; - } - data += stride; - } -} - static void convert_cmyk_to_rgba (guchar *data, int width, @@ -184,6 +159,10 @@ gdk_load_jpeg (GBytes *input_bytes, switch ((int)info.out_color_space) { case JCS_GRAYSCALE: + stride = width; + data = g_try_malloc_n (stride, height); + format = GDK_MEMORY_G8; + break; case JCS_RGB: stride = 3 * width; data = g_try_malloc_n (stride, height); @@ -217,20 +196,8 @@ gdk_load_jpeg (GBytes *input_bytes, jpeg_read_scanlines (&info, row, 1); } - switch ((int)info.out_color_space) - { - case JCS_GRAYSCALE: - convert_grayscale_to_rgb (data, width, height, stride); - format = GDK_MEMORY_R8G8B8; - break; - case JCS_RGB: - break; - case JCS_CMYK: - convert_cmyk_to_rgba (data, width, height, stride); - break; - default: - g_assert_not_reached (); - } + if (info.out_color_space == JCS_CMYK) + convert_cmyk_to_rgba (data, width, height, stride); jpeg_finish_decompress (&info); jpeg_destroy_decompress (&info); @@ -307,6 +274,7 @@ gdk_save_jpeg (GdkTexture *texture) gdk_texture_downloader_init (&downloader, texture); gdk_texture_downloader_set_format (&downloader, GDK_MEMORY_R8G8B8); + gdk_texture_downloader_set_color_state (&downloader, GDK_COLOR_STATE_SRGB); texbytes = gdk_texture_downloader_download_bytes (&downloader, &texstride); gdk_texture_downloader_finish (&downloader); texdata = g_bytes_get_data (texbytes, NULL); diff --git a/testsuite/gdk/colorstate-internal.c b/testsuite/gdk/colorstate-internal.c index 9b0d556a97..1ce04e9a6c 100644 --- a/testsuite/gdk/colorstate-internal.c +++ b/testsuite/gdk/colorstate-internal.c @@ -33,7 +33,7 @@ test_transfer (gconstpointer data) } } -typedef const float Matrix[3][3]; +typedef const float Matrix[9]; typedef struct { @@ -50,59 +50,54 @@ static MatrixTest matrices[] = { { "srgb<>rec2020", &rec2020_to_srgb, &srgb_to_rec2020 }, }; +#define IDX(i,j) 3*i+j static inline void -multiply (float res[3][3], - const float m1[3][3], - const float m2[3][3]) +multiply (float res[9], + const float m1[9], + const float m2[9]) { for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) - res[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j] + m1[i][2] * m2[2][j]; + res[IDX(i,j)] = m1[IDX(i,0)] * m2[IDX(0,j)] + + m1[IDX(i,1)] * m2[IDX(1,j)] + + m1[IDX(i,2)] * m2[IDX(2,j)]; } static inline void -difference (float res[3][3], - const float m1[3][3], - const float m2[3][3]) +difference (float res[9], + const float m1[9], + const float m2[9]) { - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - res[i][j] = m1[i][j] - m2[i][j]; + for (int i = 0; i < 9; i++) + res[i] = m1[i] - m2[i]; } static float -norm (const float m[3][3]) +norm (const float m[9]) { float sum = 0; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - sum += m[i][j] * m[i][j]; + for (int i = 0; i < 9; i++) + sum += m[i] * m[i]; return sqrtf (sum); } -static const float identity[3][3] = { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 }, -}; - static void -print_matrix (const float m[3][3]) +print_matrix (const float m[9]) { g_print ("%f %f %f\n%f %f %f\n%f %f %f\n", - m[0][0], m[0][1], m[0][2], - m[1][0], m[1][1], m[1][2], - m[2][0], m[2][1], m[2][2]); + m[0], m[1], m[2], + m[3], m[4], m[5], + m[6], m[7], m[8]); } static void test_matrix (gconstpointer data) { MatrixTest *matrix = (MatrixTest *) data; - float res[3][3]; - float res2[3][3]; + float res[9]; + float res2[9]; multiply (res, *matrix->to_xyz, *matrix->from_xyz); @@ -120,7 +115,7 @@ test_matrix (gconstpointer data) static void test_srgb_to_rec2020 (void) { - float m[3][3], res[3][3]; + float m[9], res[9]; multiply (m, xyz_to_rec2020, srgb_to_xyz); difference (res, m, srgb_to_rec2020); @@ -131,7 +126,7 @@ test_srgb_to_rec2020 (void) static void test_rec2020_to_srgb (void) { - float m[3][3], res[3][3]; + float m[9], res[9]; multiply (m, xyz_to_srgb, rec2020_to_xyz); difference (res, m, rec2020_to_srgb); diff --git a/tools/gtk-image-tool-convert.c b/tools/gtk-image-tool-convert.c index adac555ce8..79e33050ff 100644 --- a/tools/gtk-image-tool-convert.c +++ b/tools/gtk-image-tool-convert.c @@ -81,15 +81,17 @@ do_convert (int *argc, char **filenames = NULL; char *format_name = NULL; char *colorstate_name = NULL; + char *cicp_tuple = NULL; const GOptionEntry entries[] = { { "format", 0, 0, G_OPTION_ARG_STRING, &format_name, N_("Format to use"), N_("FORMAT") }, { "color-state", 0, 0, G_OPTION_ARG_STRING, &colorstate_name, N_("Color state to use"), N_("COLORSTATE") }, + { "cicp", 0, 0, G_OPTION_ARG_STRING, &cicp_tuple, N_("Color state to use, as cicp tuple"), N_("CICP") }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, N_("FILE…") }, { NULL, } }; GError *error = NULL; GdkMemoryFormat format = GDK_MEMORY_DEFAULT; - GdkColorState *color_state = gdk_color_state_get_srgb (); + GdkColorState *color_state = NULL; g_set_prgname ("gtk4-image-tool convert"); context = g_option_context_new (NULL); @@ -151,6 +153,26 @@ do_convert (int *argc, } } + if (cicp_tuple) + { + if (color_state) + { + g_printerr (_("Can't specify both --color-state and --cicp\n")); + exit (1); + } + + color_state = parse_cicp_tuple (cicp_tuple, &error); + + if (!color_state) + { + g_printerr (_("Not a supported cicp tuple: %s\n"), error->message); + exit (1); + } + } + + if (!color_state) + color_state = gdk_color_state_get_srgb (); + save_image (filenames[0], filenames[1], format, color_state); g_strfreev (filenames); diff --git a/tools/gtk-image-tool-relabel.c b/tools/gtk-image-tool-relabel.c index c068cddf03..3b872fb379 100644 --- a/tools/gtk-image-tool-relabel.c +++ b/tools/gtk-image-tool-relabel.c @@ -79,13 +79,15 @@ do_relabel (int *argc, GOptionContext *context; char **filenames = NULL; char *colorstate_name = NULL; + char *cicp_tuple = NULL; const GOptionEntry entries[] = { { "color-state", 0, 0, G_OPTION_ARG_STRING, &colorstate_name, N_("Color state to use"), N_("COLORSTATE") }, + { "cicp", 0, 0, G_OPTION_ARG_STRING, &cicp_tuple, N_("Color state to use, as cicp tuple"), N_("CICP") }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, N_("FILE…") }, { NULL, } }; GError *error = NULL; - GdkColorState *color_state = gdk_color_state_get_srgb (); + GdkColorState *color_state = NULL; g_set_prgname ("gtk4-image-tool relabel"); context = g_option_context_new (NULL); @@ -131,6 +133,26 @@ do_relabel (int *argc, } } + if (cicp_tuple) + { + if (color_state) + { + g_printerr (_("Can't specify both --color-state and --cicp\n")); + exit (1); + } + + color_state = parse_cicp_tuple (cicp_tuple, &error); + + if (!color_state) + { + g_printerr (_("Not a supported cicp tuple: %s\n"), error->message); + exit (1); + } + } + + if (!color_state) + color_state = gdk_color_state_get_srgb (); + relabel_image (filenames[0], filenames[1], color_state); g_strfreev (filenames); diff --git a/tools/gtk-image-tool-utils.c b/tools/gtk-image-tool-utils.c index e4cae80934..d4795abc99 100644 --- a/tools/gtk-image-tool-utils.c +++ b/tools/gtk-image-tool-utils.c @@ -221,3 +221,42 @@ get_color_state_name (GdkColorState *color_state) return name; } + +GdkColorState * +parse_cicp_tuple (const char *cicp_tuple, + GError **error) +{ + char **tokens; + guint64 num[4]; + GdkCicpParams *params; + GdkColorState *color_state; + + tokens = g_strsplit (cicp_tuple, "/", 0); + + if (g_strv_length (tokens) != 4 || + !g_ascii_string_to_unsigned (tokens[0], 10, 0, 255, &num[0], NULL) || + !g_ascii_string_to_unsigned (tokens[1], 10, 0, 255, &num[1], NULL) || + !g_ascii_string_to_unsigned (tokens[2], 10, 0, 255, &num[2], NULL) || + !g_ascii_string_to_unsigned (tokens[3], 10, 0, 255, &num[3], NULL)) + { + g_strfreev (tokens); + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + _("cicp must be 4 numbers, separated by /\n")); + return NULL; + } + + g_strfreev (tokens); + + params = gdk_cicp_params_new (); + + gdk_cicp_params_set_color_primaries (params, (guint) num[0]); + gdk_cicp_params_set_transfer_function (params, (guint) num[1]); + gdk_cicp_params_set_matrix_coefficients (params, (guint) num[2]); + gdk_cicp_params_set_range (params, num[3] == 0 ? GDK_CICP_RANGE_NARROW : GDK_CICP_RANGE_FULL); + color_state = gdk_cicp_params_build_color_state (params, error); + + g_object_unref (params); + + return color_state; +} diff --git a/tools/gtk-image-tool.h b/tools/gtk-image-tool.h index 9203d6e551..7ad1a61901 100644 --- a/tools/gtk-image-tool.h +++ b/tools/gtk-image-tool.h @@ -17,3 +17,6 @@ GdkColorState * find_color_state_by_name (const char *name); char ** get_color_state_names (void); char * get_color_state_name (GdkColorState *color_state); + +GdkColorState *parse_cicp_tuple (const char *cicp_tuple, + GError **error);