Files
gimp/app/base/tile-manager.c
Michael Natterer bc8d5f84d6 app: remove the "offset" API from TileManager
It made the transform code hard to read and never belonged into the
tile manager anyway. It's a simple pixel buffer that should not know
about any position in an image. Instead, pass around the offsets of
tile managers explicitly, so everything is less obscure for the price
of having more parameters. This will also help replacing TileManagers
with GeglBuffers.
2011-03-26 08:30:15 +01:00

875 lines
20 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <glib-object.h>
#include "base-types.h"
#include "tile.h"
#include "tile-cache.h"
#include "tile-manager.h"
#include "tile-manager-private.h"
#include "tile-rowhints.h"
#include "tile-swap.h"
#include "tile-private.h"
static void tile_manager_allocate_tiles (TileManager *tm);
#ifdef TILE_PROFILING
extern gint tile_exist_peak;
extern gint tile_exist_count;
#endif
#ifdef GIMP_UNSTABLE
GList *tile_managers = NULL;
#endif
GType
gimp_tile_manager_get_type (void)
{
static GType type = 0;
if (! type)
type = g_boxed_type_register_static ("TileManager",
(GBoxedCopyFunc) tile_manager_ref,
(GBoxedFreeFunc) tile_manager_unref);
return type;
}
#ifdef GIMP_UNSTABLE
void
tile_manager_exit (void)
{
if (tile_managers)
{
g_warning ("%d tile managers leaked", g_list_length (tile_managers));
while (tile_managers)
{
g_printerr ("unref tile manager %p (%d x %d)\n",
tile_managers->data,
tile_manager_width (tile_managers->data),
tile_manager_height (tile_managers->data));
tile_manager_unref (tile_managers->data);
}
}
}
#endif
static inline gint
tile_manager_get_tile_num (TileManager *tm,
gint xpixel,
gint ypixel)
{
if ((xpixel < 0) || (xpixel >= tm->width) ||
(ypixel < 0) || (ypixel >= tm->height))
return -1;
return (ypixel / TILE_HEIGHT) * tm->ntile_cols + (xpixel / TILE_WIDTH);
}
TileManager *
tile_manager_new (gint width,
gint height,
gint bpp)
{
TileManager *tm;
g_return_val_if_fail (width > 0 && height > 0, NULL);
g_return_val_if_fail (bpp > 0 && bpp <= 4, NULL);
tm = g_slice_new0 (TileManager);
tm->ref_count = 1;
tm->width = width;
tm->height = height;
tm->bpp = bpp;
tm->ntile_rows = (height + TILE_HEIGHT - 1) / TILE_HEIGHT;
tm->ntile_cols = (width + TILE_WIDTH - 1) / TILE_WIDTH;
tm->cached_num = -1;
#ifdef GIMP_UNSTABLE
tile_managers = g_list_prepend (tile_managers, tm);
#endif
return tm;
}
TileManager *
tile_manager_ref (TileManager *tm)
{
g_return_val_if_fail (tm != NULL, NULL);
tm->ref_count++;
return tm;
}
void
tile_manager_unref (TileManager *tm)
{
g_return_if_fail (tm != NULL);
tm->ref_count--;
if (tm->ref_count < 1)
{
#ifdef GIMP_UNSTABLE
tile_managers = g_list_remove (tile_managers, tm);
#endif
if (tm->cached_tile)
tile_release (tm->cached_tile, FALSE);
if (tm->tiles)
{
gint ntiles = tm->ntile_rows * tm->ntile_cols;
gint i;
for (i = 0; i < ntiles; i++)
tile_detach (tm->tiles[i], tm, i);
g_free (tm->tiles);
}
g_slice_free (TileManager, tm);
}
}
TileManager *
tile_manager_duplicate (TileManager *tm)
{
TileManager *copy;
gint n_tiles;
gint i;
g_return_val_if_fail (tm != NULL, NULL);
copy = tile_manager_new (tm->width, tm->height, tm->bpp);
tile_manager_allocate_tiles (copy);
n_tiles = tm->ntile_rows * tm->ntile_cols;
for (i = 0; i < n_tiles; i++)
{
Tile *tile;
tile = tile_manager_get (tm, i, TRUE, FALSE);
tile_manager_map (copy, i, tile);
tile_release (tile, FALSE);
}
return copy;
}
void
tile_manager_set_validate_proc (TileManager *tm,
TileValidateProc proc,
gpointer user_data)
{
g_return_if_fail (tm != NULL);
tm->validate_proc = proc;
tm->user_data = user_data;
}
Tile *
tile_manager_get_tile (TileManager *tm,
gint xpixel,
gint ypixel,
gboolean wantread,
gboolean wantwrite)
{
g_return_val_if_fail (tm != NULL, NULL);
return tile_manager_get (tm,
tile_manager_get_tile_num (tm, xpixel, ypixel),
wantread, wantwrite);
}
Tile *
tile_manager_get (TileManager *tm,
gint tile_num,
gboolean wantread,
gboolean wantwrite)
{
Tile *tile;
gint ntiles;
g_return_val_if_fail (tm != NULL, NULL);
ntiles = tm->ntile_rows * tm->ntile_cols;
if ((tile_num < 0) || (tile_num >= ntiles))
return NULL;
if (! tm->tiles)
tile_manager_allocate_tiles (tm);
tile = tm->tiles[tile_num];
if (G_UNLIKELY (wantwrite && ! wantread))
g_warning ("WRITE-ONLY TILE... UNTESTED!");
#ifdef DEBUG_TILE_MANAGER
if (G_UNLIKELY (tile->share_count && tile->write_count))
g_printerr (">> MEEPITY %d,%d <<\n", tile->share_count, tile->write_count);
#endif
if (wantread)
{
if (wantwrite)
{
if (tile_num == tm->cached_num)
{
tile_release (tm->cached_tile, FALSE);
tm->cached_tile = NULL;
tm->cached_num = -1;
}
if (tile->share_count > 1)
{
/* Copy-on-write required */
Tile *new = tile_new (tile->bpp);
new->ewidth = tile->ewidth;
new->eheight = tile->eheight;
new->valid = tile->valid;
new->size = new->ewidth * new->eheight * new->bpp;
new->data = g_new (guchar, new->size);
#ifdef TILE_PROFILING
tile_exist_count++;
if (tile_exist_count > tile_exist_peak)
tile_exist_peak = tile_exist_count;
#endif
if (tile->rowhint)
{
tile_allocate_rowhints (new);
memcpy (new->rowhint, tile->rowhint,
new->eheight * sizeof (TileRowHint));
}
if (tile->data)
{
memcpy (new->data, tile->data, new->size);
}
else
{
tile_lock (tile);
memcpy (new->data, tile->data, new->size);
tile_release (tile, FALSE);
}
tile_detach (tile, tm, tile_num);
tile_attach (new, tm, tile_num);
tile = new;
tm->tiles[tile_num] = tile;
}
/* must lock before marking dirty */
tile_lock (tile);
tile->write_count++;
tile->dirty = TRUE;
}
else
{
#ifdef DEBUG_TILE_MANAGER
if (G_UNLIKELY (tile->write_count))
g_printerr ("STINK! r/o on r/w tile (%d)\n", tile->write_count);
#endif
tile_lock (tile);
}
}
return tile;
}
Tile *
tile_manager_get_at (TileManager *tm,
gint tile_col,
gint tile_row,
gboolean wantread,
gboolean wantwrite)
{
g_return_val_if_fail (tm != NULL, NULL);
if (tile_col < 0 || tile_col >= tm->ntile_cols ||
tile_row < 0 || tile_row >= tm->ntile_rows)
return NULL;
return tile_manager_get (tm,
tile_row * tm->ntile_cols + tile_col,
wantread, wantwrite);
}
void
tile_manager_validate_tile (TileManager *tm,
Tile *tile)
{
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
tile->valid = TRUE;
if (tm->validate_proc)
{
(* tm->validate_proc) (tm, tile, tm->user_data);
}
else
{
/* Set the contents of the tile to empty */
memset (tile->data, 0, tile_size (tile));
}
#ifdef DEBUG_TILE_MANAGER
g_printerr ("%c", tm->user_data ? 'V' : 'v');
#endif
}
static void
tile_manager_allocate_tiles (TileManager *tm)
{
Tile **tiles;
const gint nrows = tm->ntile_rows;
const gint ncols = tm->ntile_cols;
const gint right_tile = tm->width - ((ncols - 1) * TILE_WIDTH);
const gint bottom_tile = tm->height - ((nrows - 1) * TILE_HEIGHT);
gint i, j, k;
g_assert (tm->tiles == NULL);
tiles = g_new (Tile *, nrows * ncols);
for (i = 0, k = 0; i < nrows; i++)
{
for (j = 0; j < ncols; j++, k++)
{
Tile *new = tile_new (tm->bpp);
tile_attach (new, tm, k);
if (j == (ncols - 1))
new->ewidth = right_tile;
if (i == (nrows - 1))
new->eheight = bottom_tile;
new->size = new->ewidth * new->eheight * new->bpp;
tiles[k] = new;
}
}
tm->tiles = tiles;
}
static void
tile_manager_invalidate_tile (TileManager *tm,
gint tile_num)
{
Tile *tile = tm->tiles[tile_num];
if (! tile->valid)
return;
if (tile_num == tm->cached_num)
{
tile_release (tm->cached_tile, FALSE);
tm->cached_tile = NULL;
tm->cached_num = -1;
}
if (tile->cached)
tile_cache_flush (tile);
if (G_UNLIKELY (tile->share_count > 1))
{
/* This tile is shared. Replace it with a new invalid tile. */
Tile *new = tile_new (tile->bpp);
new->ewidth = tile->ewidth;
new->eheight = tile->eheight;
new->size = tile->size;
tile_detach (tile, tm, tile_num);
tile_attach (new, tm, tile_num);
tile = new;
tm->tiles[tile_num] = tile;
}
tile->valid = FALSE;
if (tile->data)
{
g_free (tile->data);
tile->data = NULL;
#ifdef TILE_PROFILING
tile_exist_count--;
#endif
}
if (tile->swap_offset != -1)
{
/* If the tile is on disk, then delete its
* presence there.
*/
tile_swap_delete (tile);
}
}
static void
tile_manager_invalidate_pixel (TileManager *tm,
gint xpixel,
gint ypixel)
{
gint num = tile_manager_get_tile_num (tm, xpixel, ypixel);
if (num < 0)
return;
tile_manager_invalidate_tile (tm, num);
}
void
tile_manager_map_tile (TileManager *tm,
gint xpixel,
gint ypixel,
Tile *srctile)
{
g_return_if_fail (tm != NULL);
g_return_if_fail (srctile != NULL);
tile_manager_map (tm,
tile_manager_get_tile_num (tm, xpixel, ypixel),
srctile);
}
void
tile_manager_map (TileManager *tm,
gint tile_num,
Tile *srctile)
{
Tile *tile;
g_return_if_fail (tm != NULL);
g_return_if_fail (srctile != NULL);
g_return_if_fail (tile_num >= 0);
g_return_if_fail (tile_num < tm->ntile_rows * tm->ntile_cols);
if (G_UNLIKELY (! tm->tiles))
{
g_warning ("%s: empty tile level - initializing", G_STRLOC);
tile_manager_allocate_tiles (tm);
}
tile = tm->tiles[tile_num];
#ifdef DEBUG_TILE_MANAGER
g_printerr (")");
#endif
if (G_UNLIKELY (! srctile->valid))
g_warning("%s: srctile not validated yet! please report", G_STRLOC);
if (G_UNLIKELY (tile->ewidth != srctile->ewidth ||
tile->eheight != srctile->eheight ||
tile->bpp != srctile->bpp))
{
g_warning ("%s: nonconformant map (%p -> %p)",
G_STRLOC, srctile, tile);
}
tile_detach (tile, tm, tile_num);
#ifdef DEBUG_TILE_MANAGER
g_printerr (">");
#endif
#ifdef DEBUG_TILE_MANAGER
g_printerr (" [src:%p tm:%p tn:%d] ", srctile, tm, tile_num);
#endif
tile_attach (srctile, tm, tile_num);
tm->tiles[tile_num] = srctile;
#ifdef DEBUG_TILE_MANAGER
g_printerr ("}\n");
#endif
}
void
tile_manager_invalidate_area (TileManager *tm,
gint x,
gint y,
gint w,
gint h)
{
gint i;
gint j;
/* if no tiles have been allocated, there's no need to invalidate any */
if (! tm->tiles)
return;
for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT)))
for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH)))
{
tile_manager_invalidate_pixel (tm, j, i);
}
}
gint
tile_manager_width (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->width;
}
gint
tile_manager_height (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->height;
}
gint
tile_manager_bpp (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->bpp;
}
gint
tile_manager_tiles_per_col (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->ntile_cols;
}
gint
tile_manager_tiles_per_row (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->ntile_rows;
}
gint64
tile_manager_get_memsize (const TileManager *tm,
gboolean sparse)
{
/* the tile manager itself */
gint64 memsize = sizeof (TileManager);
if (! tm)
return 0;
/* the array of tiles */
memsize += (gint64) tm->ntile_rows * tm->ntile_cols * (sizeof (Tile) +
sizeof (gpointer));
/* the memory allocated for the tiles */
if (sparse)
{
if (tm->tiles)
{
Tile **tiles = tm->tiles;
gint64 size = TILE_WIDTH * TILE_HEIGHT * tm->bpp;
gint i, j;
for (i = 0; i < tm->ntile_rows; i++)
for (j = 0; j < tm->ntile_cols; j++, tiles++)
{
if (tile_is_valid (*tiles))
memsize += size;
}
}
}
else
{
memsize += (gint64) tm->width * tm->height * tm->bpp;
}
return memsize;
}
static inline gint
tile_manager_locate_tile (TileManager *tm,
Tile *tile)
{
TileLink *tl;
for (tl = tile->tlink; tl; tl = tl->next)
{
if (tl->tm == tm)
break;
}
if (G_UNLIKELY (tl == NULL))
{
g_warning ("%s: tile not attached to manager", G_STRLOC);
return 0;
}
return tl->tile_num;
}
void
tile_manager_get_tile_col_row (TileManager *tm,
Tile *tile,
gint *tile_col,
gint *tile_row)
{
gint tile_num;
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
g_return_if_fail (tile_col != NULL && tile_row != NULL);
tile_num = tile_manager_locate_tile (tm, tile);
*tile_col = tile_num % tm->ntile_cols;
*tile_row = tile_num / tm->ntile_cols;
}
void
tile_manager_get_tile_coordinates (TileManager *tm,
Tile *tile,
gint *x,
gint *y)
{
gint tile_col;
gint tile_row;
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
g_return_if_fail (x != NULL && y != NULL);
tile_manager_get_tile_col_row (tm, tile, &tile_col, &tile_row);
*x = TILE_WIDTH * tile_col;
*y = TILE_HEIGHT * tile_row;
}
void
tile_manager_map_over_tile (TileManager *tm,
Tile *tile,
Tile *srctile)
{
TileLink *tl;
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
g_return_if_fail (srctile != NULL);
for (tl = tile->tlink; tl; tl = tl->next)
{
if (tl->tm == tm)
break;
}
if (G_UNLIKELY (tl == NULL))
{
g_warning ("%s: tile not attached to manager", G_STRLOC);
return;
}
tile_manager_map (tm, tl->tile_num, srctile);
}
void
read_pixel_data (TileManager *tm,
gint x1,
gint y1,
gint x2,
gint y2,
guchar *buffer,
guint stride)
{
guint x, y;
for (y = y1; y <= y2; y += TILE_HEIGHT - (y % TILE_HEIGHT))
for (x = x1; x <= x2; x += TILE_WIDTH - (x % TILE_WIDTH))
{
Tile *tile = tile_manager_get_tile (tm, x, y, TRUE, FALSE);
const guchar *s = TILE_DATA_POINTER (tile, x, y);
guchar *d = buffer + stride * (y - y1) + tm->bpp * (x - x1);
guint rows, cols;
guint srcstride;
rows = tile->eheight - y % TILE_HEIGHT;
if (rows > (y2 - y + 1))
rows = y2 - y + 1;
cols = tile->ewidth - x % TILE_WIDTH;
if (cols > (x2 - x + 1))
cols = x2 - x + 1;
srcstride = tile->ewidth * tile->bpp;
while (rows--)
{
memcpy (d, s, cols * tm->bpp);
s += srcstride;
d += stride;
}
tile_release (tile, FALSE);
}
}
void
write_pixel_data (TileManager *tm,
gint x1,
gint y1,
gint x2,
gint y2,
const guchar *buffer,
guint stride)
{
guint x, y;
for (y = y1; y <= y2; y += TILE_HEIGHT - (y % TILE_HEIGHT))
for (x = x1; x <= x2; x += TILE_WIDTH - (x % TILE_WIDTH))
{
Tile *tile = tile_manager_get_tile (tm, x, y, TRUE, TRUE);
const guchar *s = buffer + stride * (y - y1) + tm->bpp * (x - x1);
guchar *d = TILE_DATA_POINTER (tile, x, y);
guint rows, cols;
guint dststride;
rows = tile->eheight - y % TILE_HEIGHT;
if (rows > (y2 - y + 1))
rows = y2 - y + 1;
cols = tile->ewidth - x % TILE_WIDTH;
if (cols > (x2 - x + 1))
cols = x2 - x + 1;
dststride = tile->ewidth * tile->bpp;
while (rows--)
{
memcpy (d, s, cols * tm->bpp);
s += stride;
d += dststride;
}
tile_release (tile, TRUE);
}
}
void
read_pixel_data_1 (TileManager *tm,
gint x,
gint y,
guchar *buffer)
{
const gint num = tile_manager_get_tile_num (tm, x, y);
if (num < 0)
return;
if (num != tm->cached_num) /* must fetch a new tile */
{
Tile *tile;
if (tm->cached_tile)
tile_release (tm->cached_tile, FALSE);
tm->cached_num = -1;
tm->cached_tile = NULL;
/* use a temporary variable instead of assigning to
* tm->cached_tile directly to make sure tm->cached_num
* and tm->cached_tile are always in a consistent state.
* (the requested tile might be invalid and needs to be
* validated, which would call tile_manager_get() recursively,
* which in turn would clear the cached tile) See bug #472770.
*/
tile = tile_manager_get (tm, num, TRUE, FALSE);
tm->cached_num = num;
tm->cached_tile = tile;
}
{
const guchar *src = TILE_DATA_POINTER (tm->cached_tile, x, y);
switch (tm->bpp)
{
case 4:
*buffer++ = *src++;
case 3:
*buffer++ = *src++;
case 2:
*buffer++ = *src++;
case 1:
*buffer++ = *src++;
}
}
}
void
write_pixel_data_1 (TileManager *tm,
gint x,
gint y,
const guchar *buffer)
{
Tile *tile = tile_manager_get_tile (tm, x, y, TRUE, TRUE);
guchar *dest = TILE_DATA_POINTER (tile, x, y);
switch (tm->bpp)
{
case 4:
*dest++ = *buffer++;
case 3:
*dest++ = *buffer++;
case 2:
*dest++ = *buffer++;
case 1:
*dest++ = *buffer++;
}
tile_release (tile, TRUE);
}