gimp/app/core/gimpprojection.c

701 lines
20 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 <gtk/gtk.h>
#include "display-types.h"
#include "tools/tools-types.h"
#include "core/gimp.h"
#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-projection.h"
#include "core/gimplist.h"
#include "widgets/gimpuimanager.h"
#include "tools/gimptool.h"
#include "tools/tool_manager.h"
#include "gimpdisplay.h"
#include "gimpdisplay-area.h"
#include "gimpdisplay-handlers.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-handlers.h"
#include "gimpdisplayshell-transform.h"
#include "gimp-intl.h"
enum
{
PROP_0,
PROP_ID,
PROP_IMAGE
};
/* local function prototypes */
static void gimp_display_class_init (GimpDisplayClass *klass);
static void gimp_display_init (GimpDisplay *gdisp);
static void gimp_display_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_display_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_display_flush_whenever (GimpDisplay *gdisp,
gboolean now);
static void gimp_display_idlerender_init (GimpDisplay *gdisp);
static gboolean gimp_display_idlerender_callback (gpointer data);
static gboolean gimp_display_idle_render_next_area (GimpDisplay *gdisp);
static void gimp_display_paint_area (GimpDisplay *gdisp,
gint x,
gint y,
gint w,
gint h);
static GimpObjectClass *parent_class = NULL;
GType
gimp_display_get_type (void)
{
static GType display_type = 0;
if (! display_type)
{
static const GTypeInfo display_info =
{
sizeof (GimpDisplayClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gimp_display_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GimpDisplay),
0, /* n_preallocs */
(GInstanceInitFunc) gimp_display_init,
};
display_type = g_type_register_static (GIMP_TYPE_OBJECT,
"GimpDisplay",
&display_info, 0);
}
return display_type;
}
static void
gimp_display_class_init (GimpDisplayClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
object_class->set_property = gimp_display_set_property;
object_class->get_property = gimp_display_get_property;
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_int ("id",
NULL, NULL,
0, G_MAXINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class, PROP_IMAGE,
g_param_spec_object ("image",
NULL, NULL,
GIMP_TYPE_IMAGE,
G_PARAM_READABLE));
}
static void
gimp_display_init (GimpDisplay *gdisp)
{
gdisp->ID = 0;
gdisp->gimage = NULL;
gdisp->instance = 0;
gdisp->shell = NULL;
gdisp->update_areas = NULL;
gdisp->idle_render.idle_id = 0;
gdisp->idle_render.update_areas = NULL;
}
static void
gimp_display_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpDisplay *gdisp = GIMP_DISPLAY (object);
switch (property_id)
{
case PROP_ID:
gdisp->ID = g_value_get_int (value);
break;
case PROP_IMAGE:
g_assert_not_reached ();
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_display_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpDisplay *gdisp = GIMP_DISPLAY (object);
switch (property_id)
{
case PROP_ID:
g_value_set_int (value, gdisp->ID);
break;
case PROP_IMAGE:
g_value_set_object (value, gdisp->gimage);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
GimpDisplay *
gimp_display_new (GimpImage *gimage,
GimpUnit unit,
gdouble scale,
GimpMenuFactory *menu_factory,
GimpUIManager *popup_manager)
{
GimpDisplay *gdisp;
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), NULL);
/* If there isn't an interface, never create a gdisplay */
if (gimage->gimp->no_interface)
return NULL;
gdisp = g_object_new (GIMP_TYPE_DISPLAY,
"id", gimage->gimp->next_display_ID++,
NULL);
/* refs the image */
gimp_display_connect (gdisp, gimage);
/* create the shell for the image */
gdisp->shell = gimp_display_shell_new (gdisp, unit, scale,
menu_factory, popup_manager);
gtk_widget_show (gdisp->shell);
return gdisp;
}
void
gimp_display_delete (GimpDisplay *gdisp)
{
GimpTool *active_tool;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
/* remove the display from the list */
gimp_container_remove (gdisp->gimage->gimp->displays,
GIMP_OBJECT (gdisp));
/* stop any active tool */
tool_manager_control_active (gdisp->gimage->gimp, HALT, gdisp);
active_tool = tool_manager_get_active (gdisp->gimage->gimp);
if (active_tool)
{
if (active_tool->focus_display == gdisp)
tool_manager_focus_display_active (gdisp->gimage->gimp, NULL);
/* clear out the pointer to this gdisp from the active tool */
if (active_tool->gdisp == gdisp)
{
active_tool->drawable = NULL;
active_tool->gdisp = NULL;
}
}
/* If this gdisplay was idlerendering at the time when it was deleted,
* deactivate the idlerendering thread before deletion!
*/
if (gdisp->idle_render.idle_id)
{
g_source_remove (gdisp->idle_render.idle_id);
gdisp->idle_render.idle_id = 0;
}
/* free the update area lists */
gimp_display_area_list_free (gdisp->update_areas);
gimp_display_area_list_free (gdisp->idle_render.update_areas);
if (gdisp->shell)
{
GtkWidget *shell = gdisp->shell;
/* set gdisp->shell to NULL *before* destroying the shell.
* all callbacks in gimpdisplayshell-callbacks.c will check
* this pointer and do nothing if the shell is in destruction.
*/
gdisp->shell = NULL;
gtk_widget_destroy (shell);
}
/* unrefs the gimage */
gimp_display_disconnect (gdisp);
g_object_unref (gdisp);
}
gint
gimp_display_get_ID (GimpDisplay *gdisp)
{
g_return_val_if_fail (GIMP_IS_DISPLAY (gdisp), -1);
return gdisp->ID;
}
GimpDisplay *
gimp_display_get_by_ID (Gimp *gimp,
gint ID)
{
GList *list;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
for (list = GIMP_LIST (gimp->displays)->list;
list;
list = g_list_next (list))
{
GimpDisplay *gdisp = list->data;
if (gdisp->ID == ID)
return gdisp;
}
return NULL;
}
void
gimp_display_reconnect (GimpDisplay *gdisp,
GimpImage *gimage)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
g_return_if_fail (GIMP_IS_IMAGE (gimage));
if (gdisp->idle_render.idle_id)
{
g_source_remove (gdisp->idle_render.idle_id);
gdisp->idle_render.idle_id = 0;
}
/* stop any active tool */
tool_manager_control_active (gdisp->gimage->gimp, HALT, gdisp);
gimp_display_shell_disconnect (GIMP_DISPLAY_SHELL (gdisp->shell));
gimp_display_disconnect (gdisp);
gimp_display_connect (gdisp, gimage);
gimp_display_add_update_area (gdisp,
0, 0,
gdisp->gimage->width,
gdisp->gimage->height);
gimp_display_shell_reconnect (GIMP_DISPLAY_SHELL (gdisp->shell));
}
void
gimp_display_add_update_area (GimpDisplay *gdisp,
gint x,
gint y,
gint w,
gint h)
{
GimpArea *area;
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
area = gimp_area_new (CLAMP (x, 0, gdisp->gimage->width),
CLAMP (y, 0, gdisp->gimage->height),
CLAMP (x + w, 0, gdisp->gimage->width),
CLAMP (y + h, 0, gdisp->gimage->height));
gdisp->update_areas = gimp_display_area_list_process (gdisp->update_areas,
area);
}
void
gimp_display_flush (GimpDisplay *gdisp)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
/* Redraw on idle time */
gimp_display_flush_whenever (gdisp, FALSE);
}
void
gimp_display_flush_now (GimpDisplay *gdisp)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
/* Redraw NOW */
gimp_display_flush_whenever (gdisp, TRUE);
}
/* Force all gdisplays to finish their idlerender projection */
void
gimp_display_finish_draw (GimpDisplay *gdisp)
{
g_return_if_fail (GIMP_IS_DISPLAY (gdisp));
if (gdisp->idle_render.idle_id)
{
g_source_remove (gdisp->idle_render.idle_id);
gdisp->idle_render.idle_id = 0;
while (gimp_display_idlerender_callback (gdisp));
}
}
/* utility function to check if the cursor is inside the active drawable */
gboolean
gimp_display_coords_in_active_drawable (GimpDisplay *gdisp,
const GimpCoords *coords)
{
GimpDrawable *drawable;
g_return_val_if_fail (GIMP_IS_DISPLAY (gdisp), FALSE);
if (!gdisp->gimage)
return FALSE;
drawable = gimp_image_active_drawable (gdisp->gimage);
if (drawable)
{
GimpItem *item;
gint x, y;
item = GIMP_ITEM (drawable);
gimp_item_offsets (item, &x, &y);
x = ROUND (coords->x) - x;
y = ROUND (coords->y) - y;
if (x < 0 || x > gimp_item_width (item))
return FALSE;
if (y < 0 || y > gimp_item_height (item))
return FALSE;
return TRUE;
}
return FALSE;
}
/* private functions */
static void
gimp_display_flush_whenever (GimpDisplay *gdisp,
gboolean now)
{
GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (gdisp->shell);
/* Flush the items in the displays and updates lists -
* but only if gdisplay has been mapped and exposed
*/
if (! shell->select)
{
g_warning ("%s: called unrealized", G_STRFUNC);
return;
}
/* First the updates... */
if (gdisp->update_areas)
{
if (now) /* Synchronous */
{
GSList *list;
for (list = gdisp->update_areas; list; list = g_slist_next (list))
{
GimpArea *area = list->data;
if ((area->x1 != area->x2) && (area->y1 != area->y2))
{
gimp_display_paint_area (gdisp,
area->x1,
area->y1,
(area->x2 - area->x1),
(area->y2 - area->y1));
}
}
}
else /* Asynchronous */
{
gimp_display_idlerender_init (gdisp);
}
/* Free the update lists */
gdisp->update_areas = gimp_display_area_list_free (gdisp->update_areas);
}
else
{
/* if there was nothing to update, we still need to start the
* selection --mitch
*/
gimp_display_shell_selection_visibility (shell, GIMP_SELECTION_ON);
}
/* Next the displays... */
gimp_display_shell_flush (shell, now);
/* ensure the consistency of the menus */
if (! now)
{
GimpContext *user_context;
gimp_ui_manager_update (shell->menubar_manager, shell);
user_context = gimp_get_user_context (gdisp->gimage->gimp);
if (gdisp == gimp_context_get_display (user_context))
gimp_ui_manager_update (shell->popup_manager, shell);
}
}
static void
gimp_display_idlerender_init (GimpDisplay *gdisp)
{
GSList *list;
GimpArea *area;
/* We need to merge the IdleRender's and the GimpDisplay's update_areas list
* to keep track of which of the updates have been flushed and hence need
* to be drawn.
*/
for (list = gdisp->update_areas; list; list = g_slist_next (list))
{
area = g_memdup (list->data, sizeof (GimpArea));
gdisp->idle_render.update_areas =
gimp_display_area_list_process (gdisp->idle_render.update_areas, area);
}
/* If an idlerender was already running, merge the remainder of its
* unrendered area with the update_areas list, and make it start work
* on the next unrendered area in the list.
*/
if (gdisp->idle_render.idle_id)
{
area =
gimp_area_new (gdisp->idle_render.basex,
gdisp->idle_render.y,
gdisp->idle_render.basex + gdisp->idle_render.width,
gdisp->idle_render.y + (gdisp->idle_render.height -
(gdisp->idle_render.y -
gdisp->idle_render.basey)));
gdisp->idle_render.update_areas =
gimp_display_area_list_process (gdisp->idle_render.update_areas, area);
gimp_display_idle_render_next_area (gdisp);
}
else
{
if (gdisp->idle_render.update_areas == NULL)
{
g_warning ("Wanted to start idlerender thread with no update_areas. (+memleak)");
return;
}
gimp_display_idle_render_next_area (gdisp);
gdisp->idle_render.idle_id = g_idle_add_full (G_PRIORITY_LOW,
gimp_display_idlerender_callback,
gdisp,
NULL);
}
/* Caller frees gdisp->update_areas */
}
/* Unless specified otherwise, display re-rendering is organised by
* IdleRender, which amalgamates areas to be re-rendered and breaks
* them into bite-sized chunks which are chewed on in a low- priority
* idle thread. This greatly improves responsiveness for many GIMP
* operations. -- Adam
*/
static gboolean
gimp_display_idlerender_callback (gpointer data)
{
const gint CHUNK_WIDTH = 256;
const gint CHUNK_HEIGHT = 128;
gint workx, worky, workw, workh;
GimpDisplay *gdisp = data;
workw = CHUNK_WIDTH;
workh = CHUNK_HEIGHT;
workx = gdisp->idle_render.x;
worky = gdisp->idle_render.y;
if (workx + workw > gdisp->idle_render.basex + gdisp->idle_render.width)
{
workw = gdisp->idle_render.basex + gdisp->idle_render.width - workx;
}
if (worky + workh > gdisp->idle_render.basey + gdisp->idle_render.height)
{
workh = gdisp->idle_render.basey + gdisp->idle_render.height - worky;
}
gimp_display_paint_area (gdisp, workx, worky, workw, workh);
gdisp->idle_render.x += CHUNK_WIDTH;
if (gdisp->idle_render.x >=
gdisp->idle_render.basex + gdisp->idle_render.width)
{
gdisp->idle_render.x = gdisp->idle_render.basex;
gdisp->idle_render.y += CHUNK_HEIGHT;
if (gdisp->idle_render.y >=
gdisp->idle_render.basey + gdisp->idle_render.height)
{
if (! gimp_display_idle_render_next_area (gdisp))
{
/* FINISHED */
gdisp->idle_render.idle_id = 0;
return FALSE;
}
}
}
/* Still work to do. */
return TRUE;
}
static gboolean
gimp_display_idle_render_next_area (GimpDisplay *gdisp)
{
GimpArea *area;
if (! gdisp->idle_render.update_areas)
return FALSE;
area = (GimpArea *) gdisp->idle_render.update_areas->data;
gdisp->idle_render.update_areas =
g_slist_remove (gdisp->idle_render.update_areas, area);
gdisp->idle_render.x = gdisp->idle_render.basex = area->x1;
gdisp->idle_render.y = gdisp->idle_render.basey = area->y1;
gdisp->idle_render.width = area->x2 - area->x1;
gdisp->idle_render.height = area->y2 - area->y1;
g_free (area);
return TRUE;
}
static void
gimp_display_paint_area (GimpDisplay *gdisp,
gint x,
gint y,
gint w,
gint h)
{
GimpDisplayShell *shell = GIMP_DISPLAY_SHELL (gdisp->shell);
gint x1, y1, x2, y2;
gdouble x1_f, y1_f, x2_f, y2_f;
/* Bounds check */
x1 = CLAMP (x, 0, gdisp->gimage->width);
y1 = CLAMP (y, 0, gdisp->gimage->height);
x2 = CLAMP (x + w, 0, gdisp->gimage->width);
y2 = CLAMP (y + h, 0, gdisp->gimage->height);
x = x1;
y = y1;
w = (x2 - x1);
h = (y2 - y1);
/* calculate the extents of the update as limited by what's visible */
gimp_display_shell_untransform_xy_f (shell,
0, 0,
&x1_f, &y1_f,
FALSE);
gimp_display_shell_untransform_xy_f (shell,
shell->disp_width, shell->disp_height,
&x2_f, &y2_f,
FALSE);
/* make sure to limit the invalidated area to a superset of the
* untransformed sub-pixel display area, not a subset.
* bug #116765. --mitch
*/
x1 = floor (x1_f);
y1 = floor (y1_f);
x2 = ceil (x2_f);
y2 = ceil (y2_f);
gimp_image_invalidate (gdisp->gimage, x, y, w, h, x1, y1, x2, y2);
/* display the area */
gimp_display_shell_transform_xy_f (shell, x, y, &x1_f, &y1_f, FALSE);
gimp_display_shell_transform_xy_f (shell, x + w, y + h, &x2_f, &y2_f, FALSE);
/* make sure to expose a superset of the transformed sub-pixel expose
* area, not a subset. bug #126942. --mitch
*/
x1 = floor (x1_f);
y1 = floor (y1_f);
x2 = ceil (x2_f);
y2 = ceil (y2_f);
gimp_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1);
}