/* 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 * . */ #include "config.h" #include #include #include #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 ()); }