summaryrefslogtreecommitdiffstats
path: root/app/core/gimpchunkiterator.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpchunkiterator.c')
-rw-r--r--app/core/gimpchunkiterator.c555
1 files changed, 555 insertions, 0 deletions
diff --git a/app/core/gimpchunkiterator.c b/app/core/gimpchunkiterator.c
new file mode 100644
index 0000000..616bb46
--- /dev/null
+++ b/app/core/gimpchunkiterator.c
@@ -0,0 +1,555 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpchunkiterator.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 <stdlib.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpchunkiterator.h"
+
+
+/* the maximal chunk size */
+#define MAX_CHUNK_WIDTH 4096
+#define MAX_CHUNK_HEIGHT 4096
+
+/* the default iteration interval */
+#define DEFAULT_INTERVAL (1.0 / 15.0) /* seconds */
+
+/* the minimal area to process per iteration */
+#define MIN_AREA_PER_ITERATION 4096
+
+/* the maximal ratio between the actual processed area and the target area,
+ * above which the current chunk height is readjusted, even in the middle of a
+ * row, to better match the target area
+ */
+#define MAX_AREA_RATIO 2.0
+
+/* the width of the target-area sliding window */
+#define TARGET_AREA_HISTORY_SIZE 3
+
+
+struct _GimpChunkIterator
+{
+ cairo_region_t *region;
+ cairo_region_t *priority_region;
+
+ GeglRectangle tile_rect;
+ GeglRectangle priority_rect;
+
+ gdouble interval;
+
+ cairo_region_t *current_region;
+ GeglRectangle current_rect;
+
+ gint current_x;
+ gint current_y;
+ gint current_height;
+
+ gint64 iteration_time;
+
+ gint64 last_time;
+ gint last_area;
+
+ gdouble target_area;
+ gdouble target_area_min;
+ gdouble target_area_history[TARGET_AREA_HISTORY_SIZE];
+ gint target_area_history_i;
+ gint target_area_history_n;
+};
+
+
+/* local function prototypes */
+
+static void gimp_chunk_iterator_set_current_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect);
+static void gimp_chunk_iterator_merge_current_rect (GimpChunkIterator *iter);
+
+static void gimp_chunk_iterator_merge (GimpChunkIterator *iter);
+
+static gboolean gimp_chunk_iterator_prepare (GimpChunkIterator *iter);
+
+static void gimp_chunk_iterator_set_target_area (GimpChunkIterator *iter,
+ gdouble target_area);
+static gdouble gimp_chunk_iterator_get_target_area (GimpChunkIterator *iter);
+static void gimp_chunk_iterator_reset_target_area (GimpChunkIterator *iter);
+
+static void gimp_chunk_iterator_calc_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect,
+ gboolean readjust_height);
+
+
+/* private functions */
+
+static void
+gimp_chunk_iterator_set_current_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect)
+{
+ cairo_region_subtract_rectangle (iter->current_region,
+ (const cairo_rectangle_int_t *) rect);
+
+ iter->current_rect = *rect;
+
+ iter->current_x = rect->x;
+ iter->current_y = rect->y;
+ iter->current_height = 0;
+}
+
+static void
+gimp_chunk_iterator_merge_current_rect (GimpChunkIterator *iter)
+{
+ GeglRectangle rect;
+
+ if (gegl_rectangle_is_empty (&iter->current_rect))
+ return;
+
+ /* merge the remainder of the current row */
+ rect.x = iter->current_x;
+ rect.y = iter->current_y;
+ rect.width = iter->current_rect.x + iter->current_rect.width -
+ iter->current_x;
+ rect.height = iter->current_height;
+
+ cairo_region_union_rectangle (iter->current_region,
+ (const cairo_rectangle_int_t *) &rect);
+
+ /* merge the remainder of the current rect */
+ rect.x = iter->current_rect.x;
+ rect.y = iter->current_y + iter->current_height;
+ rect.width = iter->current_rect.width;
+ rect.height = iter->current_rect.y + iter->current_rect.height - rect.y;
+
+ cairo_region_union_rectangle (iter->current_region,
+ (const cairo_rectangle_int_t *) &rect);
+
+ /* reset the current rect and coordinates */
+ iter->current_rect.x = 0;
+ iter->current_rect.y = 0;
+ iter->current_rect.width = 0;
+ iter->current_rect.height = 0;
+
+ iter->current_x = 0;
+ iter->current_y = 0;
+ iter->current_height = 0;
+}
+
+static void
+gimp_chunk_iterator_merge (GimpChunkIterator *iter)
+{
+ /* merge the current rect back to the current region */
+ gimp_chunk_iterator_merge_current_rect (iter);
+
+ /* merge the priority region back to the global region */
+ if (iter->priority_region)
+ {
+ cairo_region_union (iter->region, iter->priority_region);
+
+ g_clear_pointer (&iter->priority_region, cairo_region_destroy);
+
+ iter->current_region = iter->region;
+ }
+}
+
+static gboolean
+gimp_chunk_iterator_prepare (GimpChunkIterator *iter)
+{
+ if (iter->current_x == iter->current_rect.x + iter->current_rect.width)
+ {
+ iter->current_x = iter->current_rect.x;
+ iter->current_y += iter->current_height;
+ iter->current_height = 0;
+
+ if (iter->current_y == iter->current_rect.y + iter->current_rect.height)
+ {
+ GeglRectangle rect;
+
+ if (! iter->priority_region &&
+ ! gegl_rectangle_is_empty (&iter->priority_rect))
+ {
+ iter->priority_region = cairo_region_copy (iter->region);
+
+ cairo_region_intersect_rectangle (
+ iter->priority_region,
+ (const cairo_rectangle_int_t *) &iter->priority_rect);
+
+ cairo_region_subtract_rectangle (
+ iter->region,
+ (const cairo_rectangle_int_t *) &iter->priority_rect);
+ }
+
+ if (! iter->priority_region ||
+ cairo_region_is_empty (iter->priority_region))
+ {
+ iter->current_region = iter->region;
+ }
+ else
+ {
+ iter->current_region = iter->priority_region;
+ }
+
+ if (cairo_region_is_empty (iter->current_region))
+ {
+ iter->current_rect.x = 0;
+ iter->current_rect.y = 0;
+ iter->current_rect.width = 0;
+ iter->current_rect.height = 0;
+
+ iter->current_x = 0;
+ iter->current_y = 0;
+ iter->current_height = 0;
+
+ return FALSE;
+ }
+
+ cairo_region_get_rectangle (iter->current_region, 0,
+ (cairo_rectangle_int_t *) &rect);
+
+ gimp_chunk_iterator_set_current_rect (iter, &rect);
+ }
+ }
+
+ return TRUE;
+}
+
+static gint
+compare_double (const gdouble *x,
+ const gdouble *y)
+{
+ return (*x > *y) - (*x < *y);
+}
+
+static void
+gimp_chunk_iterator_set_target_area (GimpChunkIterator *iter,
+ gdouble target_area)
+{
+ gdouble target_area_history[TARGET_AREA_HISTORY_SIZE];
+
+ iter->target_area_min = MIN (iter->target_area_min, target_area);
+
+ iter->target_area_history[iter->target_area_history_i++] = target_area;
+
+ iter->target_area_history_n = MAX (iter->target_area_history_n,
+ iter->target_area_history_i);
+ iter->target_area_history_i %= TARGET_AREA_HISTORY_SIZE;
+
+ memcpy (target_area_history, iter->target_area_history,
+ iter->target_area_history_n * sizeof (gdouble));
+
+ qsort (target_area_history, iter->target_area_history_n, sizeof (gdouble),
+ (gpointer) compare_double);
+
+ iter->target_area = target_area_history[iter->target_area_history_n / 2];
+}
+
+static gdouble
+gimp_chunk_iterator_get_target_area (GimpChunkIterator *iter)
+{
+ if (iter->target_area)
+ return iter->target_area;
+ else
+ return iter->tile_rect.width * iter->tile_rect.height;
+}
+
+static void
+gimp_chunk_iterator_reset_target_area (GimpChunkIterator *iter)
+{
+ if (iter->target_area_history_n)
+ {
+ iter->target_area = iter->target_area_min;
+ iter->target_area_min = MAX_CHUNK_WIDTH * MAX_CHUNK_HEIGHT;
+ iter->target_area_history_i = 0;
+ iter->target_area_history_n = 0;
+ }
+}
+
+static void
+gimp_chunk_iterator_calc_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect,
+ gboolean readjust_height)
+{
+ gdouble target_area;
+ gdouble aspect_ratio;
+ gint offset_x;
+ gint offset_y;
+
+ if (readjust_height)
+ gimp_chunk_iterator_reset_target_area (iter);
+
+ target_area = gimp_chunk_iterator_get_target_area (iter);
+
+ aspect_ratio = (gdouble) iter->tile_rect.height /
+ (gdouble) iter->tile_rect.width;
+
+ rect->x = iter->current_x;
+ rect->y = iter->current_y;
+
+ offset_x = rect->x - iter->tile_rect.x;
+ offset_y = rect->y - iter->tile_rect.y;
+
+ if (readjust_height)
+ {
+ rect->height = RINT ((offset_y + sqrt (target_area * aspect_ratio)) /
+ iter->tile_rect.height) *
+ iter->tile_rect.height -
+ offset_y;
+
+ if (rect->height <= 0)
+ rect->height += iter->tile_rect.height;
+
+ rect->height = MIN (rect->height,
+ iter->current_rect.y + iter->current_rect.height -
+ rect->y);
+ rect->height = MIN (rect->height, MAX_CHUNK_HEIGHT);
+ }
+ else
+ {
+ rect->height = iter->current_height;
+ }
+
+ rect->width = RINT ((offset_x + (gdouble) target_area /
+ (gdouble) rect->height) /
+ iter->tile_rect.width) *
+ iter->tile_rect.width -
+ offset_x;
+
+ if (rect->width <= 0)
+ rect->width += iter->tile_rect.width;
+
+ rect->width = MIN (rect->width,
+ iter->current_rect.x + iter->current_rect.width -
+ rect->x);
+ rect->width = MIN (rect->width, MAX_CHUNK_WIDTH);
+}
+
+
+/* public functions */
+
+GimpChunkIterator *
+gimp_chunk_iterator_new (cairo_region_t *region)
+{
+ GimpChunkIterator *iter;
+
+ g_return_val_if_fail (region != NULL, NULL);
+
+ iter = g_slice_new0 (GimpChunkIterator);
+
+ iter->region = region;
+ iter->current_region = region;
+
+ g_object_get (gegl_config (),
+ "tile-width", &iter->tile_rect.width,
+ "tile-height", &iter->tile_rect.height,
+ NULL);
+
+ iter->interval = DEFAULT_INTERVAL;
+
+ return iter;
+}
+
+void
+gimp_chunk_iterator_set_tile_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect)
+{
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (rect != NULL);
+ g_return_if_fail (! gegl_rectangle_is_empty (rect));
+
+ iter->tile_rect = *rect;
+}
+
+void
+gimp_chunk_iterator_set_priority_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect)
+{
+ const GeglRectangle empty_rect = {};
+
+ g_return_if_fail (iter != NULL);
+
+ if (! rect)
+ rect = &empty_rect;
+
+ if (! gegl_rectangle_equal (rect, &iter->priority_rect))
+ {
+ iter->priority_rect = *rect;
+
+ gimp_chunk_iterator_merge (iter);
+ }
+}
+
+void
+gimp_chunk_iterator_set_interval (GimpChunkIterator *iter,
+ gdouble interval)
+{
+ g_return_if_fail (iter != NULL);
+
+ interval = MAX (interval, 0.0);
+
+ if (interval != iter->interval)
+ {
+ if (iter->interval)
+ {
+ gdouble ratio = interval / iter->interval;
+ gint i;
+
+ iter->target_area *= ratio;
+
+ for (i = 0; i < TARGET_AREA_HISTORY_SIZE; i++)
+ iter->target_area_history[i] *= ratio;
+ }
+
+ iter->interval = interval;
+ }
+}
+
+gboolean
+gimp_chunk_iterator_next (GimpChunkIterator *iter)
+{
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (! gimp_chunk_iterator_prepare (iter))
+ {
+ gimp_chunk_iterator_stop (iter, TRUE);
+
+ return FALSE;
+ }
+
+ iter->iteration_time = g_get_monotonic_time ();
+
+ iter->last_time = iter->iteration_time;
+ iter->last_area = 0;
+
+ return TRUE;
+}
+
+gboolean
+gimp_chunk_iterator_get_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect)
+{
+ gint64 time;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (rect != NULL, FALSE);
+
+ if (! gimp_chunk_iterator_prepare (iter))
+ return FALSE;
+
+ time = g_get_monotonic_time ();
+
+ if (iter->last_area >= MIN_AREA_PER_ITERATION)
+ {
+ gdouble interval;
+
+ interval = (gdouble) (time - iter->last_time) / G_TIME_SPAN_SECOND;
+
+ gimp_chunk_iterator_set_target_area (
+ iter,
+ iter->last_area * iter->interval / interval);
+
+ interval = (gdouble) (time - iter->iteration_time) / G_TIME_SPAN_SECOND;
+
+ if (interval > iter->interval)
+ return FALSE;
+ }
+
+ if (iter->current_x == iter->current_rect.x)
+ {
+ gimp_chunk_iterator_calc_rect (iter, rect, TRUE);
+ }
+ else
+ {
+ gimp_chunk_iterator_calc_rect (iter, rect, FALSE);
+
+ if (rect->width * rect->height >=
+ MAX_AREA_RATIO * gimp_chunk_iterator_get_target_area (iter))
+ {
+ GeglRectangle old_rect = *rect;
+
+ gimp_chunk_iterator_calc_rect (iter, rect, TRUE);
+
+ if (rect->height >= old_rect.height)
+ *rect = old_rect;
+ }
+ }
+
+ if (rect->height != iter->current_height)
+ {
+ /* if the chunk height changed in the middle of a row, merge the
+ * remaining area back into the current region, and reset the current
+ * area to the remainder of the row, using the new chunk height
+ */
+ if (rect->x != iter->current_rect.x)
+ {
+ GeglRectangle rem;
+
+ rem.x = rect->x;
+ rem.y = rect->y;
+ rem.width = iter->current_rect.x + iter->current_rect.width -
+ rect->x;
+ rem.height = rect->height;
+
+ gimp_chunk_iterator_merge_current_rect (iter);
+
+ gimp_chunk_iterator_set_current_rect (iter, &rem);
+ }
+
+ iter->current_height = rect->height;
+ }
+
+ iter->current_x += rect->width;
+
+ iter->last_time = time;
+ iter->last_area = rect->width * rect->height;
+
+ return TRUE;
+}
+
+cairo_region_t *
+gimp_chunk_iterator_stop (GimpChunkIterator *iter,
+ gboolean free_region)
+{
+ cairo_region_t *result = NULL;
+
+ g_return_val_if_fail (iter != NULL, NULL);
+
+ if (free_region)
+ {
+ cairo_region_destroy (iter->region);
+ }
+ else
+ {
+ gimp_chunk_iterator_merge (iter);
+
+ result = iter->region;
+ }
+
+ g_clear_pointer (&iter->priority_region, cairo_region_destroy);
+
+ g_slice_free (GimpChunkIterator, iter);
+
+ return result;
+}