summaryrefslogtreecommitdiffstats
path: root/app/core/gimpdrawable-preview.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpdrawable-preview.c')
-rw-r--r--app/core/gimpdrawable-preview.c492
1 files changed, 492 insertions, 0 deletions
diff --git a/app/core/gimpdrawable-preview.c b/app/core/gimpdrawable-preview.c
new file mode 100644
index 0000000..28ae1e2
--- /dev/null
+++ b/app/core/gimpdrawable-preview.c
@@ -0,0 +1,492 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimptilehandlervalidate.h"
+
+#include "gimp.h"
+#include "gimp-parallel.h"
+#include "gimp-utils.h"
+#include "gimpasync.h"
+#include "gimpchannel.h"
+#include "gimpchunkiterator.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpdrawable-preview.h"
+#include "gimpdrawable-private.h"
+#include "gimplayer.h"
+#include "gimptempbuf.h"
+
+#include "gimp-priorities.h"
+
+
+typedef struct
+{
+ const Babl *format;
+ GeglBuffer *buffer;
+ GeglRectangle rect;
+ gdouble scale;
+
+ GimpChunkIterator *iter;
+} SubPreviewData;
+
+
+/* local function prototypes */
+
+static SubPreviewData * sub_preview_data_new (const Babl *format,
+ GeglBuffer *buffer,
+ const GeglRectangle *rect,
+ gdouble scale);
+static void sub_preview_data_free (SubPreviewData *data);
+
+
+
+/* private functions */
+
+
+static SubPreviewData *
+sub_preview_data_new (const Babl *format,
+ GeglBuffer *buffer,
+ const GeglRectangle *rect,
+ gdouble scale)
+{
+ SubPreviewData *data = g_slice_new (SubPreviewData);
+
+ data->format = format;
+ data->buffer = g_object_ref (buffer);
+ data->rect = *rect;
+ data->scale = scale;
+
+ data->iter = NULL;
+
+ return data;
+}
+
+static void
+sub_preview_data_free (SubPreviewData *data)
+{
+ g_object_unref (data->buffer);
+
+ if (data->iter)
+ gimp_chunk_iterator_stop (data->iter, TRUE);
+
+ g_slice_free (SubPreviewData, data);
+}
+
+
+/* public functions */
+
+
+GimpTempBuf *
+gimp_drawable_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ return gimp_drawable_get_sub_preview (GIMP_DRAWABLE (viewable),
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ width,
+ height);
+}
+
+GdkPixbuf *
+gimp_drawable_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ return gimp_drawable_get_sub_pixbuf (GIMP_DRAWABLE (viewable),
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ width,
+ height);
+}
+
+const Babl *
+gimp_drawable_get_preview_format (GimpDrawable *drawable)
+{
+ gboolean alpha;
+ gboolean linear;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ alpha = gimp_drawable_has_alpha (drawable);
+ linear = gimp_drawable_get_linear (drawable);
+
+ switch (gimp_drawable_get_base_type (drawable))
+ {
+ case GIMP_GRAY:
+ return gimp_babl_format (GIMP_GRAY,
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_U8,
+ linear),
+ alpha);
+
+ case GIMP_RGB:
+ return gimp_babl_format (GIMP_RGB,
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_U8,
+ linear),
+ alpha);
+
+ case GIMP_INDEXED:
+ if (alpha)
+ return babl_format ("R'G'B'A u8");
+ else
+ return babl_format ("R'G'B' u8");
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+GimpTempBuf *
+gimp_drawable_get_sub_preview (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GeglBuffer *buffer;
+ GimpTempBuf *preview;
+ gdouble scale;
+ gint scaled_x;
+ gint scaled_y;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (src_x >= 0, NULL);
+ g_return_val_if_fail (src_y >= 0, NULL);
+ g_return_val_if_fail (src_width > 0, NULL);
+ g_return_val_if_fail (src_height > 0, NULL);
+ g_return_val_if_fail (dest_width > 0, NULL);
+ g_return_val_if_fail (dest_height > 0, NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail ((src_x + src_width) <= gimp_item_get_width (item), NULL);
+ g_return_val_if_fail ((src_y + src_height) <= gimp_item_get_height (item), NULL);
+
+ image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+
+ preview = gimp_temp_buf_new (dest_width, dest_height,
+ gimp_drawable_get_preview_format (drawable));
+
+ scale = MIN ((gdouble) dest_width / (gdouble) src_width,
+ (gdouble) dest_height / (gdouble) src_height);
+
+ scaled_x = RINT ((gdouble) src_x * scale);
+ scaled_y = RINT ((gdouble) src_y * scale);
+
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y, dest_width, dest_height),
+ scale,
+ gimp_temp_buf_get_format (preview),
+ gimp_temp_buf_get_data (preview),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ return preview;
+}
+
+GdkPixbuf *
+gimp_drawable_get_sub_pixbuf (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GeglBuffer *buffer;
+ GdkPixbuf *pixbuf;
+ gdouble scale;
+ gint scaled_x;
+ gint scaled_y;
+ GimpColorTransform *transform;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (src_x >= 0, NULL);
+ g_return_val_if_fail (src_y >= 0, NULL);
+ g_return_val_if_fail (src_width > 0, NULL);
+ g_return_val_if_fail (src_height > 0, NULL);
+ g_return_val_if_fail (dest_width > 0, NULL);
+ g_return_val_if_fail (dest_height > 0, NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail ((src_x + src_width) <= gimp_item_get_width (item), NULL);
+ g_return_val_if_fail ((src_y + src_height) <= gimp_item_get_height (item), NULL);
+
+ image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ dest_width, dest_height);
+
+ scale = MIN ((gdouble) dest_width / (gdouble) src_width,
+ (gdouble) dest_height / (gdouble) src_height);
+
+ scaled_x = RINT ((gdouble) src_x * scale);
+ scaled_y = RINT ((gdouble) src_y * scale);
+
+ 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 (dest_width, dest_height,
+ gimp_drawable_get_format (drawable));
+
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y,
+ dest_width, dest_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,
+ dest_width, dest_height),
+ dest_buf,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+
+ g_object_unref (src_buf);
+ g_object_unref (dest_buf);
+ }
+ else
+ {
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y,
+ dest_width, dest_height),
+ scale,
+ gimp_pixbuf_get_format (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ GEGL_ABYSS_CLAMP);
+ }
+
+ return pixbuf;
+}
+
+static void
+gimp_drawable_get_sub_preview_async_func (GimpAsync *async,
+ SubPreviewData *data)
+{
+ GimpTempBuf *preview;
+ GimpTileHandlerValidate *validate;
+
+ preview = gimp_temp_buf_new (data->rect.width, data->rect.height,
+ data->format);
+
+ validate = gimp_tile_handler_validate_get_assigned (data->buffer);
+
+ if (validate)
+ {
+ if (! data->iter)
+ {
+ cairo_region_t *region;
+ cairo_rectangle_int_t rect;
+
+ rect.x = floor (data->rect.x / data->scale);
+ rect.y = floor (data->rect.y / data->scale);
+ rect.width = ceil ((data->rect.x + data->rect.width) /
+ data->scale) - rect.x;
+ rect.height = ceil ((data->rect.x + data->rect.height) /
+ data->scale) - rect.y;
+
+ region = cairo_region_copy (validate->dirty_region);
+
+ cairo_region_intersect_rectangle (region, &rect);
+
+ data->iter = gimp_chunk_iterator_new (region);
+ }
+
+ if (gimp_chunk_iterator_next (data->iter))
+ {
+ GeglRectangle rect;
+
+ gimp_tile_handler_validate_begin_validate (validate);
+
+ while (gimp_chunk_iterator_get_rect (data->iter, &rect))
+ {
+ gimp_tile_handler_validate_validate (validate,
+ data->buffer, &rect,
+ FALSE, FALSE);
+ }
+
+ gimp_tile_handler_validate_end_validate (validate);
+
+ return;
+ }
+
+ data->iter = NULL;
+ }
+
+ gegl_buffer_get (data->buffer, &data->rect, data->scale,
+ gimp_temp_buf_get_format (preview),
+ gimp_temp_buf_get_data (preview),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ sub_preview_data_free (data);
+
+ gimp_async_finish_full (async,
+ preview,
+ (GDestroyNotify) gimp_temp_buf_unref);
+}
+
+GimpAsync *
+gimp_drawable_get_sub_preview_async (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GeglBuffer *buffer;
+ SubPreviewData *data;
+ gdouble scale;
+ gint scaled_x;
+ gint scaled_y;
+ static gint no_async_drawable_previews = -1;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (src_x >= 0, NULL);
+ g_return_val_if_fail (src_y >= 0, NULL);
+ g_return_val_if_fail (src_width > 0, NULL);
+ g_return_val_if_fail (src_height > 0, NULL);
+ g_return_val_if_fail (dest_width > 0, NULL);
+ g_return_val_if_fail (dest_height > 0, NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail ((src_x + src_width) <= gimp_item_get_width (item), NULL);
+ g_return_val_if_fail ((src_y + src_height) <= gimp_item_get_height (item), NULL);
+
+ image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+
+ if (no_async_drawable_previews < 0)
+ {
+ no_async_drawable_previews =
+ (g_getenv ("GIMP_NO_ASYNC_DRAWABLE_PREVIEWS") != NULL);
+ }
+
+ if (no_async_drawable_previews)
+ {
+ GimpAsync *async = gimp_async_new ();
+
+ gimp_async_finish_full (async,
+ gimp_drawable_get_sub_preview (drawable,
+ src_x,
+ src_y,
+ src_width,
+ src_height,
+ dest_width,
+ dest_height),
+ (GDestroyNotify) gimp_temp_buf_unref);
+
+ return async;
+ }
+
+ scale = MIN ((gdouble) dest_width / (gdouble) src_width,
+ (gdouble) dest_height / (gdouble) src_height);
+
+ scaled_x = RINT ((gdouble) src_x * scale);
+ scaled_y = RINT ((gdouble) src_y * scale);
+
+ data = sub_preview_data_new (
+ gimp_drawable_get_preview_format (drawable),
+ buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y, dest_width, dest_height),
+ scale);
+
+ if (gimp_tile_handler_validate_get_assigned (buffer))
+ {
+ return gimp_idle_run_async_full (
+ GIMP_PRIORITY_VIEWABLE_IDLE,
+ (GimpRunAsyncFunc) gimp_drawable_get_sub_preview_async_func,
+ data,
+ (GDestroyNotify) sub_preview_data_free);
+ }
+ else
+ {
+ return gimp_parallel_run_async_full (
+ +1,
+ (GimpRunAsyncFunc) gimp_drawable_get_sub_preview_async_func,
+ data,
+ (GDestroyNotify) sub_preview_data_free);
+ }
+}