/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikaimage-color-profile.c
 * Copyright (C) 2015-2018 Michael Natterer <mitch@gimp.org>
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

#include "libpikaconfig/pikaconfig.h"
#include "libpikacolor/pikacolor.h"
#include "libpikabase/pikabase.h"

#include "core-types.h"

#include "config/pikadialogconfig.h"

#include "gegl/pika-babl.h"
#include "gegl/pika-gegl-loops.h"

#include "pika.h"
#include "pikacontext.h"
#include "pikaerror.h"
#include "pikalayer.h"
#include "pikaimage.h"
#include "pikaimage-color-profile.h"
#include "pikaimage-colormap.h"
#include "pikaimage-private.h"
#include "pikaimage-undo.h"
#include "pikaimage-undo-push.h"
#include "pikaobjectqueue.h"
#include "pikaprogress.h"

#include "pika-intl.h"


/*  local function prototypes  */

static void   pika_image_convert_profile_layers   (PikaImage                *image,
                                                   PikaColorProfile         *src_profile,
                                                   PikaColorProfile         *dest_profile,
                                                   PikaColorRenderingIntent  intent,
                                                   gboolean                  bpc,
                                                   PikaProgress             *progress);
static void   pika_image_convert_profile_colormap (PikaImage                *image,
                                                   PikaColorProfile         *src_profile,
                                                   PikaColorProfile         *dest_profile,
                                                   PikaColorRenderingIntent  intent,
                                                   gboolean                  bpc,
                                                   PikaProgress             *progress);

static void   pika_image_fix_layer_format_spaces  (PikaImage                *image,
                                                   PikaProgress             *progress);

static void   pika_image_create_color_transforms  (PikaImage                *image);


/*  public functions  */

gboolean
pika_image_get_use_srgb_profile (PikaImage *image,
                                 gboolean  *hidden_profile)
{
  PikaImagePrivate *private;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  if (hidden_profile)
    *hidden_profile = (private->hidden_profile != NULL);

  return private->color_profile == NULL;
}

void
pika_image_set_use_srgb_profile (PikaImage *image,
                                 gboolean   use_srgb)
{
  PikaImagePrivate *private;
  gboolean          old_use_srgb;

  g_return_if_fail (PIKA_IS_IMAGE (image));

  private = PIKA_IMAGE_GET_PRIVATE (image);

  old_use_srgb = (private->color_profile == NULL);

  use_srgb = use_srgb ? TRUE : FALSE;

  if (use_srgb == old_use_srgb)
    return;

  if (use_srgb)
    {
      PikaColorProfile *profile = pika_image_get_color_profile (image);

      if (profile)
        {
          pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_CONVERT,
                                       _("Enable 'Use sRGB Profile'"));

          g_object_ref (profile);
          pika_image_assign_color_profile (image, NULL, NULL, NULL);
          _pika_image_set_hidden_profile (image, profile, TRUE);
          g_object_unref (profile);

          pika_image_undo_group_end (image);
        }
    }
  else
    {
      PikaColorProfile *hidden = _pika_image_get_hidden_profile (image);

      if (hidden)
        {
          pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_CONVERT,
                                       _("Disable 'Use sRGB Profile'"));

          g_object_ref (hidden);
          pika_image_assign_color_profile (image, hidden, NULL, NULL);
          g_object_unref (hidden);

          pika_image_undo_group_end (image);
        }
    }
}

PikaColorProfile *
_pika_image_get_hidden_profile (PikaImage *image)
{
  PikaImagePrivate *private;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  return private->hidden_profile;
}

void
_pika_image_set_hidden_profile (PikaImage        *image,
                                PikaColorProfile *profile,
                                gboolean          push_undo)
{
  PikaImagePrivate *private;

  g_return_if_fail (PIKA_IS_IMAGE (image));
  g_return_if_fail (profile == NULL || PIKA_IS_COLOR_PROFILE (profile));

  private = PIKA_IMAGE_GET_PRIVATE (image);

  if (profile != private->hidden_profile)
    {
      if (push_undo)
        pika_image_undo_push_image_hidden_profile (image, NULL);

      g_set_object (&private->hidden_profile, profile);
    }
}

gboolean
pika_image_validate_icc_parasite (PikaImage           *image,
                                  const PikaParasite  *icc_parasite,
                                  const gchar         *profile_type,
                                  gboolean            *is_builtin,
                                  GError             **error)
{
  const guint8 *data;
  guint32       data_size;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (icc_parasite != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (strcmp (pika_parasite_get_name (icc_parasite),
              profile_type) != 0)
    {
      gchar *invalid_parasite_name;

      invalid_parasite_name =
        g_strdup_printf (_("ICC profile validation failed: "
                           "Parasite's name is not '%s'"),
                           profile_type);

      g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                           invalid_parasite_name);

      g_free (invalid_parasite_name);
      return FALSE;
    }

  if (pika_parasite_get_flags (icc_parasite) != (PIKA_PARASITE_PERSISTENT |
                                                 PIKA_PARASITE_UNDOABLE))
    {
      g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                           _("ICC profile validation failed: "
                             "Parasite's flags are not (PERSISTENT | UNDOABLE)"));
      return FALSE;
    }

  data = pika_parasite_get_data (icc_parasite, &data_size);

  return pika_image_validate_icc_profile (image, data, data_size,
                                          profile_type, is_builtin, error);
}

const PikaParasite *
pika_image_get_icc_parasite (PikaImage *image)
{
  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  return pika_image_parasite_find (image, PIKA_ICC_PROFILE_PARASITE_NAME);
}

void
pika_image_set_icc_parasite (PikaImage          *image,
                             const PikaParasite *icc_parasite,
                             const gchar        *profile_type)
{
  g_return_if_fail (PIKA_IS_IMAGE (image));

  if (icc_parasite)
    {
      g_return_if_fail (pika_image_validate_icc_parasite (image, icc_parasite,
                                                          profile_type,
                                                          NULL, NULL) == TRUE);

      pika_image_parasite_attach (image, icc_parasite, TRUE);
    }
  else
    {
      pika_image_parasite_detach (image, profile_type, TRUE);
    }
}

gboolean
pika_image_validate_icc_profile (PikaImage     *image,
                                 const guint8  *data,
                                 gsize          length,
                                 const gchar   *profile_type,
                                 gboolean      *is_builtin,
                                 GError       **error)
{
  PikaColorProfile *profile;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (data != NULL || length == 0, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  profile = pika_color_profile_new_from_icc_profile (data, length, error);

  if (! profile)
    {
      if (g_strcmp0 (profile_type, PIKA_ICC_PROFILE_PARASITE_NAME) == 0)
        g_prefix_error (error, _("ICC profile validation failed: "));
      else if (g_strcmp0 (profile_type, PIKA_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0)
        g_prefix_error (error, _("Simulation ICC profile validation failed: "));

      return FALSE;
    }

  if (g_strcmp0 (profile_type, PIKA_ICC_PROFILE_PARASITE_NAME) == 0)
    {
      if (! pika_image_validate_color_profile (image, profile, is_builtin, error))
        {
          g_object_unref (profile);
          return FALSE;
        }
    }
  else
    {
      if (is_builtin)
        *is_builtin = FALSE;
    }

  g_object_unref (profile);

  return TRUE;
}

const guint8 *
pika_image_get_icc_profile (PikaImage *image,
                            gsize     *length)
{
  const PikaParasite *parasite;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);

  parasite = pika_image_parasite_find (image, PIKA_ICC_PROFILE_PARASITE_NAME);

  if (parasite)
    {
      const guint8 *data;
      guint32       data_size;

      data = pika_parasite_get_data (parasite, &data_size);

      if (length)
        *length = (gsize) data_size;

      return data;
    }

  if (length)
    *length = 0;

  return NULL;
}

gboolean
pika_image_set_icc_profile (PikaImage     *image,
                            const guint8  *data,
                            gsize          length,
                            const gchar   *profile_type,
                            GError       **error)
{
  PikaParasite *parasite = NULL;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (data == NULL || length != 0, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (data)
    {
      gboolean is_builtin;

      parasite = pika_parasite_new (profile_type,
                                    PIKA_PARASITE_PERSISTENT |
                                    PIKA_PARASITE_UNDOABLE,
                                    length, data);

      if (! pika_image_validate_icc_parasite (image, parasite, profile_type,
                                              &is_builtin, error))
        {
          pika_parasite_free (parasite);
          return FALSE;
        }

      /*  don't tag the image with the built-in profile  */
      if (is_builtin)
        {
          pika_parasite_free (parasite);
          parasite = NULL;
        }
    }

  pika_image_set_icc_parasite (image, parasite, profile_type);

  if (parasite)
    pika_parasite_free (parasite);

  return TRUE;
}

gboolean
pika_image_validate_color_profile (PikaImage        *image,
                                   PikaColorProfile *profile,
                                   gboolean         *is_builtin,
                                   GError          **error)
{
  const Babl *format;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (profile), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  format = pika_image_get_layer_format (image, TRUE);

  return pika_image_validate_color_profile_by_format (format,
                                                      profile, is_builtin,
                                                      error);
}

PikaColorProfile *
pika_image_get_color_profile (PikaImage *image)
{
  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  return PIKA_IMAGE_GET_PRIVATE (image)->color_profile;
}

gboolean
pika_image_set_color_profile (PikaImage         *image,
                              PikaColorProfile  *profile,
                              GError           **error)
{
  const guint8 *data   = NULL;
  gsize         length = 0;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (profile == NULL || PIKA_IS_COLOR_PROFILE (profile),
                        FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (profile)
    data = pika_color_profile_get_icc_profile (profile, &length);

  return pika_image_set_icc_profile (image, data, length,
                                     PIKA_ICC_PROFILE_PARASITE_NAME,
                                     error);
}

PikaColorProfile *
pika_image_get_simulation_profile (PikaImage *image)
{
  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  return PIKA_IMAGE_GET_PRIVATE (image)->simulation_profile;
}

gboolean
pika_image_set_simulation_profile (PikaImage         *image,
                                   PikaColorProfile  *profile)
{
  PikaImagePrivate *private;
  const guint8     *data   = NULL;
  gsize             length = 0;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (profile == NULL || PIKA_IS_COLOR_PROFILE (profile),
                        FALSE);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  if (profile != private->simulation_profile)
    {
      g_set_object (&private->simulation_profile, profile);
      pika_color_managed_simulation_profile_changed (PIKA_COLOR_MANAGED (image));
    }

  if (profile)
    data = pika_color_profile_get_icc_profile (profile, &length);

  return pika_image_set_icc_profile (image, data, length,
                                     PIKA_SIMULATION_ICC_PROFILE_PARASITE_NAME,
                                     NULL);
}

PikaColorRenderingIntent
pika_image_get_simulation_intent (PikaImage *image)
{
  g_return_val_if_fail (PIKA_IS_IMAGE (image),
                        PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC);

  return PIKA_IMAGE_GET_PRIVATE (image)->simulation_intent;
}

void
pika_image_set_simulation_intent (PikaImage               *image,
                                  PikaColorRenderingIntent intent)
{
  PikaImagePrivate *private;

  g_return_if_fail (PIKA_IS_IMAGE (image));

  private = PIKA_IMAGE_GET_PRIVATE (image);

  if (intent != private->simulation_intent)
    {
      PikaParasite *parasite;
      gchar         i;

      private->simulation_intent = intent;
      pika_color_managed_simulation_intent_changed (PIKA_COLOR_MANAGED (image));

      i = (gchar) intent;
      parasite = pika_parasite_new ("image-simulation-intent",
                                    PIKA_PARASITE_PERSISTENT,
                                    1, (gpointer) &i);

      pika_image_parasite_attach (image, parasite, FALSE);
      pika_parasite_free (parasite);
    }
}

gboolean
pika_image_get_simulation_bpc (PikaImage *image)
{
  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);

  return PIKA_IMAGE_GET_PRIVATE (image)->simulation_bpc;
}

void
pika_image_set_simulation_bpc (PikaImage *image,
                               gboolean   bpc)
{
  PikaImagePrivate *private;

  g_return_if_fail (PIKA_IS_IMAGE (image));

  private = PIKA_IMAGE_GET_PRIVATE (image);

  if (bpc != private->simulation_bpc)
    {
      PikaParasite *parasite;
      gchar         i;

      private->simulation_bpc = bpc;
      pika_color_managed_simulation_bpc_changed (PIKA_COLOR_MANAGED (image));

      i = (gchar) bpc;
      parasite = pika_parasite_new ("image-simulation-bpc",
                                    PIKA_PARASITE_PERSISTENT,
                                    1, (gpointer) &i);

      pika_image_parasite_attach (image, parasite, FALSE);
      pika_parasite_free (parasite);
    }
}

gboolean
pika_image_validate_color_profile_by_format (const Babl         *format,
                                             PikaColorProfile   *profile,
                                             gboolean           *is_builtin,
                                             GError            **error)
{
  g_return_val_if_fail (format != NULL, FALSE);
  g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (profile), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (pika_babl_format_get_base_type (format) == PIKA_GRAY)
    {
      if (! pika_color_profile_is_gray (profile))
        {
          g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                               _("ICC profile validation failed: "
                                 "Color profile is not for grayscale color space"));
          return FALSE;
        }
    }
  else
    {
      if (! pika_color_profile_is_rgb (profile))
        {
          g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                               _("ICC profile validation failed: "
                                 "Color profile is not for RGB color space"));
          return FALSE;
        }
    }

  if (is_builtin)
    {
      PikaColorProfile *builtin =
        pika_babl_get_builtin_color_profile (pika_babl_format_get_base_type (format),
                                             pika_babl_format_get_trc (format));

      *is_builtin = pika_color_profile_is_equal (profile, builtin);
    }

  return TRUE;
}

PikaColorProfile *
pika_image_get_builtin_color_profile (PikaImage *image)
{
  const Babl *format;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  format = pika_image_get_layer_format (image, FALSE);

  return pika_babl_get_builtin_color_profile (pika_babl_format_get_base_type (format),
                                              pika_babl_format_get_trc (format));
}

gboolean
pika_image_assign_color_profile (PikaImage         *image,
                                 PikaColorProfile  *dest_profile,
                                 PikaProgress      *progress,
                                 GError           **error)
{
  PikaColorProfile *src_profile;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (dest_profile == NULL ||
                        PIKA_IS_COLOR_PROFILE (dest_profile), FALSE);
  g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (dest_profile &&
      ! pika_image_validate_color_profile (image, dest_profile, NULL, error))
    return FALSE;

  src_profile = pika_color_managed_get_color_profile (PIKA_COLOR_MANAGED (image));

  if (src_profile == dest_profile ||
      (src_profile && dest_profile &&
       pika_color_profile_is_equal (src_profile, dest_profile)))
    return TRUE;

  if (progress)
    pika_progress_start (progress, FALSE,
                         dest_profile ?
                         _("Assigning color profile") :
                         _("Discarding color profile"));

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_CONVERT,
                               dest_profile ?
                               _("Assign color profile") :
                               _("Discard color profile"));

  _pika_image_set_hidden_profile (image, NULL, TRUE);

  pika_image_set_color_profile (image, dest_profile, NULL);
  /*  omg...  */
  pika_image_parasite_detach (image, "icc-profile-name", TRUE);

  if (pika_image_get_base_type (image) == PIKA_INDEXED)
    pika_image_colormap_update_formats (image);

  pika_image_fix_layer_format_spaces (image, progress);

  pika_image_undo_group_end (image);

  return TRUE;
}

gboolean
pika_image_convert_color_profile (PikaImage                *image,
                                  PikaColorProfile         *dest_profile,
                                  PikaColorRenderingIntent  intent,
                                  gboolean                  bpc,
                                  PikaProgress             *progress,
                                  GError                  **error)
{
  PikaColorProfile *src_profile;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (PIKA_IS_COLOR_PROFILE (dest_profile), FALSE);
  g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (! pika_image_validate_color_profile (image, dest_profile, NULL, error))
    return FALSE;

  src_profile = pika_color_managed_get_color_profile (PIKA_COLOR_MANAGED (image));

  if (pika_color_profile_is_equal (src_profile, dest_profile))
    return TRUE;

  if (progress)
    pika_progress_start (progress, FALSE,
                         _("Converting from '%s' to '%s'"),
                         pika_color_profile_get_label (src_profile),
                         pika_color_profile_get_label (dest_profile));

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_CONVERT,
                               _("Color profile conversion"));

  /* retain src_profile across pika_image_set_color_profile() */
  g_object_ref (src_profile);

  _pika_image_set_hidden_profile (image, NULL, TRUE);

  pika_image_set_color_profile (image, dest_profile, NULL);
  /*  omg...  */
  pika_image_parasite_detach (image, "icc-profile-name", TRUE);

  switch (pika_image_get_base_type (image))
    {
    case PIKA_RGB:
    case PIKA_GRAY:
      pika_image_convert_profile_layers (image,
                                         src_profile, dest_profile,
                                         intent, bpc,
                                         progress);
      break;

    case PIKA_INDEXED:
      pika_image_convert_profile_colormap (image,
                                           src_profile, dest_profile,
                                           intent, bpc,
                                           progress);
      pika_image_fix_layer_format_spaces (image, progress);
      break;
    }

  g_object_unref (src_profile);

  pika_image_undo_group_end (image);

  if (progress)
    pika_progress_end (progress);

  return TRUE;
}

void
pika_image_import_color_profile (PikaImage    *image,
                                 PikaContext  *context,
                                 PikaProgress *progress,
                                 gboolean      interactive)
{
  PikaColorProfile *profile = NULL;

  g_return_if_fail (PIKA_IS_IMAGE (image));
  g_return_if_fail (PIKA_IS_CONTEXT (context));
  g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress));

  if ((profile = pika_image_get_color_profile (image)))
    {
      PikaColorProfilePolicy     policy;
      PikaColorProfile          *dest_profile = NULL;
      PikaColorProfile          *pref_profile = NULL;
      PikaColorRenderingIntent   intent;
      gboolean                   bpc;

      policy = PIKA_DIALOG_CONFIG (image->pika->config)->color_profile_policy;
      intent = PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC;
      bpc    = TRUE;

      if (pika_image_get_base_type (image) == PIKA_GRAY)
        pref_profile = pika_color_config_get_gray_color_profile (image->pika->config->color_management, NULL);
      else
        pref_profile = pika_color_config_get_rgb_color_profile (image->pika->config->color_management, NULL);

      if (policy == PIKA_COLOR_PROFILE_POLICY_ASK)
        {
          if (pika_color_profile_is_equal (profile, pika_image_get_builtin_color_profile (image)) ||
              (pref_profile && pika_color_profile_is_equal (pref_profile, profile)))
            {
              /* If already using the default profile or the preferred
               * profile for the image type, no need to ask. Just keep
               * the profile.
               */
              policy = PIKA_COLOR_PROFILE_POLICY_KEEP;
            }
          else if (interactive)
            {
              gboolean dont_ask = FALSE;

              policy = pika_query_profile_policy (image->pika, image, context,
                                                  &dest_profile,
                                                  &intent, &bpc,
                                                  &dont_ask);

              if (dont_ask)
                {
                  g_object_set (G_OBJECT (image->pika->config),
                                "color-profile-policy", policy,
                                NULL);
                }
            }
          else
            {
              policy = PIKA_COLOR_PROFILE_POLICY_KEEP;
            }
        }

      if (policy == PIKA_COLOR_PROFILE_POLICY_CONVERT_PREFERRED ||
          policy == PIKA_COLOR_PROFILE_POLICY_CONVERT_BUILTIN)
        {
          if (! dest_profile)
            {
              if (policy == PIKA_COLOR_PROFILE_POLICY_CONVERT_PREFERRED)
                {
                  if (pika_image_get_base_type (image) == PIKA_GRAY)
                    dest_profile = pika_color_config_get_gray_color_profile (image->pika->config->color_management, NULL);
                  else
                    dest_profile = pika_color_config_get_rgb_color_profile (image->pika->config->color_management, NULL);
                }

              if (! dest_profile)
                {
                  /* Built-in policy or no preferred profile set. */
                  dest_profile = pika_image_get_builtin_color_profile (image);
                  g_object_ref (dest_profile);
                }
            }

          pika_image_convert_color_profile (image, dest_profile,
                                            intent, bpc,
                                            progress, NULL);

          g_object_unref (dest_profile);
        }

      g_clear_object (&pref_profile);
    }
}

PikaColorTransform *
pika_image_get_color_transform_to_srgb_u8 (PikaImage *image)
{
  PikaImagePrivate *private;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  pika_image_create_color_transforms (image);

  return private->transform_to_srgb_u8;
}

PikaColorTransform *
pika_image_get_color_transform_from_srgb_u8 (PikaImage *image)
{
  PikaImagePrivate *private;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  pika_image_create_color_transforms (image);

  return private->transform_from_srgb_u8;
}

PikaColorTransform *
pika_image_get_color_transform_to_srgb_double (PikaImage *image)
{
  PikaImagePrivate *private;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  pika_image_create_color_transforms (image);

  return private->transform_to_srgb_double;
}

PikaColorTransform *
pika_image_get_color_transform_from_srgb_double (PikaImage *image)
{
  PikaImagePrivate *private;

  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  private = PIKA_IMAGE_GET_PRIVATE (image);

  pika_image_create_color_transforms (image);

  return private->transform_from_srgb_double;
}

void
pika_image_color_profile_pixel_to_rgb (PikaImage  *image,
                                       const Babl *pixel_format,
                                       gpointer    pixel,
                                       PikaRGB    *color)
{
  PikaColorProfile   *profile = NULL;
  const Babl         *space   = NULL;

  g_return_if_fail (PIKA_IS_IMAGE (image));

  profile = pika_image_get_color_profile (image);

  if (profile)
    space = pika_color_profile_get_space (profile,
                                          PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                          NULL);
  if (profile)
    {
      babl_process (babl_fish (pixel_format,
                               babl_format_with_space ("R'G'B'A double", space)),
                    pixel, color, 1);
    }
  else
    {
      pika_rgba_set_pixel (color, pixel_format, pixel);
    }
}

void
pika_image_color_profile_rgb_to_pixel (PikaImage     *image,
                                       const PikaRGB *color,
                                       const Babl    *pixel_format,
                                       gpointer       pixel)
{
  PikaColorProfile   *profile = NULL;
  const Babl         *space   = NULL;

  g_return_if_fail (PIKA_IS_IMAGE (image));

  profile = pika_image_get_color_profile (image);

  if (profile)
    space = pika_color_profile_get_space (profile,
                                          PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                          NULL);

  if (profile)
    {
      babl_process (babl_fish (babl_format_with_space ("R'G'B'A double", space),
                               pixel_format),
                    color, pixel, 1);
    }
  else
    {
      pika_rgba_get_pixel (color, pixel_format, pixel);
    }
}


/*  internal API  */

void
_pika_image_free_color_profile (PikaImage *image)
{
  PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image);

  g_clear_object (&private->color_profile);
  private->layer_space = NULL;

  g_clear_object (&private->hidden_profile);

  _pika_image_free_color_transforms (image);
}

void
_pika_image_free_color_transforms (PikaImage *image)
{
  PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image);

  g_clear_object (&private->transform_to_srgb_u8);
  g_clear_object (&private->transform_from_srgb_u8);
  g_clear_object (&private->transform_to_srgb_double);
  g_clear_object (&private->transform_from_srgb_double);

  private->color_transforms_created = FALSE;
}

void
_pika_image_update_color_profile (PikaImage          *image,
                                  const PikaParasite *icc_parasite)
{
  PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image);

  _pika_image_free_color_profile (image);

  if (icc_parasite)
    {
      GError       *error = NULL;
      const guint8 *data;
      guint32       data_size;

      data = pika_parasite_get_data (icc_parasite, &data_size);
      private->color_profile =
        pika_color_profile_new_from_icc_profile (data, data_size, NULL);

      private->layer_space =
        pika_color_profile_get_space (private->color_profile,
                                      PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                      &error);
      if (! private->layer_space)
        {
          g_printerr ("%s: failed to create Babl space from profile: %s\n",
                      G_STRFUNC, error->message);
          g_clear_error (&error);
        }
    }

  pika_color_managed_profile_changed (PIKA_COLOR_MANAGED (image));
}

void
_pika_image_update_simulation_profile (PikaImage          *image,
                                       const PikaParasite *icc_parasite)
{
  PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image);

  g_clear_object (&private->simulation_profile);

  if (icc_parasite)
    {
      const guint8 *data;
      guint32       data_size;

      data = pika_parasite_get_data (icc_parasite, &data_size);
      private->simulation_profile =
        pika_color_profile_new_from_icc_profile (data, data_size, NULL);
    }

  pika_color_managed_simulation_profile_changed (PIKA_COLOR_MANAGED (image));
}


/*  private functions  */

static void
pika_image_convert_profile_layers (PikaImage                *image,
                                   PikaColorProfile         *src_profile,
                                   PikaColorProfile         *dest_profile,
                                   PikaColorRenderingIntent  intent,
                                   gboolean                  bpc,
                                   PikaProgress             *progress)
{
  PikaObjectQueue *queue;
  GList           *layers;
  GList           *list;
  PikaDrawable    *drawable;

  queue    = pika_object_queue_new (progress);
  progress = PIKA_PROGRESS (queue);

  layers = pika_image_get_layer_list (image);

  for (list = layers; list; list = g_list_next (list))
    {
      if (! pika_viewable_get_children (list->data))
        pika_object_queue_push (queue, list->data);
    }

  g_list_free (layers);

  while ((drawable = pika_object_queue_pop (queue)))
    {
      PikaItem   *item = PIKA_ITEM (drawable);
      GeglBuffer *buffer;
      gboolean    alpha;

      alpha = pika_drawable_has_alpha (drawable);

      buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                                pika_item_get_width  (item),
                                                pika_item_get_height (item)),
                                pika_image_get_layer_format (image, alpha));

      pika_gegl_convert_color_profile (pika_drawable_get_buffer (drawable),
                                       NULL,
                                       src_profile,
                                       buffer,
                                       NULL,
                                       dest_profile,
                                       intent, bpc,
                                       progress);

      pika_drawable_set_buffer (drawable, TRUE, NULL, buffer);
      g_object_unref (buffer);
    }

  g_object_unref (queue);
}

static void
pika_image_convert_profile_colormap (PikaImage                *image,
                                     PikaColorProfile         *src_profile,
                                     PikaColorProfile         *dest_profile,
                                     PikaColorRenderingIntent  intent,
                                     gboolean                  bpc,
                                     PikaProgress             *progress)
{
  PikaColorTransform      *transform;
  const Babl              *src_format;
  const Babl              *dest_format;
  PikaColorTransformFlags  flags = 0;
  guchar                  *cmap;
  gint                     n_colors;

  n_colors = pika_image_get_colormap_size (image);
  cmap     = pika_image_get_colormap (image);

  if (bpc)
    flags |= PIKA_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;

  src_format  = pika_color_profile_get_format (src_profile,
                                               babl_format ("R'G'B' u8"),
                                               PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                               NULL);
  dest_format = pika_color_profile_get_format (dest_profile,
                                               babl_format ("R'G'B' u8"),
                                               PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                               NULL);

  transform = pika_color_transform_new (src_profile,  src_format,
                                        dest_profile, dest_format,
                                        intent, flags);

  if (transform)
    {
      pika_color_transform_process_pixels (transform,
                                           babl_format ("R'G'B' u8"), cmap,
                                           babl_format ("R'G'B' u8"), cmap,
                                           n_colors);
      g_object_unref (transform);

      pika_image_set_colormap (image, cmap, n_colors, TRUE);
    }
  else
    {
      g_warning ("pika_color_transform_new() failed!");
    }

  g_free (cmap);
}

static void
pika_image_fix_layer_format_spaces (PikaImage    *image,
                                    PikaProgress *progress)
{
  PikaObjectQueue *queue;
  GList           *layers;
  GList           *list;
  PikaLayer       *layer;

  queue = pika_object_queue_new (progress);

  layers = pika_image_get_layer_list (image);

  for (list = layers; list; list = g_list_next (list))
    {
      if (! pika_viewable_get_children (list->data))
        pika_object_queue_push (queue, list->data);
    }

  g_list_free (layers);

  while ((layer = pika_object_queue_pop (queue)))
    {
      pika_layer_fix_format_space (layer, TRUE, TRUE);
    }

  g_object_unref (queue);
}

static void
pika_image_create_color_transforms (PikaImage *image)
{
  PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image);

  if (private->color_profile &&
      ! private->color_transforms_created)
    {
      PikaColorProfile        *srgb_profile;
      PikaColorTransformFlags  flags = 0;

      srgb_profile = pika_color_profile_new_rgb_srgb ();

      flags |= PIKA_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
      flags |= PIKA_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;

      private->transform_to_srgb_u8 =
        pika_color_transform_new (private->color_profile,
                                  pika_image_get_layer_format (image, TRUE),
                                  srgb_profile,
                                  babl_format ("R'G'B'A u8"),
                                  PIKA_COLOR_RENDERING_INTENT_PERCEPTUAL,
                                  flags);

      private->transform_from_srgb_u8 =
        pika_color_transform_new (srgb_profile,
                                  babl_format ("R'G'B'A u8"),
                                  private->color_profile,
                                  pika_image_get_layer_format (image, TRUE),
                                  PIKA_COLOR_RENDERING_INTENT_PERCEPTUAL,
                                  flags);

      private->transform_to_srgb_double =
        pika_color_transform_new (private->color_profile,
                                  pika_image_get_layer_format (image, TRUE),
                                  srgb_profile,
                                  babl_format ("R'G'B'A double"),
                                  PIKA_COLOR_RENDERING_INTENT_PERCEPTUAL,
                                  flags);

      private->transform_from_srgb_double =
        pika_color_transform_new (srgb_profile,
                                  babl_format ("R'G'B'A double"),
                                  private->color_profile,
                                  pika_image_get_layer_format (image, TRUE),
                                  PIKA_COLOR_RENDERING_INTENT_PERCEPTUAL,
                                  flags);

      g_object_unref (srgb_profile);

      private->color_transforms_created = TRUE;
    }
}
