/* 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 * * 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 . */ #include "config.h" #include #include #include #include #include "libpikacolor/pikacolor.h" #include "libpikamath/pikamath.h" #include "core-types.h" #include "config/pikacoreconfig.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-loops.h" #include "gegl/pikatilehandlervalidate.h" #include "pika.h" #include "pika-parallel.h" #include "pika-utils.h" #include "pikaasync.h" #include "pikachannel.h" #include "pikachunkiterator.h" #include "pikaimage.h" #include "pikaimage-color-profile.h" #include "pikadrawable-preview.h" #include "pikadrawable-private.h" #include "pikalayer.h" #include "pikatempbuf.h" #include "pika-priorities.h" typedef struct { const Babl *format; GeglBuffer *buffer; GeglRectangle rect; gdouble scale; PikaChunkIterator *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) pika_chunk_iterator_stop (data->iter, TRUE); g_slice_free (SubPreviewData, data); } /* public functions */ PikaTempBuf * pika_drawable_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height) { PikaItem *item = PIKA_ITEM (viewable); PikaImage *image = pika_item_get_image (item); if (! image->pika->config->layer_previews) return NULL; return pika_drawable_get_sub_preview (PIKA_DRAWABLE (viewable), 0, 0, pika_item_get_width (item), pika_item_get_height (item), width, height); } GdkPixbuf * pika_drawable_get_new_pixbuf (PikaViewable *viewable, PikaContext *context, gint width, gint height) { PikaItem *item = PIKA_ITEM (viewable); PikaImage *image = pika_item_get_image (item); if (! image->pika->config->layer_previews) return NULL; return pika_drawable_get_sub_pixbuf (PIKA_DRAWABLE (viewable), 0, 0, pika_item_get_width (item), pika_item_get_height (item), width, height); } const Babl * pika_drawable_get_preview_format (PikaDrawable *drawable) { const Babl *space; gboolean alpha; PikaTRCType trc; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); space = pika_drawable_get_space (drawable); alpha = pika_drawable_has_alpha (drawable); trc = pika_drawable_get_trc (drawable); switch (pika_drawable_get_base_type (drawable)) { case PIKA_GRAY: return pika_babl_format (PIKA_GRAY, pika_babl_precision (PIKA_COMPONENT_TYPE_U8, trc), alpha, space); case PIKA_RGB: case PIKA_INDEXED: return pika_babl_format (PIKA_RGB, pika_babl_precision (PIKA_COMPONENT_TYPE_U8, trc), alpha, space); } g_return_val_if_reached (NULL); } PikaTempBuf * pika_drawable_get_sub_preview (PikaDrawable *drawable, gint src_x, gint src_y, gint src_width, gint src_height, gint dest_width, gint dest_height) { PikaItem *item; PikaImage *image; GeglBuffer *buffer; PikaTempBuf *preview; gdouble scale; gint scaled_x; gint scaled_y; g_return_val_if_fail (PIKA_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 = PIKA_ITEM (drawable); g_return_val_if_fail ((src_x + src_width) <= pika_item_get_width (item), NULL); g_return_val_if_fail ((src_y + src_height) <= pika_item_get_height (item), NULL); image = pika_item_get_image (item); if (! image->pika->config->layer_previews) return NULL; buffer = pika_drawable_get_buffer (drawable); preview = pika_temp_buf_new (dest_width, dest_height, pika_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, pika_temp_buf_get_format (preview), pika_temp_buf_get_data (preview), GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP); return preview; } GdkPixbuf * pika_drawable_get_sub_pixbuf (PikaDrawable *drawable, gint src_x, gint src_y, gint src_width, gint src_height, gint dest_width, gint dest_height) { PikaItem *item; PikaImage *image; GeglBuffer *buffer; GdkPixbuf *pixbuf; gdouble scale; gint scaled_x; gint scaled_y; PikaColorTransform *transform; g_return_val_if_fail (PIKA_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 = PIKA_ITEM (drawable); g_return_val_if_fail ((src_x + src_width) <= pika_item_get_width (item), NULL); g_return_val_if_fail ((src_y + src_height) <= pika_item_get_height (item), NULL); image = pika_item_get_image (item); if (! image->pika->config->layer_previews) return NULL; buffer = pika_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 = 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 (dest_width, dest_height, pika_drawable_get_format (drawable)); gegl_buffer_get (buffer, GEGL_RECTANGLE (scaled_x, scaled_y, dest_width, dest_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, 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, pika_pixbuf_get_format (pixbuf), gdk_pixbuf_get_pixels (pixbuf), gdk_pixbuf_get_rowstride (pixbuf), GEGL_ABYSS_CLAMP); } return pixbuf; } static void pika_drawable_get_sub_preview_async_func (PikaAsync *async, SubPreviewData *data) { PikaTempBuf *preview; PikaTileHandlerValidate *validate; preview = pika_temp_buf_new (data->rect.width, data->rect.height, data->format); validate = pika_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 = pika_chunk_iterator_new (region); } if (pika_chunk_iterator_next (data->iter)) { GeglRectangle rect; pika_tile_handler_validate_begin_validate (validate); while (pika_chunk_iterator_get_rect (data->iter, &rect)) { pika_tile_handler_validate_validate (validate, data->buffer, &rect, FALSE, FALSE); } pika_tile_handler_validate_end_validate (validate); return; } data->iter = NULL; } gegl_buffer_get (data->buffer, &data->rect, data->scale, pika_temp_buf_get_format (preview), pika_temp_buf_get_data (preview), GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP); sub_preview_data_free (data); pika_async_finish_full (async, preview, (GDestroyNotify) pika_temp_buf_unref); } PikaAsync * pika_drawable_get_sub_preview_async (PikaDrawable *drawable, gint src_x, gint src_y, gint src_width, gint src_height, gint dest_width, gint dest_height) { PikaItem *item; PikaImage *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 (PIKA_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 = PIKA_ITEM (drawable); g_return_val_if_fail ((src_x + src_width) <= pika_item_get_width (item), NULL); g_return_val_if_fail ((src_y + src_height) <= pika_item_get_height (item), NULL); image = pika_item_get_image (item); if (! image->pika->config->layer_previews) return NULL; buffer = pika_drawable_get_buffer (drawable); if (no_async_drawable_previews < 0) { no_async_drawable_previews = (g_getenv ("PIKA_NO_ASYNC_DRAWABLE_PREVIEWS") != NULL); } if (no_async_drawable_previews) { PikaAsync *async = pika_async_new (); pika_async_finish_full (async, pika_drawable_get_sub_preview (drawable, src_x, src_y, src_width, src_height, dest_width, dest_height), (GDestroyNotify) pika_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 ( pika_drawable_get_preview_format (drawable), buffer, GEGL_RECTANGLE (scaled_x, scaled_y, dest_width, dest_height), scale); if (pika_tile_handler_validate_get_assigned (buffer)) { return pika_idle_run_async_full ( PIKA_PRIORITY_VIEWABLE_IDLE, (PikaRunAsyncFunc) pika_drawable_get_sub_preview_async_func, data, (GDestroyNotify) sub_preview_data_free); } else { return pika_parallel_run_async_full ( +1, (PikaRunAsyncFunc) pika_drawable_get_sub_preview_async_func, data, (GDestroyNotify) sub_preview_data_free); } }