From ee19ad54d617ad9753197aabac109b0ddd21ebdf Mon Sep 17 00:00:00 2001 From: Jehan Date: Sat, 9 Dec 2023 17:03:38 +0900 Subject: [PATCH] app, libgimpcolor: 2 new libgimpcolor functions. Adding gimp_color_is_out_of_gamut() and gimp_color_is_out_of_self_gamut() and using them where relevant. --- app/widgets/gimpcolorhistory.c | 29 +++--- app/widgets/gimpfgbgeditor.c | 45 +-------- libgimpcolor/gimpcolor.c | 178 +++++++++++++++++++++++++++++++++ libgimpcolor/gimpcolor.def | 2 + libgimpcolor/gimpcolor.h | 3 + libgimpwidgets/gimpcolorarea.c | 72 +------------ 6 files changed, 197 insertions(+), 132 deletions(-) diff --git a/app/widgets/gimpcolorhistory.c b/app/widgets/gimpcolorhistory.c index 09e4d2ae97..7dbef8c6d0 100644 --- a/app/widgets/gimpcolorhistory.c +++ b/app/widgets/gimpcolorhistory.c @@ -24,6 +24,7 @@ #include #include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" #include "libgimpwidgets/gimpwidgets.h" #include "widgets-types.h" @@ -504,24 +505,18 @@ gimp_color_history_palette_dirty (GimpColorHistory *history) /* Now that palette colors can be any model and any space, just looking at * whether they are out of [0; 1] range is not enough (a same color could * be in or out range depending on the color space it is stored as). - * I guess that what we are really looking for is whether a color is - * out-of-gamut for the specifically active image. - * TODO + * What we are really looking for is: + * 1. Whether they are out of the palette (indexed image case); + * 2. Whether they are out of the active image's space + * (independently of their own space). */ -#if 0 - if (/* Common out-of-gamut case */ - (rgb.r < 0.0 || rgb.r > 1.0 || - rgb.g < 0.0 || rgb.g > 1.0 || - rgb.b < 0.0 || rgb.b > 1.0) || - /* Indexed images */ - (colormap_palette && ! gimp_palette_find_entry (colormap_palette, color, NULL)) || - /* Grayscale images */ - (base_type == GIMP_GRAY && - (ABS (rgb.r - rgb.g) > CHANNEL_EPSILON || - ABS (rgb.r - rgb.b) > CHANNEL_EPSILON || - ABS (rgb.g - rgb.b) > CHANNEL_EPSILON))) - oog = TRUE; -#endif + if (colormap_palette) + oog = (! gimp_palette_find_entry (colormap_palette, color, NULL)); + else if (history->active_image) + oog = gimp_color_is_out_of_gamut (color, gimp_image_get_layer_space (history->active_image)); + else + oog = gimp_color_is_out_of_self_gamut (color); + gimp_color_area_set_out_of_gamut (GIMP_COLOR_AREA (history->color_areas[i]), oog); g_signal_handlers_unblock_by_func (history->color_areas[i], diff --git a/app/widgets/gimpfgbgeditor.c b/app/widgets/gimpfgbgeditor.c index 3eea981b66..43d54b79df 100644 --- a/app/widgets/gimpfgbgeditor.c +++ b/app/widgets/gimpfgbgeditor.c @@ -933,50 +933,7 @@ gimp_fg_bg_editor_draw_color_frame (GimpFgBgEditor *editor, { const Babl *target_space = gimp_image_get_layer_space (editor->active_image); - if (base_type == GIMP_GRAY) - { - gfloat gray[1]; - - gegl_color_get_pixel (color, - babl_format_with_space ("Y' float", target_space), - gray); - is_out_of_gamut = ((gray[0] < 0.0 && -gray[0] > CHANNEL_EPSILON) || - (gray[0] > 1.0 && gray[0] - 1.0 > CHANNEL_EPSILON)); - - if (! is_out_of_gamut) - { - gdouble rgb[3]; - - /* Grayscale colors can be out of gamut if the color is out of the [0; - * 1] range in the target space and also if they can be converted to - * RGB with non-equal components. - */ - gegl_color_get_pixel (color, - babl_format_with_space ("R'G'B' double", target_space), - rgb); - is_out_of_gamut = (ABS (rgb[0] - rgb[0]) > CHANNEL_EPSILON || - ABS (rgb[1] - rgb[1]) > CHANNEL_EPSILON || - ABS (rgb[2] - rgb[2]) > CHANNEL_EPSILON); - } - } - else - { - gdouble rgb[3]; - - gegl_color_get_pixel (color, - babl_format_with_space ("R'G'B' double", target_space), - rgb); - /* We make sure that each component is within [0; 1], but accept a small - * error of margin (we don't want to show small precision errors as - * out-of-gamut colors). - */ - is_out_of_gamut = ((rgb[0] < 0.0 && -rgb[0] > CHANNEL_EPSILON) || - (rgb[0] > 1.0 && rgb[0] - 1.0 > CHANNEL_EPSILON) || - (rgb[1] < 0.0 && -rgb[1] > CHANNEL_EPSILON) || - (rgb[1] > 1.0 && rgb[1] - 1.0 > CHANNEL_EPSILON) || - (rgb[2] < 0.0 && -rgb[2] > CHANNEL_EPSILON) || - (rgb[2] > 1.0 && rgb[2] - 1.0 > CHANNEL_EPSILON)); - } + is_out_of_gamut = gimp_color_is_out_of_gamut (color, target_space); } else { diff --git a/libgimpcolor/gimpcolor.c b/libgimpcolor/gimpcolor.c index 5d8b5be2ca..d3e6399dc2 100644 --- a/libgimpcolor/gimpcolor.c +++ b/libgimpcolor/gimpcolor.c @@ -144,6 +144,184 @@ gimp_color_is_perceptually_identical (GeglColor *color1, #undef SQR } +/** + * gimp_color_is_out_of_self_gamut: + * @color: a [class@Gegl.Color] + * + * Determine whether @color is out of its own space gamut. This can only + * happen if the color space is unbounded and any of the color component + * is out of the `[0; 1]` range. + * A small error of margin is accepted, so that for instance a component + * at -0.0000001 is not making the whole color to be considered as + * out-of-gamut while it may just be computation imprecision. + * + * Returns: whether the color is out of its own color space gamut. + * + * Since: 3.0 + **/ +gboolean +gimp_color_is_out_of_self_gamut (GeglColor *color) +{ + const Babl *format; + const Babl *space; + const Babl *ctype; + gboolean oog = FALSE; + + format = gegl_color_get_format (color); + space = babl_format_get_space (format); + /* XXX assuming that all components have the same type. */ + ctype = babl_format_get_type (format, 0); + + if (ctype == babl_type ("half") || + ctype == babl_type ("float") || + ctype == babl_type ("double")) + { + /* Only unbounded colors can be out-of-gamut. */ + const Babl *model; + + model = babl_format_get_model (format); + +#define CHANNEL_EPSILON 1e-3 + if (model == babl_model ("R'G'B'") || + model == babl_model ("R~G~B~") || + model == babl_model ("RGB") || + model == babl_model ("R'G'B'A") || + model == babl_model ("R~G~B~A") || + model == babl_model ("RGBA")) + { + gdouble rgb[3]; + + gegl_color_get_pixel (color, babl_format_with_space ("RGB double", space), rgb); + + oog = ((rgb[0] < 0.0 && -rgb[0] > CHANNEL_EPSILON) || + (rgb[0] > 1.0 && rgb[0] - 1.0 > CHANNEL_EPSILON) || + (rgb[1] < 0.0 && -rgb[1] > CHANNEL_EPSILON) || + (rgb[1] > 1.0 && rgb[1] - 1.0 > CHANNEL_EPSILON) || + (rgb[2] < 0.0 && -rgb[2] > CHANNEL_EPSILON) || + (rgb[2] > 1.0 && rgb[2] - 1.0 > CHANNEL_EPSILON)); + } + else if (model == babl_model ("Y'") || + model == babl_model ("Y~") || + model == babl_model ("Y") || + model == babl_model ("Y'A") || + model == babl_model ("Y~A") || + model == babl_model ("YA")) + { + gdouble gray[1]; + + gegl_color_get_pixel (color, babl_format_with_space ("Y double", space), gray); + oog = ((gray[0] < 0.0 && -gray[0] > CHANNEL_EPSILON) || + (gray[0] > 1.0 && gray[0] - 1.0 > CHANNEL_EPSILON)); + } + else if (model == babl_model ("CMYK") || + model == babl_model ("CMYKA") || + model == babl_model ("cmyk") || + model == babl_model ("cmykA")) + { + gdouble cmyk[4]; + + gegl_color_get_pixel (color, babl_format_with_space ("CMYK double", space), cmyk); + oog = ((cmyk[0] < 0.0 && -cmyk[0] > CHANNEL_EPSILON) || + (cmyk[0] > 1.0 && cmyk[0] - 1.0 > CHANNEL_EPSILON) || + (cmyk[1] < 0.0 && -cmyk[1] > CHANNEL_EPSILON) || + (cmyk[1] > 1.0 && cmyk[1] - 1.0 > CHANNEL_EPSILON) || + (cmyk[2] < 0.0 && -cmyk[2] > CHANNEL_EPSILON) || + (cmyk[2] > 1.0 && cmyk[2] - 1.0 > CHANNEL_EPSILON) || + (cmyk[3] < 0.0 && -cmyk[3] > CHANNEL_EPSILON) || + (cmyk[3] > 1.0 && cmyk[3] - 1.0 > CHANNEL_EPSILON)); + } +#undef CHANNEL_EPSILON + } + + return oog; +} + +/** + * gimp_color_is_out_of_gamut: + * @color: a [class@Gegl.Color] + * @space: a color space to convert @color to. + * + * Determine whether @color is out of its @space gamut. + * A small error of margin is accepted, so that for instance a component + * at -0.0000001 is not making the whole color to be considered as + * out-of-gamut while it may just be computation imprecision. + * + * Returns: whether the color is out of @space gamut. + * + * Since: 3.0 + **/ +gboolean +gimp_color_is_out_of_gamut (GeglColor *color, + const Babl *space) +{ + gboolean is_out_of_gamut = FALSE; + +#define CHANNEL_EPSILON 1e-3 + if (babl_space_is_gray (space)) + { + gfloat gray[1]; + + gegl_color_get_pixel (color, + babl_format_with_space ("Y' float", space), + gray); + is_out_of_gamut = ((gray[0] < 0.0 && -gray[0] > CHANNEL_EPSILON) || + (gray[0] > 1.0 && gray[0] - 1.0 > CHANNEL_EPSILON)); + + if (! is_out_of_gamut) + { + gdouble rgb[3]; + + /* Grayscale colors can be out of gamut if the color is out of the [0; + * 1] range in the target space and also if they can be converted to + * RGB with non-equal components. + */ + gegl_color_get_pixel (color, + babl_format_with_space ("R'G'B' double", space), + rgb); + is_out_of_gamut = (ABS (rgb[0] - rgb[0]) > CHANNEL_EPSILON || + ABS (rgb[1] - rgb[1]) > CHANNEL_EPSILON || + ABS (rgb[2] - rgb[2]) > CHANNEL_EPSILON); + } + } + else if (babl_space_is_cmyk (space)) + { + gdouble cmyk[4]; + + gegl_color_get_pixel (color, + babl_format_with_space ("CMYK double", space), + cmyk); + /* We make sure that each component is within [0; 1], but accept a small + * error of margin (we don't want to show small precision errors as + * out-of-gamut colors). + */ + is_out_of_gamut = ((cmyk[0] < 0.0 && -cmyk[0] > CHANNEL_EPSILON) || + (cmyk[0] > 1.0 && cmyk[0] - 1.0 > CHANNEL_EPSILON) || + (cmyk[1] < 0.0 && -cmyk[1] > CHANNEL_EPSILON) || + (cmyk[1] > 1.0 && cmyk[1] - 1.0 > CHANNEL_EPSILON) || + (cmyk[2] < 0.0 && -cmyk[2] > CHANNEL_EPSILON) || + (cmyk[2] > 1.0 && cmyk[2] - 1.0 > CHANNEL_EPSILON) || + (cmyk[3] < 0.0 && -cmyk[3] > CHANNEL_EPSILON) || + (cmyk[3] > 1.0 && cmyk[3] - 1.0 > CHANNEL_EPSILON)); + } + else + { + gdouble rgb[3]; + + gegl_color_get_pixel (color, + babl_format_with_space ("R'G'B' double", space), + rgb); + is_out_of_gamut = ((rgb[0] < 0.0 && -rgb[0] > CHANNEL_EPSILON) || + (rgb[0] > 1.0 && rgb[0] - 1.0 > CHANNEL_EPSILON) || + (rgb[1] < 0.0 && -rgb[1] > CHANNEL_EPSILON) || + (rgb[1] > 1.0 && rgb[1] - 1.0 > CHANNEL_EPSILON) || + (rgb[2] < 0.0 && -rgb[2] > CHANNEL_EPSILON) || + (rgb[2] > 1.0 && rgb[2] - 1.0 > CHANNEL_EPSILON)); + } +#undef CHANNEL_EPSILON + + return is_out_of_gamut; +} + /* Private functions. */ diff --git a/libgimpcolor/gimpcolor.def b/libgimpcolor/gimpcolor.def index e3a2f54c7b..f7a076ef2a 100644 --- a/libgimpcolor/gimpcolor.def +++ b/libgimpcolor/gimpcolor.def @@ -19,6 +19,8 @@ EXPORTS gimp_cmyka_get_uchar gimp_cmyka_set gimp_cmyka_set_uchar + gimp_color_is_out_of_gamut + gimp_color_is_out_of_self_gamut gimp_color_is_perceptually_identical gimp_color_managed_get_color_profile gimp_color_managed_get_icc_profile diff --git a/libgimpcolor/gimpcolor.h b/libgimpcolor/gimpcolor.h index c0a52ad8de..6280104d05 100644 --- a/libgimpcolor/gimpcolor.h +++ b/libgimpcolor/gimpcolor.h @@ -56,6 +56,9 @@ gboolean gimp_color_is_perceptually_identical (GeglColor *color1, GeglColor * gimp_color_parse_css (const gchar *css, gint len); +gboolean gimp_color_is_out_of_self_gamut (GeglColor *color); +gboolean gimp_color_is_out_of_gamut (GeglColor *color, + const Babl *space); G_END_DECLS diff --git a/libgimpwidgets/gimpcolorarea.c b/libgimpwidgets/gimpcolorarea.c index 98eee9cf67..4608e9bb81 100644 --- a/libgimpwidgets/gimpcolorarea.c +++ b/libgimpwidgets/gimpcolorarea.c @@ -461,77 +461,7 @@ gimp_color_area_draw (GtkWidget *widget, } if (priv->config && ! oog) - { - const Babl *format; - const Babl *space; - const Babl *ctype; - - format = gegl_color_get_format (priv->color); - space = babl_format_get_space (format); - /* XXX assuming that all components have the same type. */ - ctype = babl_format_get_type (format, 0); - - if (ctype == babl_type ("half") || - ctype == babl_type ("float") || - ctype == babl_type ("double")) - { - /* Only unbounded colors can be out-of-gamut. */ - const Babl *model; - - model = babl_format_get_model (format); - -#define CHANNEL_EPSILON 1e-3 - if (model == babl_model ("R'G'B'") || - model == babl_model ("R~G~B~") || - model == babl_model ("RGB") || - model == babl_model ("R'G'B'A") || - model == babl_model ("R~G~B~A") || - model == babl_model ("RGBA")) - { - gdouble rgb[3]; - - gegl_color_get_pixel (priv->color, babl_format_with_space ("RGB double", space), rgb); - - oog = ((rgb[0] < 0.0 && -rgb[0] > CHANNEL_EPSILON) || - (rgb[0] > 1.0 && rgb[0] - 1.0 > CHANNEL_EPSILON) || - (rgb[1] < 0.0 && -rgb[1] > CHANNEL_EPSILON) || - (rgb[1] > 1.0 && rgb[1] - 1.0 > CHANNEL_EPSILON) || - (rgb[2] < 0.0 && -rgb[2] > CHANNEL_EPSILON) || - (rgb[2] > 1.0 && rgb[2] - 1.0 > CHANNEL_EPSILON)); - } - else if (model == babl_model ("Y'") || - model == babl_model ("Y~") || - model == babl_model ("Y") || - model == babl_model ("Y'A") || - model == babl_model ("Y~A") || - model == babl_model ("YA")) - { - gdouble gray[1]; - - gegl_color_get_pixel (priv->color, babl_format_with_space ("Y double", space), gray); - oog = ((gray[0] < 0.0 && -gray[0] > CHANNEL_EPSILON) || - (gray[0] > 1.0 && gray[0] - 1.0 > CHANNEL_EPSILON)); - } - else if (model == babl_model ("CMYK") || - model == babl_model ("CMYKA") || - model == babl_model ("cmyk") || - model == babl_model ("cmykA")) - { - gdouble cmyk[4]; - - gegl_color_get_pixel (priv->color, babl_format_with_space ("CMYK double", space), cmyk); - oog = ((cmyk[0] < 0.0 && -cmyk[0] > CHANNEL_EPSILON) || - (cmyk[0] > 1.0 && cmyk[0] - 1.0 > CHANNEL_EPSILON) || - (cmyk[1] < 0.0 && -cmyk[1] > CHANNEL_EPSILON) || - (cmyk[1] > 1.0 && cmyk[1] - 1.0 > CHANNEL_EPSILON) || - (cmyk[2] < 0.0 && -cmyk[2] > CHANNEL_EPSILON) || - (cmyk[2] > 1.0 && cmyk[2] - 1.0 > CHANNEL_EPSILON) || - (cmyk[3] < 0.0 && -cmyk[3] > CHANNEL_EPSILON) || - (cmyk[3] > 1.0 && cmyk[3] - 1.0 > CHANNEL_EPSILON)); - } -#undef CHANNEL_EPSILON - } - } + oog = gimp_color_is_out_of_self_gamut (priv->color); if (priv->config && oog) {