diff --git a/gdk/gdkcolor.c b/gdk/gdkcolor.c new file mode 100644 index 0000000000..a7dc080346 --- /dev/null +++ b/gdk/gdkcolor.c @@ -0,0 +1,301 @@ +/* GDK - The GIMP Drawing Kit + * + * Copyright (C) 2021 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gdkcolorprivate.h" +#include "gdkcolorstateprivate.h" +#include "gdkrgbaprivate.h" + +/*< private > + * GdkColor: + * @color_state: the color state to interpret the values in + * @values: the 3 coordinates that define the color, followed + * by the alpha value + * + * A `GdkColor` represents a color. + * + * The color state defines the meaning and range of the values. + * E.g., the srgb color state has r, g, b components representing + * red, green and blue with a range of [0,1], while the oklch color + * state has l, c, h components representing luminosity, chromaticity + * and hue, with l ranging from 0 to 1 and c from 0 to about 0.4, while + * h is interpreted as angle in degrees. + * + * value[3] is always the alpha value with a range of [0,1]. + * + * The values are also available under the names red, green, blue + * and alpha, or r, g, b and a. + */ + +/*< private > + * gdk_color_init: + * @self: the `GdkColor` struct to initialize + * @color_state: the color state + * @values: the values + * + * Initializes the `GdkColor` with the given color state + * and values. + * + * Note that this takes a reference on @color_state that + * must be freed by calling [function@Gdk.Color.finish] + * when the `GdkColor` is no longer needed. + */ +void +(gdk_color_init) (GdkColor *self, + GdkColorState *color_state, + const float values[4]) +{ + _gdk_color_init (self, color_state, values); +} + +/*< private > + * gdk_color_init_copy: + * @self: the `GdkColor` struct to initialize + * @color: the `GdkColor` to copy + * + * Initializes the `GdkColor` by copying the contents + * of another `GdkColor`. + * + * Note that this takes a reference on the color state + * that must be freed by calling [function@Gdk.Color.finish] + * when the `GdkColor` is no longer needed. + */ +void +(gdk_color_init_copy) (GdkColor *self, + const GdkColor *color) +{ + _gdk_color_init_copy (self, color); +} + +/*< private > + * gdk_color_init_from_rgba: + * @self: the `GdkColor` struct to initialize + * @rgba: the `GdkRGBA` to copy + * + * Initializes the `GdkColor` by copying the contents + * of a `GdkRGBA`. + * + * Note that `GdkRGBA` colors are always in the sRGB + * color state. + * + * Note that this takes a reference on the color state + * that must be freed by calling [function@Gdk.Color.finish] + * when the `GdkColor` is no longer needed. + */ +void +(gdk_color_init_from_rgba) (GdkColor *self, + const GdkRGBA *rgba) +{ + _gdk_color_init_from_rgba (self, rgba); +} + +/*< private > + * @self: a `GdkColor` + * + * Drop the reference on the color state of @self. + * + * After this, @self is empty and can be initialized again + * with [function@Gdk.Color.init] and its variants. + */ +void +(gdk_color_finish) (GdkColor *self) +{ + _gdk_color_finish (self); +} + +/*< private > + * gdk_color_equal: + * @self: a `GdkColor` + * @other: another `GdkColor` + * + * Compares two `GdkColor` structs for equality. + * + * Returns: `TRUE` if @self and @other are equal + */ +gboolean +(gdk_color_equal) (const GdkColor *self, + const GdkColor *other) +{ + return _gdk_color_equal (self, other); +} + +/*< private > + * gdk_color_is_clear: + * @self: a `GdkColor` + * + * Returns whether @self is fully transparent. + * + * Returns: `TRUE` if @self is transparent + */ +gboolean +(gdk_color_is_clear) (const GdkColor *self) +{ + return _gdk_color_is_clear (self); +} + +/*< private > + * gdk_color_is_opaque: + * @self: a `GdkColor` + * + * Returns whether @self is fully opaque. + * + * Returns: `TRUE` if @self if opaque + */ +gboolean +(gdk_color_is_opaque) (const GdkColor *self) +{ + return _gdk_color_is_opaque (self); +} + +/*< private > + * gdk_color_convert: + * @self: the `GdkColor` to store the result in + * @color_state: the target color start + * @other: the `GdkColor` to convert + * + * Converts a given `GdkColor` to another color state. + * + * After the conversion, @self will represent the same + * color as @other in @color_state, to the degree possible. + * + * Different color states have different gamuts of colors + * they can represent, and converting a color to a color + * state with a smaller gamut may yield an 'out of gamut' + * result. + */ +void +(gdk_color_convert) (GdkColor *self, + GdkColorState *color_state, + const GdkColor *other) +{ + gdk_color_convert (self, color_state, other); +} + +/*< private > + * gdk_color_to_float: + * @self: a `GdkColor` + * @target: the color state to convert to + * @values: the location to store the result in + * + * Converts a given `GdkColor to another color state + * and stores the result in a `float[4]`. + */ +void +(gdk_color_to_float) (const GdkColor *self, + GdkColorState *target, + float values[4]) +{ + gdk_color_to_float (self, target, values); +} + +/*< private > + * gdk_color_from_rgba: + * @self: the `GdkColor` to store the result in + * @color_state: the target color state + * @rgba: the `GdkRGBA` to convert + * + * Converts a given `GdkRGBA` to the target @color_state. + */ +void +gdk_color_from_rgba (GdkColor *self, + GdkColorState *color_state, + const GdkRGBA *rgba) +{ + GdkColor tmp = { + .color_state = GDK_COLOR_STATE_SRGB, + .r = rgba->red, + .g = rgba->green, + .b = rgba->blue, + .a = rgba->alpha + }; + + gdk_color_convert (self, color_state, &tmp); + gdk_color_finish (&tmp); +} + +/*< private > + * gdk_color_get_depth: + * @self: a `GdkColor` + * + * Returns the preferred depth for the color state of @self. + * + * Returns: the preferred depth + */ +GdkMemoryDepth +(gdk_color_get_depth) (const GdkColor *self) +{ + return gdk_color_state_get_depth (self->color_state); +} + +/*< private > + * gdk_color_print: + * @self: the `GdkColor` to print + * @string: the string to print to + * + * Appends a representation of @self to @string. + * + * The representation is inspired by CSS3 colors, + * but not 100% identical, and looks like this: + * + * color(NAME R G B / A) + * + * where `NAME` identifies color state, and + * `R`, `G`, `B` and `A` are the components of the color. + * + * The alpha may be omitted if it is 1. + */ +void +gdk_color_print (const GdkColor *self, + GString *string) +{ + if (gdk_color_state_equal (self->color_state, GDK_COLOR_STATE_SRGB)) + { + gdk_rgba_print ((const GdkRGBA *) self->values, string); + } + else + { + g_string_append_printf (string, "color(%s %g %g %g", + gdk_color_state_get_name (self->color_state), + self->r, self->g, self->b); + if (self->a < 1) + g_string_append_printf (string, " / %g", self->a); + g_string_append_c (string, ')'); + } +} + +/*< private > + * gdk_color_print: + * @self: the `GdkColor` to print + * + * Create a string representation of @self. + * + * See [method@Gdk.Color.print] for details about + * the format. + + * Returns: (transfer full): a newly-allocated string + */ +char * +gdk_color_to_string (const GdkColor *self) +{ + GString *string = g_string_new (""); + gdk_color_print (self, string); + return g_string_free (string, FALSE); +} + + diff --git a/gdk/gdkcolorimpl.h b/gdk/gdkcolorimpl.h new file mode 100644 index 0000000000..4245e5aeee --- /dev/null +++ b/gdk/gdkcolorimpl.h @@ -0,0 +1,135 @@ +/* GDK - The GIMP Drawing Kit + * + * Copyright (C) 2021 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include "gdkcolorstateprivate.h" + + +#define gdk_color_init(...) _gdk_color_init (__VA_ARGS__) +static inline void +_gdk_color_init (GdkColor *self, + GdkColorState *color_state, + const float values[4]) +{ + self->color_state = gdk_color_state_ref (color_state); + memcpy (self->values, values, sizeof (float) * 4); +} + +#define gdk_color_init_copy(self, color) _gdk_color_init_copy ((self), (color)) +static inline void +_gdk_color_init_copy (GdkColor *self, + const GdkColor *color) +{ + _gdk_color_init (self, color->color_state, color->values); +} + +#define gdk_color_init_from_rgb(self, rgba) _gdk_color_init_from_rgba ((self), (rgba)) +static inline void +_gdk_color_init_from_rgba (GdkColor *self, + const GdkRGBA *rgba) +{ + _gdk_color_init (self, GDK_COLOR_STATE_SRGB, (const float *) rgba); +} + +#define gdk_color_finish(self) _gdk_color_finish ((self)) +static inline void +_gdk_color_finish (GdkColor *self) +{ + gdk_color_state_unref (self->color_state); + self->color_state = NULL; +} + +#define gdk_color_get_color_state(self) _gdk_color_get_color_state ((self)) +static inline GdkColorState * +_gdk_color_get_color_state (const GdkColor *self) +{ + return self->color_state; +} + +#define gdk_color_equal(self, other) _gdk_color_equal ((self), (other)) +static inline gboolean +_gdk_color_equal (const GdkColor *self, + const GdkColor *other) +{ + return self->values[0] == other->values[0] && + self->values[1] == other->values[1] && + self->values[2] == other->values[2] && + self->values[3] == other->values[3] && + gdk_color_state_equal (self->color_state, other->color_state); +} + +#define gdk_color_is_clear(self) _gdk_color_is_clear ((self)) +static inline gboolean +_gdk_color_is_clear (const GdkColor *self) +{ + return self->alpha < (255.f / 65535.f); +} + +#define gdk_color_is_opaque(self) _gdk_color_is_opaque ((self)) +static inline gboolean +_gdk_color_is_opaque (const GdkColor *self) +{ + return self->alpha > (65280.f / 65535.f); +} + +#define gdk_color_convert(self, cs, other) _gdk_color_convert ((self), (cs), (other)) +static inline void +_gdk_color_convert (GdkColor *self, + GdkColorState *color_state, + const GdkColor *other) +{ + if (gdk_color_state_equal (color_state, other->color_state)) + { + gdk_color_init_copy (self, other); + return; + } + + self->color_state = gdk_color_state_ref (color_state); + + gdk_color_state_convert_color (other->color_state, + other->values, + self->color_state, + self->values); +} + +#define gdk_color_to_float(self, cs, values) _gdk_color_to_float ((self), (cs), (values)) +static inline void +_gdk_color_to_float (const GdkColor *self, + GdkColorState *color_state, + float values[4]) +{ + if (gdk_color_state_equal (self->color_state, color_state)) + { + memcpy (values, self->values, sizeof (float) * 4); + return; + } + + gdk_color_state_convert_color (self->color_state, + self->values, + color_state, + values); +} + +#define gdk_color_get_depth(self) _gdk_color_get_depth ((self)) +static inline GdkMemoryDepth +_gdk_color_get_depth (const GdkColor *self) +{ + return gdk_color_state_get_depth (self->color_state); +} + diff --git a/gdk/gdkcolorprivate.h b/gdk/gdkcolorprivate.h new file mode 100644 index 0000000000..cb4185b006 --- /dev/null +++ b/gdk/gdkcolorprivate.h @@ -0,0 +1,107 @@ +/* GDK - The GIMP Drawing Kit + * + * Copyright (C) 2021 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include +#include +#include +#include + + +typedef struct _GdkColor GdkColor; + +/* The interpretation of the first 3 components depends on the color state. + * values[3] is always alpha. + */ +struct _GdkColor +{ + GdkColorState *color_state; + union { + float values[4]; + struct { + float r; + float g; + float b; + float a; + }; + struct { + float red; + float green; + float blue; + float alpha; + }; + }; +}; + +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, r) == G_STRUCT_OFFSET (GdkColor, red)); +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, g) == G_STRUCT_OFFSET (GdkColor, green)); +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, b) == G_STRUCT_OFFSET (GdkColor, blue)); +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, a) == G_STRUCT_OFFSET (GdkColor, alpha)); + +/* The committee notes that since all known implementations but one "get it right" + * this may well not be a defect at all. + * https://open-std.org/JTC1/SC22/WG14/www/docs/n2396.htm#dr_496 + */ +#ifndef _MSC_VER +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, r) == G_STRUCT_OFFSET (GdkColor, values[0])); +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, g) == G_STRUCT_OFFSET (GdkColor, values[1])); +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, b) == G_STRUCT_OFFSET (GdkColor, values[2])); +G_STATIC_ASSERT (G_STRUCT_OFFSET (GdkColor, a) == G_STRUCT_OFFSET (GdkColor, values[3])); +#endif + +#define GDK_COLOR_INIT_SRGB(r,g,b,a) { \ + .color_state = GDK_COLOR_STATE_SRGB, \ + .values = { (r), (g), (b), (a) } \ +} + +void gdk_color_init (GdkColor *self, + GdkColorState *color_state, + const float values[4]); +void gdk_color_init_copy (GdkColor *self, + const GdkColor *color); +void gdk_color_init_from_rgba (GdkColor *self, + const GdkRGBA *rgba); +void gdk_color_finish (GdkColor *self); + +gboolean gdk_color_equal (const GdkColor *color1, + const GdkColor *color2); +gboolean gdk_color_is_clear (const GdkColor *self); +gboolean gdk_color_is_opaque (const GdkColor *self); +GdkMemoryDepth gdk_color_get_depth (const GdkColor *self); + +void gdk_color_convert (GdkColor *self, + GdkColorState *color_state, + const GdkColor *other); + +void gdk_color_to_float (const GdkColor *self, + GdkColorState *target, + float values[4]); + +void gdk_color_from_rgba (GdkColor *self, + GdkColorState *color_state, + const GdkRGBA *rgba); + +void gdk_color_print (const GdkColor *self, + GString *string); + +char * gdk_color_to_string (const GdkColor *self); + +#include "gdkcolorimpl.h" + +G_END_DECLS diff --git a/gdk/meson.build b/gdk/meson.build index 14b706d76c..78609a9870 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -7,6 +7,7 @@ gdk_public_sources = files([ 'gdkcairocontext.c', 'gdkcicpparams.c', 'gdkclipboard.c', + 'gdkcolor.c', 'gdkcolorstate.c', 'gdkcontentdeserializer.c', 'gdkcontentformats.c', diff --git a/testsuite/gdk/colorstate-internal.c b/testsuite/gdk/colorstate-internal.c index b471cdb58f..a95dd87577 100644 --- a/testsuite/gdk/colorstate-internal.c +++ b/testsuite/gdk/colorstate-internal.c @@ -1,5 +1,6 @@ #include #include "gdkcolorstateprivate.h" +#include "gdkcolorprivate.h" #include #include "gdkcolordefs.h" @@ -134,6 +135,29 @@ test_rec2020_to_srgb (void) g_assert_cmpfloat_with_epsilon (norm (res), 0, 0.001); } +/* Verify that this color is different enough in srgb-linear and srgb + * to be detected. + */ +static void +test_color_mislabel (void) +{ + GdkColor color; + GdkColor color1; + GdkColor color2; + guint red1, red2; + + gdk_color_init (&color, gdk_color_state_get_srgb_linear (), (float[]) { 0.604, 0, 0, 1 }); + gdk_color_convert (&color1, gdk_color_state_get_srgb (), &color); + gdk_color_init (&color2, gdk_color_state_get_srgb (), (float[]) { 0.604, 0, 0, 1 }); + + g_assert_true (!gdk_color_equal (&color1, &color2)); + + red1 = round (color1.red * 255.0); + red2 = round (color2.red * 255.0); + + g_assert_true (red1 != red2); +} + int main (int argc, char *argv[]) { @@ -157,6 +181,7 @@ main (int argc, char *argv[]) g_test_add_func ("/colorstate/matrix/srgb_to_rec2020", test_srgb_to_rec2020); g_test_add_func ("/colorstate/matrix/rec2020_to_srgb", test_rec2020_to_srgb); + g_test_add_func ("/color/mislabel", test_color_mislabel); return g_test_run (); }