/*
 *  $Id: rgba.c 29478 2026-02-14 13:56:53Z yeti-dn $
 *  Copyright (C) 2004-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/serializable-boxed.h"
#include "libgwyddion/serializable-utils.h"
#include "libgwyddion/rgba.h"

#include "libgwyddion/internal.h"

#define TYPE_NAME "GwyRGBA"

enum {
    ITEM_R,
    ITEM_G,
    ITEM_B,
    ITEM_A,
    NUM_ITEMS,
};

static void     itemize             (gpointer boxed,
                                     GwySerializableGroup *group);
static gpointer construct           (GwySerializableGroup *group,
                                     GwyErrorList **error_list);
static void     compute_color_quarks(const gchar *prefix,
                                     GQuark quarks[4]);

static GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "r", .ctype = GWY_SERIALIZABLE_DOUBLE, },
    { .name = "g", .ctype = GWY_SERIALIZABLE_DOUBLE, },
    { .name = "b", .ctype = GWY_SERIALIZABLE_DOUBLE, },
    { .name = "a", .ctype = GWY_SERIALIZABLE_DOUBLE, },
};

static void
add_pspecs(void)
{
    serializable_items[ITEM_R].aux.pspec = g_param_spec_double(serializable_items[ITEM_R].name, NULL, NULL,
                                                               0.0, 1.0, 0.0, G_PARAM_STATIC_STRINGS);
    serializable_items[ITEM_G].aux.pspec = g_param_spec_double(serializable_items[ITEM_G].name, NULL, NULL,
                                                               0.0, 1.0, 0.0, G_PARAM_STATIC_STRINGS);
    serializable_items[ITEM_B].aux.pspec = g_param_spec_double(serializable_items[ITEM_B].name, NULL, NULL,
                                                               0.0, 1.0, 0.0, G_PARAM_STATIC_STRINGS);
    serializable_items[ITEM_A].aux.pspec = g_param_spec_double(serializable_items[ITEM_A].name, NULL, NULL,
                                                               0.0, 1.0, 1.0, G_PARAM_STATIC_STRINGS);
    gwy_fill_serializable_defaults_pspec(serializable_items, NUM_ITEMS, FALSE);
}

GType
gwy_rgba_get_type(void)
{
    static GType type = 0;

    if (G_UNLIKELY(!type)) {
        type = g_boxed_type_register_static(TYPE_NAME, (GBoxedCopyFunc)gwy_rgba_copy, (GBoxedFreeFunc)gwy_rgba_free);
        static const GwySerializableBoxedInfo boxed_info = {
            sizeof(GwyRGBA), NUM_ITEMS, itemize, construct, NULL, NULL,
        };
        add_pspecs();
        gwy_serializable_boxed_register_static(type, &boxed_info);
    }

    return type;
}

/**
 * gwy_rgba_new:
 * @r: The red component.
 * @g: The green component.
 * @b: The blue component.
 * @a: The alpha (opacity) value.
 *
 * Creates RGBA colour specification.
 *
 * This is mostly useful for language bindings.
 *
 * Returns: New RGBA colour structure.  The result should be freed with gwy_rgba_free().
 **/
GwyRGBA*
gwy_rgba_new(gdouble r,
             gdouble g,
             gdouble b,
             gdouble a)
{
    GwyRGBA *rgba = g_new(GwyRGBA, 1);

    rgba->r = r;
    rgba->g = g;
    rgba->b = b;
    rgba->a = a;

    return rgba;
}

/**
 * gwy_rgba_copy:
 * @rgba: A RGBA color.
 *
 * Makes a copy of a rgba structure. The result must be freed using
 * gwy_rgba_free().
 *
 * Returns: A copy of @rgba.
 **/
GwyRGBA*
gwy_rgba_copy(const GwyRGBA *rgba)
{
    g_return_val_if_fail(rgba, NULL);
    return g_memdup(rgba, sizeof(GwyRGBA));
}

/**
 * gwy_rgba_free:
 * @rgba: A RGBA color.
 *
 * Frees an rgba structure created with gwy_rgba_copy().
 **/
void
gwy_rgba_free(GwyRGBA *rgba)
{
    g_return_if_fail(rgba);
    g_free(rgba);
}

/**
 * gwy_rgba_interpolate:
 * @src1: Color at point @x = 0.0.
 * @src2: Color at point @x = 1.0.
 * @x: Point in interval 0..1 to take color from.
 * @rgba: A #GwyRGBA to store the result to.
 *
 * Linearly interpolates two colors, including alpha blending.
 *
 * Correct blending of two not fully opaque colors is tricky.  Always use this function, not simple independent
 * interpolation of r, g, b, and a.
 **/
void
gwy_rgba_interpolate(const GwyRGBA *src1,
                     const GwyRGBA *src2,
                     gdouble x,
                     GwyRGBA *rgba)
{
    /* for alpha = 0.0 there's actually no limit, but average is psychologicaly better than some random value */
    if (G_LIKELY(src1->a == src2->a)) {
        rgba->a = src1->a;
        rgba->r = x*src2->r + (1.0 - x)*src1->r;
        rgba->g = x*src2->g + (1.0 - x)*src1->g;
        rgba->b = x*src2->b + (1.0 - x)*src1->b;
        return;
    }

    if (src2->a == 0.0) {
        rgba->a = (1.0 - x)*src1->a;
        rgba->r = src1->r;
        rgba->g = src1->g;
        rgba->b = src1->b;
        return;
    }
    if (src1->a == 0.0) {
        rgba->a = x*src2->a;
        rgba->r = src2->r;
        rgba->g = src2->g;
        rgba->b = src2->b;
        return;
    }

    /* nothing helped, it's a general case however, for meaningful values, rgba->a cannot be 0.0 */
    rgba->a = x*src2->a + (1.0 - x)*src1->a;
    rgba->r = (x*src2->a*src2->r + (1.0 - x)*src1->a*src1->r)/rgba->a;
    rgba->g = (x*src2->a*src2->g + (1.0 - x)*src1->a*src1->g)/rgba->a;
    rgba->b = (x*src2->a*src2->b + (1.0 - x)*src1->a*src1->b)/rgba->a;
}

/**
 * gwy_rgba_get_from_dict:
 * @rgba: A RGBA color.
 * @dict: A #GwyDict to get the color components from.
 * @prefix: Prefix in @dict, e.g. "/foo/mask" (it would try to fetch "/foo/mask/red", "/foo/mask/green", etc. then).
 *
 * Gets RGBA color components from a dictionary.
 *
 * This is a convenience function to get the components in the common arrangement. #GwyRGBA is a serializable boxed
 * type and can be stored directly in #GwyDict. This function is, therefore, useful for settings and legacy storage,
 * where the components are stored individualy.
 *
 * Returns: Whether all @rgba components were successfully found and set.
 **/
gboolean
gwy_rgba_get_from_dict(GwyRGBA *rgba,
                       GwyDict *dict,
                       const gchar *prefix)
{
    GQuark keys[4];
    gboolean ok = TRUE;

    g_return_val_if_fail(rgba && dict && prefix, FALSE);

    compute_color_quarks(prefix, keys);
    ok &= gwy_dict_gis_double(dict, keys[0], &rgba->r);
    ok &= gwy_dict_gis_double(dict, keys[1], &rgba->g);
    ok &= gwy_dict_gis_double(dict, keys[2], &rgba->b);
    ok &= gwy_dict_gis_double(dict, keys[3], &rgba->a);

    return ok;
}

/**
 * gwy_rgba_store_to_dict:
 * @rgba: A RGBA color.
 * @dict: A #GwyDict to store the color components to.
 * @prefix: Prefix in @dict, e.g. "/foo/mask" (it will store "/foo/mask/red", "/foo/mask/green", etc. then).
 *
 * Stores RGBA color components to a dictionary.
 *
 * This is a convenience function to store the components in the common arrangement. #GwyRGBA is a serializable boxed
 * type and can be stored directly in #GwyDict. This function is, therefore, useful for settings and legacy storage,
 * where the components are stored individualy.
 **/
void
gwy_rgba_store_to_dict(const GwyRGBA *rgba,
                       GwyDict *dict,
                       const gchar *prefix)
{
    GQuark keys[4];

    g_return_if_fail(rgba && dict && prefix);

    compute_color_quarks(prefix, keys);
    gwy_dict_set_double(dict, keys[0], rgba->r);
    gwy_dict_set_double(dict, keys[1], rgba->g);
    gwy_dict_set_double(dict, keys[2], rgba->b);
    gwy_dict_set_double(dict, keys[3], rgba->a);
}

/**
 * gwy_rgba_remove_from_dict:
 * @dict: A #GwyDict to remove the color components from.
 * @prefix: Prefix in @dict, e.g. "/foo/mask" (it will remove "/foo/mask/red", "/foo/mask/green", etc. then).
 *
 * Removes RGBA color components from a dictionary.
 *
 * This is a convenience function to remove the components in the common arrangement. #GwyRGBA is a serializable boxed
 * type and can be stored directly in #GwyDict. This function is, therefore, useful for settings and legacy storage,
 * where the components are stored individualy.
 *
 * Returns: %TRUE if anything was removed.
 **/
gboolean
gwy_rgba_remove_from_dict(GwyDict *dict,
                          const gchar *prefix)
{
    g_return_val_if_fail(dict && prefix, FALSE);

    GQuark keys[4];
    compute_color_quarks(prefix, keys);
    return (gwy_dict_remove(dict, keys[0]) + gwy_dict_remove(dict, keys[1])
            + gwy_dict_remove(dict, keys[2]) + gwy_dict_remove(dict, keys[3]));
}

static void
compute_color_quarks(const gchar *prefix, GQuark quarks[4])
{
    guint len = strlen(prefix);
    gchar key[len + sizeof("/alpha")];

    g_stpcpy(g_stpcpy(key, prefix), "/red");
    quarks[0] = g_quark_from_string(key);
    strcpy(key + len + 1, "green");
    quarks[1] = g_quark_from_string(key);
    strcpy(key + len + 1, "blue");
    quarks[2] = g_quark_from_string(key);
    strcpy(key + len + 1, "alpha");
    quarks[3] = g_quark_from_string(key);
}

/**
 * gwy_rgba_to_hex6:
 * @rgba: A RGBA color.  Its alpha component is ignored, only RGB is used.
 * @hexout: (out):
 *          Buffer at least seven character long where the hexadecimal representation (e.g. "ff0000" for red) will be
 *          stored.
 *
 * Formats the R, G and B components of a RGBA color to hexadecimal string.
 *
 * The component order is R, G and B.  The output has always exactly 6 bytes and does not include any "#" prefix which
 * is used in some contexts.
 **/
void
gwy_rgba_to_hex6(const GwyRGBA *rgba,
                 gchar *hexout)
{
    g_return_if_fail(rgba);
    g_return_if_fail(hexout);
    g_snprintf(hexout, 7, "%02x%02x%02x",
               float_to_hex(rgba->r), float_to_hex(rgba->g), float_to_hex(rgba->b));
}

/**
 * gwy_rgba_to_hex8:
 * @rgba: A RGBA color.
 * @hexout: (out):
 *          Buffer at least nine character long where the hexadecimal representation (e.g. "ffff0000" for opaque red)
 *          will be stored.
 *
 * Formats all components of a RGBA color to hexadecimal string.
 *
 * The component order is A, R, G and B.  Note that while this order is a common it is by no means universal.  The
 * output has always exactly 8 bytes and does not include any "#" prefix which is used in some contexts.
 **/
void
gwy_rgba_to_hex8(const GwyRGBA *rgba,
                 gchar *hexout)
{
    g_return_if_fail(rgba);
    g_return_if_fail(hexout);
    g_snprintf(hexout, 9, "%02x%02x%02x%02x",
               float_to_hex(rgba->a), float_to_hex(rgba->r), float_to_hex(rgba->g), float_to_hex(rgba->b));
}

/**
 * gwy_rgba_to_pixbuf_pixel:
 * @rgba: A RGBA color.
 *
 * Converts a RGBA color to pixbuf pixel.
 *
 * The returned pixel value includes opacity.  If @rgba is partially transparent, so is the pixel.
 *
 * Returns: The pixel value as a 32-bit integer.
 **/
guint32
gwy_rgba_to_pixbuf_pixel(const GwyRGBA *rgba)
{
    g_return_val_if_fail(rgba, 0);

    return ((guint32)float_to_hex(rgba->a)
            | (((guint32)float_to_hex(rgba->b)) << 8)
            | (((guint32)float_to_hex(rgba->g)) << 16)
            | (((guint32)float_to_hex(rgba->r)) << 24));
}

/**
 * gwy_rgba_from_pixbuf_pixel:
 * @rgba: A #GwyRGBA to store the result to.
 * @pixel: Pixbuf pixel value as a 32-bit integer.
 *
 * Converts a pixbuf pixel value to a RGBA color.
 *
 * The conversion includes opacity.  If the opacity channel is undefined or should be ignored, you need to either set
 * the lowest byte of @pixel to 0xff or fix @rgba afterwards.
 **/
void
gwy_rgba_from_pixbuf_pixel(GwyRGBA *rgba,
                           guint32 pixel)
{
    g_return_if_fail(rgba);
    rgba->a = (pixel & 0xffu)/255.0;
    rgba->b = ((pixel >> 8) & 0xffu)/255.0;
    rgba->g = ((pixel >> 16) & 0xffu)/255.0;
    rgba->r = ((pixel >> 24) & 0xffu)/255.0;
}

/**
 * gwy_rgba_equal:
 * @rgba: A #GwyRGBA colour.
 * @other: Another #GwyRGBA colour.
 *
 * Checks two RGBA colours for equality.
 *
 * Returns: %TRUE if the two colours are identical, %FALSE if they differ.
 **/
gboolean
gwy_rgba_equal(const GwyRGBA *rgba,
               const GwyRGBA *other)
{
    g_return_val_if_fail(rgba && other, FALSE);
    return rgba->r == other->r && rgba->g == other->g && rgba->b == other->b && rgba->a == other->a;
}

static void
itemize(gpointer boxed, GwySerializableGroup *group)
{
    GwyRGBA *rgba = (GwyRGBA*)boxed;
    gwy_serializable_group_append_double(group, serializable_items + ITEM_R, rgba->r);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_G, rgba->g);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_B, rgba->b);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_A, rgba->a);
}

static gpointer
construct(GwySerializableGroup *group, GwyErrorList **error_list)
{
    GwySerializableItem its[NUM_ITEMS];
    gwy_assign(its, serializable_items, NUM_ITEMS);
    gwy_deserialize_filter_items(its, NUM_ITEMS, group, TYPE_NAME, error_list);

    GwyRGBA *rgba = g_new(GwyRGBA, 1);
    rgba->r = its[ITEM_R].value.v_double;
    rgba->g = its[ITEM_G].value.v_double;
    rgba->b = its[ITEM_B].value.v_double;
    rgba->a = its[ITEM_A].value.v_double;
    return rgba;
}

/**
 * SECTION: rgba
 * @title: GwyRGBA
 * @short_description: Bit depth independent RGBA colors
 *
 * #GwyRGBA is a bit depth independent representation of an RGB or RGBA color, using floating point values from the
 * [0,1] interval.
 *
 * #GwyRGBA is not an object, but a simple struct that can be allocated on stack on created with g_new() or malloc().
 * It is registered as a serializable boxed type and can used with functions such as gwy_dict_get_boxed() and
 * gwy_dict_set_boxed(). Helper functions for #GwyDict storage by component (gwy_rgba_store_to_dict(),
 * gwy_rgba_get_from_dict()) are also provided.
 *
 * #GwyRGBA is fact bit-for-bit identical to #GdkRGBA form GTK+. However, the fields are named differently and
 * libgwyddion is a non-GUI library, so it cannot use Gdk types.
 **/

/**
 * GwyRGBA:
 * @r: The red component.
 * @g: The green component.
 * @b: The blue component.
 * @a: The alpha (opacity) value.
 *
 * RGB[A] color specification type.
 *
 * All values are from the range [0,1].  The components are not alpha-premultiplied.
 **/

/* vim: set cin et columns=120 tw=118 ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
