gimp/app/core/gimpchunkiterator.c

474 lines
13 KiB
C
Raw Normal View History

/* 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 <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 processed area on the current iteration, above which we
* calculate a new target area to render on the next iteration
*/
#define MIN_AREA_PER_UPDATE 1024
/* the width of the target-area sliding window */
#define TARGET_AREA_HISTORY_SIZE 5
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;
gdouble current_area;
gdouble target_area;
gdouble target_area_history[TARGET_AREA_HISTORY_SIZE];
gint target_area_history_i;
};
/* 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);
/* 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;
}
static void
gimp_chunk_iterator_merge_current_rect (GimpChunkIterator *iter)
{
GeglRectangle rect;
gint current_height = 0;
if (gegl_rectangle_is_empty (&iter->current_rect))
return;
/* merge the remainder of the current row */
if (iter->current_x != iter->current_rect.x)
{
current_height = iter->current_height;
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 = 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 + 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;
}
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);
}
}
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;
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;
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;
}
/* 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;
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->current_area = 0;
return TRUE;
}
static gint
compare_double (const gdouble *x,
const gdouble *y)
{
return (*y < *x) - (*x < *y);
}
gboolean
gimp_chunk_iterator_get_rect (GimpChunkIterator *iter,
GeglRectangle *rect)
{
gdouble interval;
gdouble aspect_ratio;
gint offset_x;
gint offset_y;
g_return_val_if_fail (iter != NULL, FALSE);
g_return_val_if_fail (rect != NULL, FALSE);
if (! gimp_chunk_iterator_prepare (iter))
return FALSE;
interval = (gdouble) (g_get_monotonic_time () - iter->iteration_time) /
G_TIME_SPAN_SECOND;
if (iter->current_area > 0 && interval > iter->interval)
{
/* adjust the target area to be processed on the next iteration,
* according to the area processed during this iteration and the elapsed
* time, in order to match the desired interval.
*/
if (iter->current_area >= MIN_AREA_PER_UPDATE)
{
gdouble target_area_history[TARGET_AREA_HISTORY_SIZE];
iter->target_area_history[iter->target_area_history_i] =
iter->current_area * iter->interval / interval;
/* calculate the median target area of the last
* TARGET_AREA_HISTORY_SIZE iterations
*/
memcpy (target_area_history, iter->target_area_history,
sizeof (target_area_history));
qsort (target_area_history, TARGET_AREA_HISTORY_SIZE,
sizeof (gdouble), (gpointer) compare_double);
iter->target_area = target_area_history[TARGET_AREA_HISTORY_SIZE / 2];
iter->target_area_history_i++;
iter->target_area_history_i %= TARGET_AREA_HISTORY_SIZE;
}
return FALSE;
}
if (! iter->target_area)
{
gint i;
iter->target_area = iter->tile_rect.width * iter->tile_rect.height;
for (i = 0; i < TARGET_AREA_HISTORY_SIZE; i++)
iter->target_area_history[i] = iter->target_area;
iter->target_area_history_i = 0;
}
aspect_ratio = (gdouble) iter->tile_rect.width /
(gdouble) iter->tile_rect.height;
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;
rect->width = ceil ((offset_x + sqrt (iter->target_area * aspect_ratio)) /
iter->tile_rect.width) *
iter->tile_rect.width -
offset_x;
rect->height = ceil ((offset_y + (gdouble) iter->target_area /
(gdouble) rect->width) /
iter->tile_rect.height) *
iter->tile_rect.height -
offset_y;
rect->width = MIN (rect->width,
iter->current_rect.x + iter->current_rect.width -
rect->x);
rect->width = MIN (rect->width, MAX_CHUNK_WIDTH);
rect->height = MIN (rect->height,
iter->current_rect.y + iter->current_rect.height -
rect->y);
rect->height = MIN (rect->height, MAX_CHUNK_HEIGHT);
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->current_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
{
if (gimp_chunk_iterator_prepare (iter))
gimp_chunk_iterator_merge (iter);
result = iter->region;
}
g_clear_pointer (&iter->priority_region, cairo_region_destroy);
g_slice_free (GimpChunkIterator, iter);
return result;
}