/* 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
 *
 * pikaimageproxy.c
 * Copyright (C) 2019 Ell
 *
 * 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 <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

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

#include "core-types.h"

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

#include "pikaimage.h"
#include "pikaimage-color-profile.h"
#include "pikaimage-preview.h"
#include "pikaimageproxy.h"
#include "pikapickable.h"
#include "pikaprojectable.h"
#include "pikatempbuf.h"


enum
{
  PROP_0,
  PROP_IMAGE,
  PROP_SHOW_ALL,
  PROP_BUFFER
};


struct _PikaImageProxyPrivate
{
  PikaImage     *image;
  gboolean       show_all;

  GeglRectangle  bounding_box;
  gboolean       frozen;
};


/*  local function prototypes  */

static void               pika_image_proxy_pickable_iface_init      (PikaPickableInterface      *iface);
static void               pika_image_proxy_color_managed_iface_init (PikaColorManagedInterface  *iface);

static void               pika_image_proxy_finalize                 (GObject                    *object);
static void               pika_image_proxy_set_property             (GObject                    *object,
                                                                     guint                       property_id,
                                                                     const GValue               *value,
                                                                     GParamSpec                 *pspec);
static void               pika_image_proxy_get_property             (GObject                    *object,
                                                                     guint                       property_id,
                                                                     GValue                     *value,
                                                                     GParamSpec                 *pspec);

static gboolean           pika_image_proxy_get_size                 (PikaViewable               *viewable,
                                                                     gint                       *width,
                                                                     gint                       *height);
static void               pika_image_proxy_get_preview_size         (PikaViewable               *viewable,
                                                                     gint                        size,
                                                                     gboolean                    is_popup,
                                                                     gboolean                    dot_for_dot,
                                                                     gint                       *width,
                                                                     gint                       *height);
static gboolean           pika_image_proxy_get_popup_size           (PikaViewable               *viewable,
                                                                     gint                        width,
                                                                     gint                        height,
                                                                     gboolean                    dot_for_dot,
                                                                     gint                       *popup_width,
                                                                     gint                       *popup_height);
static PikaTempBuf      * pika_image_proxy_get_new_preview          (PikaViewable               *viewable,
                                                                     PikaContext                *context,
                                                                     gint                        width,
                                                                     gint                        height);
static GdkPixbuf        * pika_image_proxy_get_new_pixbuf           (PikaViewable               *viewable,
                                                                     PikaContext                *context,
                                                                     gint                        width,
                                                                     gint                        height);
static gchar            * pika_image_proxy_get_description          (PikaViewable               *viewable,
                                                                     gchar                     **tooltip);

static void               pika_image_proxy_flush                    (PikaPickable               *pickable);
static const Babl       * pika_image_proxy_get_format               (PikaPickable               *pickable);
static const Babl       * pika_image_proxy_get_format_with_alpha    (PikaPickable               *pickable);
static GeglBuffer       * pika_image_proxy_get_buffer               (PikaPickable               *pickable);
static gboolean           pika_image_proxy_get_pixel_at             (PikaPickable               *pickable,
                                                                     gint                        x,
                                                                     gint                        y,
                                                                     const Babl                 *format,
                                                                     gpointer                    pixel);
static gdouble            pika_image_proxy_get_opacity_at           (PikaPickable               *pickable,
                                                                     gint                        x,
                                                                     gint                        y);
static void               pika_image_proxy_get_pixel_average        (PikaPickable               *pickable,
                                                                     const GeglRectangle        *rect,
                                                                     const Babl                 *format,
                                                                     gpointer                    pixel);
static void               pika_image_proxy_pixel_to_rgb            (PikaPickable               *pickable,
                                                                    const Babl                 *format,
                                                                    gpointer                    pixel,
                                                                    PikaRGB                    *color);
static void               pika_image_proxy_rgb_to_pixel            (PikaPickable               *pickable,
                                                                    const PikaRGB              *color,
                                                                    const Babl                 *format,
                                                                    gpointer                    pixel);

static const guint8     * pika_image_proxy_get_icc_profile          (PikaColorManaged           *managed,
                                                                     gsize                      *len);
static PikaColorProfile * pika_image_proxy_get_color_profile        (PikaColorManaged           *managed);
static void               pika_image_proxy_profile_changed          (PikaColorManaged           *managed);

static void               pika_image_proxy_image_frozen_notify      (PikaImage                  *image,
                                                                     const GParamSpec           *pspec,
                                                                     PikaImageProxy             *image_proxy);
static void               pika_image_proxy_image_invalidate_preview (PikaImage                  *image,
                                                                     PikaImageProxy             *image_proxy);
static void               pika_image_proxy_image_size_changed       (PikaImage                  *image,
                                                                     PikaImageProxy             *image_proxy);
static void               pika_image_proxy_image_bounds_changed     (PikaImage                  *image,
                                                                     gint                        old_x,
                                                                     gint                        old_y,
                                                                     PikaImageProxy             *image_proxy);
static void               pika_image_proxy_image_profile_changed    (PikaImage                  *image,
                                                                     PikaImageProxy             *image_proxy);

static void               pika_image_proxy_set_image                (PikaImageProxy             *image_proxy,
                                                                     PikaImage                  *image);
static PikaPickable     * pika_image_proxy_get_pickable             (PikaImageProxy             *image_proxy);
static void               pika_image_proxy_update_bounding_box      (PikaImageProxy             *image_proxy);
static void               pika_image_proxy_update_frozen            (PikaImageProxy             *image_proxy);


G_DEFINE_TYPE_WITH_CODE (PikaImageProxy, pika_image_proxy, PIKA_TYPE_VIEWABLE,
                         G_ADD_PRIVATE (PikaImageProxy)
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_PICKABLE,
                                                pika_image_proxy_pickable_iface_init)
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_COLOR_MANAGED,
                                                pika_image_proxy_color_managed_iface_init))

#define parent_class pika_image_proxy_parent_class


/*  private functions  */


static void
pika_image_proxy_class_init (PikaImageProxyClass *klass)
{
  GObjectClass      *object_class   = G_OBJECT_CLASS (klass);
  PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass);

  object_class->finalize            = pika_image_proxy_finalize;
  object_class->set_property        = pika_image_proxy_set_property;
  object_class->get_property        = pika_image_proxy_get_property;

  viewable_class->default_icon_name = "pika-image";
  viewable_class->get_size          = pika_image_proxy_get_size;
  viewable_class->get_preview_size  = pika_image_proxy_get_preview_size;
  viewable_class->get_popup_size    = pika_image_proxy_get_popup_size;
  viewable_class->get_new_preview   = pika_image_proxy_get_new_preview;
  viewable_class->get_new_pixbuf    = pika_image_proxy_get_new_pixbuf;
  viewable_class->get_description   = pika_image_proxy_get_description;

  g_object_class_install_property (object_class, PROP_IMAGE,
                                   g_param_spec_object ("image",
                                                        NULL, NULL,
                                                        PIKA_TYPE_IMAGE,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_SHOW_ALL,
                                   g_param_spec_boolean ("show-all",
                                                         NULL, NULL,
                                                         FALSE,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
}

static void
pika_image_proxy_pickable_iface_init (PikaPickableInterface *iface)
{
  iface->flush                 = pika_image_proxy_flush;
  iface->get_image             = (gpointer) pika_image_proxy_get_image;
  iface->get_format            = pika_image_proxy_get_format;
  iface->get_format_with_alpha = pika_image_proxy_get_format_with_alpha;
  iface->get_buffer            = pika_image_proxy_get_buffer;
  iface->get_pixel_at          = pika_image_proxy_get_pixel_at;
  iface->get_opacity_at        = pika_image_proxy_get_opacity_at;
  iface->get_pixel_average     = pika_image_proxy_get_pixel_average;
  iface->pixel_to_rgb          = pika_image_proxy_pixel_to_rgb;
  iface->rgb_to_pixel          = pika_image_proxy_rgb_to_pixel;
}

static void
pika_image_proxy_color_managed_iface_init (PikaColorManagedInterface *iface)
{
  iface->get_icc_profile   = pika_image_proxy_get_icc_profile;
  iface->get_color_profile = pika_image_proxy_get_color_profile;
  iface->profile_changed   = pika_image_proxy_profile_changed;
}

static void
pika_image_proxy_init (PikaImageProxy *image_proxy)
{
  image_proxy->priv = pika_image_proxy_get_instance_private (image_proxy);
}

static void
pika_image_proxy_finalize (GObject *object)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (object);

  pika_image_proxy_set_image (image_proxy, NULL);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
pika_image_proxy_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (object);

  switch (property_id)
    {
    case PROP_IMAGE:
      pika_image_proxy_set_image (image_proxy,
                                  g_value_get_object (value));
      break;

    case PROP_SHOW_ALL:
      pika_image_proxy_set_show_all (image_proxy,
                                     g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_image_proxy_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (object);

  switch (property_id)
    {
    case PROP_IMAGE:
      g_value_set_object (value,
                          pika_image_proxy_get_image (image_proxy));
      break;

    case PROP_SHOW_ALL:
      g_value_set_boolean (value,
                           pika_image_proxy_get_show_all (image_proxy));
      break;

    case PROP_BUFFER:
      g_value_set_object (value,
                          pika_pickable_get_buffer (
                            PIKA_PICKABLE (image_proxy)));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gboolean
pika_image_proxy_get_size (PikaViewable *viewable,
                           gint         *width,
                           gint         *height)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable);

  *width  = image_proxy->priv->bounding_box.width;
  *height = image_proxy->priv->bounding_box.height;

  return TRUE;
}

static void
pika_image_proxy_get_preview_size (PikaViewable *viewable,
                                   gint          size,
                                   gboolean      is_popup,
                                   gboolean      dot_for_dot,
                                   gint         *width,
                                   gint         *height)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable);
  PikaImage      *image       = image_proxy->priv->image;
  gdouble         xres;
  gdouble         yres;
  gint            viewable_width;
  gint            viewable_height;

  pika_image_get_resolution (image, &xres, &yres);

  pika_viewable_get_size (viewable, &viewable_width, &viewable_height);

  pika_viewable_calc_preview_size (viewable_width,
                                   viewable_height,
                                   size,
                                   size,
                                   dot_for_dot,
                                   xres,
                                   yres,
                                   width,
                                   height,
                                   NULL);
}

static gboolean
pika_image_proxy_get_popup_size (PikaViewable *viewable,
                                 gint          width,
                                 gint          height,
                                 gboolean      dot_for_dot,
                                 gint         *popup_width,
                                 gint         *popup_height)
{
  gint viewable_width;
  gint viewable_height;

  pika_viewable_get_size (viewable, &viewable_width, &viewable_height);

  if (viewable_width > width || viewable_height > height)
    {
      gboolean scaling_up;

      pika_viewable_calc_preview_size (viewable_width,
                                       viewable_height,
                                       width  * 2,
                                       height * 2,
                                       dot_for_dot, 1.0, 1.0,
                                       popup_width,
                                       popup_height,
                                       &scaling_up);

      if (scaling_up)
        {
          *popup_width  = viewable_width;
          *popup_height = viewable_height;
        }

      return TRUE;
    }

  return FALSE;
}

static PikaTempBuf *
pika_image_proxy_get_new_preview (PikaViewable *viewable,
                                  PikaContext  *context,
                                  gint          width,
                                  gint          height)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable);
  PikaImage      *image       = image_proxy->priv->image;
  PikaPickable   *pickable;
  const Babl     *format;
  GeglRectangle   bounding_box;
  PikaTempBuf    *buf;
  gdouble         scale_x;
  gdouble         scale_y;
  gdouble         scale;

  pickable     = pika_image_proxy_get_pickable     (image_proxy);
  bounding_box = pika_image_proxy_get_bounding_box (image_proxy);

  scale_x = (gdouble) width  / (gdouble) bounding_box.width;
  scale_y = (gdouble) height / (gdouble) bounding_box.height;

  scale   = MIN (scale_x, scale_y);

  format = pika_image_get_preview_format (image);

  buf = pika_temp_buf_new (width, height, format);

  gegl_buffer_get (pika_pickable_get_buffer (pickable),
                   GEGL_RECTANGLE (bounding_box.x * scale,
                                   bounding_box.y * scale,
                                   width,
                                   height),
                   scale,
                   pika_temp_buf_get_format (buf),
                   pika_temp_buf_get_data (buf),
                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);

  return buf;
}

static GdkPixbuf *
pika_image_proxy_get_new_pixbuf (PikaViewable *viewable,
                                 PikaContext  *context,
                                 gint          width,
                                 gint          height)
{
  PikaImageProxy     *image_proxy = PIKA_IMAGE_PROXY (viewable);
  PikaImage          *image       = image_proxy->priv->image;
  PikaPickable       *pickable;
  GeglRectangle       bounding_box;
  GdkPixbuf          *pixbuf;
  gdouble             scale_x;
  gdouble             scale_y;
  gdouble             scale;
  PikaColorTransform *transform;

  pickable     = pika_image_proxy_get_pickable     (image_proxy);
  bounding_box = pika_image_proxy_get_bounding_box (image_proxy);

  scale_x = (gdouble) width  / (gdouble) bounding_box.width;
  scale_y = (gdouble) height / (gdouble) bounding_box.height;

  scale   = MIN (scale_x, scale_y);

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
                           width, height);

  transform = pika_image_get_color_transform_to_srgb_u8 (image);

  if (transform)
    {
      PikaTempBuf *temp_buf;
      GeglBuffer  *src_buf;
      GeglBuffer  *dest_buf;

      temp_buf = pika_temp_buf_new (width, height,
                                    pika_pickable_get_format (pickable));

      gegl_buffer_get (pika_pickable_get_buffer (pickable),
                       GEGL_RECTANGLE (bounding_box.x * scale,
                                       bounding_box.y * scale,
                                       width,
                                       height),
                       scale,
                       pika_temp_buf_get_format (temp_buf),
                       pika_temp_buf_get_data (temp_buf),
                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);

      src_buf  = pika_temp_buf_create_buffer (temp_buf);
      dest_buf = pika_pixbuf_create_buffer (pixbuf);

      pika_temp_buf_unref (temp_buf);

      pika_color_transform_process_buffer (transform,
                                           src_buf,
                                           GEGL_RECTANGLE (0, 0,
                                                           width, height),
                                           dest_buf,
                                           GEGL_RECTANGLE (0, 0, 0, 0));

      g_object_unref (src_buf);
      g_object_unref (dest_buf);
    }
  else
    {
      gegl_buffer_get (pika_pickable_get_buffer (pickable),
                       GEGL_RECTANGLE (bounding_box.x * scale,
                                       bounding_box.y * scale,
                                       width,
                                       height),
                       scale,
                       pika_pixbuf_get_format (pixbuf),
                       gdk_pixbuf_get_pixels (pixbuf),
                       gdk_pixbuf_get_rowstride (pixbuf),
                       GEGL_ABYSS_CLAMP);
    }

  return pixbuf;
}

static gchar *
pika_image_proxy_get_description (PikaViewable  *viewable,
                                  gchar        **tooltip)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable);
  PikaImage      *image       = image_proxy->priv->image;

  if (tooltip)
    *tooltip = g_strdup (pika_image_get_display_path (image));

  return g_strdup_printf ("%s-%d",
                          pika_image_get_display_name (image),
                          pika_image_get_id (image));
}

static void
pika_image_proxy_flush (PikaPickable *pickable)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  pika_pickable_flush (proxy_pickable);
}

static const Babl *
pika_image_proxy_get_format (PikaPickable *pickable)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  return pika_pickable_get_format (proxy_pickable);
}

static const Babl *
pika_image_proxy_get_format_with_alpha (PikaPickable *pickable)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  return pika_pickable_get_format_with_alpha (proxy_pickable);
}

static GeglBuffer *
pika_image_proxy_get_buffer (PikaPickable *pickable)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  return pika_pickable_get_buffer (proxy_pickable);
}

static gboolean
pika_image_proxy_get_pixel_at (PikaPickable *pickable,
                               gint          x,
                               gint          y,
                               const Babl   *format,
                               gpointer      pixel)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  return pika_pickable_get_pixel_at (proxy_pickable, x, y, format, pixel);
}

static gdouble
pika_image_proxy_get_opacity_at (PikaPickable *pickable,
                                 gint          x,
                                 gint          y)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  return pika_pickable_get_opacity_at (proxy_pickable, x, y);
}

static void
pika_image_proxy_get_pixel_average (PikaPickable        *pickable,
                                    const GeglRectangle *rect,
                                    const Babl          *format,
                                    gpointer             pixel)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  pika_pickable_get_pixel_average (proxy_pickable, rect, format, pixel);
}

static void
pika_image_proxy_pixel_to_rgb (PikaPickable *pickable,
                               const Babl   *format,
                               gpointer      pixel,
                               PikaRGB      *color)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  pika_pickable_pixel_to_rgb (proxy_pickable, format, pixel, color);
}

static void
pika_image_proxy_rgb_to_pixel (PikaPickable  *pickable,
                               const PikaRGB *color,
                               const Babl    *format,
                               gpointer       pixel)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable);
  PikaPickable   *proxy_pickable;

  proxy_pickable = pika_image_proxy_get_pickable (image_proxy);

  pika_pickable_rgb_to_pixel (proxy_pickable, color, format, pixel);
}

static const guint8 *
pika_image_proxy_get_icc_profile (PikaColorManaged *managed,
                                  gsize            *len)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (managed);

  return pika_color_managed_get_icc_profile (
    PIKA_COLOR_MANAGED (image_proxy->priv->image),
    len);
}

static PikaColorProfile *
pika_image_proxy_get_color_profile (PikaColorManaged *managed)
{
  PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (managed);

  return pika_color_managed_get_color_profile (
    PIKA_COLOR_MANAGED (image_proxy->priv->image));
}

static void
pika_image_proxy_profile_changed (PikaColorManaged *managed)
{
  pika_viewable_invalidate_preview (PIKA_VIEWABLE (managed));
}

static void
pika_image_proxy_image_frozen_notify (PikaImage        *image,
                                      const GParamSpec *pspec,
                                      PikaImageProxy   *image_proxy)
{
  pika_image_proxy_update_frozen (image_proxy);
}

static void
pika_image_proxy_image_invalidate_preview (PikaImage      *image,
                                           PikaImageProxy *image_proxy)
{
  pika_viewable_invalidate_preview (PIKA_VIEWABLE (image_proxy));
}

static void
pika_image_proxy_image_size_changed (PikaImage      *image,
                                     PikaImageProxy *image_proxy)
{
  pika_image_proxy_update_bounding_box (image_proxy);
}

static void
pika_image_proxy_image_bounds_changed (PikaImage      *image,
                                       gint            old_x,
                                       gint            old_y,
                                       PikaImageProxy *image_proxy)
{
  pika_image_proxy_update_bounding_box (image_proxy);
}

static void
pika_image_proxy_image_profile_changed (PikaImage      *image,
                                        PikaImageProxy *image_proxy)
{
  pika_color_managed_profile_changed (PIKA_COLOR_MANAGED (image_proxy));
}

static void
pika_image_proxy_set_image (PikaImageProxy *image_proxy,
                            PikaImage      *image)
{
  if (image_proxy->priv->image)
    {
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        pika_image_proxy_image_frozen_notify,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        pika_image_proxy_image_invalidate_preview,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        pika_image_proxy_image_size_changed,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        pika_image_proxy_image_bounds_changed,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        pika_image_proxy_image_profile_changed,
        image_proxy);

      g_object_unref (image_proxy->priv->image);
    }

  image_proxy->priv->image = image;

  if (image_proxy->priv->image)
    {
      g_object_ref (image_proxy->priv->image);

      g_signal_connect (
        image_proxy->priv->image, "notify::frozen",
        G_CALLBACK (pika_image_proxy_image_frozen_notify),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "invalidate-preview",
        G_CALLBACK (pika_image_proxy_image_invalidate_preview),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "size-changed",
        G_CALLBACK (pika_image_proxy_image_size_changed),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "bounds-changed",
        G_CALLBACK (pika_image_proxy_image_bounds_changed),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "profile-changed",
        G_CALLBACK (pika_image_proxy_image_profile_changed),
        image_proxy);

      pika_image_proxy_update_bounding_box (image_proxy);
      pika_image_proxy_update_frozen (image_proxy);

      pika_viewable_invalidate_preview (PIKA_VIEWABLE (image_proxy));
    }
}

static PikaPickable *
pika_image_proxy_get_pickable (PikaImageProxy *image_proxy)
{
  PikaImage *image = image_proxy->priv->image;

  if (! image_proxy->priv->show_all)
    return PIKA_PICKABLE (image);
  else
    return PIKA_PICKABLE (pika_image_get_projection (image));
}

static void
pika_image_proxy_update_bounding_box (PikaImageProxy *image_proxy)
{
  PikaImage     *image = image_proxy->priv->image;
  GeglRectangle  bounding_box;

  if (pika_viewable_preview_is_frozen (PIKA_VIEWABLE (image_proxy)))
    return;

  if (! image_proxy->priv->show_all)
    {
      bounding_box.x      = 0;
      bounding_box.y      = 0;
      bounding_box.width  = pika_image_get_width  (image);
      bounding_box.height = pika_image_get_height (image);
    }
  else
    {
      bounding_box = pika_projectable_get_bounding_box (
        PIKA_PROJECTABLE (image));
    }

  if (! gegl_rectangle_equal (&bounding_box,
                              &image_proxy->priv->bounding_box))
    {
      image_proxy->priv->bounding_box = bounding_box;

      pika_viewable_size_changed (PIKA_VIEWABLE (image_proxy));
    }
}

static void
pika_image_proxy_update_frozen (PikaImageProxy *image_proxy)
{
  gboolean frozen;

  frozen = pika_viewable_preview_is_frozen (
    PIKA_VIEWABLE (image_proxy->priv->image));

  if (frozen != image_proxy->priv->frozen)
    {
      image_proxy->priv->frozen = frozen;

      if (frozen)
        {
          pika_viewable_preview_freeze (PIKA_VIEWABLE (image_proxy));
        }
      else
        {
          pika_viewable_preview_thaw (PIKA_VIEWABLE (image_proxy));

          pika_image_proxy_update_bounding_box (image_proxy);
        }
    }
}


/*  public functions  */


PikaImageProxy *
pika_image_proxy_new (PikaImage *image)
{
  g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);

  return g_object_new (PIKA_TYPE_IMAGE_PROXY,
                       "image", image,
                       NULL);
}

PikaImage *
pika_image_proxy_get_image (PikaImageProxy *image_proxy)
{
  g_return_val_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy), NULL);

  return image_proxy->priv->image;
}

void
pika_image_proxy_set_show_all (PikaImageProxy *image_proxy,
                               gboolean        show_all)
{
  g_return_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy));

  if (show_all != image_proxy->priv->show_all)
    {
      image_proxy->priv->show_all = show_all;

      pika_image_proxy_update_bounding_box (image_proxy);
    }
}

gboolean
pika_image_proxy_get_show_all (PikaImageProxy *image_proxy)
{
  g_return_val_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy), FALSE);

  return image_proxy->priv->show_all;
}

GeglRectangle
pika_image_proxy_get_bounding_box (PikaImageProxy *image_proxy)
{
  g_return_val_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy),
                        *GEGL_RECTANGLE (0, 0, 0, 0));

  return image_proxy->priv->bounding_box;
}
