ScriptFu: fix #10978 handling of color

Revise handling of GeglColor, formerly GimpRGB.

Marshall to/from Scheme lists of numeric.
Marshall from string CSS names and notations.

Conversion to/from GRAY, GRAYA, RGB, RGBA.

See color.scm test script.
This commit is contained in:
lloyd konneker 2024-10-17 12:12:54 -04:00 committed by Lloyd Konneker
parent e92212d02a
commit 6f84a76e7a
2 changed files with 218 additions and 82 deletions

View File

@ -260,9 +260,65 @@ get_item_from_ID_in_script (scheme *sc,
return NULL; /* no error */
}
/* Caller owns the returned GeglColor.
/* Walk a Scheme list of numerics, clamping into a C array of bytes.
* Expects one to four numerics.
* Returns length of list, in range [0,4]
* Length zero denotes an error: not a numeric, or list is empty.
* A list longer than expected (>4) is not an error, but extra list is not used.
*/
static void
marshal_list_of_numeric_to_rgba (scheme *sc,
pointer color_list,
guchar (*rgba)[4], /* OUT */
guint *length) /* OUT */
{
*length = 0;
for (guint i=0; i<4; i++)
{
if (sc->vptr->is_number (sc->vptr->pair_car (color_list)))
{
(*rgba)[i] = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)),
0, 255);
*length = *length + 1;
color_list = sc->vptr->pair_cdr (color_list);
}
else
{
/* Reached end of list or non-numeric. */
return;
}
}
/* *length is in [0,4] and *rgba is filled in with same count. */
}
/* Walk a C array of bytes (rgba) creating Scheme list of numeric.
* Returns list whose length is in range [0,4].
* The list is memory managed by scheme.
*/
static pointer
marshal_rgba_to_list_of_numeric (scheme *sc,
guchar rgba[4],
guint length)
{
pointer result_list = sc->NIL;
/* Walk rgba in reverse. */
for (gint i=length-1; i>=0; i--)
{
/* Prepend to list, returning new list. */
result_list = sc->vptr->cons (sc,
sc->vptr->mk_integer (sc, rgba[i]),
result_list);
}
/* list is numerics from rgba and length (list) == length. */
return result_list;
}
/* Returns a GeglColor from a scheme list.
* Caller owns the returned GeglColor.
* Returns NULL on failure:
* - list wrong length
* - list wrong length, not >1
* - list elements not numbers.
*/
GeglColor *
@ -270,73 +326,109 @@ marshal_component_list_to_color (scheme *sc,
pointer color_list)
{
GeglColor *color_result;
guchar r = 0, g = 0, b = 0;
guchar rgba[4];
guint list_length;
/* FIXME dispatch on list length and create different format colors */
if (sc->vptr->list_length (sc, color_list) != 3)
return NULL;
if (sc->vptr->is_number (sc->vptr->pair_car (color_list)))
r = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)),
0, 255);
marshal_list_of_numeric_to_rgba (sc, color_list, &rgba, &list_length);
/* list_length is the count of numerics used. */
/* Dispatch on list length and create different format colors */
if (list_length == 3 || list_length == 4)
{
/* RGBA */
if (list_length == 3)
{
/* gegl has no three byte rgb. * ScriptFu sets alpha to 1.0 */
rgba[3] = 255;
}
/* Assert rgba is full */
color_result = gegl_color_new ("black");
gegl_color_set_rgba_with_space (color_result,
(gdouble) rgba[0] / 255.0,
(gdouble) rgba[1] / 255.0,
(gdouble) rgba[2] / 255.0,
(gdouble) rgba[3] / 255.0,
NULL); /* NULL defaults to sRGB */
}
else if (list_length == 1)
{
/* GRAY */
GBytes *bytes = g_bytes_new (rgba, 1);
color_result = gegl_color_new ("black");
gegl_color_set_bytes (color_result, babl_format ("Y' u8"), bytes);
g_free (bytes);
}
else if (list_length == 2)
{
/* GRAYA */
GBytes *bytes = g_bytes_new (rgba, 2);
color_result = gegl_color_new ("black");
gegl_color_set_bytes (color_result, babl_format ("Y'A u8"), bytes);
g_free (bytes);
}
else
return NULL;
{
color_result = NULL;
}
color_list = sc->vptr->pair_cdr (color_list);
if (sc->vptr->is_number (sc->vptr->pair_car (color_list)))
g = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)),
0, 255);
else
return NULL;
color_list = sc->vptr->pair_cdr (color_list);
if (sc->vptr->is_number (sc->vptr->pair_car (color_list)))
b = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)),
0, 255);
else
return NULL;
color_result = gegl_color_new ("black");
gegl_color_set_rgba_with_space (color_result,
(gdouble) r / 255.0,
(gdouble) g / 255.0,
(gdouble) b / 255.0,
1.0, NULL);
return color_result;
}
/* Returns (0 0 0) if color is NULL. */
/* FIXME this should return a list
/* Returns a Scheme list of integers or empty list,
* the same length as the count of components in the color.
* E.G. gimp-drawable-get-pixel may return indexed, rgb, or rgba.
* List is length:
* 1 GRAY
* 2 GRAYA
* 3 RGB
* 4 RGBA
* Returns NIL when color is NULL.
*/
pointer
marshal_color_to_component_list (scheme *sc,
GeglColor *color)
{
guchar rgb[3] = { 0 };
pointer result;
guint count_components;
guchar rgba[4] = { 0 };
/* Warn when color has different count of components than
* the 3 of the pixel we are converting to.
*/
if (babl_format_get_n_components (gegl_color_get_format (color)) != 3)
if (color == NULL)
{
g_warning ("%s converting to pixel with loss/gain of components", G_STRFUNC);
result = sc->NIL;
}
else
{
count_components = babl_format_get_n_components (gegl_color_get_format (color));
switch (count_components)
{
case 3:
gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgba);
result = marshal_rgba_to_list_of_numeric (sc, rgba, 3);
break;
case 4:
gegl_color_get_pixel (color, babl_format ("R'G'B'A u8"), rgba);
result = marshal_rgba_to_list_of_numeric (sc, rgba, 4);
break;
case 1:
/* Grayscale with TRC from space */
gegl_color_get_pixel (color, babl_format ("Y' u8"), rgba);
result = marshal_rgba_to_list_of_numeric (sc, rgba, 1);
break;
case 2:
/* Grayscale with TRC from space, separate alpha. */
gegl_color_get_pixel (color, babl_format ("Y'A u8"), rgba);
result = marshal_rgba_to_list_of_numeric (sc, rgba, 2);
break;
default:
g_warning ("%s unhandled count of color components: %d", G_STRFUNC, count_components);
result = sc->NIL;
}
}
if (color)
gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgb);
/* else will return (0 0 0) */
return sc->vptr->cons (
sc,
sc->vptr->mk_integer (sc, rgb[0]),
sc->vptr->cons (sc,
sc->vptr->mk_integer (sc, rgb[1]),
sc->vptr->cons (sc,
sc->vptr->mk_integer (sc, rgb[2]),
sc->NIL)));
return result;
}
/* ColorArray */

View File

@ -37,10 +37,11 @@
* and default values, only the declarations of defaults etc.
* Since a GimpProcedureConfig carries the values
* and GParamSpec carries the defaults.
* - ScriptFu might not support RGB triplet repr
* - ScriptFu might support other formats
*
* Complex:
* PDB and widgets traffic in GeglColor but SF dumbs it down to a Scheme list (r g b)
* PDB and widgets traffic in GeglColor but SF dumbs it down to a Scheme list
* e.g. (r g b) (r g b a) or (y) or (y a)
*
* More SF code deals with GeglColor:
* see scheme_marshall.c we marshall from GeglColor to/from Scheme lists of numbers.
@ -49,19 +50,17 @@
/* Return the Scheme representation.
* Caller owns returned string.
*
* An alias: knows SFColorType is GeglColor
*/
gchar*
sf_color_get_repr (SFColorType arg_value)
{
guchar rgb[3] = { 0 };
if (arg_value)
gegl_color_get_pixel (arg_value, babl_format ("R'G'B' u8"), rgb);
return g_strdup_printf ("'(%d %d %d)", (gint) rgb[0], (gint) rgb[1], (gint) rgb[2]);
return sf_color_get_repr_from_gegl_color (arg_value);
}
/* Returns GeglColor from SFColorType.
/* Returns GeglColor from arg of type SFColorType.
* When arg is NULL, returns GeglColor transparent.
*
* Returned GeglColor is owned by caller.
*/
@ -78,19 +77,25 @@ void
sf_color_set_from_gegl_color (SFColorType *arg_value,
GeglColor *color)
{
const Babl *format = gegl_color_get_format (color);
guint8 pixel[48];
gegl_color_get_pixel (color, format, pixel);
if (*arg_value)
gegl_color_set_pixel (*arg_value, format, pixel);
{
/* Arg is already a GeglColor, change its color. */
const Babl *format = gegl_color_get_format (color);
guint8 pixel[48];
gegl_color_get_pixel (color, format, pixel);
gegl_color_set_pixel (*arg_value, format, pixel);
}
else
*arg_value = gegl_color_duplicate (color);
{
/* Arg is NULL. Duplicate given color. */
*arg_value = gegl_color_duplicate (color);
}
}
/* Set the default for an arg of type SFColorType from a string name.
*
* Keep default value to put in default of param spec when creating procedure.
* The default value is later put in default of param spec when creating procedure.
*
* Also, the old-style dialog resets from the kept default value.
* Versus new-style dialog, using ProcedureConfig, which resets from a paramspec.
@ -115,12 +120,20 @@ sf_color_arg_set_default_by_name (SFArg *arg,
}
else
{
/* ScriptFu does not let an author specify RGBA, only RGB. */
gimp_color_set_alpha (color, 1.0);
/* css named colors are in sRGB.
* You cannot name a grayscale color.
* However, "transparent" is also a css named color.
*/
/* We don't override alpha:
* Not calling: gimp_color_set_alpha (color, 1.0);
*/
/* Copying a struct that is not allocated, not setting a pointer. */
g_clear_object (&arg->default_value.sfa_color);
arg->default_value.sfa_color = color;
/* Expect the default is NULL, but call the setter anyway,
* which sets it when not already NULL.
*/
sf_color_set_from_gegl_color (&arg->default_value.sfa_color, color);
g_object_unref (color);
}
return result;
}
@ -146,7 +159,7 @@ GeglColor*
sf_color_arg_get_default_color (SFArg *arg)
{
/* require the default was set earlier.
* No easy way to assert it was set,
* No easy way to assert it was set.
*/
return sf_color_get_gegl_color (arg->default_value.sfa_color);
}
@ -154,22 +167,53 @@ sf_color_arg_get_default_color (SFArg *arg)
/* Methods for conversion GeglColor to/from Scheme representation. */
/* Caller owns returned string.*/
/* Convert GeglColor to scheme representation as list of numeric.
* List is length in [0,4].
* Caller owns returned string.
* Length depends on format: GRAY, GRAYA, RGB, or RGBA.
* Returns literal for empty list when color is NULL.
*/
gchar*
sf_color_get_repr_from_gegl_color (GeglColor *color)
{
guchar rgb[3] = { 0 };
gchar *result;
/* Warn when color has different count of components than
* the 3 of the pixel we are converting to.
*/
if (babl_format_get_n_components (gegl_color_get_format (color)) != 3)
if (color == NULL)
{
g_warning ("%s converting to pixel with loss/gain of components", G_STRFUNC);
result = g_strdup_printf ("'()");
}
else
{
guint count_components = babl_format_get_n_components (gegl_color_get_format (color));
guchar rgba[4] = { 0 };
/* Dispatch on count of components. */
switch (count_components)
{
case 1:
gegl_color_get_pixel (color, babl_format ("Y' u8"), rgba);
result = g_strdup_printf ("'(%d)", (gint) rgba[0]);
break;
case 2:
gegl_color_get_pixel (color, babl_format ("Y'A u8"), rgba);
result = g_strdup_printf ("'(%d %d)", (gint) rgba[0], (gint) rgba[1]);
break;
case 3:
gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgba);
result = g_strdup_printf ("'(%d %d %d)", (gint) rgba[0], (gint) rgba[1], (gint) rgba[2]);
break;
case 4:
gegl_color_get_pixel (color, babl_format ("R'G'B'A u8"), rgba);
result = g_strdup_printf ("'(%d %d %d %d)",
(gint) rgba[0], (gint) rgba[1], (gint) rgba[2], (gint) rgba[3]);
break;
default:
g_warning ("%s unhandled count of color components", G_STRFUNC);
result = g_strdup_printf ("'()");
}
}
gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgb);
return g_strdup_printf ("'(%d %d %d)", (gint) rgb[0], (gint) rgb[1], (gint) rgb[2]);
return result;
}
/* Caller owns the returned GeglColor.