/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpimageproxy.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 "libgimpcolor/gimpcolor.h"

#include "core-types.h"

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

#include "gimpimage.h"
#include "gimpimage-color-profile.h"
#include "gimpimage-preview.h"
#include "gimpimageproxy.h"
#include "gimppickable.h"
#include "gimpprojectable.h"
#include "gimptempbuf.h"


enum
{
  PROP_0,
  PROP_IMAGE,
  PROP_SHOW_ALL,
  PROP_BUFFER
};


struct _GimpImageProxyPrivate
{
  GimpImage     *image;
  gboolean       show_all;

  GeglRectangle  bounding_box;
  gboolean       frozen;
};


/*  local function prototypes  */

static void               gimp_image_proxy_pickable_iface_init      (GimpPickableInterface      *iface);
static void               gimp_image_proxy_color_managed_iface_init (GimpColorManagedInterface  *iface);

static void               gimp_image_proxy_finalize                 (GObject                    *object);
static void               gimp_image_proxy_set_property             (GObject                    *object,
                                                                     guint                       property_id,
                                                                     const GValue               *value,
                                                                     GParamSpec                 *pspec);
static void               gimp_image_proxy_get_property             (GObject                    *object,
                                                                     guint                       property_id,
                                                                     GValue                     *value,
                                                                     GParamSpec                 *pspec);

static gboolean           gimp_image_proxy_get_size                 (GimpViewable               *viewable,
                                                                     gint                       *width,
                                                                     gint                       *height);
static void               gimp_image_proxy_get_preview_size         (GimpViewable               *viewable,
                                                                     gint                        size,
                                                                     gboolean                    is_popup,
                                                                     gboolean                    dot_for_dot,
                                                                     gint                       *width,
                                                                     gint                       *height);
static gboolean           gimp_image_proxy_get_popup_size           (GimpViewable               *viewable,
                                                                     gint                        width,
                                                                     gint                        height,
                                                                     gboolean                    dot_for_dot,
                                                                     gint                       *popup_width,
                                                                     gint                       *popup_height);
static GimpTempBuf      * gimp_image_proxy_get_new_preview          (GimpViewable               *viewable,
                                                                     GimpContext                *context,
                                                                     gint                        width,
                                                                     gint                        height);
static GdkPixbuf        * gimp_image_proxy_get_new_pixbuf           (GimpViewable               *viewable,
                                                                     GimpContext                *context,
                                                                     gint                        width,
                                                                     gint                        height);
static gchar            * gimp_image_proxy_get_description          (GimpViewable               *viewable,
                                                                     gchar                     **tooltip);

static void               gimp_image_proxy_flush                    (GimpPickable               *pickable);
static const Babl       * gimp_image_proxy_get_format               (GimpPickable               *pickable);
static const Babl       * gimp_image_proxy_get_format_with_alpha    (GimpPickable               *pickable);
static GeglBuffer       * gimp_image_proxy_get_buffer               (GimpPickable               *pickable);
static gboolean           gimp_image_proxy_get_pixel_at             (GimpPickable               *pickable,
                                                                     gint                        x,
                                                                     gint                        y,
                                                                     const Babl                 *format,
                                                                     gpointer                    pixel);
static gdouble            gimp_image_proxy_get_opacity_at           (GimpPickable               *pickable,
                                                                     gint                        x,
                                                                     gint                        y);
static void               gimp_image_proxy_get_pixel_average        (GimpPickable               *pickable,
                                                                     const GeglRectangle        *rect,
                                                                     const Babl                 *format,
                                                                     gpointer                    pixel);
static void               gimp_image_proxy_pixel_to_srgb            (GimpPickable               *pickable,
                                                                     const Babl                 *format,
                                                                     gpointer                    pixel,
                                                                     GimpRGB                    *color);
static void               gimp_image_proxy_srgb_to_pixel            (GimpPickable               *pickable,
                                                                     const GimpRGB              *color,
                                                                     const Babl                 *format,
                                                                     gpointer                    pixel);

static const guint8     * gimp_image_proxy_get_icc_profile          (GimpColorManaged           *managed,
                                                                     gsize                      *len);
static GimpColorProfile * gimp_image_proxy_get_color_profile        (GimpColorManaged           *managed);
static void               gimp_image_proxy_profile_changed          (GimpColorManaged           *managed);

static void               gimp_image_proxy_image_frozen_notify      (GimpImage                  *image,
                                                                     const GParamSpec           *pspec,
                                                                     GimpImageProxy             *image_proxy);
static void               gimp_image_proxy_image_invalidate_preview (GimpImage                  *image,
                                                                     GimpImageProxy             *image_proxy);
static void               gimp_image_proxy_image_size_changed       (GimpImage                  *image,
                                                                     GimpImageProxy             *image_proxy);
static void               gimp_image_proxy_image_bounds_changed     (GimpImage                  *image,
                                                                     gint                        old_x,
                                                                     gint                        old_y,
                                                                     GimpImageProxy             *image_proxy);
static void               gimp_image_proxy_image_profile_changed    (GimpImage                  *image,
                                                                     GimpImageProxy             *image_proxy);

static void               gimp_image_proxy_set_image                (GimpImageProxy             *image_proxy,
                                                                     GimpImage                  *image);
static GimpPickable     * gimp_image_proxy_get_pickable             (GimpImageProxy             *image_proxy);
static void               gimp_image_proxy_update_bounding_box      (GimpImageProxy             *image_proxy);
static void               gimp_image_proxy_update_frozen            (GimpImageProxy             *image_proxy);


G_DEFINE_TYPE_WITH_CODE (GimpImageProxy, gimp_image_proxy, GIMP_TYPE_VIEWABLE,
                         G_ADD_PRIVATE (GimpImageProxy)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
                                                gimp_image_proxy_pickable_iface_init)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
                                                gimp_image_proxy_color_managed_iface_init))

#define parent_class gimp_image_proxy_parent_class


/*  private functions  */


static void
gimp_image_proxy_class_init (GimpImageProxyClass *klass)
{
  GObjectClass      *object_class   = G_OBJECT_CLASS (klass);
  GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);

  object_class->finalize            = gimp_image_proxy_finalize;
  object_class->set_property        = gimp_image_proxy_set_property;
  object_class->get_property        = gimp_image_proxy_get_property;

  viewable_class->default_icon_name = "gimp-image";
  viewable_class->get_size          = gimp_image_proxy_get_size;
  viewable_class->get_preview_size  = gimp_image_proxy_get_preview_size;
  viewable_class->get_popup_size    = gimp_image_proxy_get_popup_size;
  viewable_class->get_new_preview   = gimp_image_proxy_get_new_preview;
  viewable_class->get_new_pixbuf    = gimp_image_proxy_get_new_pixbuf;
  viewable_class->get_description   = gimp_image_proxy_get_description;

  g_object_class_install_property (object_class, PROP_IMAGE,
                                   g_param_spec_object ("image",
                                                        NULL, NULL,
                                                        GIMP_TYPE_IMAGE,
                                                        GIMP_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,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

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

static void
gimp_image_proxy_pickable_iface_init (GimpPickableInterface *iface)
{
  iface->flush                 = gimp_image_proxy_flush;
  iface->get_image             = (gpointer) gimp_image_proxy_get_image;
  iface->get_format            = gimp_image_proxy_get_format;
  iface->get_format_with_alpha = gimp_image_proxy_get_format_with_alpha;
  iface->get_buffer            = gimp_image_proxy_get_buffer;
  iface->get_pixel_at          = gimp_image_proxy_get_pixel_at;
  iface->get_opacity_at        = gimp_image_proxy_get_opacity_at;
  iface->get_pixel_average     = gimp_image_proxy_get_pixel_average;
  iface->pixel_to_srgb         = gimp_image_proxy_pixel_to_srgb;
  iface->srgb_to_pixel         = gimp_image_proxy_srgb_to_pixel;
}

static void
gimp_image_proxy_color_managed_iface_init (GimpColorManagedInterface *iface)
{
  iface->get_icc_profile   = gimp_image_proxy_get_icc_profile;
  iface->get_color_profile = gimp_image_proxy_get_color_profile;
  iface->profile_changed   = gimp_image_proxy_profile_changed;
}

static void
gimp_image_proxy_init (GimpImageProxy *image_proxy)
{
  image_proxy->priv = gimp_image_proxy_get_instance_private (image_proxy);
}

static void
gimp_image_proxy_finalize (GObject *object)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (object);

  gimp_image_proxy_set_image (image_proxy, NULL);

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

static void
gimp_image_proxy_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (object);

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

    case PROP_SHOW_ALL:
      gimp_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
gimp_image_proxy_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (object);

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

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

    case PROP_BUFFER:
      g_value_set_object (value,
                          gimp_pickable_get_buffer (
                            GIMP_PICKABLE (image_proxy)));
      break;

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

static gboolean
gimp_image_proxy_get_size (GimpViewable *viewable,
                           gint         *width,
                           gint         *height)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);

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

  return TRUE;
}

static void
gimp_image_proxy_get_preview_size (GimpViewable *viewable,
                                   gint          size,
                                   gboolean      is_popup,
                                   gboolean      dot_for_dot,
                                   gint         *width,
                                   gint         *height)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
  GimpImage      *image       = image_proxy->priv->image;
  gdouble         xres;
  gdouble         yres;
  gint            viewable_width;
  gint            viewable_height;

  gimp_image_get_resolution (image, &xres, &yres);

  gimp_viewable_get_size (viewable, &viewable_width, &viewable_height);

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

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

  gimp_viewable_get_size (viewable, &viewable_width, &viewable_height);

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

      gimp_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 GimpTempBuf *
gimp_image_proxy_get_new_preview (GimpViewable *viewable,
                                  GimpContext  *context,
                                  gint          width,
                                  gint          height)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
  GimpImage      *image       = image_proxy->priv->image;
  GimpPickable   *pickable;
  const Babl     *format;
  GeglRectangle   bounding_box;
  GimpTempBuf    *buf;
  gdouble         scale_x;
  gdouble         scale_y;
  gdouble         scale;

  pickable     = gimp_image_proxy_get_pickable     (image_proxy);
  bounding_box = gimp_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 = gimp_image_get_preview_format (image);

  buf = gimp_temp_buf_new (width, height, format);

  gegl_buffer_get (gimp_pickable_get_buffer (pickable),
                   GEGL_RECTANGLE (bounding_box.x * scale,
                                   bounding_box.y * scale,
                                   width,
                                   height),
                   scale,
                   gimp_temp_buf_get_format (buf),
                   gimp_temp_buf_get_data (buf),
                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);

  return buf;
}

static GdkPixbuf *
gimp_image_proxy_get_new_pixbuf (GimpViewable *viewable,
                                 GimpContext  *context,
                                 gint          width,
                                 gint          height)
{
  GimpImageProxy     *image_proxy = GIMP_IMAGE_PROXY (viewable);
  GimpImage          *image       = image_proxy->priv->image;
  GimpPickable       *pickable;
  GeglRectangle       bounding_box;
  GdkPixbuf          *pixbuf;
  gdouble             scale_x;
  gdouble             scale_y;
  gdouble             scale;
  GimpColorTransform *transform;

  pickable     = gimp_image_proxy_get_pickable     (image_proxy);
  bounding_box = gimp_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 = gimp_image_get_color_transform_to_srgb_u8 (image);

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

      temp_buf = gimp_temp_buf_new (width, height,
                                    gimp_pickable_get_format (pickable));

      gegl_buffer_get (gimp_pickable_get_buffer (pickable),
                       GEGL_RECTANGLE (bounding_box.x * scale,
                                       bounding_box.y * scale,
                                       width,
                                       height),
                       scale,
                       gimp_temp_buf_get_format (temp_buf),
                       gimp_temp_buf_get_data (temp_buf),
                       GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);

      src_buf  = gimp_temp_buf_create_buffer (temp_buf);
      dest_buf = gimp_pixbuf_create_buffer (pixbuf);

      gimp_temp_buf_unref (temp_buf);

      gimp_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 (gimp_pickable_get_buffer (pickable),
                       GEGL_RECTANGLE (bounding_box.x * scale,
                                       bounding_box.y * scale,
                                       width,
                                       height),
                       scale,
                       gimp_pixbuf_get_format (pixbuf),
                       gdk_pixbuf_get_pixels (pixbuf),
                       gdk_pixbuf_get_rowstride (pixbuf),
                       GEGL_ABYSS_CLAMP);
    }

  return pixbuf;
}

static gchar *
gimp_image_proxy_get_description (GimpViewable  *viewable,
                                  gchar        **tooltip)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
  GimpImage      *image       = image_proxy->priv->image;

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

  return g_strdup_printf ("%s-%d",
                          gimp_image_get_display_name (image),
                          gimp_image_get_ID (image));
}

static void
gimp_image_proxy_flush (GimpPickable *pickable)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

  gimp_pickable_flush (proxy_pickable);
}

static const Babl *
gimp_image_proxy_get_format (GimpPickable *pickable)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

  return gimp_pickable_get_format (proxy_pickable);
}

static const Babl *
gimp_image_proxy_get_format_with_alpha (GimpPickable *pickable)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

  return gimp_pickable_get_format_with_alpha (proxy_pickable);
}

static GeglBuffer *
gimp_image_proxy_get_buffer (GimpPickable *pickable)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

  return gimp_pickable_get_buffer (proxy_pickable);
}

static gboolean
gimp_image_proxy_get_pixel_at (GimpPickable *pickable,
                               gint          x,
                               gint          y,
                               const Babl   *format,
                               gpointer      pixel)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

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

static gdouble
gimp_image_proxy_get_opacity_at (GimpPickable *pickable,
                                 gint          x,
                                 gint          y)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

  return gimp_pickable_get_opacity_at (proxy_pickable, x, y);
}

static void
gimp_image_proxy_get_pixel_average (GimpPickable        *pickable,
                                    const GeglRectangle *rect,
                                    const Babl          *format,
                                    gpointer             pixel)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

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

static void
gimp_image_proxy_pixel_to_srgb (GimpPickable *pickable,
                                const Babl   *format,
                                gpointer      pixel,
                                GimpRGB      *color)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

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

static void
gimp_image_proxy_srgb_to_pixel (GimpPickable  *pickable,
                                const GimpRGB *color,
                                const Babl    *format,
                                gpointer       pixel)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
  GimpPickable   *proxy_pickable;

  proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);

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

static const guint8 *
gimp_image_proxy_get_icc_profile (GimpColorManaged *managed,
                                  gsize            *len)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (managed);

  return gimp_color_managed_get_icc_profile (
    GIMP_COLOR_MANAGED (image_proxy->priv->image),
    len);
}

static GimpColorProfile *
gimp_image_proxy_get_color_profile (GimpColorManaged *managed)
{
  GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (managed);

  return gimp_color_managed_get_color_profile (
    GIMP_COLOR_MANAGED (image_proxy->priv->image));
}

static void
gimp_image_proxy_profile_changed (GimpColorManaged *managed)
{
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (managed));
}

static void
gimp_image_proxy_image_frozen_notify (GimpImage        *image,
                                      const GParamSpec *pspec,
                                      GimpImageProxy   *image_proxy)
{
  gimp_image_proxy_update_frozen (image_proxy);
}

static void
gimp_image_proxy_image_invalidate_preview (GimpImage      *image,
                                           GimpImageProxy *image_proxy)
{
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image_proxy));
}

static void
gimp_image_proxy_image_size_changed (GimpImage      *image,
                                     GimpImageProxy *image_proxy)
{
  gimp_image_proxy_update_bounding_box (image_proxy);
}

static void
gimp_image_proxy_image_bounds_changed (GimpImage      *image,
                                       gint            old_x,
                                       gint            old_y,
                                       GimpImageProxy *image_proxy)
{
  gimp_image_proxy_update_bounding_box (image_proxy);
}

static void
gimp_image_proxy_image_profile_changed (GimpImage      *image,
                                        GimpImageProxy *image_proxy)
{
  gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image_proxy));
}

static void
gimp_image_proxy_set_image (GimpImageProxy *image_proxy,
                            GimpImage      *image)
{
  if (image_proxy->priv->image)
    {
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        gimp_image_proxy_image_frozen_notify,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        gimp_image_proxy_image_invalidate_preview,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        gimp_image_proxy_image_size_changed,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        gimp_image_proxy_image_bounds_changed,
        image_proxy);
      g_signal_handlers_disconnect_by_func (
        image_proxy->priv->image,
        gimp_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 (gimp_image_proxy_image_frozen_notify),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "invalidate-preview",
        G_CALLBACK (gimp_image_proxy_image_invalidate_preview),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "size-changed",
        G_CALLBACK (gimp_image_proxy_image_size_changed),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "bounds-changed",
        G_CALLBACK (gimp_image_proxy_image_bounds_changed),
        image_proxy);
      g_signal_connect (
        image_proxy->priv->image, "profile-changed",
        G_CALLBACK (gimp_image_proxy_image_profile_changed),
        image_proxy);

      gimp_image_proxy_update_bounding_box (image_proxy);
      gimp_image_proxy_update_frozen (image_proxy);

      gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image_proxy));
    }
}

static GimpPickable *
gimp_image_proxy_get_pickable (GimpImageProxy *image_proxy)
{
  GimpImage *image = image_proxy->priv->image;

  if (! image_proxy->priv->show_all)
    return GIMP_PICKABLE (image);
  else
    return GIMP_PICKABLE (gimp_image_get_projection (image));
}

static void
gimp_image_proxy_update_bounding_box (GimpImageProxy *image_proxy)
{
  GimpImage     *image = image_proxy->priv->image;
  GeglRectangle  bounding_box;

  if (gimp_viewable_preview_is_frozen (GIMP_VIEWABLE (image_proxy)))
    return;

  if (! image_proxy->priv->show_all)
    {
      bounding_box.x      = 0;
      bounding_box.y      = 0;
      bounding_box.width  = gimp_image_get_width  (image);
      bounding_box.height = gimp_image_get_height (image);
    }
  else
    {
      bounding_box = gimp_projectable_get_bounding_box (
        GIMP_PROJECTABLE (image));
    }

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

      gimp_viewable_size_changed (GIMP_VIEWABLE (image_proxy));
    }
}

static void
gimp_image_proxy_update_frozen (GimpImageProxy *image_proxy)
{
  gboolean frozen;

  frozen = gimp_viewable_preview_is_frozen (
    GIMP_VIEWABLE (image_proxy->priv->image));

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

      if (frozen)
        {
          gimp_viewable_preview_freeze (GIMP_VIEWABLE (image_proxy));
        }
      else
        {
          gimp_viewable_preview_thaw (GIMP_VIEWABLE (image_proxy));

          gimp_image_proxy_update_bounding_box (image_proxy);
        }
    }
}


/*  public functions  */


GimpImageProxy *
gimp_image_proxy_new (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

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

GimpImage *
gimp_image_proxy_get_image (GimpImageProxy *image_proxy)
{
  g_return_val_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy), NULL);

  return image_proxy->priv->image;
}

void
gimp_image_proxy_set_show_all (GimpImageProxy *image_proxy,
                               gboolean        show_all)
{
  g_return_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy));

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

      gimp_image_proxy_update_bounding_box (image_proxy);
    }
}

gboolean
gimp_image_proxy_get_show_all (GimpImageProxy *image_proxy)
{
  g_return_val_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy), FALSE);

  return image_proxy->priv->show_all;
}

GeglRectangle
gimp_image_proxy_get_bounding_box (GimpImageProxy *image_proxy)
{
  g_return_val_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy),
                        *GEGL_RECTANGLE (0, 0, 0, 0));

  return image_proxy->priv->bounding_box;
}
