Add GdkColor

For now, this is all private api.

Parts of it will be opened up in 4.18.
This commit is contained in:
Matthias Clasen 2024-08-03 12:13:27 -04:00
parent ffc89e40a0
commit 13a8704f51
5 changed files with 569 additions and 0 deletions

301
gdk/gdkcolor.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

135
gdk/gdkcolorimpl.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

107
gdk/gdkcolorprivate.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gdk/gdktypes.h>
#include <gdk/gdkcolorstate.h>
#include <gdk/gdkrgba.h>
#include <gdk/gdkmemoryformatprivate.h>
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

View File

@ -7,6 +7,7 @@ gdk_public_sources = files([
'gdkcairocontext.c',
'gdkcicpparams.c',
'gdkclipboard.c',
'gdkcolor.c',
'gdkcolorstate.c',
'gdkcontentdeserializer.c',
'gdkcontentformats.c',

View File

@ -1,5 +1,6 @@
#include <gdk/gdk.h>
#include "gdkcolorstateprivate.h"
#include "gdkcolorprivate.h"
#include <math.h>
#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 ();
}