gimp/libgimpwidgets/gimppreviewarea.c

1957 lines
57 KiB
C

/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpcolor/gimpcolor.h"
#include "gimpwidgetstypes.h"
#include "gimppreviewarea.h"
#include "gimpwidgetsutils.h"
#include "libgimp/libgimp-intl.h"
/**
* SECTION: gimppreviewarea
* @title: GimpPreviewArea
* @short_description: A general purpose preview widget which caches
* its pixel data.
*
* A general purpose preview widget which caches its pixel data.
**/
enum
{
PROP_0,
PROP_CHECK_SIZE,
PROP_CHECK_TYPE
};
#define DEFAULT_CHECK_SIZE GIMP_CHECK_SIZE_MEDIUM_CHECKS
#define DEFAULT_CHECK_TYPE GIMP_CHECK_TYPE_GRAY_CHECKS
#define CHECK_COLOR(area, row, col) \
(((((area)->offset_y + (row)) & size) ^ \
(((area)->offset_x + (col)) & size)) ? dark : light)
typedef struct _GimpPreviewAreaPrivate GimpPreviewAreaPrivate;
struct _GimpPreviewAreaPrivate
{
GimpColorConfig *config;
GimpColorTransform *transform;
};
#define GET_PRIVATE(obj) \
((GimpPreviewAreaPrivate *) gimp_preview_area_get_instance_private ((GimpPreviewArea *) (obj)))
static void gimp_preview_area_dispose (GObject *object);
static void gimp_preview_area_finalize (GObject *object);
static void gimp_preview_area_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_preview_area_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void gimp_preview_area_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static gboolean gimp_preview_area_expose (GtkWidget *widget,
GdkEventExpose *event);
static void gimp_preview_area_queue_draw (GimpPreviewArea *area,
gint x,
gint y,
gint width,
gint height);
static gint gimp_preview_area_image_type_bytes (GimpImageType type);
static void gimp_preview_area_create_transform (GimpPreviewArea *area);
static void gimp_preview_area_destroy_transform (GimpPreviewArea *area);
G_DEFINE_TYPE_WITH_PRIVATE (GimpPreviewArea, gimp_preview_area,
GTK_TYPE_DRAWING_AREA)
#define parent_class gimp_preview_area_parent_class
static void
gimp_preview_area_class_init (GimpPreviewAreaClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = gimp_preview_area_dispose;
object_class->finalize = gimp_preview_area_finalize;
object_class->set_property = gimp_preview_area_set_property;
object_class->get_property = gimp_preview_area_get_property;
widget_class->size_allocate = gimp_preview_area_size_allocate;
widget_class->expose_event = gimp_preview_area_expose;
g_object_class_install_property (object_class, PROP_CHECK_SIZE,
g_param_spec_enum ("check-size",
_("Check Size"),
"The size of the checkerboard pattern indicating transparency",
GIMP_TYPE_CHECK_SIZE,
DEFAULT_CHECK_SIZE,
GIMP_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_CHECK_TYPE,
g_param_spec_enum ("check-type",
_("Check Style"),
"The colors of the checkerboard pattern indicating transparency",
GIMP_TYPE_CHECK_TYPE,
DEFAULT_CHECK_TYPE,
GIMP_PARAM_READWRITE));
}
static void
gimp_preview_area_init (GimpPreviewArea *area)
{
area->check_size = DEFAULT_CHECK_SIZE;
area->check_type = DEFAULT_CHECK_TYPE;
area->buf = NULL;
area->colormap = NULL;
area->offset_x = 0;
area->offset_y = 0;
area->width = 0;
area->height = 0;
area->rowstride = 0;
area->max_width = -1;
area->max_height = -1;
gimp_widget_track_monitor (GTK_WIDGET (area),
G_CALLBACK (gimp_preview_area_destroy_transform),
NULL);
}
static void
gimp_preview_area_dispose (GObject *object)
{
GimpPreviewArea *area = GIMP_PREVIEW_AREA (object);
gimp_preview_area_set_color_config (area, NULL);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_preview_area_finalize (GObject *object)
{
GimpPreviewArea *area = GIMP_PREVIEW_AREA (object);
g_clear_pointer (&area->buf, g_free);
g_clear_pointer (&area->colormap, g_free);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gimp_preview_area_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpPreviewArea *area = GIMP_PREVIEW_AREA (object);
switch (property_id)
{
case PROP_CHECK_SIZE:
area->check_size = g_value_get_enum (value);
break;
case PROP_CHECK_TYPE:
area->check_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_preview_area_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpPreviewArea *area = GIMP_PREVIEW_AREA (object);
switch (property_id)
{
case PROP_CHECK_SIZE:
g_value_set_enum (value, area->check_size);
break;
case PROP_CHECK_TYPE:
g_value_set_enum (value, area->check_type);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_preview_area_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GimpPreviewArea *area = GIMP_PREVIEW_AREA (widget);
gint width;
gint height;
if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
width = (area->max_width > 0 ?
MIN (allocation->width, area->max_width) : allocation->width);
height = (area->max_height > 0 ?
MIN (allocation->height, area->max_height) : allocation->height);
if (width != area->width || height != area->height)
{
if (area->buf)
{
g_free (area->buf);
area->buf = NULL;
area->rowstride = 0;
}
area->width = width;
area->height = height;
}
}
static gboolean
gimp_preview_area_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GimpPreviewArea *area = GIMP_PREVIEW_AREA (widget);
GimpPreviewAreaPrivate *priv = GET_PRIVATE (area);
GtkAllocation allocation;
GdkPixbuf *pixbuf;
GdkRectangle rect;
cairo_t *cr;
if (! area->buf)
return FALSE;
gtk_widget_get_allocation (widget, &allocation);
rect.x = (allocation.width - area->width) / 2;
rect.y = (allocation.height - area->height) / 2;
rect.width = area->width;
rect.height = area->height;
if (! priv->transform)
gimp_preview_area_create_transform (area);
if (priv->transform)
{
const Babl *format = babl_format ("R'G'B' u8");
gint rowstride = ((area->width * 3) + 3) & ~3;
guchar *buf = g_new (guchar, rowstride * area->height);
guchar *src = area->buf;
guchar *dest = buf;
gint i;
for (i = 0; i < area->height; i++)
{
gimp_color_transform_process_pixels (priv->transform,
format, src,
format, dest,
area->width);
src += area->rowstride;
dest += rowstride;
}
pixbuf = gdk_pixbuf_new_from_data (buf,
GDK_COLORSPACE_RGB,
FALSE,
8,
rect.width,
rect.height,
rowstride,
(GdkPixbufDestroyNotify) g_free, NULL);
}
else
{
pixbuf = gdk_pixbuf_new_from_data (area->buf,
GDK_COLORSPACE_RGB,
FALSE,
8,
rect.width,
rect.height,
area->rowstride,
NULL, NULL);
}
cr = gdk_cairo_create (gtk_widget_get_window (widget));
gdk_cairo_region (cr, event->region);
cairo_clip (cr);
gdk_cairo_set_source_pixbuf (cr, pixbuf, rect.x, rect.y);
cairo_paint (cr);
cairo_destroy (cr);
g_object_unref (pixbuf);
return FALSE;
}
static void
gimp_preview_area_queue_draw (GimpPreviewArea *area,
gint x,
gint y,
gint width,
gint height)
{
GtkWidget *widget = GTK_WIDGET (area);
GtkAllocation allocation;
gtk_widget_get_allocation (widget, &allocation);
x += (allocation.width - area->width) / 2;
y += (allocation.height - area->height) / 2;
gtk_widget_queue_draw_area (widget, x, y, width, height);
}
static gint
gimp_preview_area_image_type_bytes (GimpImageType type)
{
switch (type)
{
case GIMP_GRAY_IMAGE:
case GIMP_INDEXED_IMAGE:
return 1;
case GIMP_GRAYA_IMAGE:
case GIMP_INDEXEDA_IMAGE:
return 2;
case GIMP_RGB_IMAGE:
return 3;
case GIMP_RGBA_IMAGE:
return 4;
default:
g_return_val_if_reached (0);
break;
}
}
static void
gimp_preview_area_create_transform (GimpPreviewArea *area)
{
GimpPreviewAreaPrivate *priv = GET_PRIVATE (area);
if (priv->config)
{
static GimpColorProfile *profile = NULL;
const Babl *format = babl_format ("R'G'B' u8");
if (G_UNLIKELY (! profile))
profile = gimp_color_profile_new_rgb_srgb ();
priv->transform = gimp_widget_get_color_transform (GTK_WIDGET (area),
priv->config,
profile,
format,
format);
}
}
static void
gimp_preview_area_destroy_transform (GimpPreviewArea *area)
{
GimpPreviewAreaPrivate *priv = GET_PRIVATE (area);
if (priv->transform)
{
g_object_unref (priv->transform);
priv->transform = NULL;
}
gtk_widget_queue_draw (GTK_WIDGET (area));
}
/**
* gimp_preview_area_new:
*
* Creates a new #GimpPreviewArea widget.
*
* Return value: a new #GimpPreviewArea widget.
*
* Since GIMP 2.2
**/
GtkWidget *
gimp_preview_area_new (void)
{
return g_object_new (GIMP_TYPE_PREVIEW_AREA, NULL);
}
/**
* gimp_preview_area_draw:
* @area: a #GimpPreviewArea widget.
* @x: x offset in preview
* @y: y offset in preview
* @width: buffer width
* @height: buffer height
* @type: the #GimpImageType of @buf
* @buf: a #guchar buffer that contains the preview pixel data.
* @rowstride: rowstride of @buf
*
* Draws @buf on @area and queues a redraw on the given rectangle.
*
* Since GIMP 2.2
**/
void
gimp_preview_area_draw (GimpPreviewArea *area,
gint x,
gint y,
gint width,
gint height,
GimpImageType type,
const guchar *buf,
gint rowstride)
{
const guchar *src;
guchar *dest;
guint size;
guchar light;
guchar dark;
gint row;
gint col;
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
g_return_if_fail (width >= 0 && height >= 0);
if (width == 0 || height == 0)
return;
g_return_if_fail (buf != NULL);
g_return_if_fail (rowstride > 0);
if (x + width < 0 || x >= area->width)
return;
if (y + height < 0 || y >= area->height)
return;
if (x < 0)
{
gint bpp = gimp_preview_area_image_type_bytes (type);
buf -= x * bpp;
width += x;
x = 0;
}
if (x + width > area->width)
width = area->width - x;
if (y < 0)
{
buf -= y * rowstride;
height += y;
y = 0;
}
if (y + height > area->height)
height = area->height - y;
if (! area->buf)
{
area->rowstride = ((area->width * 3) + 3) & ~3;
area->buf = g_new (guchar, area->rowstride * area->height);
}
size = 1 << (2 + area->check_size);
gimp_checks_get_shades (area->check_type, &light, &dark);
src = buf;
dest = area->buf + x * 3 + y * area->rowstride;
switch (type)
{
case GIMP_RGB_IMAGE:
for (row = 0; row < height; row++)
{
memcpy (dest, src, 3 * width);
src += rowstride;
dest += area->rowstride;
}
break;
case GIMP_RGBA_IMAGE:
for (row = y; row < y + height; row++)
{
const guchar *s = src;
guchar *d = dest;
for (col = x; col < x + width; col++, s += 4, d+= 3)
{
switch (s[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = s[0];
d[1] = s[1];
d[2] = s[2];
break;
default:
{
register guint alpha = s[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (s[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (s[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (s[2] - check) * alpha) >> 8;
}
break;
}
}
src += rowstride;
dest += area->rowstride;
}
break;
case GIMP_GRAY_IMAGE:
for (row = 0; row < height; row++)
{
const guchar *s = src;
guchar *d = dest;
for (col = 0; col < width; col++, s++, d += 3)
{
d[0] = d[1] = d[2] = s[0];
}
src += rowstride;
dest += area->rowstride;
}
break;
case GIMP_GRAYA_IMAGE:
for (row = y; row < y + height; row++)
{
const guchar *s = src;
guchar *d = dest;
for (col = x; col < x + width; col++, s += 2, d+= 3)
{
switch (s[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = d[1] = d[2] = s[0];
break;
default:
{
register guint alpha = s[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = d[1] = d[2] =
((check << 8) + (s[0] - check) * alpha) >> 8;
}
break;
}
}
src += rowstride;
dest += area->rowstride;
}
break;
case GIMP_INDEXED_IMAGE:
g_return_if_fail (area->colormap != NULL);
for (row = 0; row < height; row++)
{
const guchar *s = src;
guchar *d = dest;
for (col = 0; col < width; col++, s++, d += 3)
{
const guchar *colormap = area->colormap + 3 * s[0];
d[0] = colormap[0];
d[1] = colormap[1];
d[2] = colormap[2];
}
src += rowstride;
dest += area->rowstride;
}
break;
case GIMP_INDEXEDA_IMAGE:
g_return_if_fail (area->colormap != NULL);
for (row = y; row < y + height; row++)
{
const guchar *s = src;
guchar *d = dest;
for (col = x; col < x + width; col++, s += 2, d += 3)
{
const guchar *colormap = area->colormap + 3 * s[0];
switch (s[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = colormap[0];
d[1] = colormap[1];
d[2] = colormap[2];
break;
default:
{
register guint alpha = s[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (colormap[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (colormap[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (colormap[2] - check) * alpha) >> 8;
}
break;
}
}
src += rowstride;
dest += area->rowstride;
}
break;
}
gimp_preview_area_queue_draw (area, x, y, width, height);
}
/**
* gimp_preview_area_blend:
* @area: a #GimpPreviewArea widget.
* @x: x offset in preview
* @y: y offset in preview
* @width: buffer width
* @height: buffer height
* @type: the #GimpImageType of @buf1 and @buf2
* @buf1: a #guchar buffer that contains the pixel data for
* the lower layer
* @rowstride1: rowstride of @buf1
* @buf2: a #guchar buffer that contains the pixel data for
* the upper layer
* @rowstride2: rowstride of @buf2
* @opacity: The opacity of the first layer.
*
* Composites @buf1 on @buf2 with the given @opacity, draws the result
* to @area and queues a redraw on the given rectangle.
*
* Since GIMP 2.2
**/
void
gimp_preview_area_blend (GimpPreviewArea *area,
gint x,
gint y,
gint width,
gint height,
GimpImageType type,
const guchar *buf1,
gint rowstride1,
const guchar *buf2,
gint rowstride2,
guchar opacity)
{
const guchar *src1;
const guchar *src2;
guchar *dest;
guint size;
guchar light;
guchar dark;
gint row;
gint col;
gint i;
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
g_return_if_fail (width >= 0 && height >= 0);
if (width == 0 || height == 0)
return;
g_return_if_fail (buf1 != NULL);
g_return_if_fail (buf2 != NULL);
g_return_if_fail (rowstride1 > 0);
g_return_if_fail (rowstride2 > 0);
switch (opacity)
{
case 0:
gimp_preview_area_draw (area, x, y, width, height,
type, buf1, rowstride1);
return;
case 255:
gimp_preview_area_draw (area, x, y, width, height,
type, buf2, rowstride2);
return;
default:
break;
}
if (x + width < 0 || x >= area->width)
return;
if (y + height < 0 || y >= area->height)
return;
if (x < 0)
{
gint bpp = gimp_preview_area_image_type_bytes (type);
buf1 -= x * bpp;
buf2 -= x * bpp;
width += x;
x = 0;
}
if (x + width > area->width)
width = area->width - x;
if (y < 0)
{
buf1 -= y * rowstride1;
buf2 -= y * rowstride2;
height += y;
y = 0;
}
if (y + height > area->height)
height = area->height - y;
if (! area->buf)
{
area->rowstride = ((area->width * 3) + 3) & ~3;
area->buf = g_new (guchar, area->rowstride * area->height);
}
size = 1 << (2 + area->check_size);
gimp_checks_get_shades (area->check_type, &light, &dark);
src1 = buf1;
src2 = buf2;
dest = area->buf + x * 3 + y * area->rowstride;
switch (type)
{
case GIMP_RGB_IMAGE:
for (row = 0; row < height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 3, s2 += 3, d+= 3)
{
d[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8;
d[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8;
d[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * opacity) >> 8;
}
src1 += rowstride1;
src2 += rowstride2;
dest += area->rowstride;
}
break;
case GIMP_RGBA_IMAGE:
for (row = y; row < y + height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 4, s2 += 4, d+= 3)
{
guchar inter[4];
if (s1[3] == s2[3])
{
inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8;
inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8;
inter[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * opacity) >> 8;
inter[3] = s1[3];
}
else
{
inter[3] = ((s1[3] << 8) + (s2[3] - s1[3]) * opacity) >> 8;
if (inter[3])
{
for (i = 0; i < 3; i++)
{
gushort a = s1[i] * s1[3];
gushort b = s2[i] * s2[3];
inter[i] =
(((a << 8) + (b - a) * opacity) >> 8) / inter[3];
}
}
}
switch (inter[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = inter[0];
d[1] = inter[1];
d[2] = inter[2];
break;
default:
{
register guint alpha = inter[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (inter[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (inter[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (inter[2] - check) * alpha) >> 8;
}
break;
}
}
src1 += rowstride1;
src2 += rowstride2;
dest += area->rowstride;
}
break;
case GIMP_GRAY_IMAGE:
for (row = 0; row < height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
guchar *d = dest;
for (col = 0; col < width; col++, s1++, s2++, d += 3)
{
d[0] = d[1] = d[2] =
((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8;
}
src1 += rowstride1;
src2 += rowstride2;
dest += area->rowstride;
}
break;
case GIMP_GRAYA_IMAGE:
for (row = y; row < y + height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 2, s2 += 2, d+= 3)
{
guchar inter[2] = { 0, };
if (s1[1] == s2[1])
{
inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * opacity) >> 8;
inter[1] = s1[1];
}
else
{
inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8;
if (inter[1])
{
gushort a = s1[0] * s1[1];
gushort b = s2[0] * s2[1];
inter[0] =
(((a << 8) + (b - a) * opacity) >> 8) / inter[1];
}
}
switch (inter[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = d[1] = d[2] = inter[0];
break;
default:
{
register guint alpha = inter[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = d[1] = d[2] =
((check << 8) + (inter[0] - check) * alpha) >> 8;
}
break;
}
}
src1 += rowstride1;
src2 += rowstride2;
dest += area->rowstride;
}
break;
case GIMP_INDEXED_IMAGE:
g_return_if_fail (area->colormap != NULL);
for (row = 0; row < height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
guchar *d = dest;
for (col = 0; col < width; col++, s1++, s2++, d += 3)
{
const guchar *cmap1 = area->colormap + 3 * s1[0];
const guchar *cmap2 = area->colormap + 3 * s2[0];
d[0] = ((cmap1[0] << 8) + (cmap2[0] - cmap1[0]) * opacity) >> 8;
d[1] = ((cmap1[1] << 8) + (cmap2[1] - cmap1[1]) * opacity) >> 8;
d[2] = ((cmap1[2] << 8) + (cmap2[2] - cmap1[2]) * opacity) >> 8;
}
src1 += rowstride1;
src2 += rowstride2;
dest += area->rowstride;
}
break;
case GIMP_INDEXEDA_IMAGE:
g_return_if_fail (area->colormap != NULL);
for (row = y; row < y + height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 2, s2 += 2, d += 3)
{
const guchar *cmap1 = area->colormap + 3 * s1[0];
const guchar *cmap2 = area->colormap + 3 * s2[0];
guchar inter[4];
if (s1[1] == s2[1])
{
inter[0] = (((cmap1[0] << 8) +
(cmap2[0] - cmap1[0]) * opacity) >> 8);
inter[1] = (((cmap1[1] << 8) +
(cmap2[1] - cmap1[1]) * opacity) >> 8);
inter[2] = (((cmap1[2] << 8) +
(cmap2[2] - cmap1[2]) * opacity) >> 8);
inter[3] = s1[1];
}
else
{
inter[3] = ((s1[1] << 8) + (s2[1] - s1[1]) * opacity) >> 8;
if (inter[3])
{
for (i = 0; i < 3; i++)
{
gushort a = cmap1[i] * s1[1];
gushort b = cmap2[i] * s2[1];
inter[i] =
(((a << 8) + (b - a) * opacity) >> 8) / inter[3];
}
}
}
switch (inter[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = inter[0];
d[1] = inter[1];
d[2] = inter[2];
break;
default:
{
register guint alpha = inter[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (inter[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (inter[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (inter[2] - check) * alpha) >> 8;
}
break;
}
}
src1 += rowstride1;
src2 += rowstride2;
dest += area->rowstride;
}
break;
}
gimp_preview_area_queue_draw (area, x, y, width, height);
}
/**
* gimp_preview_area_mask:
* @area: a #GimpPreviewArea widget.
* @x: x offset in preview
* @y: y offset in preview
* @width: buffer width
* @height: buffer height
* @type: the #GimpImageType of @buf1 and @buf2
* @buf1: a #guchar buffer that contains the pixel data for
* the lower layer
* @rowstride1: rowstride of @buf1
* @buf2: a #guchar buffer that contains the pixel data for
* the upper layer
* @rowstride2: rowstride of @buf2
* @mask: a #guchar buffer representing the mask of the second
* layer.
* @rowstride_mask: rowstride for the mask.
*
* Composites @buf1 on @buf2 with the given @mask, draws the result on
* @area and queues a redraw on the given rectangle.
*
* Since GIMP 2.2
**/
void
gimp_preview_area_mask (GimpPreviewArea *area,
gint x,
gint y,
gint width,
gint height,
GimpImageType type,
const guchar *buf1,
gint rowstride1,
const guchar *buf2,
gint rowstride2,
const guchar *mask,
gint rowstride_mask)
{
const guchar *src1;
const guchar *src2;
const guchar *src_mask;
guchar *dest;
guint size;
guchar light;
guchar dark;
gint row;
gint col;
gint i;
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
g_return_if_fail (width >= 0 && height >= 0);
if (width == 0 || height == 0)
return;
g_return_if_fail (buf1 != NULL);
g_return_if_fail (buf2 != NULL);
g_return_if_fail (mask != NULL);
g_return_if_fail (rowstride1 > 0);
g_return_if_fail (rowstride2 > 0);
g_return_if_fail (rowstride_mask > 0);
if (x + width < 0 || x >= area->width)
return;
if (y + height < 0 || y >= area->height)
return;
if (x < 0)
{
gint bpp = gimp_preview_area_image_type_bytes (type);
buf1 -= x * bpp;
buf2 -= x * bpp;
mask -= x;
width += x;
x = 0;
}
if (x + width > area->width)
width = area->width - x;
if (y < 0)
{
buf1 -= y * rowstride1;
buf2 -= y * rowstride2;
mask -= y * rowstride_mask;
height += y;
y = 0;
}
if (y + height > area->height)
height = area->height - y;
if (! area->buf)
{
area->rowstride = ((area->width * 3) + 3) & ~3;
area->buf = g_new (guchar, area->rowstride * area->height);
}
size = 1 << (2 + area->check_size);
gimp_checks_get_shades (area->check_type, &light, &dark);
src1 = buf1;
src2 = buf2;
src_mask = mask;
dest = area->buf + x * 3 + y * area->rowstride;
switch (type)
{
case GIMP_RGB_IMAGE:
for (row = 0; row < height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
const guchar *m = src_mask;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 3, s2 += 3, m++, d+= 3)
{
d[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8;
d[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8;
d[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * m[0]) >> 8;
}
src1 += rowstride1;
src2 += rowstride2;
src_mask += rowstride_mask;
dest += area->rowstride;
}
break;
case GIMP_RGBA_IMAGE:
for (row = y; row < y + height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
const guchar *m = src_mask;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 4, s2 += 4, m++, d+= 3)
{
switch (m[0])
{
case 0:
switch (s1[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = s1[0];
d[1] = s1[1];
d[2] = s1[2];
break;
default:
{
register guint alpha = s1[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (s1[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (s1[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (s1[2] - check) * alpha) >> 8;
}
break;
}
break;
case 255:
switch (s2[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = s2[0];
d[1] = s2[1];
d[2] = s2[2];
break;
default:
{
register guint alpha = s2[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (s2[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (s2[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (s2[2] - check) * alpha) >> 8;
}
break;
}
break;
default:
{
guchar inter[4];
if (s1[3] == s2[3])
{
inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8;
inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8;
inter[2] = ((s1[2] << 8) + (s2[2] - s1[2]) * m[0]) >> 8;
inter[3] = s1[3];
}
else
{
inter[3] = ((s1[3] << 8) + (s2[3] - s1[3]) * m[0]) >> 8;
if (inter[3])
{
for (i = 0; i < 3; i++)
{
gushort a = s1[i] * s1[3];
gushort b = s2[i] * s2[3];
inter[i] =
(((a << 8) + (b - a) * m[0]) >> 8) / inter[3];
}
}
}
switch (inter[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = inter[0];
d[1] = inter[1];
d[2] = inter[2];
break;
default:
{
register guint alpha = inter[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = (((check << 8) +
(inter[0] - check) * alpha) >> 8);
d[1] = (((check << 8) +
(inter[1] - check) * alpha) >> 8);
d[2] = (((check << 8) +
(inter[2] - check) * alpha) >> 8);
}
break;
}
}
break;
}
}
src1 += rowstride1;
src2 += rowstride2;
src_mask += rowstride_mask;
dest += area->rowstride;
}
break;
case GIMP_GRAY_IMAGE:
for (row = 0; row < height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
const guchar *m = src_mask;
guchar *d = dest;
for (col = 0; col < width; col++, s1++, s2++, m++, d += 3)
d[0] = d[1] = d[2] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8;
src1 += rowstride1;
src2 += rowstride2;
src_mask += rowstride_mask;
dest += area->rowstride;
}
break;
case GIMP_GRAYA_IMAGE:
for (row = y; row < y + height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
const guchar *m = src_mask;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 2, s2 += 2, m++, d+= 3)
{
switch (m[0])
{
case 0:
switch (s1[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = d[1] = d[2] = s1[0];
break;
default:
{
register guint alpha = s1[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = d[1] = d[2] =
((check << 8) + (s1[0] - check) * alpha) >> 8;
}
break;
}
break;
case 255:
switch (s2[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = d[1] = d[2] = s2[0];
break;
default:
{
register guint alpha = s2[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = d[1] = d[2] =
((check << 8) + (s2[0] - check) * alpha) >> 8;
}
break;
}
break;
default:
{
guchar inter[2] = { 0, };
if (s1[1] == s2[1])
{
inter[0] = ((s1[0] << 8) + (s2[0] - s1[0]) * m[0]) >> 8;
inter[1] = s1[1];
}
else
{
inter[1] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8;
if (inter[1])
{
gushort a = s1[0] * s1[1];
gushort b = s2[0] * s2[1];
inter[0] =
(((a << 8) + (b - a) * m[0]) >> 8) / inter[1];
}
}
switch (inter[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = d[1] = d[2] = inter[0];
break;
default:
{
register guint alpha = inter[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = d[1] = d[2] =
((check << 8) + (inter[0] - check) * alpha) >> 8;
}
break;
}
}
break;
}
}
src1 += rowstride1;
src2 += rowstride2;
src_mask += rowstride_mask;
dest += area->rowstride;
}
break;
case GIMP_INDEXED_IMAGE:
g_return_if_fail (area->colormap != NULL);
for (row = 0; row < height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
const guchar *m = src_mask;
guchar *d = dest;
for (col = 0; col < width; col++, s1++, s2++, m++, d += 3)
{
const guchar *cmap1 = area->colormap + 3 * s1[0];
const guchar *cmap2 = area->colormap + 3 * s2[0];
d[0] = ((cmap1[0] << 8) + (cmap2[0] - cmap1[0]) * m[0]) >> 8;
d[1] = ((cmap1[1] << 8) + (cmap2[1] - cmap1[1]) * m[0]) >> 8;
d[2] = ((cmap1[2] << 8) + (cmap2[2] - cmap1[2]) * m[0]) >> 8;
}
src1 += rowstride1;
src2 += rowstride2;
src_mask += rowstride_mask;
dest += area->rowstride;
}
break;
case GIMP_INDEXEDA_IMAGE:
g_return_if_fail (area->colormap != NULL);
for (row = y; row < y + height; row++)
{
const guchar *s1 = src1;
const guchar *s2 = src2;
const guchar *m = src_mask;
guchar *d = dest;
for (col = x; col < x + width; col++, s1 += 2, s2 += 2, m++, d += 3)
{
const guchar *cmap1 = area->colormap + 3 * s1[0];
const guchar *cmap2 = area->colormap + 3 * s2[0];
switch (m[0])
{
case 0:
switch (s1[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = cmap1[0];
d[1] = cmap1[1];
d[2] = cmap1[2];
break;
default:
{
register guint alpha = s1[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (cmap1[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (cmap1[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (cmap1[2] - check) * alpha) >> 8;
}
break;
}
break;
case 255:
switch (s2[1])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = cmap2[0];
d[1] = cmap2[1];
d[2] = cmap2[2];
break;
default:
{
register guint alpha = s2[1] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] = ((check << 8) + (cmap2[0] - check) * alpha) >> 8;
d[1] = ((check << 8) + (cmap2[1] - check) * alpha) >> 8;
d[2] = ((check << 8) + (cmap2[2] - check) * alpha) >> 8;
}
break;
}
break;
default:
{
guchar inter[4];
if (s1[1] == s2[1])
{
inter[0] = (((cmap1[0] << 8) +
(cmap2[0] - cmap1[0]) * m[0]) >> 8);
inter[1] = (((cmap1[1] << 8) +
(cmap2[1] - cmap1[1]) * m[0]) >> 8);
inter[2] = (((cmap1[2] << 8) +
(cmap2[2] - cmap1[2]) * m[0]) >> 8);
inter[3] = s1[1];
}
else
{
inter[3] = ((s1[1] << 8) + (s2[1] - s1[1]) * m[0]) >> 8;
if (inter[3])
{
for (i = 0 ; i < 3 ; i++)
{
gushort a = cmap1[i] * s1[1];
gushort b = cmap2[i] * s2[1];
inter[i] = ((((a << 8) + (b - a) * m[0]) >> 8)
/ inter[3]);
}
}
}
switch (inter[3])
{
case 0:
d[0] = d[1] = d[2] = CHECK_COLOR (area, row, col);
break;
case 255:
d[0] = inter[0];
d[1] = inter[1];
d[2] = inter[2];
break;
default:
{
register guint alpha = inter[3] + 1;
register guint check = CHECK_COLOR (area, row, col);
d[0] =
((check << 8) + (inter[0] - check) * alpha) >> 8;
d[1] =
((check << 8) + (inter[1] - check) * alpha) >> 8;
d[2] =
((check << 8) + (inter[2] - check) * alpha) >> 8;
}
break;
}
}
break;
}
}
src1 += rowstride1;
src2 += rowstride2;
src_mask += rowstride_mask;
dest += area->rowstride;
}
break;
}
gimp_preview_area_queue_draw (area, x, y, width, height);
}
/**
* gimp_preview_area_fill:
* @area: a #GimpPreviewArea widget.
* @x: x offset in preview
* @y: y offset in preview
* @width: width of the rectangle to fill
* @height: height of the rectangle to fill
* @red: red component of the fill color (0-255)
* @green: green component of the fill color (0-255)
* @blue: red component of the fill color (0-255)
*
* Fills the given rectangle of @area in the given color and queues a
* redraw.
*
* Since GIMP 2.2
**/
void
gimp_preview_area_fill (GimpPreviewArea *area,
gint x,
gint y,
gint width,
gint height,
guchar red,
guchar green,
guchar blue)
{
guchar *dest;
guchar *d;
gint row;
gint col;
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
g_return_if_fail (width >= 0 && height >= 0);
if (width == 0 || height == 0)
return;
if (x + width < 0 || x >= area->width)
return;
if (y + height < 0 || y >= area->height)
return;
if (x < 0)
{
width += x;
x = 0;
}
if (x + width > area->width)
width = area->width - x;
if (y < 0)
{
height += y;
y = 0;
}
if (y + height > area->height)
height = area->height - y;
if (! area->buf)
{
area->rowstride = ((area->width * 3) + 3) & ~3;
area->buf = g_new (guchar, area->rowstride * area->height);
}
dest = area->buf + x * 3 + y * area->rowstride;
/* colorize first row */
for (col = 0, d = dest; col < width; col++, d+= 3)
{
d[0] = red;
d[1] = green;
d[2] = blue;
}
/* copy first row to remaining rows */
for (row = 1, d = dest; row < height; row++)
{
d += area->rowstride;
memcpy (d, dest, width * 3);
}
gimp_preview_area_queue_draw (area, x, y, width, height);
}
/**
* gimp_preview_area_set_offsets:
* @area: a #GimpPreviewArea
* @x: horizontal offset
* @y: vertical offset
*
* Sets the offsets of the previewed area. This information is used
* when drawing the checkerboard and to determine the dither offsets.
*
* Since: 2.2
**/
void
gimp_preview_area_set_offsets (GimpPreviewArea *area,
gint x,
gint y)
{
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
area->offset_x = x;
area->offset_y = y;
}
/**
* gimp_preview_area_set_colormap:
* @area: a #GimpPreviewArea
* @colormap: a #guchar buffer that contains the colormap
* @num_colors: the number of colors in the colormap
*
* Sets the colormap for the #GimpPreviewArea widget. You need to
* call this function before you use gimp_preview_area_draw() with
* an image type of %GIMP_INDEXED_IMAGE or %GIMP_INDEXEDA_IMAGE.
*
* Since GIMP 2.2
**/
void
gimp_preview_area_set_colormap (GimpPreviewArea *area,
const guchar *colormap,
gint num_colors)
{
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
g_return_if_fail (colormap != NULL || num_colors == 0);
g_return_if_fail (num_colors >= 0 && num_colors <= 256);
if (num_colors > 0)
{
if (area->colormap)
memset (area->colormap, 0, 3 * 256);
else
area->colormap = g_new0 (guchar, 3 * 256);
memcpy (area->colormap, colormap, 3 * num_colors);
}
else
{
g_free (area->colormap);
area->colormap = NULL;
}
}
/**
* gimp_preview_area_set_color_config:
* @area: a #GimpPreviewArea widget.
* @config: a #GimpColorConfig object.
*
* Sets the color management configuration to use with this preview area.
*
* Since: 2.10
*/
void
gimp_preview_area_set_color_config (GimpPreviewArea *area,
GimpColorConfig *config)
{
GimpPreviewAreaPrivate *priv;
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config));
priv = GET_PRIVATE (area);
if (config != priv->config)
{
if (priv->config)
{
g_signal_handlers_disconnect_by_func (priv->config,
gimp_preview_area_destroy_transform,
area);
gimp_preview_area_destroy_transform (area);
}
g_set_object (&priv->config, config);
if (priv->config)
{
g_signal_connect_swapped (priv->config, "notify",
G_CALLBACK (gimp_preview_area_destroy_transform),
area);
}
}
}
/**
* gimp_preview_area_set_max_size:
* @area: a #GimpPreviewArea widget
* @width: the maximum width in pixels or -1 to unset the limit
* @height: the maximum height in pixels or -1 to unset the limit
*
* Usually a #GimpPreviewArea fills the size that it is
* allocated. This function allows you to limit the preview area to a
* maximum size. If a larger size is allocated for the widget, the
* preview will draw itself centered into the allocated area.
*
* Since: 2.2
**/
void
gimp_preview_area_set_max_size (GimpPreviewArea *area,
gint width,
gint height)
{
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
area->max_width = width;
area->max_height = height;
}
/* popup menu */
static void
gimp_preview_area_menu_toggled (GtkWidget *item,
GimpPreviewArea *area)
{
gboolean active = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
if (active)
{
const gchar *name = g_object_get_data (G_OBJECT (item),
"gimp-preview-area-prop-name");
if (name)
{
gint value =
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
"gimp-preview-area-prop-value"));
g_object_set (area,
name, value,
NULL);
}
}
}
static GtkWidget *
gimp_preview_area_menu_new (GimpPreviewArea *area,
const gchar *property)
{
GParamSpec *pspec;
GEnumClass *enum_class;
GEnumValue *enum_value;
GtkWidget *menu;
GtkWidget *item;
GSList *group = NULL;
gint value;
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (area), property);
g_return_val_if_fail (G_IS_PARAM_SPEC_ENUM (pspec), NULL);
g_object_get (area,
property, &value,
NULL);
enum_class = G_PARAM_SPEC_ENUM (pspec)->enum_class;
menu = gtk_menu_new ();
for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
{
const gchar *name = gimp_enum_value_get_desc (enum_class, enum_value);
item = gtk_radio_menu_item_new_with_label (group, name);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
g_object_set_data (G_OBJECT (item),
"gimp-preview-area-prop-name",
(gpointer) property);
g_object_set_data (G_OBJECT (item),
"gimp-preview-area-prop-value",
GINT_TO_POINTER (enum_value->value));
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
(enum_value->value == value));
g_signal_connect (item, "toggled",
G_CALLBACK (gimp_preview_area_menu_toggled),
area);
group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
}
item = gtk_menu_item_new_with_label (g_param_spec_get_nick (pspec));
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
gtk_widget_show (item);
return item;
}
/**
* gimp_preview_area_menu_popup:
* @area: a #GimpPreviewArea
* @event: the button event that causes the menu to popup or %NULL
*
* Creates a popup menu that allows one to configure the size and type of
* the checkerboard pattern that the @area uses to visualize transparency.
*
* Since: 2.2
**/
void
gimp_preview_area_menu_popup (GimpPreviewArea *area,
GdkEventButton *event)
{
GtkWidget *menu;
g_return_if_fail (GIMP_IS_PREVIEW_AREA (area));
menu = gtk_menu_new ();
gtk_menu_set_screen (GTK_MENU (menu),
gtk_widget_get_screen (GTK_WIDGET (area)));
gtk_menu_shell_append (GTK_MENU_SHELL (menu),
gimp_preview_area_menu_new (area, "check-type"));
gtk_menu_shell_append (GTK_MENU_SHELL (menu),
gimp_preview_area_menu_new (area, "check-size"));
if (event)
gtk_menu_popup (GTK_MENU (menu),
NULL, NULL, NULL, NULL, event->button, event->time);
else
gtk_menu_popup (GTK_MENU (menu),
NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
}