gimp/app/core/gimpimage-projection.c

745 lines
22 KiB
C

/* 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 <glib-object.h>
#include "libgimpcolor/gimpcolor.h"
#include "core-types.h"
#include "base/pixel-region.h"
#include "base/tile-manager.h"
#include "base/tile.h"
#include "paint-funcs/paint-funcs.h"
#include "gimp.h"
#include "gimpimage.h"
#include "gimpimage-projection.h"
#include "gimplayer.h"
#include "gimplayermask.h"
#include "gimplist.h"
#include "floating_sel.h"
/* local function prototypes */
static void gimp_image_projection_validate_tile (TileManager *tm,
Tile *tile);
static void gimp_image_construct_layers (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h);
static void gimp_image_construct_channels (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h);
static void gimp_image_initialize_projection (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h);
static void gimp_image_construct (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h);
static void project_intensity (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest,
PixelRegion *mask);
static void project_intensity_alpha (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest,
PixelRegion *mask);
static void project_indexed (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest);
static void project_indexed_alpha (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest,
PixelRegion *mask);
static void project_channel (GimpImage *gimage,
GimpChannel *channel,
PixelRegion *src,
PixelRegion *src2);
/* public functions */
void
gimp_image_projection_allocate (GimpImage *gimage)
{
if (gimage->projection)
gimp_image_projection_free (gimage);
/* Find the number of bytes required for the projection.
* This includes the intensity channels and an alpha channel
* if one doesn't exist.
*/
switch (gimp_image_base_type (gimage))
{
case GIMP_RGB:
case GIMP_INDEXED:
gimage->proj_bytes = 4;
gimage->proj_type = RGBA_GIMAGE;
break;
case GIMP_GRAY:
gimage->proj_bytes = 2;
gimage->proj_type = GRAYA_GIMAGE;
break;
default:
g_assert_not_reached ();
}
/* allocate the new projection */
gimage->projection = tile_manager_new (gimage->width, gimage->height,
gimage->proj_bytes);
tile_manager_set_user_data (gimage->projection, (void *) gimage);
tile_manager_set_validate_proc (gimage->projection,
gimp_image_projection_validate_tile);
}
void
gimp_image_projection_free (GimpImage *gimage)
{
g_return_if_fail (GIMP_IS_IMAGE (gimage));
if (gimage->projection)
{
tile_manager_destroy (gimage->projection);
gimage->projection = NULL;
}
}
TileManager *
gimp_image_projection (GimpImage *gimage)
{
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
if ((gimage->projection == NULL) ||
(tile_manager_width (gimage->projection) != gimage->width) ||
(tile_manager_height (gimage->projection) != gimage->height))
{
gimp_image_projection_allocate (gimage);
}
return gimage->projection;
}
GimpImageType
gimp_image_projection_type (const GimpImage *gimage)
{
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);
return gimage->proj_type;
}
gint
gimp_image_projection_bytes (const GimpImage *gimage)
{
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);
return gimage->proj_bytes;
}
gint
gimp_image_projection_opacity (const GimpImage *gimage)
{
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), -1);
return OPAQUE_OPACITY;
}
guchar *
gimp_image_projection_get_color_at (GimpImage *gimage,
gint x,
gint y)
{
Tile *tile;
guchar *src;
guchar *dest;
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
if (x < 0 || y < 0 || x >= gimage->width || y >= gimage->height)
return NULL;
dest = g_new (guchar, 5);
tile = tile_manager_get_tile (gimp_image_projection (gimage), x, y,
TRUE, FALSE);
src = tile_data_pointer (tile, x % TILE_WIDTH, y % TILE_HEIGHT);
gimp_image_get_color (gimage, gimp_image_projection_type (gimage), dest, src);
if (GIMP_IMAGE_TYPE_HAS_ALPHA (gimp_image_projection_type (gimage)))
dest[3] = src[gimp_image_projection_bytes (gimage) - 1];
else
dest[3] = 255;
dest[4] = 0;
tile_release (tile, FALSE);
return dest;
}
void
gimp_image_invalidate (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h,
gint x1,
gint y1,
gint x2,
gint y2)
{
Tile *tile;
TileManager *tm;
gint i, j;
gint startx, starty;
gint endx, endy;
gint tilex, tiley;
g_return_if_fail (GIMP_IS_IMAGE (gimage));
tm = gimp_image_projection (gimage);
startx = x;
starty = y;
endx = x + w;
endy = y + h;
/* invalidate all tiles which are located outside of the displayed area
* all tiles inside the displayed area are constructed.
*/
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 = tile_manager_get_tile (tm, j, i, FALSE, FALSE);
/* check if the tile is outside the bounds */
if ((MIN ((j + tile_ewidth(tile)), x2) - MAX (j, x1)) <= 0)
{
tile_invalidate_tile (&tile, tm, j, i);
if (j < x1)
startx = MAX (startx, (j + tile_ewidth(tile)));
else
endx = MIN (endx, j);
}
else if (MIN ((i + tile_eheight(tile)), y2) - MAX (i, y1) <= 0)
{
tile_invalidate_tile (&tile, tm, j, i);
if (i < y1)
starty = MAX (starty, (i + tile_eheight(tile)));
else
endy = MIN (endy, i);
}
else
{
/* If the tile is not valid, make sure we get the entire tile
* in the construction extents
*/
if (tile_is_valid (tile) == FALSE)
{
tilex = j - (j % TILE_WIDTH);
tiley = i - (i % TILE_HEIGHT);
startx = MIN (startx, tilex);
endx = MAX (endx, tilex + tile_ewidth (tile));
starty = MIN (starty, tiley);
endy = MAX (endy, tiley + tile_eheight (tile));
tile_mark_valid (tile); /* hmmmmmmm..... */
}
}
}
if ((endx - startx) > 0 && (endy - starty) > 0)
gimp_image_construct (gimage,
startx, starty,
(endx - startx), (endy - starty));
}
void
gimp_image_invalidate_without_render (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h,
gint x1,
gint y1,
gint x2,
gint y2)
{
Tile *tile;
TileManager *tm;
gint i, j;
g_return_if_fail (GIMP_IS_IMAGE (gimage));
tm = gimp_image_projection (gimage);
/* invalidate all tiles which are located outside of the displayed area
* all tiles inside the displayed area are constructed.
*/
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 = tile_manager_get_tile (tm, j, i, FALSE, FALSE);
/* check if the tile is outside the bounds */
if ((MIN ((j + tile_ewidth(tile)), x2) - MAX (j, x1)) <= 0)
{
tile_invalidate_tile (&tile, tm, j, i);
}
else if (MIN ((i + tile_eheight(tile)), y2) - MAX (i, y1) <= 0)
{
tile_invalidate_tile (&tile, tm, j, i);
}
}
}
/* private functions */
static void
gimp_image_projection_validate_tile (TileManager *tm,
Tile *tile)
{
GimpImage *gimage;
gint x, y;
gint w, h;
/* Get the gimage from the tilemanager */
gimage = (GimpImage *) tile_manager_get_user_data (tm);
gimp_set_busy_until_idle (gimage->gimp);
/* Find the coordinates of this tile */
tile_manager_get_tile_coordinates (tm, tile, &x, &y);
w = tile_ewidth (tile);
h = tile_eheight (tile);
gimp_image_construct (gimage, x, y, w, h);
}
static void
gimp_image_construct_layers (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h)
{
GimpLayer *layer;
gint x1, y1, x2, y2;
PixelRegion src1PR, src2PR, maskPR;
PixelRegion * mask;
GList *list;
GList *reverse_list;
gint off_x;
gint off_y;
/* composite the floating selection if it exists */
if ((layer = gimp_image_floating_sel (gimage)))
floating_sel_composite (layer, x, y, w, h, FALSE);
reverse_list = NULL;
for (list = GIMP_LIST (gimage->layers)->list;
list;
list = g_list_next (list))
{
layer = (GimpLayer *) list->data;
/* only add layers that are visible and not floating selections
* to the list
*/
if (! gimp_layer_is_floating_sel (layer) &&
gimp_drawable_get_visible (GIMP_DRAWABLE (layer)))
{
reverse_list = g_list_prepend (reverse_list, layer);
}
}
for (list = reverse_list; list; list = g_list_next (list))
{
layer = (GimpLayer *) list->data;
gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y);
x1 = CLAMP (off_x, x, x + w);
y1 = CLAMP (off_y, y, y + h);
x2 = CLAMP (off_x + gimp_drawable_width (GIMP_DRAWABLE (layer)),
x, x + w);
y2 = CLAMP (off_y + gimp_drawable_height (GIMP_DRAWABLE (layer)),
y, y + h);
/* configure the pixel regions */
pixel_region_init (&src1PR, gimp_image_projection (gimage),
x1, y1, (x2 - x1), (y2 - y1),
TRUE);
/* If we're showing the layer mask instead of the layer... */
if (layer->mask && layer->mask->show_mask)
{
pixel_region_init (&src2PR,
gimp_drawable_data (GIMP_DRAWABLE (layer->mask)),
(x1 - off_x), (y1 - off_y),
(x2 - x1), (y2 - y1), FALSE);
copy_gray_to_region (&src2PR, &src1PR);
}
/* Otherwise, normal */
else
{
pixel_region_init (&src2PR,
gimp_drawable_data (GIMP_DRAWABLE (layer)),
(x1 - off_x), (y1 - off_y),
(x2 - x1), (y2 - y1), FALSE);
if (layer->mask && layer->mask->apply_mask)
{
pixel_region_init (&maskPR,
gimp_drawable_data (GIMP_DRAWABLE (layer->mask)),
(x1 - off_x), (y1 - off_y),
(x2 - x1), (y2 - y1), FALSE);
mask = &maskPR;
}
else
mask = NULL;
/* Based on the type of the layer, project the layer onto the
* projection image...
*/
switch (gimp_drawable_type (GIMP_DRAWABLE (layer)))
{
case RGB_GIMAGE: case GRAY_GIMAGE:
/* no mask possible */
project_intensity (gimage, layer, &src2PR, &src1PR, mask);
break;
case RGBA_GIMAGE: case GRAYA_GIMAGE:
project_intensity_alpha (gimage, layer, &src2PR, &src1PR, mask);
break;
case INDEXED_GIMAGE:
/* no mask possible */
project_indexed (gimage, layer, &src2PR, &src1PR);
break;
case INDEXEDA_GIMAGE:
project_indexed_alpha (gimage, layer, &src2PR, &src1PR, mask);
break;
default:
break;
}
}
gimage->construct_flag = TRUE; /* something was projected */
}
g_list_free (reverse_list);
}
static void
gimp_image_construct_channels (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h)
{
GimpChannel *channel;
PixelRegion src1PR;
PixelRegion src2PR;
GList *list;
GList *reverse_list = NULL;
/* reverse the channel list */
for (list = GIMP_LIST (gimage->channels)->list;
list;
list = g_list_next (list))
{
reverse_list = g_list_prepend (reverse_list, list->data);
}
for (list = reverse_list; list; list = g_list_next (list))
{
channel = (GimpChannel *) list->data;
if (gimp_drawable_get_visible (GIMP_DRAWABLE (channel)))
{
/* configure the pixel regions */
pixel_region_init (&src1PR,
gimp_image_projection (gimage),
x, y, w, h,
TRUE);
pixel_region_init (&src2PR,
gimp_drawable_data (GIMP_DRAWABLE (channel)),
x, y, w, h,
FALSE);
project_channel (gimage, channel, &src1PR, &src2PR);
gimage->construct_flag = TRUE;
}
}
g_list_free (reverse_list);
}
static void
gimp_image_initialize_projection (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h)
{
GList *list;
GimpLayer *layer;
gint coverage = 0;
PixelRegion PR;
guchar clear[4] = { 0, 0, 0, 0 };
/* this function determines whether a visible layer
* provides complete coverage over the image. If not,
* the projection is initialized to transparent
*/
for (list = GIMP_LIST (gimage->layers)->list;
list;
list = g_list_next (list))
{
gint off_x, off_y;
layer = (GimpLayer *) list->data;
gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y);
if (gimp_drawable_get_visible (GIMP_DRAWABLE (layer)) &&
! gimp_layer_has_alpha (layer) &&
(off_x <= x) &&
(off_y <= y) &&
(off_x + gimp_drawable_width (GIMP_DRAWABLE (layer)) >= x + w) &&
(off_y + gimp_drawable_height (GIMP_DRAWABLE (layer)) >= y + h))
{
coverage = 1;
break;
}
}
if (!coverage)
{
pixel_region_init (&PR, gimp_image_projection (gimage),
x, y, w, h, TRUE);
color_region (&PR, clear);
}
}
static void
gimp_image_construct (GimpImage *gimage,
gint x,
gint y,
gint w,
gint h)
{
g_return_if_fail (GIMP_IS_IMAGE (gimage));
#if 0
gint xoff;
gint yoff;
/* set the construct flag, used to determine if anything
* has been written to the gimage raw image yet.
*/
gimage->construct_flag = FALSE;
if (gimage->layers)
{
gimp_drawable_offsets (GIMP_DRAWABLE ((GimpLayer*) gimage->layers->data),
&xoff, &yoff);
}
if ((gimage->layers) && /* There's a layer. */
(! g_slist_next (gimage->layers)) && /* It's the only layer. */
(gimp_layer_has_alpha ((GimpLayer *) (gimage->layers->data))) &&
/* It's !flat. */
(gimp_drawable_get_visible (GIMP_DRAWABLE (gimage->layers->data))) &&
/* It's visible. */
(gimp_drawable_width (GIMP_DRAWABLE (gimage->layers->data)) ==
gimage->width) &&
(gimp_drawable_height (GIMP_DRAWABLE (gimage->layers->data)) ==
gimage->height) && /* Covers all. */
(!gimp_drawable_is_indexed (GIMP_DRAWABLE (gimage->layers->data))) &&
/* Not indexed. */
(((GimpLayer *)(gimage->layers->data))->opacity == OPAQUE_OPACITY)
/* Opaque */
)
{
gint xoff;
gint yoff;
gimp_drawable_offsets (GIMP_DRAWABLE (gimage->layers->data),
&xoff, &yoff);
if ((xoff==0) && (yoff==0)) /* Starts at 0,0 */
{
PixelRegion srcPR, destPR;
gpointer pr;
g_warning("Can use cow-projection hack. Yay!");
pixel_region_init (&srcPR, gimp_drawable_data
(GIMP_DRAWABLE (gimage->layers->data)),
x, y, w,h, FALSE);
pixel_region_init (&destPR,
gimp_image_projection (gimage),
x, y, w,h, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &destPR);
pr != NULL;
pr = pixel_regions_process (pr))
{
tile_manager_map_over_tile (destPR.tiles,
destPR.curtile, srcPR.curtile);
}
gimage->construct_flag = TRUE;
gimp_image_construct_channels (gimage, x, y, w, h);
return;
}
}
#else
gimage->construct_flag = FALSE;
#endif
/* First, determine if the projection image needs to be
* initialized--this is the case when there are no visible
* layers that cover the entire canvas--either because layers
* are offset or only a floating selection is visible
*/
gimp_image_initialize_projection (gimage, x, y, w, h);
/* call functions which process the list of layers and
* the list of channels
*/
gimp_image_construct_layers (gimage, x, y, w, h);
gimp_image_construct_channels (gimage, x, y, w, h);
}
static void
project_intensity (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest,
PixelRegion *mask)
{
if (! gimage->construct_flag)
initial_region (src, dest, mask, NULL, layer->opacity,
layer->mode, gimage->visible, INITIAL_INTENSITY);
else
combine_regions (dest, src, dest, mask, NULL, layer->opacity,
layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN);
}
static void
project_intensity_alpha (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest,
PixelRegion *mask)
{
if (! gimage->construct_flag)
initial_region (src, dest, mask, NULL, layer->opacity,
layer->mode, gimage->visible, INITIAL_INTENSITY_ALPHA);
else
combine_regions (dest, src, dest, mask, NULL, layer->opacity,
layer->mode, gimage->visible, COMBINE_INTEN_A_INTEN_A);
}
static void
project_indexed (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest)
{
if (! gimage->construct_flag)
initial_region (src, dest, NULL, gimage->cmap, layer->opacity,
layer->mode, gimage->visible, INITIAL_INDEXED);
else
g_warning ("%s: unable to project indexed image.", G_GNUC_PRETTY_FUNCTION);
}
static void
project_indexed_alpha (GimpImage *gimage,
GimpLayer *layer,
PixelRegion *src,
PixelRegion *dest,
PixelRegion *mask)
{
if (! gimage->construct_flag)
initial_region (src, dest, mask, gimage->cmap, layer->opacity,
layer->mode, gimage->visible, INITIAL_INDEXED_ALPHA);
else
combine_regions (dest, src, dest, mask, gimage->cmap, layer->opacity,
layer->mode, gimage->visible, COMBINE_INTEN_A_INDEXED_A);
}
static void
project_channel (GimpImage *gimage,
GimpChannel *channel,
PixelRegion *src,
PixelRegion *src2)
{
guchar col[3];
guchar opacity;
gint type;
gimp_rgba_get_uchar (&channel->color,
&col[0], &col[1], &col[2], &opacity);
if (! gimage->construct_flag)
{
type = (channel->show_masked) ?
INITIAL_CHANNEL_MASK : INITIAL_CHANNEL_SELECTION;
initial_region (src2, src, NULL, col, opacity,
GIMP_NORMAL_MODE, NULL, type);
}
else
{
type = (channel->show_masked) ?
COMBINE_INTEN_A_CHANNEL_MASK : COMBINE_INTEN_A_CHANNEL_SELECTION;
combine_regions (src, src2, src, NULL, col, opacity,
GIMP_NORMAL_MODE, NULL, type);
}
}