/* The GIMP -- an 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include "base-types.h" #include "tile.h" #include "tile-private.h" #include "tile-cache.h" #include "tile-manager.h" #include "tile-manager-private.h" #include "tile-swap.h" 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_new0 (TileManager, 1); 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; 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) { 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_free (tm); } } void tile_manager_set_validate_proc (TileManager *tm, TileValidateProc proc) { g_return_if_fail (tm != NULL); tm->validate_proc = proc; } Tile * tile_manager_get_tile (TileManager *tm, gint xpixel, gint ypixel, gint wantread, gint 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, gint wantread, gint wantwrite) { Tile **tiles; Tile **tile_ptr; gint ntiles; gint nrows, ncols; gint right_tile; gint bottom_tile; gint i, j, k; 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) { tm->tiles = g_new (Tile *, ntiles); tiles = tm->tiles; nrows = tm->ntile_rows; ncols = tm->ntile_cols; right_tile = tm->width - ((ncols - 1) * TILE_WIDTH); bottom_tile = tm->height - ((nrows - 1) * TILE_HEIGHT); for (i = 0, k = 0; i < nrows; i++) { for (j = 0; j < ncols; j++, k++) { Tile *new = g_new (Tile, 1); tile_init (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; } } } tile_ptr = &tm->tiles[tile_num]; if (G_UNLIKELY (wantwrite && !wantread)) g_warning ("WRITE-ONLY TILE... UNTESTED!"); #ifdef DEBUG_TILE_MANAGER if (G_UNLIKELY ((*tile_ptr)->share_count && (*tile_ptr)->write_count)) g_printerr (">> MEEPITY %d,%d <<\n", (*tile_ptr)->share_count, (*tile_ptr)->write_count); #endif if (wantread) { if (wantwrite) { if ((*tile_ptr)->share_count > 1) { /* Copy-on-write required */ Tile *new = g_new (Tile, 1); tile_init (new, (*tile_ptr)->bpp); new->ewidth = (*tile_ptr)->ewidth; new->eheight = (*tile_ptr)->eheight; new->valid = (*tile_ptr)->valid; new->size = new->ewidth * new->eheight * new->bpp; new->data = g_new (guchar, new->size); if ((*tile_ptr)->rowhint) new->rowhint = g_memdup ((*tile_ptr)->rowhint, new->eheight * sizeof (TileRowHint)); if ((*tile_ptr)->data) { memcpy (new->data, (*tile_ptr)->data, new->size); } else { tile_lock (*tile_ptr); memcpy (new->data, (*tile_ptr)->data, new->size); tile_release (*tile_ptr, FALSE); } tile_detach (*tile_ptr, tm, tile_num); tile_attach (new, tm, tile_num); *tile_ptr = new; } (*tile_ptr)->write_count++; (*tile_ptr)->dirty = TRUE; } #ifdef DEBUG_TILE_MANAGER else { if (G_UNLIKELY ((*tile_ptr)->write_count)) g_printerr ("STINK! r/o on r/w tile (%d)\n", (*tile_ptr)->write_count); } #endif tile_lock (*tile_ptr); } return *tile_ptr; } void tile_manager_validate (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); #ifdef DEBUG_TILE_MANAGER g_printerr ("%c", tm->user_data ? 'V' : 'v'); #endif } void tile_manager_invalidate_tiles (TileManager *tm, Tile *toplevel_tile) { gdouble x, y; gint row, col; g_return_if_fail (tm != NULL); g_return_if_fail (toplevel_tile != NULL); col = toplevel_tile->tlink->tile_num % tm->ntile_cols; row = toplevel_tile->tlink->tile_num / tm->ntile_cols; x = (col * TILE_WIDTH + toplevel_tile->ewidth / 2.0) / (gdouble) tm->width; y = (row * TILE_HEIGHT + toplevel_tile->eheight / 2.0) / (gdouble) tm->height; if (tm->tiles) { gint num; col = x * tm->width / TILE_WIDTH; row = y * tm->height / TILE_HEIGHT; num = row * tm->ntile_cols + col; tile_invalidate (&tm->tiles[num], tm, num); } } void tile_invalidate_tile (Tile **tile_ptr, TileManager *tm, gint xpixel, gint ypixel) { gint num; g_return_if_fail (tile_ptr != NULL); g_return_if_fail (tm != NULL); num = tile_manager_get_tile_num (tm, xpixel, ypixel); if (num < 0) return; tile_invalidate (tile_ptr, tm, num); } void tile_invalidate (Tile **tile_ptr, TileManager *tm, gint tile_num) { Tile *tile = *tile_ptr; g_return_if_fail (tile_ptr != NULL); g_return_if_fail (tm != NULL); if (! tile->valid) return; if (G_UNLIKELY (tile->share_count > 1)) { /* This tile is shared. Replace it with a new, invalid tile. */ Tile *new = g_new (Tile, 1); g_print ("invalidating shared tile (executing buggy code!!!)\n"); tile_init (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 = *tile_ptr = new; } if (tile->listhead) tile_cache_flush (tile); tile->valid = FALSE; if (tile->data) { g_free (tile->data); tile->data = NULL; } if (tile->swap_offset != -1) { /* If the tile is on disk, then delete its * presence there. */ tile_swap_delete (tile); } } void tile_manager_map_tile (TileManager *tm, gint xpixel, gint ypixel, Tile *srctile) { gint num; g_return_if_fail (tm != NULL); g_return_if_fail (srctile != NULL); num = tile_manager_get_tile_num (tm, xpixel, ypixel); if (G_UNLIKELY (num < 0)) { g_warning ("%s: tile coordinates out of range.", G_GNUC_FUNCTION); return; } tile_manager_map (tm, num, srctile); } void tile_manager_map (TileManager *tm, gint tile_num, Tile *srctile) { Tile **tiles; Tile **tile_ptr; gint ntiles; gint nrows, ncols; gint right_tile; gint bottom_tile; gint i, j, k; g_return_if_fail (tm != NULL); g_return_if_fail (srctile != NULL); ntiles = tm->ntile_rows * tm->ntile_cols; if (G_UNLIKELY ((tile_num < 0) || (tile_num >= ntiles))) { g_warning ("%s: tile out of range", G_GNUC_FUNCTION); return; } if (G_UNLIKELY (! tm->tiles)) { g_warning ("%s: empty tile level - initializing", G_GNUC_FUNCTION); tm->tiles = g_new (Tile *, ntiles); tiles = tm->tiles; nrows = tm->ntile_rows; ncols = tm->ntile_cols; right_tile = tm->width - ((ncols - 1) * TILE_WIDTH); bottom_tile = tm->height - ((nrows - 1) * TILE_HEIGHT); for (i = 0, k = 0; i < nrows; i++) { for (j = 0; j < ncols; j++, k++) { Tile *new = g_new (Tile, 1); #ifdef DEBUG_TILE_MANAGER g_printerr (","); #endif tile_init (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; } } } tile_ptr = &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_GNUC_FUNCTION); if (G_UNLIKELY ((*tile_ptr)->ewidth != srctile->ewidth || (*tile_ptr)->eheight != srctile->eheight || (*tile_ptr)->bpp != srctile->bpp)) { g_warning ("%s: nonconformant map (%p -> %p)", G_GNUC_FUNCTION, srctile, *tile_ptr); } tile_detach (*tile_ptr, 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); *tile_ptr = srctile; #ifdef DEBUG_TILE_MANAGER g_printerr ("}\n"); #endif } void tile_manager_set_user_data (TileManager *tm, gpointer user_data) { g_return_if_fail (tm != NULL); tm->user_data = user_data; } gpointer tile_manager_get_user_data (const TileManager *tm) { g_return_val_if_fail (tm != NULL, NULL); return tm->user_data; } 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; } void tile_manager_get_offsets (const TileManager *tm, gint *x, gint *y) { g_return_if_fail (tm != NULL); g_return_if_fail (x != NULL && y != NULL); *x = tm->x; *y = tm->y; } void tile_manager_set_offsets (TileManager *tm, gint x, gint y) { g_return_if_fail (tm != NULL); tm->x = x; tm->y = y; } gint64 tile_manager_get_memsize (const TileManager *tm, gboolean sparse) { gint64 memsize; g_return_val_if_fail (tm != NULL, 0); /* the tile manager itself */ memsize = sizeof (TileManager); /* 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; } void tile_manager_get_tile_coordinates (TileManager *tm, Tile *tile, gint *x, gint *y) { TileLink *tl; g_return_if_fail (tm != NULL); g_return_if_fail (x != NULL && y != 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_GNUC_FUNCTION); return; } *x = TILE_WIDTH * (tl->tile_num % tm->ntile_cols); *y = TILE_HEIGHT * (tl->tile_num / tm->ntile_cols); } 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_GNUC_FUNCTION); return; } tile_manager_map (tm, tl->tile_num, srctile); } PixelDataHandle * request_pixel_data (TileManager *tm, gint x1, gint y1, gint x2, gint y2, gboolean wantread, gboolean wantwrite) { PixelDataHandlePrivate *pdh; guint tile_num_1, tile_num_2; guint w, h; pdh = g_new (PixelDataHandlePrivate, 1); pdh->tm = tm; pdh->readable = wantread; pdh->writeable = wantwrite; pdh->x1 = x1; pdh->y1 = y1; pdh->x2 = x2; pdh->y2 = y2; pdh->public.width = w = (x2 - x1) + 1; pdh->public.height = h = (y2 - y1) + 1; tile_num_1 = tile_manager_get_tile_num (tm, x1, y1); tile_num_2 = tile_manager_get_tile_num (tm, x2, y2); if (tile_num_1 == tile_num_2) { pdh->tile = tile_manager_get (tm, tile_num_1, wantread, wantwrite); pdh->public.data = tile_data_pointer (pdh->tile, x1 % TILE_WIDTH, y1 % TILE_HEIGHT); pdh->public.stride = tile_bpp (pdh->tile) * tile_ewidth (pdh->tile); pdh->local_buffer = FALSE; } else { pdh->public.data = g_new (guchar, w * h * tm->bpp); pdh->public.stride = tm->bpp * w; pdh->local_buffer = TRUE; pdh->tile = NULL; if (wantread) read_pixel_data (tm, x1, y1, x2, y2, pdh->public.data, pdh->public.stride); } return (PixelDataHandle *) pdh; } void release_pixel_data (PixelDataHandle *xpdh) { PixelDataHandlePrivate *pdh = (PixelDataHandlePrivate *) xpdh; if (pdh->local_buffer) { if (pdh->writeable) write_pixel_data (pdh->tm, pdh->x1, pdh->y1, pdh->x2, pdh->y2, pdh->public.data, pdh->public.stride); g_free (pdh->public.data); } else { tile_release (pdh->tile, pdh->writeable); } g_free (pdh); } void read_pixel_data (TileManager *tm, gint x1, gint y1, gint x2, gint y2, guchar *buffer, guint stride) { Tile *t; guchar *s, *d; guint x, y; guint rows, cols; guint srcstride; for (y = y1; y <= y2; y += TILE_HEIGHT - (y % TILE_HEIGHT)) for (x = x1; x <= x2; x += TILE_WIDTH - (x % TILE_WIDTH)) { t = tile_manager_get_tile (tm, x, y, TRUE, FALSE); s = tile_data_pointer (t, x % TILE_WIDTH, y % TILE_HEIGHT); d = buffer + stride * (y - y1) + tm->bpp * (x - x1); rows = tile_eheight (t) - y % TILE_HEIGHT; if (rows > (y2 - y + 1)) rows = y2 - y + 1; cols = tile_ewidth (t) - x % TILE_WIDTH; if (cols > (x2 - x + 1)) cols = x2 - x + 1; srcstride = tile_ewidth (t) * tile_bpp (t); while (rows --) { memcpy (d, s, cols * tm->bpp); s += srcstride; d += stride; } tile_release (t, FALSE); } } void write_pixel_data (TileManager *tm, gint x1, gint y1, gint x2, gint y2, const guchar *buffer, guint stride) { Tile *t; const guchar *s; guchar *d; guint x, y; guint rows, cols; guint dststride; for (y = y1; y <= y2; y += TILE_HEIGHT - (y % TILE_HEIGHT)) for (x = x1; x <= x2; x += TILE_WIDTH - (x % TILE_WIDTH)) { t = tile_manager_get_tile (tm, x, y, TRUE, TRUE); s = buffer + stride * (y - y1) + tm->bpp * (x - x1); d = tile_data_pointer (t, x % TILE_WIDTH, y % TILE_HEIGHT); rows = tile_eheight (t) - y % TILE_HEIGHT; if (rows > (y2 - y + 1)) rows = y2 - y + 1; cols = tile_ewidth (t) - x % TILE_WIDTH; if (cols > (x2 - x + 1)) cols = x2 - x + 1; dststride = tile_ewidth (t) * tile_bpp (t); while (rows --) { memcpy (d, s, cols * tm->bpp); s += stride; d += dststride; } tile_release (t, TRUE); } } void read_pixel_data_1 (TileManager *tm, gint x, gint y, guchar *buffer) { if (x >= 0 && y >= 0 && x < tm->width && y < tm->height) { gint num = tile_manager_get_tile_num (tm, x, y); if (num != tm->cached_num) /* must fetch a new tile */ { if (tm->cached_tile) tile_release (tm->cached_tile, FALSE); tm->cached_num = num; tm->cached_tile = tile_manager_get (tm, num, TRUE, FALSE); } if (tm->cached_tile) { const guchar *src = tile_data_pointer (tm->cached_tile, x % TILE_WIDTH, y % TILE_HEIGHT); 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 % TILE_WIDTH, y % TILE_HEIGHT); switch (tm->bpp) { case 4: *dest++ = *buffer++; case 3: *dest++ = *buffer++; case 2: *dest++ = *buffer++; case 1: *dest++ = *buffer++; } tile_release (tile, TRUE); }