Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gnome/gdk/   (Gnome Linux Desktop Version 4.23.2©)  Datei vom 30.5.2026 mit Größe 35 kB image not shown  

Quelle  gdkcolorstate.c

  Sprache: C
 

/* gdkcolorstate.c
 *
 * Copyright 2024 Matthias Clasen
 *
 * 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"

#define GDK_COLOR_STATE_IMPL
#include "gdkcolorstateprivate.h"

#include <math.h>

#include "gdkcolordefs.h"

#include <glib/gi18n-lib.h>

/**
 * GdkColorState:
 *
 * Provides information to interpret colors and pixels in a variety of ways.
 *
 * They are also known as
 * [*color spaces*](https://en.wikipedia.org/wiki/Color_space).
 *
 * Crucially, GTK knows how to convert colors from one color
 * state to another.
 *
 * `GdkColorState` objects are immutable and therefore threadsafe.
 *
 * Since: 4.16
 */


G_DEFINE_BOXED_TYPE (GdkColorState, gdk_color_state,
                     gdk_color_state_ref, gdk_color_state_unref);

/* {{{ Public API */

/**
 * gdk_color_state_ref:
 * @self: a `GdkColorState`
 *
 * Increase the reference count of @self.
 *
 * Returns: the object that was passed in
 *
 * Since: 4.16
 */

GdkColorState *
(gdk_color_state_ref) (GdkColorState *self)
{
  return _gdk_color_state_ref (self);
}

/**
 * gdk_color_state_unref:
 * @self:a `GdkColorState`
 *
 * Decrease the reference count of @self.
 *
 * Unless @self is static, it will be freed
 * when the reference count reaches zero.
 *
 * Since: 4.16
 */

void
(gdk_color_state_unref) (GdkColorState *self)
{
  _gdk_color_state_unref (self);
}

/**
 * gdk_color_state_get_srgb:
 *
 * Returns the color state object representing the sRGB color space.
 *
 * This color state uses the primaries defined by BT.709-6 and the transfer function
 * defined by IEC 61966-2-1.
 *
 * It is equivalent to the [Cicp](class.CicpParams.html) tuple 1/13/0/1.
 *
 * See e.g. [the CSS Color Module](https://www.w3.org/TR/css-color-4/#predefined-sRGB)
 * for details about this colorstate.
 *
 * Returns: the color state object for sRGB
 *
 * Since: 4.16
 */

GdkColorState *
gdk_color_state_get_srgb (void)
{
  return GDK_COLOR_STATE_SRGB;
}

/**
 * gdk_color_state_get_srgb_linear:
 *
 * Returns the color state object representing the linearized sRGB color space.
 *
 * This color state uses the primaries defined by BT.709-6 and a linear transfer function.
 *
 * It is equivalent to the [Cicp](class.CicpParams.html) tuple 1/8/0/1.
 *
 * See e.g. [the CSS Color Module](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear)
 * for details about this colorstate.
 *
 * Returns: the color state object for linearized sRGB
 *
 * Since: 4.16
 */

GdkColorState *
gdk_color_state_get_srgb_linear (void)
{
  return GDK_COLOR_STATE_SRGB_LINEAR;
}

/**
 * gdk_color_state_get_rec2100_pq:
 *
 * Returns the color state object representing the rec2100-pq color space.
 *
 * This color state uses the primaries defined by BT.2020-2 and BT.2100-0 and the transfer
 * function defined by SMPTE ST 2084 and BT.2100-2.
 *
 * It is equivalent to the [Cicp](class.CicpParams.html) tuple 9/16/0/1.
 *
 * See e.g. [the CSS HDR Module](https://drafts.csswg.org/css-color-hdr/#valdef-color-rec2100-pq)
 * for details about this colorstate.
 *
 * Returns: the color state object for rec2100-pq
 *
 * Since: 4.16
 */

GdkColorState *
gdk_color_state_get_rec2100_pq (void)
{
  return GDK_COLOR_STATE_REC2100_PQ;
}

/**
 * gdk_color_state_get_rec2100_linear:
 *
 * Returns the color state object representing the linear rec2100 color space.
 *
 * This color state uses the primaries defined by BT.2020-2 and BT.2100-0 and a linear
 * transfer function.
 *
 * It is equivalent to the [Cicp](class.CicpParams.html) tuple 9/8/0/1.
 *
 * See e.g. [the CSS HDR Module](https://drafts.csswg.org/css-color-hdr/#valdef-color-rec2100-linear)
 * for details about this colorstate.
 *
 * Returns: the color state object for linearized rec2100
 *
 * Since: 4.16
 */

GdkColorState *
gdk_color_state_get_rec2100_linear (void)
{
  return GDK_COLOR_STATE_REC2100_LINEAR;
}

/**
 * gdk_color_state_get_oklab:
 *
 * Returns the color state object representing the oklab color space.
 *
 * This is a perceptually uniform color state.
 *
 * Returns: the color state object for oklab
 *
 * Since: 4.18
 */

GdkColorState *
gdk_color_state_get_oklab (void)
{
  return GDK_COLOR_STATE_OKLAB;
}

/**
 * gdk_color_state_get_oklch:
 *
 * Returns the color state object representing the oklch color space.
 *
 * This is the polar variant of oklab, in which the hue is encoded as
 * a polar coordinate.
 *
 * Returns: the color state object for oklch
 *
 * Since: 4.18
 */

GdkColorState *
gdk_color_state_get_oklch (void)
{
  return GDK_COLOR_STATE_OKLCH;
}

/**
 * gdk_color_state_equal:
 * @self: a `GdkColorState`
 * @other: another `GdkColorStatee`
 *
 * Compares two `GdkColorStates` for equality.
 *
 * Note that this function is not guaranteed to be perfect and two objects
 * describing the same color state may compare not equal. However, different
 * color states will never compare equal.
 *
 * Returns: %TRUE if the two color states compare equal
 *
 * Since: 4.16
 */

gboolean
(gdk_color_state_equal) (GdkColorState *self,
                         GdkColorState *other)
{
  return _gdk_color_state_equal (self, other);
}

/**
 * gdk_color_state_equivalent:
 * @self: a `GdkColorState`
 * @other: another `GdkColorStatee`
 *
 * Compares two `GdkColorStates` for equivalence.
 *
 * Two objects that represent the same color state should be equivalent,
 * even though they may not be equal in the sense of [method@Gdk.ColorState.equal].
 *
 * Returns: %TRUE if the two color states are equivalent
 *
 * Since: 4.20
 */

gboolean
(gdk_color_state_equivalent) (GdkColorState *self,
                              GdkColorState *other)
{
  return _gdk_color_state_equivalent (self, other);
}

/**
 * gdk_color_state_create_cicp_params:
 * @self: a `GdkColorState`
 *
 * Create a [class@Gdk.CicpParams] representing the colorstate.
 *
 * It is not guaranteed that every `GdkColorState` can be
 * represented with Cicp parameters. If that is the case,
 * this function returns `NULL`.
 *
 * Returns: (transfer full) (nullable): A new [class@Gdk.CicpParams]
 *
 * Since: 4.16
 */

GdkCicpParams *
gdk_color_state_create_cicp_params (GdkColorState *self)
{
  const GdkCicp *cicp = gdk_color_state_get_cicp (self);

  if (cicp)
    return gdk_cicp_params_new_for_cicp (cicp);

  return NULL;
}

/* }}} */
/* {{{ Conversion functions */

typedef void (* GdkTransferFunc) (float v[3]);
typedef void (* GdkConvertFunc)  (GdkColorState *self,
                                  float          values[4]);
typedef const float GdkColorMatrix[9];

#define IDENTITY ((float*)0)
#define NONE ((GdkTransferFunc)0)

#define CONVERT_FUNC(name) \
static void \
gdk_convert_ ## name (GdkColorState  *self, \
                      float         (*values)[4], \
                      gsize           n_values) \
{ \
  for (gsize i = 0; i < n_values; i++) \
    { \
      name (self, values[i]); \
    } \
}

#define TRANSFORM(name, eotf, matrix, nonlinear, matrix2, oetf) \
static inline void \
name (GdkColorState  *self, \
      float           values[4]) \
{ \
  if (eotf != NONE) \
    eotf (values); \
  if (matrix != IDENTITY) \
    { \
      float res[3]; \
      res[0] = matrix[0] * values[0] + matrix[1] * values[1] + matrix[2] * values[2]; \
      res[1] = matrix[3] * values[0] + matrix[4] * values[1] + matrix[5] * values[2]; \
      res[2] = matrix[6] * values[0] + matrix[7] * values[1] + matrix[8] * values[2]; \
      values[0] = res[0]; \
      values[1] = res[1]; \
      values[2] = res[2]; \
    } \
  if (nonlinear != NONE) \
    nonlinear (values); \
  if (matrix2 != IDENTITY) \
    { \
      float res[3]; \
      res[0] = matrix2[0] * values[0] + matrix2[1] * values[1] + matrix2[2] * values[2]; \
      res[1] = matrix2[3] * values[0] + matrix2[4] * values[1] + matrix2[5] * values[2]; \
      res[2] = matrix2[6] * values[0] + matrix2[7] * values[1] + matrix2[8] * values[2]; \
      values[0] = res[0]; \
      values[1] = res[1]; \
      values[2] = res[2]; \
    } \
  if (oetf != NONE) \
    oetf (values); \
} \
CONVERT_FUNC (name)

#define TRANSFORM_PAIR(name, func1, func2) \
static inline void \
name (GdkColorState *self, \
      float          values[4]) \
{ \
  func1 (self, values); \
  func2 (self, values); \
} \
CONVERT_FUNC (name)

TRANSFORM(srgb_to_srgb_linear,           srgb_eotf, IDENTITY,        NONE, IDENTITY, NONE)
TRANSFORM(srgb_linear_to_srgb,           NONE,      IDENTITY,        NONE, IDENTITY, srgb_oetf)
TRANSFORM(rec2100_pq_to_rec2100_linear,  pq_eotf,   IDENTITY,        NONE, IDENTITY, NONE)
TRANSFORM(rec2100_linear_to_rec2100_pq,  NONE,      IDENTITY,        NONE, IDENTITY, pq_oetf)
TRANSFORM(srgb_linear_to_rec2100_linear, NONE,      srgb_to_rec2020, NONE, IDENTITY, NONE)
TRANSFORM(rec2100_linear_to_srgb_linear, NONE,      rec2020_to_srgb, NONE, IDENTITY, NONE)
TRANSFORM(srgb_to_rec2100_linear,        srgb_eotf, srgb_to_rec2020, NONE, IDENTITY, NONE)
TRANSFORM(rec2100_pq_to_srgb_linear,     pq_eotf,   rec2020_to_srgb, NONE, IDENTITY, NONE)
TRANSFORM(srgb_linear_to_rec2100_pq,     NONE,      srgb_to_rec2020, NONE, IDENTITY, pq_oetf)
TRANSFORM(rec2100_linear_to_srgb,        NONE,      rec2020_to_srgb, NONE, IDENTITY, srgb_oetf)
TRANSFORM(srgb_to_rec2100_pq,            srgb_eotf, srgb_to_rec2020, NONE, IDENTITY, pq_oetf)
TRANSFORM(rec2100_pq_to_srgb,            pq_eotf,   rec2020_to_srgb, NONE, IDENTITY, srgb_oetf)

TRANSFORM(oklab_to_srgb_linear,          NONE,      oklab_to_lms,    from_oklab_nl, lms_to_srgb,    NONE)
TRANSFORM(oklab_to_srgb,                 NONE,      oklab_to_lms,    from_oklab_nl, lms_to_srgb,    srgb_oetf)
TRANSFORM(oklab_to_rec2100_linear,       NONE,      oklab_to_lms,    from_oklab_nl, lms_to_rec2020, NONE)
TRANSFORM(oklab_to_rec2100_pq,           NONE,      oklab_to_lms,    from_oklab_nl, lms_to_rec2020, pq_oetf)
TRANSFORM(srgb_linear_to_oklab,          NONE,      srgb_to_lms,     to_oklab_nl,   lms_to_oklab,   NONE)
TRANSFORM(srgb_to_oklab,                 srgb_eotf, srgb_to_lms,     to_oklab_nl,   lms_to_oklab,   NONE)
TRANSFORM(rec2100_linear_to_oklab,       NONE,      rec2020_to_lms,  to_oklab_nl,   lms_to_oklab,   NONE)
TRANSFORM(rec2100_pq_to_oklab,           pq_eotf,   rec2020_to_lms,  to_oklab_nl,   lms_to_oklab,   NONE)

#define DEG_TO_RAD(x) ((x) * G_PI / 180)
#define RAD_TO_DEG(x) ((x) * 180 / G_PI)

static inline void
_sincosf (float  angle,
          float *out_s,
          float *out_c)
{
#ifdef HAVE_SINCOSF
  sincosf (angle, out_s, out_c);
#else
  *out_s = sinf (angle);
  *out_c = cosf (angle);
#endif
}

static void
oklch_to_oklab (GdkColorState *self,
                float          values[4])
{
  float L, C, H, a, b;

  L = values[0];
  C = values[1];
  H = values[2];

  _sincosf (DEG_TO_RAD (H), &b, &a);
  a *= C;
  b *= C;

  values[0] = L;
  values[1] = a;
  values[2] = b;
}

static void
oklab_to_oklch (GdkColorState *self,
                float          values[4])
{
  float L, a, b, C, H;

  L = values[0];
  a = values[1];
  b = values[2];

  C = hypotf (a, b);
  H = RAD_TO_DEG (atan2 (b, a));

  H = fmod (H, 360);
  if (H < 0)
    H += 360;

  values[0] = L;
  values[1] = C;
  values[2] = H;
}

TRANSFORM_PAIR (srgb_to_oklch,           srgb_to_oklab,           oklab_to_oklch)
TRANSFORM_PAIR (srgb_linear_to_oklch,    srgb_linear_to_oklab,    oklab_to_oklch)
TRANSFORM_PAIR (rec2100_pq_to_oklch,     rec2100_pq_to_oklab,     oklab_to_oklch)
TRANSFORM_PAIR (rec2100_linear_to_oklch, rec2100_linear_to_oklab, oklab_to_oklch)
TRANSFORM_PAIR (oklch_to_srgb,           oklch_to_oklab,          oklab_to_srgb)
TRANSFORM_PAIR (oklch_to_srgb_linear,    oklch_to_oklab,          oklab_to_srgb_linear)
TRANSFORM_PAIR (oklch_to_rec2100_pq,     oklch_to_oklab,          oklab_to_rec2100_pq)
TRANSFORM_PAIR (oklch_to_rec2100_linear, oklch_to_oklab,          oklab_to_rec2100_linear)

/* }}} */
/* {{{ Default implementation */
/* {{{ Vfuncs */

static gboolean
gdk_default_color_state_equal (GdkColorState *self,
                               GdkColorState *other)
{
  return self == other;
}

static const char *
gdk_default_color_state_get_name (GdkColorState *color_state)
{
  GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;

  return self->name;
}

static GdkColorState *
gdk_default_color_state_get_no_srgb_tf (GdkColorState *color_state)
{
  GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;

  return self->no_srgb;
}

static GdkFloatColorConvert
gdk_default_color_state_get_convert_to (GdkColorState  *color_state,
                                        GdkColorState  *target)
{
  GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;

  if (GDK_IS_DEFAULT_COLOR_STATE (target))
    return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];

  return NULL;
}

static GdkFloatColorConvert
gdk_default_color_state_get_convert_from (GdkColorState  *color_state,
                                          GdkColorState  *source)
{
  /* This is ok because the default-to-default conversion functions
   * don't use the passed colorstate at all.
   */

  return gdk_default_color_state_get_convert_to (source, color_state);
}

static const GdkCicp *
gdk_default_color_state_get_cicp (GdkColorState *color_state)
{
  GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;

  if (self->cicp.color_primaries == 0)
    return NULL;

  return &self->cicp;
}

/* }}} */

static const
GdkColorStateClass GDK_DEFAULT_COLOR_STATE_CLASS = {
  .free = NULL, /* crash here if this ever happens */
  .equal = gdk_default_color_state_equal,
  .get_name = gdk_default_color_state_get_name,
  .get_no_srgb_tf = gdk_default_color_state_get_no_srgb_tf,
  .get_convert_to = gdk_default_color_state_get_convert_to,
  .get_convert_from = gdk_default_color_state_get_convert_from,
  .get_cicp = gdk_default_color_state_get_cicp,
};

GdkDefaultColorState gdk_default_color_states[] = {
  [GDK_COLOR_STATE_ID_SRGB] = {
    .parent = {
      .klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
      .ref_count = 0,
      .depth = GDK_MEMORY_U8,
      .hue_channel = GDK_COLOR_CHANNEL_ALPHA,
      .rendering_color_state = GDK_COLOR_STATE_SRGB,
      .rendering_color_state_linear = GDK_COLOR_STATE_SRGB_LINEAR,
    },
    .name = "srgb",
    .no_srgb = GDK_COLOR_STATE_SRGB_LINEAR,
    .convert_to = {
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_srgb_to_srgb_linear,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_srgb_to_rec2100_pq,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_srgb_to_rec2100_linear,
    },
    .cicp = { 11301 },
  },
  [GDK_COLOR_STATE_ID_SRGB_LINEAR] = {
    .parent = {
      .klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
      .ref_count = 0,
      .depth = GDK_MEMORY_U8,
      .hue_channel = GDK_COLOR_CHANNEL_ALPHA,
      .rendering_color_state = GDK_COLOR_STATE_SRGB_LINEAR,
      .rendering_color_state_linear = GDK_COLOR_STATE_SRGB_LINEAR,
    },
    .name = "srgb-linear",
    .no_srgb = NULL,
    .convert_to = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_srgb_linear_to_srgb,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_srgb_linear_to_rec2100_pq,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_srgb_linear_to_rec2100_linear,
    },
    .cicp = { 1801 },
  },
  [GDK_COLOR_STATE_ID_REC2100_PQ] = {
    .parent = {
      .klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
      .ref_count = 0,
      .depth = GDK_MEMORY_FLOAT16,
      .hue_channel = GDK_COLOR_CHANNEL_ALPHA,
      .rendering_color_state = GDK_COLOR_STATE_REC2100_PQ,
      .rendering_color_state_linear = GDK_COLOR_STATE_REC2100_LINEAR,
    },
    .name = "rec2100-pq",
    .no_srgb = NULL,
    .convert_to = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_rec2100_pq_to_srgb,
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_rec2100_pq_to_srgb_linear,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_rec2100_pq_to_rec2100_linear,
    },
    .cicp = { 91601 },
  },
  [GDK_COLOR_STATE_ID_REC2100_LINEAR] = {
    .parent = {
      .klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
      .ref_count = 0,
      .depth = GDK_MEMORY_FLOAT16,
      .hue_channel = GDK_COLOR_CHANNEL_ALPHA,
      .rendering_color_state = GDK_COLOR_STATE_REC2100_LINEAR,
      .rendering_color_state_linear = GDK_COLOR_STATE_REC2100_LINEAR,
    },
    .name = "rec2100-linear",
    .no_srgb = NULL,
    .convert_to = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_rec2100_linear_to_srgb,
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_rec2100_linear_to_srgb_linear,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_rec2100_linear_to_rec2100_pq,
    },
    .cicp = { 9801 },
  },
};

G_STATIC_ASSERT (G_N_ELEMENTS (gdk_default_color_states) == GDK_COLOR_STATE_N_IDS);

static gboolean
gdk_builtin_color_state_equal (GdkColorState *self,
                               GdkColorState *other)
{
  return self == other;
}

static const char *
gdk_builtin_color_state_get_name (GdkColorState *color_state)
{
  GdkBuiltinColorState *self = (GdkBuiltinColorState *) color_state;

  return self->name;
}

/*<private>
 * gdk_color_state_get_hue_channel:
 * @self: the color state
 * @out_channel: (out caller-allocates): the channel
 *
 * Checks if the color state has a hue channel and returns it if
 * there is one.
 *
 * Unlike other channels, hue channels are periodic, so they may
 * need different treatment in various places.
 *
 * Returns: true if the color state has a hue channel
 **/

gboolean
gdk_color_state_get_hue_channel (GdkColorState   *self,
                                 GdkColorChannel *out_channel)
{
  if (self->hue_channel == GDK_COLOR_CHANNEL_ALPHA)
    return FALSE;

  *out_channel = self->hue_channel;
  return TRUE;
}

static GdkColorState *
gdk_builtin_color_state_get_no_srgb_tf (GdkColorState *color_state)
{
  return NULL;
}

static GdkFloatColorConvert
gdk_builtin_color_state_get_convert_to (GdkColorState  *color_state,
                                        GdkColorState  *target)
{
  GdkBuiltinColorState *self = (GdkBuiltinColorState *) color_state;

  if (GDK_IS_DEFAULT_COLOR_STATE (target))
    return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];

  return NULL;
}

static GdkFloatColorConvert
gdk_builtin_color_state_get_convert_from (GdkColorState  *color_state,
                                          GdkColorState  *source)
{
  GdkBuiltinColorState *self = (GdkBuiltinColorState *) color_state;

  if (GDK_IS_DEFAULT_COLOR_STATE (source))
    return self->convert_from[GDK_DEFAULT_COLOR_STATE_ID (source)];

  return NULL;
}

static const GdkCicp *
gdk_builtin_color_state_get_cicp (GdkColorState *color_state)
{
  return NULL;
}

/* }}} */

static const
GdkColorStateClass GDK_BUILTIN_COLOR_STATE_CLASS = {
  .free = NULL, /* crash here if this ever happens */
  .equal = gdk_builtin_color_state_equal,
  .get_name = gdk_builtin_color_state_get_name,
  .get_no_srgb_tf = gdk_builtin_color_state_get_no_srgb_tf,
  .get_convert_to = gdk_builtin_color_state_get_convert_to,
  .get_convert_from = gdk_builtin_color_state_get_convert_from,
  .get_cicp = gdk_builtin_color_state_get_cicp,
};

GdkBuiltinColorState gdk_builtin_color_states[] = {
  [GDK_BUILTIN_COLOR_STATE_ID_OKLAB] = {
    .parent = {
      .klass = &GDK_BUILTIN_COLOR_STATE_CLASS,
      .ref_count = 0,
      .depth = GDK_MEMORY_FLOAT16,
      .hue_channel = GDK_COLOR_CHANNEL_ALPHA,
      .rendering_color_state = GDK_COLOR_STATE_SRGB,
    },
    .name = "oklab",
    .convert_to = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_oklab_to_srgb,
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_oklab_to_srgb_linear,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_oklab_to_rec2100_pq,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_oklab_to_rec2100_linear,
    },
    .convert_from = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_srgb_to_oklab,
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_srgb_linear_to_oklab,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_rec2100_pq_to_oklab,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_rec2100_linear_to_oklab,
    },
  },
  [GDK_BUILTIN_COLOR_STATE_ID_OKLCH] = {
    .parent = {
      .klass = &GDK_BUILTIN_COLOR_STATE_CLASS,
      .ref_count = 0,
      .depth = GDK_MEMORY_FLOAT16,
      .hue_channel = GDK_COLOR_CHANNEL_BLUE,
      .rendering_color_state = GDK_COLOR_STATE_SRGB,
    },
    .name = "oklch",
    .convert_to = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_oklch_to_srgb,
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_oklch_to_srgb_linear,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_oklch_to_rec2100_pq,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_oklch_to_rec2100_linear,
    },
    .convert_from = {
      [GDK_COLOR_STATE_ID_SRGB] = gdk_convert_srgb_to_oklch,
      [GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_srgb_linear_to_oklch,
      [GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_rec2100_pq_to_oklch,
      [GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_rec2100_linear_to_oklch,
    },
  },
};

/* }}} */
/* {{{ Cicp implementation */

typedef struct _GdkCicpColorState GdkCicpColorState;
struct _GdkCicpColorState
{
  GdkColorState parent;

  GdkColorState *no_srgb;

  char *name;

  GdkTransferFunc eotf;
  GdkTransferFunc oetf;

  float to_srgb[9];
  float to_rec2020[9];
  float from_srgb[9];
  float from_rec2020[9];

  const float *from_yuv;
  const float *to_yuv;

  GdkCicp cicp;
};

/* {{{ Conversion functions */

#define TRANSFORM_FROM_CICP(name, matrix, oetf) \
static void \
name (GdkColorState  *color_state, \
      float         (*values)[4], \
      gsize           n_values) \
{ \
  GdkCicpColorState *self = (GdkCicpColorState *) color_state; \
\
  transform_from_cicp (self, oetf, self->matrix, values, n_values); \
}

static void
transform_from_cicp (GdkCicpColorState *self,
                     GdkTransferFunc    oetf,
                     float              matrix[9],
                     float            (*values)[4],
                     gsize              n_values)
{
  for (gsize i = 0; i < n_values; i++)
    {
      if (self->cicp.range == GDK_CICP_RANGE_NARROW)
        {
          values[i][0] = CLAMP ((values[i][0] - 16.0/255.0) * 255.0 / 224.00.01.0);
          values[i][1] = CLAMP ((values[i][1] - 16.0/255.0) * 255.0 / 219.00.01.0);
          values[i][2] = CLAMP ((values[i][2] - 16.0/255.0) * 255.0 / 224.00.01.0);
        }
      if (self->from_yuv)
        {
          float res[3];
          values[i][0] -= 0.5;
          values[i][2] -= 0.5;
          res[0] = self->from_yuv[0] * values[i][0] + self->from_yuv[1] * values[i][1] + self->from_yuv[2] * values[i][2];
          res[1] = self->from_yuv[3] * values[i][0] + self->from_yuv[4] * values[i][1] + self->from_yuv[5] * values[i][2];
          res[2] = self->from_yuv[6] * values[i][0] + self->from_yuv[7] * values[i][1] + self->from_yuv[8] * values[i][2];
          values[i][0] = res[0];
          values[i][1] = res[1];
          values[i][2] = res[2];
        }
      if (self->eotf != NONE)
        self->eotf (values[i]);
      if (matrix != IDENTITY)
        {
          float res[3];
          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];
        }
      if (oetf != NONE)
        oetf (values[i]);
    }
}

#define TRANSFORM_TO_CICP(name, eotf, matrix) \
static void \
name (GdkColorState  *color_state, \
      float         (*values)[4], \
      gsize           n_values) \
{ \
  GdkCicpColorState *self = (GdkCicpColorState *) color_state; \
\
  transform_to_cicp (self, eotf, self->matrix, values, n_values); \
}

static void
transform_to_cicp (GdkCicpColorState *self,
                   GdkTransferFunc    eotf,
                   float              matrix[9],
                   float            (*values)[4],
                   gsize              n_values)
{
  for (gsize i = 0; i < n_values; i++)
    {
      if (eotf != NONE)
        eotf (values[i]);
      if (matrix != IDENTITY)
        {
          float res[3];
          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];
        }
      else
        {
          float tmp = values[i][2];
          values[i][2] = values[i][0];
          values[i][0] = values[i][1];
          values[i][1] = tmp;
        }
      if (self->oetf != NONE)
        self->oetf (values[i]);
      if (self->to_yuv)
        {
          float res[3];
          res[0] = self->to_yuv[0] * values[i][0] + self->to_yuv[1] * values[i][1] + self->to_yuv[2] * values[i][2];
          res[1] = self->to_yuv[3] * values[i][0] + self->to_yuv[4] * values[i][1] + self->to_yuv[5] * values[i][2];
          res[2] = self->to_yuv[6] * values[i][0] + self->to_yuv[7] * values[i][1] + self->to_yuv[8] * values[i][2];
          values[i][0] = res[0] + 0.5;
          values[i][1] = res[1];
          values[i][2] = res[2] + 0.5;
        }
      values[i][0] = CLAMP (values[i][0], 0.01.0);
      values[i][1] = CLAMP (values[i][1], 0.01.0);
      values[i][2] = CLAMP (values[i][2], 0.01.0);
      if (self->cicp.range == GDK_CICP_RANGE_NARROW)
        {
          values[i][0] = values[i][0] * 224.0 / 255.0 + 16.0 / 255.0;
          values[i][1] = values[i][1] * 219.0 / 255.0 + 16.0 / 255.0;
          values[i][2] = values[i][2] * 224.0 / 255.0 + 16.0 / 255.0;
        }
    }
}

TRANSFORM_FROM_CICP(gdk_convert_cicp_to_srgb,           to_srgb,      srgb_oetf)
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_srgb_linear,    to_srgb,      NONE)
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_rec2100_pq,     to_rec2020,   pq_oetf)
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_rec2100_linear, to_rec2020,   NONE)

TRANSFORM_TO_CICP(gdk_convert_cicp_from_srgb,           srgb_eotf,   from_srgb)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_srgb_linear,    NONE,        from_srgb)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_rec2100_pq,     pq_eotf,     from_rec2020)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_rec2100_linear, NONE,        from_rec2020)

/* }}} */
/* {{{ Vfuncs */

static void
gdk_cicp_color_state_free (GdkColorState *cs)
{
  GdkCicpColorState *self = (GdkCicpColorState *) cs;

  g_free (self->name);

  if (self->no_srgb)
    gdk_color_state_unref (self->no_srgb);

  g_free (self);
}

static gboolean
gdk_cicp_color_state_equal (GdkColorState *self,
                            GdkColorState *other)
{
  GdkCicpColorState *cs1 = (GdkCicpColorState *) self;
  GdkCicpColorState *cs2 = (GdkCicpColorState *) other;

  return gdk_cicp_equal (&cs1->cicp, &cs2->cicp);
}

static const char *
gdk_cicp_color_state_get_name (GdkColorState *self)
{
  GdkCicpColorState *cs = (GdkCicpColorState *) self;

  return cs->name;
}

static GdkColorState *
gdk_cicp_color_state_get_no_srgb_tf (GdkColorState *self)
{
  GdkCicpColorState *cs = (GdkCicpColorState *) self;

  return cs->no_srgb;
}

static GdkFloatColorConvert
gdk_cicp_color_state_get_convert_to (GdkColorState *self,
                                     GdkColorState *target)
{
  if (!GDK_IS_DEFAULT_COLOR_STATE (target))
    return NULL;

  switch (GDK_DEFAULT_COLOR_STATE_ID (target))
    {
    case GDK_COLOR_STATE_ID_SRGB:
      return gdk_convert_cicp_to_srgb;
    case GDK_COLOR_STATE_ID_SRGB_LINEAR:
      return gdk_convert_cicp_to_srgb_linear;
    case GDK_COLOR_STATE_ID_REC2100_PQ:
      return gdk_convert_cicp_to_rec2100_pq;
    case GDK_COLOR_STATE_ID_REC2100_LINEAR:
      return gdk_convert_cicp_to_rec2100_linear;

    case GDK_COLOR_STATE_N_IDS:
    default:
      g_assert_not_reached ();
    }

  return NULL;
}

static GdkFloatColorConvert
gdk_cicp_color_state_get_convert_from (GdkColorState *self,
                                       GdkColorState *source)
{
  if (!GDK_IS_DEFAULT_COLOR_STATE (source))
    return NULL;

  switch (GDK_DEFAULT_COLOR_STATE_ID (source))
    {
    case GDK_COLOR_STATE_ID_SRGB:
      return gdk_convert_cicp_from_srgb;
    case GDK_COLOR_STATE_ID_SRGB_LINEAR:
      return gdk_convert_cicp_from_srgb_linear;
    case GDK_COLOR_STATE_ID_REC2100_PQ:
      return gdk_convert_cicp_from_rec2100_pq;
    case GDK_COLOR_STATE_ID_REC2100_LINEAR:
      return gdk_convert_cicp_from_rec2100_linear;

    case GDK_COLOR_STATE_N_IDS:
    default:
      g_assert_not_reached ();
    }

  return NULL;
}

static const GdkCicp *
gdk_cicp_color_state_get_cicp (GdkColorState  *color_state)
{
  GdkCicpColorState *self = (GdkCicpColorState *) color_state;

  return &self->cicp;
}

/* }}} */

static const
GdkColorStateClass GDK_CICP_COLOR_STATE_CLASS = {
  .free = gdk_cicp_color_state_free,
  .equal = gdk_cicp_color_state_equal,
  .get_name = gdk_cicp_color_state_get_name,
  .get_no_srgb_tf = gdk_cicp_color_state_get_no_srgb_tf,
  .get_convert_to = gdk_cicp_color_state_get_convert_to,
  .get_convert_from = gdk_cicp_color_state_get_convert_from,
  .get_cicp = gdk_cicp_color_state_get_cicp,
};

static inline float *
multiply (float       res[9],
          const float m1[9],
          const float m2[9])
{
#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 *
gdk_color_state_new_for_cicp (const GdkCicp  *cicp,
                              GError        **error)
{
  GdkCicpColorState *self;
  GdkTransferFunc eotf;
  GdkTransferFunc oetf;
  gconstpointer to_xyz;
  gconstpointer from_xyz;
  gconstpointer to_yuv = NULL;
  gconstpointer from_yuv = NULL;

  if (cicp->color_primaries == 2 ||
      cicp->transfer_function == 2 ||
      cicp->matrix_coefficients == 2)
    {
      g_set_error (error,
                   G_IO_ERROR, G_IO_ERROR_FAILED,
                   _("cicp: Unspecified parameters not supported"));
      return NULL;
    }

  for (guint i = 0; i < GDK_COLOR_STATE_N_IDS; i++)
    {
      if (gdk_cicp_equal (cicp, &gdk_default_color_states[i].cicp))
        return (GdkColorState *) &gdk_default_color_states[i];
    }

  switch (cicp->transfer_function)
    {
    case 1:
    case 6:
    case 14:
    case 15:
      eotf = bt709_eotf;
      oetf = bt709_oetf;
      break;
    case 4:
      eotf = gamma22_eotf;
      oetf = gamma22_oetf;
      break;
    case 5:
      eotf = gamma28_eotf;
      oetf = gamma28_oetf;
      break;
    case 8:
      eotf = NONE;
      oetf = NONE;
      break;
    case 13:
      eotf = srgb_eotf;
      oetf = srgb_oetf;
      break;
    case 16:
      eotf = pq_eotf;
      oetf = pq_oetf;
      break;
    case 18:
      eotf = hlg_eotf;
      oetf = hlg_oetf;
      break;
    default:
      g_set_error (error,
                   G_IO_ERROR, G_IO_ERROR_FAILED,
                   _("cicp: Transfer function %u not supported"),
                   cicp->transfer_function);
      return NULL;
    }

  switch (cicp->color_primaries)
    {
    case 1:
      to_xyz = srgb_to_xyz;
      from_xyz = xyz_to_srgb;
      break;
    case 5:
      to_xyz = pal_to_xyz;
      from_xyz = xyz_to_pal;
      break;
    case 6:
    case 7:
      to_xyz = ntsc_to_xyz;
      from_xyz = xyz_to_ntsc;
      break;
    case 9:
      to_xyz = rec2020_to_xyz;
      from_xyz = xyz_to_rec2020;
      break;
    case 12:
      to_xyz = p3_to_xyz;
      from_xyz = xyz_to_p3;
      break;
    default:
      g_set_error (error,
                   G_IO_ERROR, G_IO_ERROR_FAILED,
                   _("cicp: Color primaries %u not supported"),
                   cicp->color_primaries);
      return NULL;
    }

  switch (cicp->matrix_coefficients)
    {
    case 0:
      to_yuv = IDENTITY;
      from_yuv = IDENTITY;
      break;
    case 1:
      to_yuv = rgb_to_bt709;
      from_yuv = bt709_to_rgb;
      break;
    case 5:
    case 6:
      to_yuv = rgb_to_bt601;
      from_yuv = bt601_to_rgb;
      break;
    case 9:
      to_yuv = rgb_to_bt2020;
      from_yuv = bt2020_to_rgb;
      break;
    default:
      g_set_error (error,
                   G_IO_ERROR, G_IO_ERROR_FAILED,
                   _("cicp: Matrix coefficients %u, %s not supported"),
                   cicp->matrix_coefficients,
                   cicp->range == GDK_CICP_RANGE_NARROW ? "narrow" : "full");
      return NULL;
    }

  self = g_new0 (GdkCicpColorState, 1);

  self->parent.klass = &GDK_CICP_COLOR_STATE_CLASS;
  self->parent.ref_count = 1;

  /* sRGB is special-cased by being a default colorstate */
  self->parent.rendering_color_state = GDK_COLOR_STATE_REC2100_PQ;
  self->parent.rendering_color_state_linear = GDK_COLOR_STATE_REC2100_LINEAR;

  self->parent.depth = GDK_MEMORY_FLOAT16;
  self->parent.hue_channel = GDK_COLOR_CHANNEL_ALPHA;

  memcpy (&self->cicp, cicp, sizeof (GdkCicp));

  self->to_yuv = to_yuv;
  self->from_yuv = from_yuv;

  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->name = g_strdup_printf ("cicp-%u/%u/%u/%u",
                                cicp->color_primaries,
                                cicp->transfer_function,
                                cicp->matrix_coefficients,
                                cicp->range);

  if (cicp->transfer_function == 13)
    {
      self->no_srgb = gdk_color_state_new_for_cicp (&(GdkCicp) {
                                                      cicp->color_primaries,
                                                      8,
                                                      cicp->matrix_coefficients,
                                                      cicp->range },
                                                    NULL);
    }

  return (GdkColorState *) self;
}

/* }}} */
/* {{{ Private API */

/*<private>
 * gdk_color_state_get_name:
 * @self: a colorstate
 *
 * Returns the name of @self.
 *
 * This is *not* a translated, user-visible string.
 *
 * Returns: (transfer none): a name for representing the color state
 *     in diagnostic output
 */

const char *
gdk_color_state_get_name (GdkColorState *self)
{
  return self->klass->get_name (self);
}

/*<private>
 * gdk_color_state_get_no_srgb_tf:
 * @self: a colorstate
 *
 * This function checks if the colorstate uses an sRGB transfer function
 * as final operation. In that case, it is suitable for use with GL_SRGB
 * (and the Vulkan equivalents).
 *
 * If it is suitable, the colorstate without the transfer function is
 * returned. Otherwise, this function returns NULL.
 *
 * Returns: (transfer none): the colorstate without sRGB transfer function.
 **/

GdkColorState *
gdk_color_state_get_no_srgb_tf (GdkColorState *self)
{
  if (!GDK_DEBUG_CHECK (LINEAR))
    return FALSE;

  return self->klass->get_no_srgb_tf (self);
}

GdkColorState *
gdk_color_state_yuv (void)
{
  static GdkColorState *yuv_cs;

  if (g_once_init_enter_pointer (&yuv_cs))
    {
      GdkColorState *cs;

      cs = gdk_color_state_new_for_cicp (&(GdkCicp) { 11350 }, NULL);
      g_assert (cs);

      g_once_init_leave_pointer (&yuv_cs, cs);
    }

  return yuv_cs;
}

/* }}} */

/* vim:set foldmethod=marker: */

Messung V0.5 in Prozent
C=97 H=97 G=96

¤ Dauer der Verarbeitung: 0.8 Sekunden  (vorverarbeitet am  2026-07-03) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.