#include "gdkwaylandcolor-private.h"
#include "gdksurface-wayland-private.h"
#include <gdk/wayland/color-management-v1-client-protocol.h>
#include <gdk/wayland/color-representation-v1-client-protocol.h>
typedef struct _ImageDescription ImageDescription;
static const uint primaries_map[] = {
[WP_COLOR_MANAGER_V1_PRIMARIES_SRGB] = 1 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M] = 4 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_PAL] = 5 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_NTSC] = 6 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM] = 8 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_BT2020] = 9 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ] = 10 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3] = 11 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3] = 12 ,
[WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB] = 0 ,
};
static uint
wl_to_cicp_primaries (enum wp_color_manager_v1_primaries cp)
{
return primaries_map[cp];
}
static enum wp_color_manager_v1_primaries
cicp_to_wl_primaries (uint cp)
{
for (uint32_t i = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; i < G_N_ELEMENTS (primaries_map); i++)
if (primaries_map[i] == cp)
return (enum wp_color_manager_v1_primaries)i;
return 0 ;
}
static const uint primaries_primaries[][8 ] = {
[WP_COLOR_MANAGER_V1_PRIMARIES_SRGB] = { 640000 , 330000 , 300000 , 600000 , 150000 , 60000 , 312700 , 329000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M] = { 670000 , 330000 , 210000 , 710000 , 140000 , 80000 , 310000 , 316000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_PAL] = { 640000 , 330000 , 290000 , 600000 , 150000 , 60000 , 312700 , 329000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_NTSC] = { 630000 , 340000 , 310000 , 595000 , 155000 , 70000 , 312700 , 329000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM] = { 243000 , 692000 , 145000 , 49000 , 681000 , 319000 , 310000 , 316000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_BT2020] = { 708000 , 292000 , 170000 , 797000 , 131000 , 46000 , 312700 , 329000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ] = {1000000 , 0 , 0 ,1000000 , 0 , 0 , 333333 , 333333 },
[WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3] = { 680000 , 320000 , 265000 , 690000 , 150000 , 60000 , 314000 , 351000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3] = { 680000 , 320000 , 265000 , 690000 , 150000 , 60000 , 312700 , 329000 },
[WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB] = { 640000 , 330000 , 210000 , 710000 , 150000 , 60000 , 312700 , 329000 },
};
static const uint *
wl_primaries_to_primaries (enum wp_color_manager_v1_primaries primaries)
{
return primaries_primaries[primaries];
}
static gboolean
primaries_to_wl_primaries (const uint primaries[8 ],
enum wp_color_manager_v1_primaries *out_primaries)
{
for (uint32_t i = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; i < G_N_ELEMENTS (primaries_primaries); i++)
{
uint32_t j;
for (j = 0 ; j < 8 ; j++)
{
if (primaries[j] != primaries_primaries[i][j])
break ;
}
if (j == 8 )
{
*out_primaries = i;
return TRUE ;
}
}
return FALSE ;
}
static const uint transfer_map[] = {
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886] = 1 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22] = 4 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28] = 5 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240] = 7 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR] = 8 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100] = 9 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316] = 10 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC] = 11 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB] = 13 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB] = 13 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ] = 16 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428] = 17 ,
[WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG] = 18 ,
};
static uint
wl_to_cicp_transfer (enum wp_color_manager_v1_transfer_function tf)
{
return transfer_map[tf];
}
static enum wp_color_manager_v1_transfer_function
cicp_to_wl_transfer (uint tf)
{
for (uint32_t i = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886; i < G_N_ELEMENTS (transfer_map); i++)
if (transfer_map[i] == tf)
return (enum wp_color_manager_v1_transfer_function)i;
return 0 ;
}
struct {
enum wp_color_representation_surface_v1_coefficients wp;
uint cicp;
const char *name;
} coefficients_map[] = {
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY, .cicp = 0 , .name = "identity" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT709, .cicp = 1 , .name = "bt709" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_FCC, .cicp = 4 , .name = "fcc" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601, .cicp = 5 , .name = "bt601" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_SMPTE240, .cicp = 7 , .name = "smpte240" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020, .cicp = 9 , .name = "bt2020" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020_CL, .cicp = 10 , .name = "bt2020-cl" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_ICTCP, .cicp = 14 , .name = "ictcp" },
};
static enum wp_color_representation_surface_v1_coefficients
cicp_to_wl_coefficients (uint m)
{
if (m == 6 )
m = 5 ;
for (uint32_t i = 0 ; i < G_N_ELEMENTS (coefficients_map); i++)
if (coefficients_map[i].cicp == m)
return coefficients_map[i].wp;
return 0 ;
}
static const char *
wl_coefficients_name (enum wp_color_representation_surface_v1_coefficients value)
{
for (uint32_t i = 0 ; i < G_N_ELEMENTS (coefficients_map); i++)
if (coefficients_map[i].wp == value)
return coefficients_map[i].name;
return "invalid" ;
}
struct {
enum wp_color_representation_surface_v1_alpha_mode wp;
const char *name;
} alpha_modes[] = {
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL, .name = "premultiplied-electrical" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_OPTICAL, .name = "premultiplied-optical" },
{ .wp = WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_STRAIGHT, .name = "straight" },
};
static const char *
wl_alpha_name (enum wp_color_representation_surface_v1_alpha_mode alpha)
{
for (uint32_t i = 0 ; i < G_N_ELEMENTS (alpha_modes); i++)
if (alpha_modes[i].wp == alpha)
return alpha_modes[i].name;
return "invalid" ;
}
static enum wp_color_representation_surface_v1_alpha_mode
gdk_alpha_to_wl_alpha (GdkMemoryAlpha alpha)
{
switch (alpha)
{
case GDK_MEMORY_ALPHA_PREMULTIPLIED:
case GDK_MEMORY_ALPHA_OPAQUE:
return WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL;
case GDK_MEMORY_ALPHA_STRAIGHT:
return WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_STRAIGHT;
default :
g_assert_not_reached ();
}
}
static const char *
wl_range_name (enum wp_color_representation_surface_v1_range range)
{
switch (range)
{
case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL:
return "full" ;
case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED:
return "limited" ;
default :
g_assert_not_reached ();
}
}
struct _GdkWaylandColor
{
GdkWaylandDisplay *display;
struct wp_color_manager_v1 *color_manager;
struct {
unsigned int intents;
unsigned int features;
unsigned int transfers;
unsigned int primaries;
} color_manager_supported;
GHashTable *cs_to_desc; /* GdkColorState => wp_image_description_v1 or NULL */
GHashTable *id_to_cs; /* uint32 identifier => GdkColorState */
struct wp_color_representation_manager_v1 *color_representation_manager;
struct {
unsigned int alpha_modes;
unsigned int coefficients_limited;
unsigned int coefficients_full;
} color_representation_supported;
};
static uint32_t
color_state_hash (gconstpointer data)
{
GdkColorState *cs = (GdkColorState *) data;
const GdkCicp *cicp;
cicp = gdk_color_state_get_cicp (cs);
if (cicp)
{
GdkCicp norm;
gdk_cicp_normalize (cicp, &norm);
return norm.color_primaries << 24 | norm.transfer_function;
}
return 0 ;
}
static gboolean
color_state_equal (gconstpointer a,
gconstpointer b)
{
GdkColorState *csa = (GdkColorState *) a;
GdkColorState *csb = (GdkColorState *) b;
const GdkCicp *cicpa, *cicpb;
GdkCicp norma, normb;
cicpa = gdk_color_state_get_cicp (csa);
cicpb = gdk_color_state_get_cicp (csb);
if (cicpa == NULL || cicpb == NULL)
return FALSE ;
gdk_cicp_normalize (cicpa, &norma);
gdk_cicp_normalize (cicpb, &normb);
return norma.color_primaries == normb.color_primaries &&
norma.transfer_function == normb.transfer_function;
}
static void
wp_color_manager_v1_supported_intent (void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t render_intent)
{
GdkWaylandColor *color = data;
color->color_manager_supported.intents |= (1 << render_intent);
}
static void
wp_color_manager_v1_supported_feature (void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t feature)
{
GdkWaylandColor *color = data;
color->color_manager_supported.features |= (1 << feature);
}
static void
wp_color_manager_v1_supported_tf_named (void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t tf)
{
GdkWaylandColor *color = data;
color->color_manager_supported.transfers |= (1 << tf);
}
static void
wp_color_manager_v1_supported_primaries_named (void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t primaries)
{
GdkWaylandColor *color = data;
color->color_manager_supported.primaries |= (1 << primaries);
}
static void
wp_color_manager_v1_done (void *data,
struct wp_color_manager_v1 *wp_color_manager_v1)
{
}
static struct wp_color_manager_v1_listener color_manager_listener = {
wp_color_manager_v1_supported_intent,
wp_color_manager_v1_supported_feature,
wp_color_manager_v1_supported_tf_named,
wp_color_manager_v1_supported_primaries_named,
wp_color_manager_v1_done,
};
GdkWaylandColor *
gdk_wayland_color_new (GdkWaylandDisplay *display)
{
GdkWaylandColor *color;
color = g_new0 (GdkWaylandColor, 1 );
color->display = display;
color->cs_to_desc = g_hash_table_new_full (color_state_hash,
color_state_equal,
(GDestroyNotify) gdk_color_state_unref,
(GDestroyNotify) wp_image_description_v1_destroy);
color->id_to_cs = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify) gdk_color_state_unref);
return color;
}
void
gdk_wayland_color_set_color_manager (GdkWaylandColor *color,
struct wl_registry *registry,
uint32_t id,
uint32_t version)
{
color->color_manager = wl_registry_bind (registry,
id,
&wp_color_manager_v1_interface,
MIN (version, 1 ));
wp_color_manager_v1_add_listener (color->color_manager,
&color_manager_listener,
color);
}
void
gdk_wayland_color_free (GdkWaylandColor *color)
{
g_clear_pointer (&color->color_manager, wp_color_manager_v1_destroy);
g_hash_table_unref (color->cs_to_desc);
g_hash_table_unref (color->id_to_cs);
g_clear_pointer (&color->color_representation_manager, wp_color_representation_manager_v1_destroy);
g_free (color);
}
struct wl_proxy *
gdk_wayland_color_get_color_manager (GdkWaylandColor *color)
{
return (struct wl_proxy *) color->color_manager;
}
struct wl_proxy *
gdk_wayland_color_get_color_representation_manager (GdkWaylandColor *color)
{
return (struct wl_proxy *) color->color_representation_manager;
}
typedef struct _CsImageDescListenerData {
GdkWaylandColor *color;
GdkColorState *color_state;
gboolean sync;
gboolean done;
} CsImageDescListenerData;
static void
wp_color_representation_manager_v1_supported_alpha_mode (void *data,
struct wp_color_representation_manager_v1 *wp_color_representation_manager_v1,
uint32_t alpha_mode)
{
GdkWaylandColor *color = data;
color->color_representation_supported.alpha_modes |= 1 << alpha_mode;
}
static void
wp_color_representation_manager_v1_supported_coefficients_and_ranges (void *data,
struct wp_color_representation_manager_v1 *wp_color_representation_manager_v1,
uint32_t coefficients,
uint32_t range)
{
GdkWaylandColor *color = data;
switch (range)
{
case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL:
color->color_representation_supported.coefficients_full |= 1 << coefficients;
break ;
case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED:
color->color_representation_supported.coefficients_limited |= 1 << coefficients;
break ;
default :
g_assert_not_reached ();
}
}
static void
wp_color_representation_manager_v1_done (void *data,
struct wp_color_representation_manager_v1 *wp_color_representation_manager_v1)
{
}
static struct wp_color_representation_manager_v1_listener color_representation_manager_listener = {
wp_color_representation_manager_v1_supported_alpha_mode,
wp_color_representation_manager_v1_supported_coefficients_and_ranges,
wp_color_representation_manager_v1_done,
};
void
gdk_wayland_color_set_color_representation (GdkWaylandColor *color,
struct wl_registry *registry,
uint32_t id,
uint32_t version)
{
g_assert (color->color_representation_manager == NULL);
color->color_representation_manager = wl_registry_bind (registry,
id,
&wp_color_representation_manager_v1_interface,
MIN (version, 1 ));
wp_color_representation_manager_v1_add_listener (color->color_representation_manager,
&color_representation_manager_listener,
color);
}
static void
cs_image_listener_data_free (CsImageDescListenerData *csi)
{
csi->done = TRUE ;
if (csi->sync)
return ;
g_free (csi);
}
static void
cs_image_desc_failed (void *data,
struct wp_image_description_v1 *desc,
uint32_t cause,
const char *msg)
{
CsImageDescListenerData *csi = data;
g_warning ("Failed to get one of the standard image descriptions: %s" , msg);
wp_image_description_v1_destroy (desc);
g_hash_table_insert (csi->color->cs_to_desc,
gdk_color_state_ref (csi->color_state),
NULL);
cs_image_listener_data_free (csi);
}
static void
cs_image_desc_ready (void *data,
struct wp_image_description_v1 *desc,
uint32_t identity)
{
CsImageDescListenerData *csi = data;
g_hash_table_insert (csi->color->cs_to_desc,
gdk_color_state_ref (csi->color_state),
desc);
g_hash_table_insert (csi->color->id_to_cs,
GUINT_TO_POINTER (identity),
gdk_color_state_ref (csi->color_state));
cs_image_listener_data_free (csi);
}
static struct wp_image_description_v1_listener cs_image_desc_listener = {
cs_image_desc_failed,
cs_image_desc_ready,
};
static void
create_image_desc (GdkWaylandColor *color,
GdkColorState *cs,
gboolean sync)
{
CsImageDescListenerData data;
struct wp_image_description_creator_params_v1 *creator;
struct wp_image_description_v1 *desc;
const GdkCicp *cicp;
GdkCicp norm;
uint32_t primaries, tf;
cicp = gdk_color_state_get_cicp (cs);
if (!cicp)
{
GDK_DISPLAY_DEBUG (GDK_DISPLAY (color->display), MISC,
"Unsupported color state %s: Not a CICP colorstate" ,
gdk_color_state_get_name (cs));
g_hash_table_insert (color->cs_to_desc, gdk_color_state_ref (cs), NULL);
return ;
}
gdk_cicp_normalize (cicp, &norm);
primaries = cicp_to_wl_primaries (norm.color_primaries);
tf = cicp_to_wl_transfer (norm.transfer_function);
if (((color->color_manager_supported.primaries & (1 << primaries)) == 0 &&
(color->color_manager_supported.features & (1 << WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES)) == 0 ) ||
(color->color_manager_supported.transfers & (1 << tf)) == 0 )
{
GDK_DISPLAY_DEBUG (GDK_DISPLAY (color->display), MISC,
"Unsupported color state %s: Primaries or transfer function unsupported" ,
gdk_color_state_get_name (cs));
g_hash_table_insert (color->cs_to_desc, gdk_color_state_ref (cs), NULL);
return ;
}
g_assert (color->color_manager != NULL);
data.color = color;
data.color_state = cs;
data.sync = sync;
data.done = FALSE ;
creator = wp_color_manager_v1_create_parametric_creator (color->color_manager);
if (color->color_manager_supported.primaries & (1 << primaries))
{
wp_image_description_creator_params_v1_set_primaries_named (creator, primaries);
}
else
{
const uint *p = wl_primaries_to_primaries (primaries);
wp_image_description_creator_params_v1_set_primaries (creator,
p[0 ], p[1 ],
p[2 ], p[3 ],
p[4 ], p[5 ],
p[6 ], p[7 ]);
}
wp_image_description_creator_params_v1_set_tf_named (creator, tf);
desc = wp_image_description_creator_params_v1_create (creator);
if (sync)
{
struct wl_event_queue *event_queue;
event_queue = wl_display_create_queue (color->display->wl_display);
wl_proxy_set_queue ((struct wl_proxy *) desc, event_queue);
wp_image_description_v1_add_listener (desc, &cs_image_desc_listener, &data);
while (!data.done)
gdk_wayland_display_dispatch_queue (GDK_DISPLAY (color->display), event_queue);
wl_event_queue_destroy (event_queue);
}
else
{
wp_image_description_v1_add_listener (desc, &cs_image_desc_listener, g_memdup2 (&data, sizeof data));
}
}
gboolean
gdk_wayland_color_prepare (GdkWaylandColor *color)
{
if (color->color_manager)
{
if (GDK_DISPLAY_DEBUG_CHECK (GDK_DISPLAY (color->display), MISC))
{
struct {
const char *name;
enum wp_color_manager_v1_render_intent value;
} intents[] = {
{ "perceptual" , WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL },
{ "relative" , WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE },
{ "saturation" , WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION },
{ "absolute" , WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE },
{ "relative-bpc" , WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC },
#ifdef WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE_NO_ADAPTATION_SINCE_VERSION
{ "absolute-no-adaptation" , WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE_NO_ADAPTATION },
#endif
};
struct {
const char *name;
enum wp_color_manager_v1_feature value;
} features[] = {
{ "icc-v2-v4" , WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4 },
{ "parametric" , WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC },
{ "set-primaries" , WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES },
{ "set-tf-power" , WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER },
{ "set-luminances" , WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES },
{ "set-mastering-display-primaries" , WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES },
{ "extended-target-volume" , WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME },
{ "windows-scrgb" , WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB },
};
struct {
const char *name;
enum wp_color_manager_v1_primaries value;
} primaries[] = {
{ "srgb" , WP_COLOR_MANAGER_V1_PRIMARIES_SRGB },
{ "pal-m" , WP_COLOR_MANAGER_V1_PRIMARIES_PAL_M },
{ "pal" , WP_COLOR_MANAGER_V1_PRIMARIES_PAL },
{ "ntsc" , WP_COLOR_MANAGER_V1_PRIMARIES_NTSC },
{ "generic-film" , WP_COLOR_MANAGER_V1_PRIMARIES_GENERIC_FILM },
{ "bt2020" , WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 },
{ "cie1931-xyz" , WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ },
{ "dci-p3" , WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3 },
{ "display-p3" , WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3 },
{ "adobe-rgb" , WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB },
};
struct {
const char *name;
enum wp_color_manager_v1_transfer_function value;
} tf[] = {
{ "bt1886" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886 },
{ "gamma22" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22 },
{ "gamma28" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28 },
{ "st240" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240 },
{ "ext-linear" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR },
{ "log100" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100 },
{ "log316" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316 },
{ "xvycc" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC },
{ "srgb" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB },
{ "ext-srgb" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB },
{ "st2084-pq" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ },
{ "st428" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428 },
{ "hlg" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG },
#ifdef WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4_SINCE_VERSION
{ "compound-power24" , WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4 },
#endif
};
unsigned int len;
len = 0 ;
for (int i = 0 ; i < G_N_ELEMENTS (intents); i++)
len = MAX (len, strlen (intents[i].name));
for (int i = 0 ; i < G_N_ELEMENTS (intents); i++)
gdk_debug_message ("Rendering intent %2u (%s): %*s%s" ,
intents[i].value,
intents[i].name,
(int ) (len - strlen (intents[i].name)), "" ,
color->color_manager_supported.intents & (1 << intents[i].value) ? "✓" : "✗" );
len = 0 ;
for (int i = 0 ; i < G_N_ELEMENTS (features); i++)
len = MAX (len, strlen (features[i].name));
for (int i = 0 ; i < G_N_ELEMENTS (features); i++)
gdk_debug_message ("Feature %2u (%s): %*s%s" ,
features[i].value,
features[i].name,
(int ) (len - strlen (features[i].name)), "" ,
color->color_manager_supported.features & (1 << features[i].value) ? "✓" : "✗" );
len = 0 ;
for (int i = 0 ; i < G_N_ELEMENTS (primaries); i++)
len = MAX (len, strlen (primaries[i].name));
for (int i = 0 ; i < G_N_ELEMENTS (primaries); i++)
gdk_debug_message ("Primaries %2u (%s): %*s%s" ,
primaries[i].value,
primaries[i].name,
(int ) (len - strlen (primaries[i].name)), "" ,
color->color_manager_supported.primaries & (1 << primaries[i].value) ? "✓" : "✗" );
len = 0 ;
for (int i = 0 ; i < G_N_ELEMENTS (tf); i++)
len = MAX (len, strlen (tf[i].name));
for (int i = 0 ; i < G_N_ELEMENTS (tf); i++)
gdk_debug_message ("Transfer function %2u (%s): %*s%s" ,
tf[i].value,
tf[i].name,
(int ) (len - strlen (tf[i].name)), "" ,
color->color_manager_supported.transfers & (1 << tf[i].value) ? "✓" : "✗" );
}
}
if (color->color_manager &&
!(color->color_manager_supported.intents & (1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL)))
{
GDK_DISPLAY_DEBUG (GDK_DISPLAY (color->display), MISC, "Not using color management: Missing perceptual render intent" );
g_clear_pointer (&color->color_manager, wp_color_manager_v1_destroy);
}
if (color->color_manager &&
(!(color->color_manager_supported.features & (1 << WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC)) ||
!(color->color_manager_supported.transfers & (1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB)) ||
!((color->color_manager_supported.primaries & (1 << WP_COLOR_MANAGER_V1_PRIMARIES_SRGB)) ||
(color->color_manager_supported.features & (1 << WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES)))))
{
GDK_DISPLAY_DEBUG (GDK_DISPLAY (color->display), MISC, "Not using color management: Can't create srgb image description" );
g_clear_pointer (&color->color_manager, wp_color_manager_v1_destroy);
}
if (color->color_manager)
{
create_image_desc (color, GDK_COLOR_STATE_SRGB, FALSE );
if (color->color_manager_supported.transfers & (1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR))
create_image_desc (color, GDK_COLOR_STATE_SRGB_LINEAR, FALSE );
if ((color->color_manager_supported.primaries & (1 << WP_COLOR_MANAGER_V1_PRIMARIES_BT2020) ||
(color->color_manager_supported.features & (1 << WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES))))
{
if (color->color_manager_supported.transfers & (1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ))
create_image_desc (color, GDK_COLOR_STATE_REC2100_PQ, FALSE );
if (color->color_manager_supported.transfers & (1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR))
create_image_desc (color, GDK_COLOR_STATE_REC2100_LINEAR, FALSE );
}
}
if (color->color_representation_manager)
{
if (GDK_DISPLAY_DEBUG_CHECK (GDK_DISPLAY (color->display), MISC))
{
unsigned int len;
len = 0 ;
for (int i = 0 ; i < G_N_ELEMENTS (alpha_modes); i++)
len = MAX (len, strlen (alpha_modes[i].name));
for (int i = 0 ; i < G_N_ELEMENTS (alpha_modes); i++)
gdk_debug_message ("Alpha mode %2u (%s): %*s%s" ,
alpha_modes[i].wp,
alpha_modes[i].name,
(int ) (len - strlen (alpha_modes[i].name)), "" ,
color->color_representation_supported.alpha_modes & (1 << alpha_modes[i].wp) ? "✓" : "✗" );
len = 0 ;
for (int i = 0 ; i < G_N_ELEMENTS (coefficients_map); i++)
len = MAX (len, strlen (coefficients_map[i].name));
for (int i = 0 ; i < G_N_ELEMENTS (coefficients_map); i++)
gdk_debug_message ("Coefficients (full range) %2u (%s): %*s%s" ,
coefficients_map[i].wp,
coefficients_map[i].name,
(int ) (len - strlen (coefficients_map[i].name)), "" ,
color->color_representation_supported.coefficients_full & (1 << coefficients_map[i].wp) ? "✓" : "✗" );
for (int i = 0 ; i < G_N_ELEMENTS (coefficients_map); i++)
gdk_debug_message ("Coefficients (limited range) %2u (%s): %*s%s" ,
coefficients_map[i].wp,
coefficients_map[i].name,
(int ) (len - strlen (coefficients_map[i].name)), "" ,
color->color_representation_supported.coefficients_limited & (1 << coefficients_map[i].wp) ? "✓" : "✗" );
}
}
if (color->color_representation_manager &&
!(color->color_representation_supported.coefficients_full & (1 << WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY)))
{
GDK_DISPLAY_DEBUG (GDK_DISPLAY (color->display), MISC, "Not using color representation: Can't create identity transform" );
g_clear_pointer (&color->color_representation_manager, wp_color_representation_manager_v1_destroy);
}
return color->color_manager != NULL || color->color_representation_manager != NULL;
}
struct _GdkWaylandColorSurface
{
GdkWaylandColor *color;
struct wl_surface *wl_surface;
struct wp_color_management_surface_v1 *mgmt_surface;
struct wp_color_management_surface_feedback_v1 *mgmt_feedback;
ImageDescription *current_desc;
GdkColorStateChanged callback;
gpointer data;
struct wp_color_representation_surface_v1 *repr_surface;
};
struct _ImageDescription
{
GdkWaylandColorSurface *surface;
struct wp_image_description_v1 *image_desc;
struct wp_image_description_info_v1 *info;
uint32_t identity;
int32_t icc;
uint32_t icc_size;
int32_t r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y;
uint32_t primaries;
uint32_t tf_power;
uint32_t tf_named;
uint32_t min_lum, max_lum, ref_lum;
int32_t target_r_x, target_r_y, target_g_x, target_g_y, target_b_x, target_b_y, target_w_x, target_w_y;
uint32_t target_min_lum, target_max_lum;
uint32_t target_max_cll, target_max_fall;
unsigned int has_icc : 1 ;
unsigned int has_primaries : 1 ;
unsigned int has_primaries_named : 1 ;
unsigned int has_tf_power : 1 ;
unsigned int has_tf_named : 1 ;
unsigned int has_luminances : 1 ;
unsigned int has_target_primaries : 1 ;
unsigned int has_target_luminance : 1 ;
unsigned int has_target_max_cll : 1 ;
unsigned int has_target_max_fall : 1 ;
};
static GdkColorState *
gdk_color_state_from_image_description_bits (ImageDescription *desc)
{
if (desc->has_primaries_named && desc->has_tf_named)
{
GdkCicp cicp;
cicp.color_primaries = wl_to_cicp_primaries (desc->primaries);
cicp.transfer_function = wl_to_cicp_transfer (desc->tf_named);
cicp.matrix_coefficients = 0 ;
cicp.range = GDK_CICP_RANGE_FULL;
return gdk_color_state_new_for_cicp (&cicp, NULL);
}
else
return NULL;
}
static void
gdk_wayland_color_surface_clear_image_desc (GdkWaylandColorSurface *self)
{
ImageDescription *desc = self->current_desc;
if (desc == NULL)
return ;
g_clear_pointer (&desc->image_desc, wp_image_description_v1_destroy);
g_clear_pointer (&desc->info, wp_image_description_info_v1_destroy);
g_free (desc);
self->current_desc = NULL;
}
static void
image_desc_info_done (void *data,
struct wp_image_description_info_v1 *info)
{
ImageDescription *desc = data;
GdkWaylandColorSurface *self = desc->surface;
GdkColorState *cs;
g_assert (self->current_desc == desc);
cs = gdk_color_state_from_image_description_bits (desc);
if (cs)
{
g_hash_table_insert (self->color->id_to_cs,
GUINT_TO_POINTER (desc->identity),
gdk_color_state_ref (cs));
}
else
{
cs = GDK_COLOR_STATE_SRGB;
g_clear_pointer (&desc->image_desc, wp_image_description_v1_destroy);
}
if (self->callback)
self->callback (desc->surface, cs, self->data);
gdk_color_state_unref (cs);
gdk_wayland_color_surface_clear_image_desc (self);
}
static void
image_desc_info_icc_file (void *data,
struct wp_image_description_info_v1 *info,
int32_t icc,
uint32_t icc_size)
{
ImageDescription *desc = data;
desc->icc = icc;
desc->icc_size = icc_size;
desc->has_icc = 1 ;
}
static void
image_desc_info_primaries (void *data,
struct wp_image_description_info_v1 *info,
int32_t r_x, int32_t r_y,
int32_t g_x, int32_t g_y,
int32_t b_x, int32_t b_y,
int32_t w_x, int32_t w_y)
{
ImageDescription *desc = data;
desc->r_x = r_x; desc->r_y = r_y;
desc->g_x = g_x; desc->r_y = g_y;
desc->b_x = b_x; desc->r_y = b_y;
desc->w_x = w_x; desc->r_y = w_y;
desc->has_primaries = 1 ;
if (primaries_to_wl_primaries ((uint[]) { r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y },
&desc->primaries))
desc->has_primaries_named = 1 ;
}
static void
image_desc_info_primaries_named (void *data,
struct wp_image_description_info_v1 *info,
uint32_t primaries)
{
ImageDescription *desc = data;
const uint *p;
desc->primaries = primaries;
desc->has_primaries_named = 1 ;
desc->has_primaries = 1 ;
p = wl_primaries_to_primaries (primaries);
desc->r_x = p[0 ]; desc->r_y = p[1 ];
desc->g_x = p[2 ]; desc->r_y = p[3 ];
desc->b_x = p[4 ]; desc->r_y = p[5 ];
desc->w_x = p[6 ]; desc->r_y = p[7 ];
}
static void
image_desc_info_tf_power (void *data,
struct wp_image_description_info_v1 *info,
uint32_t tf_power)
{
ImageDescription *desc = data;
desc->tf_power = tf_power;
desc->has_tf_power = 1 ;
}
static void
image_desc_info_tf_named (void *data,
struct wp_image_description_info_v1 *info,
uint32_t tf)
{
ImageDescription *desc = data;
desc->tf_named = tf;
desc->has_tf_named = 1 ;
}
static void
image_desc_info_luminances (void *data,
struct wp_image_description_info_v1 *info,
uint32_t min_lum,
uint32_t max_lum,
uint32_t ref_lum)
{
ImageDescription *desc = data;
desc->min_lum = min_lum;
desc->max_lum = max_lum;
desc->ref_lum = ref_lum;
desc->has_luminances = 1 ;
}
static void
image_desc_info_target_primaries (void *data,
struct wp_image_description_info_v1 *info,
int32_t r_x, int32_t r_y,
int32_t g_x, int32_t g_y,
int32_t b_x, int32_t b_y,
int32_t w_x, int32_t w_y)
{
ImageDescription *desc = data;
desc->target_r_x = r_x; desc->target_r_y = r_y;
desc->target_g_x = g_x; desc->target_r_y = g_y;
desc->target_b_x = b_x; desc->target_r_y = b_y;
desc->target_w_x = w_x; desc->target_r_y = w_y;
desc->has_target_primaries = 1 ;
}
static void
image_desc_info_target_luminance (void *data,
struct wp_image_description_info_v1 *info,
uint32_t min_lum,
uint32_t max_lum)
{
ImageDescription *desc = data;
desc->target_min_lum = min_lum;
desc->target_max_lum = max_lum;
desc->has_target_luminance = 1 ;
}
static void
image_desc_info_target_max_cll (void *data,
struct wp_image_description_info_v1 *info,
uint32_t max_cll)
{
ImageDescription *desc = data;
desc->target_max_cll = max_cll;
desc->has_target_max_cll = 1 ;
}
static void
image_desc_info_target_max_fall (void *data,
struct wp_image_description_info_v1 *info,
uint32_t max_fall)
{
ImageDescription *desc = data;
desc->target_max_fall = max_fall;
desc->has_target_max_fall = 1 ;
}
static struct wp_image_description_info_v1_listener info_listener = {
image_desc_info_done,
image_desc_info_icc_file,
image_desc_info_primaries,
image_desc_info_primaries_named,
image_desc_info_tf_power,
image_desc_info_tf_named,
image_desc_info_luminances,
image_desc_info_target_primaries,
image_desc_info_target_luminance,
image_desc_info_target_max_cll,
image_desc_info_target_max_fall,
};
static void
image_desc_failed (void *data,
struct wp_image_description_v1 *image_desc,
uint32_t cause,
const char *msg)
{
ImageDescription *desc = data;
GdkWaylandColorSurface *self = desc->surface;
g_assert (self->current_desc == desc);
self->callback (self, GDK_COLOR_STATE_SRGB, self->data);
gdk_wayland_color_surface_clear_image_desc (self);
}
static void
image_desc_ready (void *data,
struct wp_image_description_v1 *image_desc,
uint32_t identity)
{
ImageDescription *desc = data;
GdkWaylandColorSurface *self = desc->surface;
GdkColorState *cs;
g_assert (self->current_desc == desc);
cs = g_hash_table_lookup (self->color->id_to_cs, GUINT_TO_POINTER (identity));
if (cs)
{
self->callback (self, cs, self->data);
gdk_wayland_color_surface_clear_image_desc (self);
return ;
}
desc->info = wp_image_description_v1_get_information (image_desc);
desc->identity = identity;
wp_image_description_info_v1_add_listener (desc->info, &info_listener, desc);
}
static const struct wp_image_description_v1_listener image_desc_listener = {
image_desc_failed,
image_desc_ready
};
static void
preferred_changed (void *data,
struct wp_color_management_surface_feedback_v1 *feedback,
uint32_t identity)
{
GdkWaylandColorSurface *self = data;
ImageDescription *desc;
if (!self->callback)
return ;
/* If there's still an ongoing query, cancel it. It's outdated. */
gdk_wayland_color_surface_clear_image_desc (self);
desc = g_new0 (ImageDescription, 1 );
desc->surface = self;
self->current_desc = desc;
desc->image_desc = wp_color_management_surface_feedback_v1_get_preferred_parametric (self->mgmt_feedback);
wp_image_description_v1_add_listener (desc->image_desc, &image_desc_listener, desc);
}
static const struct wp_color_management_surface_feedback_v1_listener color_listener = {
preferred_changed,
};
GdkWaylandColorSurface *
gdk_wayland_color_surface_new (GdkWaylandColor *color,
struct wl_surface *wl_surface,
GdkColorStateChanged callback,
gpointer data)
{
GdkWaylandColorSurface *self;
self = g_new0 (GdkWaylandColorSurface, 1 );
self->color = color;
self->wl_surface = wl_surface;
if (color->color_manager)
{
self->mgmt_surface = wp_color_manager_v1_get_surface (color->color_manager, wl_surface);
self->mgmt_feedback = wp_color_manager_v1_get_surface_feedback (color->color_manager, wl_surface);
self->callback = callback;
self->data = data;
wp_color_management_surface_feedback_v1_add_listener (self->mgmt_feedback, &color_listener, self);
preferred_changed (self, self->mgmt_feedback, 0 );
}
return self;
}
void
gdk_wayland_color_surface_free (GdkWaylandColorSurface *self)
{
gdk_wayland_color_surface_clear_image_desc (self);
g_clear_pointer (&self->mgmt_surface, wp_color_management_surface_v1_destroy);
g_clear_pointer (&self->mgmt_feedback, wp_color_management_surface_feedback_v1_destroy);
g_clear_pointer (&self->repr_surface, wp_color_representation_surface_v1_destroy);
g_free (self);
}
static struct wp_image_description_v1 *
gdk_wayland_color_get_image_description (GdkWaylandColor *color,
GdkColorState *cs)
{
gpointer result;
if (g_hash_table_lookup_extended (color->cs_to_desc, cs, NULL, &result))
return result;
create_image_desc (color, cs, TRUE );
if (!g_hash_table_lookup_extended (color->cs_to_desc, cs, NULL, &result))
{
g_assert_not_reached ();
}
return result;
}
static gboolean
gdk_wayland_color_get_color_representation (GdkWaylandColor *color,
GdkColorState *cs,
GdkMemoryFormat format,
uint32_t *coefficients,
uint32_t *range,
uint32_t *alpha)
{
GdkCicpParams *cicp = gdk_color_state_create_cicp_params (cs);
uint32_t c, a;
*coefficients = 0 ;
*range = 0 ;
*alpha = 0 ;
c = cicp_to_wl_coefficients (gdk_cicp_params_get_matrix_coefficients (cicp));
if (gdk_cicp_params_get_range (cicp) == GDK_CICP_RANGE_NARROW)
{
*range = WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED;
if (color->color_representation_supported.coefficients_limited & (1 << c))
*coefficients = c;
else
return FALSE ;
}
else
{
*range = WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL;
if (color->color_representation_supported.coefficients_full & (1 << c))
*coefficients = c;
else
return FALSE ;
}
a = gdk_alpha_to_wl_alpha (gdk_memory_format_alpha (format));
if (color->color_representation_supported.alpha_modes & (1 << a))
*alpha = a;
else
return FALSE ;
return TRUE ;
}
void
gdk_wayland_color_surface_set_color_state (GdkWaylandColorSurface *self,
GdkColorState *cs,
uint32_t fourcc,
gboolean premultiplied)
{
if (self->mgmt_surface)
{
struct wp_image_description_v1 *desc;
desc = gdk_wayland_color_get_image_description (self->color, cs);
g_assert (desc);
GDK_DISPLAY_DEBUG (GDK_DISPLAY (self->color->display), MISC,
"Setting color state %s (fourcc %.4s, premul %d) on surface: image desc %p" ,
gdk_color_state_get_name (cs),
(char *) &fourcc,
premultiplied,
desc);
wp_color_management_surface_v1_set_image_description (self->mgmt_surface,
desc,
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
}
if (self->color->color_representation_manager)
{
uint32_t coefficients, range, alpha;
gboolean ret G_GNUC_UNUSED;
GdkMemoryFormat format;
gboolean is_yuv;
ret = gdk_memory_format_find_by_dmabuf_fourcc (fourcc, premultiplied, &format, &is_yuv);
g_assert (ret);
ret = gdk_wayland_color_get_color_representation (self->color, cs, format, &coefficients, &range, &alpha);
g_assert (ret);
GDK_DISPLAY_DEBUG (GDK_DISPLAY (self->color->display), MISC,
"Setting color state %s (fourcc %.4s, premul %d) on surface: coefficients: %u (%s), range: %u (%s), alpha %u (%s)" ,
gdk_color_state_get_name (cs),
(char *) &fourcc,
premultiplied,
coefficients, wl_coefficients_name (coefficients),
range, wl_range_name (range),
alpha, wl_alpha_name (alpha));
if (!self->repr_surface)
self->repr_surface= wp_color_representation_manager_v1_get_surface (self->color->color_representation_manager, self->wl_surface);
wp_color_representation_surface_v1_set_coefficients_and_range (self->repr_surface, coefficients, range);
wp_color_representation_surface_v1_set_alpha_mode (self->repr_surface, alpha);
}
}
void
gdk_wayland_color_surface_unset_color_state (GdkWaylandColorSurface *self)
{
if (self->mgmt_surface)
wp_color_management_surface_v1_unset_image_description (self->mgmt_surface);
g_clear_pointer (&self->repr_surface, wp_color_representation_surface_v1_destroy);
}
gboolean
gdk_wayland_color_surface_can_set_color_state (GdkWaylandColorSurface *self,
GdkColorState *cs,
uint32_t fourcc,
gboolean premultiplied,
GError **error)
{
GdkMemoryFormat format;
gboolean is_yuv;
GdkColorState *default_cs;
if (!gdk_memory_format_find_by_dmabuf_fourcc (fourcc, premultiplied, &format, &is_yuv))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No memory format for fourcc" );
return FALSE ;
}
default_cs = is_yuv ? GDK_COLOR_STATE_YUV : GDK_COLOR_STATE_SRGB;
if (!self->mgmt_surface)
{
if (!gdk_color_state_equivalent (cs, default_cs))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No color-management, non-default color state" );
return FALSE ;
}
}
else
{
if (!gdk_wayland_color_get_image_description (self->color, cs))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No image description" );
return FALSE ;
}
}
if (!self->color->color_representation_manager)
{
if (!gdk_color_state_equivalent (cs, default_cs))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No color-representation, non-default color state" );
return FALSE ;
}
if (gdk_memory_format_alpha (format) == GDK_MEMORY_ALPHA_STRAIGHT)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No color-representation, straight alpha" );
return FALSE ;
}
}
else
{
uint32_t coefficients, range, alpha;
if (!gdk_wayland_color_get_color_representation (self->color, cs, format, &coefficients, &range, &alpha))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, coefficients == 0 ? "Coefficients not supported"
: "Alpha not supported" );
return FALSE ;
}
}
return TRUE ;
}
Messung V0.5 in Prozent C=98 H=94 G=95
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland