1043 lines
32 KiB
C
1043 lines
32 KiB
C
/* LIBPIKA - The PIKA Library
|
|
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
|
*
|
|
* pikacolorarea.c
|
|
* Copyright (C) 2001-2002 Sven Neumann <sven@gimp.org>
|
|
*
|
|
* 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
|
|
* Library 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 <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikacolor/pikacolor.h"
|
|
#include "libpikaconfig/pikaconfig.h"
|
|
|
|
#include "pikawidgetstypes.h"
|
|
|
|
#include "pikacairo-utils.h"
|
|
#include "pikacolorarea.h"
|
|
#include "pikawidgetsutils.h"
|
|
#include "pikawidgets-private.h"
|
|
|
|
|
|
/**
|
|
* SECTION: pikacolorarea
|
|
* @title: PikaColorArea
|
|
* @short_description: Displays a #PikaRGB color, optionally with
|
|
* alpha-channel.
|
|
*
|
|
* Displays a #PikaRGB color, optionally with alpha-channel.
|
|
**/
|
|
|
|
|
|
#define DRAG_PREVIEW_SIZE 32
|
|
#define DRAG_ICON_OFFSET -8
|
|
|
|
|
|
enum
|
|
{
|
|
COLOR_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_COLOR,
|
|
PROP_TYPE,
|
|
PROP_DRAG_MASK,
|
|
PROP_DRAW_BORDER
|
|
};
|
|
|
|
|
|
struct _PikaColorAreaPrivate
|
|
{
|
|
PikaColorConfig *config;
|
|
PikaColorTransform *transform;
|
|
|
|
guchar *buf;
|
|
guint width;
|
|
guint height;
|
|
guint rowstride;
|
|
|
|
PikaColorAreaType type;
|
|
PikaRGB color;
|
|
guint draw_border : 1;
|
|
guint needs_render : 1;
|
|
|
|
gboolean out_of_gamut;
|
|
};
|
|
|
|
#define GET_PRIVATE(obj) (((PikaColorArea *) (obj))->priv)
|
|
|
|
|
|
static void pika_color_area_dispose (GObject *object);
|
|
static void pika_color_area_finalize (GObject *object);
|
|
static void pika_color_area_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_color_area_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void pika_color_area_size_allocate (GtkWidget *widget,
|
|
GtkAllocation *allocation);
|
|
static gboolean pika_color_area_draw (GtkWidget *widget,
|
|
cairo_t *cr);
|
|
static void pika_color_area_render_buf (GtkWidget *widget,
|
|
PikaColorAreaType type,
|
|
guchar *buf,
|
|
guint width,
|
|
guint height,
|
|
guint rowstride,
|
|
PikaRGB *color);
|
|
static void pika_color_area_render (PikaColorArea *area);
|
|
|
|
static void pika_color_area_drag_begin (GtkWidget *widget,
|
|
GdkDragContext *context);
|
|
static void pika_color_area_drag_end (GtkWidget *widget,
|
|
GdkDragContext *context);
|
|
static void pika_color_area_drag_data_received (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint time);
|
|
static void pika_color_area_drag_data_get (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint time);
|
|
|
|
static void pika_color_area_create_transform (PikaColorArea *area);
|
|
static void pika_color_area_destroy_transform (PikaColorArea *area);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaColorArea, pika_color_area,
|
|
GTK_TYPE_DRAWING_AREA)
|
|
|
|
#define parent_class pika_color_area_parent_class
|
|
|
|
static guint pika_color_area_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static const GtkTargetEntry target = { "application/x-color", 0 };
|
|
|
|
|
|
static void
|
|
pika_color_area_class_init (PikaColorAreaClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
PikaRGB color;
|
|
|
|
pika_color_area_signals[COLOR_CHANGED] =
|
|
g_signal_new ("color-changed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (PikaColorAreaClass, color_changed),
|
|
NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
object_class->dispose = pika_color_area_dispose;
|
|
object_class->finalize = pika_color_area_finalize;
|
|
object_class->get_property = pika_color_area_get_property;
|
|
object_class->set_property = pika_color_area_set_property;
|
|
|
|
widget_class->size_allocate = pika_color_area_size_allocate;
|
|
widget_class->draw = pika_color_area_draw;
|
|
|
|
widget_class->drag_begin = pika_color_area_drag_begin;
|
|
widget_class->drag_end = pika_color_area_drag_end;
|
|
widget_class->drag_data_received = pika_color_area_drag_data_received;
|
|
widget_class->drag_data_get = pika_color_area_drag_data_get;
|
|
|
|
klass->color_changed = NULL;
|
|
|
|
pika_rgba_set (&color, 0.0, 0.0, 0.0, 1.0);
|
|
|
|
/**
|
|
* PikaColorArea:color:
|
|
*
|
|
* The color displayed in the color area.
|
|
*
|
|
* Since: 2.4
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_COLOR,
|
|
pika_param_spec_rgb ("color",
|
|
"Color",
|
|
"The displayed color",
|
|
TRUE, &color,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
/**
|
|
* PikaColorArea:type:
|
|
*
|
|
* The type of the color area.
|
|
*
|
|
* Since: 2.4
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_TYPE,
|
|
g_param_spec_enum ("type",
|
|
"Type",
|
|
"The type of the color area",
|
|
PIKA_TYPE_COLOR_AREA_TYPE,
|
|
PIKA_COLOR_AREA_FLAT,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
/**
|
|
* PikaColorArea:drag-type:
|
|
*
|
|
* The modifier mask that should trigger drags.
|
|
*
|
|
* Since: 2.4
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_DRAG_MASK,
|
|
g_param_spec_flags ("drag-mask",
|
|
"Drag Mask",
|
|
"The modifier mask that triggers dragging the color",
|
|
GDK_TYPE_MODIFIER_TYPE,
|
|
0,
|
|
PIKA_PARAM_WRITABLE));
|
|
/**
|
|
* PikaColorArea:draw-border:
|
|
*
|
|
* Whether to draw a thin border in the foreground color around the area.
|
|
*
|
|
* Since: 2.4
|
|
*/
|
|
g_object_class_install_property (object_class, PROP_DRAW_BORDER,
|
|
g_param_spec_boolean ("draw-border",
|
|
"Draw Border",
|
|
"Whether to draw a thin border in the foreground color around the area",
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE));
|
|
}
|
|
|
|
static void
|
|
pika_color_area_init (PikaColorArea *area)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
area->priv = pika_color_area_get_instance_private (area);
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
priv->buf = NULL;
|
|
priv->width = 0;
|
|
priv->height = 0;
|
|
priv->rowstride = 0;
|
|
priv->draw_border = FALSE;
|
|
|
|
gtk_drag_dest_set (GTK_WIDGET (area),
|
|
GTK_DEST_DEFAULT_HIGHLIGHT |
|
|
GTK_DEST_DEFAULT_MOTION |
|
|
GTK_DEST_DEFAULT_DROP,
|
|
&target, 1,
|
|
GDK_ACTION_COPY);
|
|
|
|
pika_widget_track_monitor (GTK_WIDGET (area),
|
|
G_CALLBACK (pika_color_area_destroy_transform),
|
|
NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_dispose (GObject *object)
|
|
{
|
|
PikaColorArea *area = PIKA_COLOR_AREA (object);
|
|
|
|
pika_color_area_set_color_config (area, NULL);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_finalize (GObject *object)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (object);
|
|
|
|
g_clear_pointer (&priv->buf, g_free);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_COLOR:
|
|
g_value_set_boxed (value, &priv->color);
|
|
break;
|
|
|
|
case PROP_TYPE:
|
|
g_value_set_enum (value, priv->type);
|
|
break;
|
|
|
|
case PROP_DRAW_BORDER:
|
|
g_value_set_boolean (value, priv->draw_border);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_color_area_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaColorArea *area = PIKA_COLOR_AREA (object);
|
|
GdkModifierType drag_mask;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_COLOR:
|
|
pika_color_area_set_color (area, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_TYPE:
|
|
pika_color_area_set_type (area, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_DRAG_MASK:
|
|
drag_mask = g_value_get_flags (value) & (GDK_BUTTON1_MASK |
|
|
GDK_BUTTON2_MASK |
|
|
GDK_BUTTON3_MASK);
|
|
if (drag_mask)
|
|
gtk_drag_source_set (GTK_WIDGET (area),
|
|
drag_mask,
|
|
&target, 1,
|
|
GDK_ACTION_COPY | GDK_ACTION_MOVE);
|
|
else
|
|
gtk_drag_source_unset (GTK_WIDGET (area));
|
|
break;
|
|
|
|
case PROP_DRAW_BORDER:
|
|
pika_color_area_set_draw_border (area, g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_color_area_size_allocate (GtkWidget *widget,
|
|
GtkAllocation *allocation)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (widget);
|
|
|
|
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
|
|
|
|
if (allocation->width != priv->width ||
|
|
allocation->height != priv->height)
|
|
{
|
|
priv->width = allocation->width;
|
|
priv->height = allocation->height;
|
|
|
|
priv->rowstride = priv->width * 4 + 4;
|
|
|
|
g_free (priv->buf);
|
|
priv->buf = g_new (guchar, priv->rowstride * priv->height);
|
|
|
|
priv->needs_render = TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
pika_color_area_draw (GtkWidget *widget,
|
|
cairo_t *cr)
|
|
{
|
|
PikaColorArea *area = PIKA_COLOR_AREA (widget);
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (area);
|
|
GtkStyleContext *context = gtk_widget_get_style_context (widget);
|
|
cairo_surface_t *buffer;
|
|
|
|
if (! priv->buf)
|
|
return FALSE;
|
|
|
|
if (priv->needs_render)
|
|
pika_color_area_render (area);
|
|
|
|
if (! priv->transform)
|
|
pika_color_area_create_transform (area);
|
|
|
|
if (priv->transform)
|
|
{
|
|
const Babl *format = babl_format ("cairo-RGB24");
|
|
guchar *buf = g_new (guchar, priv->rowstride * priv->height);
|
|
guchar *src = priv->buf;
|
|
guchar *dest = buf;
|
|
guint i;
|
|
|
|
for (i = 0; i < priv->height; i++)
|
|
{
|
|
pika_color_transform_process_pixels (priv->transform,
|
|
format, src,
|
|
format, dest,
|
|
priv->width);
|
|
|
|
src += priv->rowstride;
|
|
dest += priv->rowstride;
|
|
}
|
|
|
|
buffer = cairo_image_surface_create_for_data (buf,
|
|
CAIRO_FORMAT_RGB24,
|
|
priv->width,
|
|
priv->height,
|
|
priv->rowstride);
|
|
cairo_surface_set_user_data (buffer, NULL,
|
|
buf, (cairo_destroy_func_t) g_free);
|
|
}
|
|
else
|
|
{
|
|
buffer = cairo_image_surface_create_for_data (priv->buf,
|
|
CAIRO_FORMAT_RGB24,
|
|
priv->width,
|
|
priv->height,
|
|
priv->rowstride);
|
|
}
|
|
|
|
cairo_set_source_surface (cr, buffer, 0.0, 0.0);
|
|
cairo_surface_destroy (buffer);
|
|
|
|
if (! gtk_widget_is_sensitive (widget))
|
|
{
|
|
static cairo_pattern_t *pattern = NULL;
|
|
|
|
if (! pattern)
|
|
{
|
|
static const guchar stipple[] = { 0, 255, 0, 0,
|
|
255, 0, 0, 0 };
|
|
cairo_surface_t *surface;
|
|
gint stride;
|
|
|
|
stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8, 2);
|
|
|
|
surface = cairo_image_surface_create_for_data ((guchar *) stipple,
|
|
CAIRO_FORMAT_A8,
|
|
2, 2, stride);
|
|
pattern = cairo_pattern_create_for_surface (surface);
|
|
cairo_surface_destroy (surface);
|
|
|
|
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
|
|
}
|
|
|
|
cairo_mask (cr, pattern);
|
|
}
|
|
else
|
|
{
|
|
cairo_paint (cr);
|
|
}
|
|
|
|
if (priv->config &&
|
|
((priv->color.r < 0.0 || priv->color.r > 1.0 ||
|
|
priv->color.g < 0.0 || priv->color.g > 1.0 ||
|
|
priv->color.b < 0.0 || priv->color.b > 1.0) ||
|
|
priv->out_of_gamut))
|
|
{
|
|
PikaRGB color;
|
|
gint side = MIN (priv->width, priv->height) * 2 / 3;
|
|
|
|
cairo_move_to (cr, priv->width, 0);
|
|
cairo_line_to (cr, priv->width - side, 0);
|
|
cairo_line_to (cr, priv->width, side);
|
|
cairo_line_to (cr, priv->width, 0);
|
|
|
|
pika_color_config_get_out_of_gamut_color (priv->config, &color);
|
|
pika_cairo_set_source_rgb (cr, &color);
|
|
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
if (priv->draw_border)
|
|
{
|
|
GdkRGBA color;
|
|
|
|
cairo_set_line_width (cr, 1.0);
|
|
|
|
gtk_style_context_get_color (context, gtk_widget_get_state_flags (widget),
|
|
&color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
cairo_rectangle (cr, 0.5, 0.5, priv->width - 1, priv->height - 1);
|
|
|
|
cairo_stroke (cr);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_new:
|
|
* @color: A pointer to a #PikaRGB struct.
|
|
* @type: The type of color area to create.
|
|
* @drag_mask: The event_mask that should trigger drags.
|
|
*
|
|
* Creates a new #PikaColorArea widget.
|
|
*
|
|
* This returns a preview area showing the color. It handles color
|
|
* DND. If the color changes, the "color_changed" signal is emitted.
|
|
*
|
|
* Returns: Pointer to the new #PikaColorArea widget.
|
|
**/
|
|
GtkWidget *
|
|
pika_color_area_new (const PikaRGB *color,
|
|
PikaColorAreaType type,
|
|
GdkModifierType drag_mask)
|
|
{
|
|
return g_object_new (PIKA_TYPE_COLOR_AREA,
|
|
"color", color,
|
|
"type", type,
|
|
"drag-mask", drag_mask,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_set_color:
|
|
* @area: Pointer to a #PikaColorArea.
|
|
* @color: Pointer to a #PikaRGB struct that defines the new color.
|
|
*
|
|
* Sets @area to a different @color.
|
|
**/
|
|
void
|
|
pika_color_area_set_color (PikaColorArea *area,
|
|
const PikaRGB *color)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
if (pika_rgba_distance (&priv->color, color) < PIKA_RGBA_EPSILON)
|
|
return;
|
|
|
|
priv->color = *color;
|
|
|
|
priv->needs_render = TRUE;
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
|
|
g_object_notify (G_OBJECT (area), "color");
|
|
|
|
g_signal_emit (area, pika_color_area_signals[COLOR_CHANGED], 0);
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_get_color:
|
|
* @area: Pointer to a #PikaColorArea.
|
|
* @color: (out caller-allocates): Pointer to a #PikaRGB struct
|
|
* that is used to return the color.
|
|
*
|
|
* Retrieves the current color of the @area.
|
|
**/
|
|
void
|
|
pika_color_area_get_color (PikaColorArea *area,
|
|
PikaRGB *color)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
*color = priv->color;
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_has_alpha:
|
|
* @area: Pointer to a #PikaColorArea.
|
|
*
|
|
* Checks whether the @area shows transparency information. This is determined
|
|
* via the @area's #PikaColorAreaType.
|
|
*
|
|
* Returns: %TRUE if @area shows transparency information, %FALSE otherwise.
|
|
**/
|
|
gboolean
|
|
pika_color_area_has_alpha (PikaColorArea *area)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_val_if_fail (PIKA_IS_COLOR_AREA (area), FALSE);
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
return priv->type != PIKA_COLOR_AREA_FLAT;
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_set_type:
|
|
* @area: Pointer to a #PikaColorArea.
|
|
* @type: A #PikaColorAreaType.
|
|
*
|
|
* Changes the type of @area. The #PikaColorAreaType determines
|
|
* whether the widget shows transparency information and chooses the
|
|
* size of the checkerboard used to do that.
|
|
**/
|
|
void
|
|
pika_color_area_set_type (PikaColorArea *area,
|
|
PikaColorAreaType type)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
if (priv->type != type)
|
|
{
|
|
priv->type = type;
|
|
|
|
priv->needs_render = TRUE;
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
|
|
g_object_notify (G_OBJECT (area), "type");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_enable_drag:
|
|
* @area: A #PikaColorArea.
|
|
* @drag_mask: The bitmask of buttons that can start the drag.
|
|
*
|
|
* Allows dragging the color displayed with buttons identified by
|
|
* @drag_mask. The drag supports targets of type "application/x-color".
|
|
*
|
|
* Note that setting a @drag_mask of 0 disables the drag ability.
|
|
**/
|
|
void
|
|
pika_color_area_enable_drag (PikaColorArea *area,
|
|
GdkModifierType drag_mask)
|
|
{
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
|
|
g_object_set (area,
|
|
"drag-mask", drag_mask,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_set_draw_border:
|
|
* @area: Pointer to a #PikaColorArea.
|
|
* @draw_border: whether to draw a border or not
|
|
*
|
|
* The @area can draw a thin border in the foreground color around
|
|
* itself. This function toggles this behavior on and off. The
|
|
* default is not draw a border.
|
|
**/
|
|
void
|
|
pika_color_area_set_draw_border (PikaColorArea *area,
|
|
gboolean draw_border)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
draw_border = draw_border ? TRUE : FALSE;
|
|
|
|
if (priv->draw_border != draw_border)
|
|
{
|
|
priv->draw_border = draw_border;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
|
|
g_object_notify (G_OBJECT (area), "draw-border");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_set_out_of_gamut:
|
|
* @area: a #PikaColorArea widget.
|
|
* @out_of_gamut: whether to show an out-of-gamut indicator
|
|
*
|
|
* Sets the color area to render as an out-of-gamut color, i.e. with a
|
|
* small triangle on a corner using the color management out of gamut
|
|
* color (as per pika_color_area_set_color_config()).
|
|
*
|
|
* By default, @area will render as out-of-gamut for any RGB color with
|
|
* a channel out of the [0; 1] range. This function allows to consider
|
|
* more colors out of gamut (for instance non-gray colors on a grayscale
|
|
* image, or colors absent of palettes in indexed images, etc.)
|
|
*
|
|
* Since: 2.10.10
|
|
*/
|
|
void
|
|
pika_color_area_set_out_of_gamut (PikaColorArea *area,
|
|
gboolean out_of_gamut)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
|
|
priv = GET_PRIVATE (area);
|
|
if (priv->out_of_gamut != out_of_gamut)
|
|
{
|
|
priv->out_of_gamut = out_of_gamut;
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_color_area_set_color_config:
|
|
* @area: a #PikaColorArea widget.
|
|
* @config: a #PikaColorConfig object.
|
|
*
|
|
* Sets the color management configuration to use with this color area.
|
|
*
|
|
* Since: 2.10
|
|
*/
|
|
void
|
|
pika_color_area_set_color_config (PikaColorArea *area,
|
|
PikaColorConfig *config)
|
|
{
|
|
PikaColorAreaPrivate *priv;
|
|
|
|
g_return_if_fail (PIKA_IS_COLOR_AREA (area));
|
|
g_return_if_fail (config == NULL || PIKA_IS_COLOR_CONFIG (config));
|
|
|
|
priv = GET_PRIVATE (area);
|
|
|
|
if (config != priv->config)
|
|
{
|
|
if (priv->config)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (priv->config,
|
|
pika_color_area_destroy_transform,
|
|
area);
|
|
|
|
pika_color_area_destroy_transform (area);
|
|
}
|
|
|
|
g_set_object (&priv->config, config);
|
|
|
|
if (priv->config)
|
|
{
|
|
g_signal_connect_swapped (priv->config, "notify",
|
|
G_CALLBACK (pika_color_area_destroy_transform),
|
|
area);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
pika_color_area_render_buf (GtkWidget *widget,
|
|
PikaColorAreaType type,
|
|
guchar *buf,
|
|
guint width,
|
|
guint height,
|
|
guint rowstride,
|
|
PikaRGB *color)
|
|
{
|
|
guint x, y;
|
|
guint check_size = 0;
|
|
guchar light[3];
|
|
guchar dark[3];
|
|
guchar opaque[3];
|
|
guchar *p;
|
|
gdouble frac;
|
|
|
|
switch (type)
|
|
{
|
|
case PIKA_COLOR_AREA_FLAT:
|
|
check_size = 0;
|
|
break;
|
|
|
|
case PIKA_COLOR_AREA_SMALL_CHECKS:
|
|
check_size = PIKA_CHECK_SIZE_SM;
|
|
break;
|
|
|
|
case PIKA_COLOR_AREA_LARGE_CHECKS:
|
|
check_size = PIKA_CHECK_SIZE;
|
|
break;
|
|
}
|
|
|
|
pika_rgb_get_uchar (color, opaque, opaque + 1, opaque + 2);
|
|
|
|
if (check_size == 0 || color->a == 1.0)
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
p = buf + y * rowstride;
|
|
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
PIKA_CAIRO_RGB24_SET_PIXEL (p,
|
|
opaque[0],
|
|
opaque[1],
|
|
opaque[2]);
|
|
|
|
p += 4;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
light[0] = (PIKA_CHECK_LIGHT +
|
|
(color->r - PIKA_CHECK_LIGHT) * color->a) * 255.999;
|
|
light[1] = (PIKA_CHECK_LIGHT +
|
|
(color->g - PIKA_CHECK_LIGHT) * color->a) * 255.999;
|
|
light[2] = (PIKA_CHECK_LIGHT +
|
|
(color->b - PIKA_CHECK_LIGHT) * color->a) * 255.999;
|
|
|
|
dark[0] = (PIKA_CHECK_DARK +
|
|
(color->r - PIKA_CHECK_DARK) * color->a) * 255.999;
|
|
dark[1] = (PIKA_CHECK_DARK +
|
|
(color->g - PIKA_CHECK_DARK) * color->a) * 255.999;
|
|
dark[2] = (PIKA_CHECK_DARK +
|
|
(color->b - PIKA_CHECK_DARK) * color->a) * 255.999;
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
p = buf + y * rowstride;
|
|
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
if ((width - x) * height > y * width)
|
|
{
|
|
PIKA_CAIRO_RGB24_SET_PIXEL (p,
|
|
opaque[0],
|
|
opaque[1],
|
|
opaque[2]);
|
|
p += 4;
|
|
|
|
continue;
|
|
}
|
|
|
|
frac = y - (gdouble) ((width - x) * height) / (gdouble) width;
|
|
|
|
if (((x / check_size) ^ (y / check_size)) & 1)
|
|
{
|
|
if ((gint) frac)
|
|
{
|
|
PIKA_CAIRO_RGB24_SET_PIXEL (p,
|
|
light[0],
|
|
light[1],
|
|
light[2]);
|
|
}
|
|
else
|
|
{
|
|
PIKA_CAIRO_RGB24_SET_PIXEL (p,
|
|
((gdouble) light[0] * frac +
|
|
(gdouble) opaque[0] * (1.0 - frac)),
|
|
((gdouble) light[1] * frac +
|
|
(gdouble) opaque[1] * (1.0 - frac)),
|
|
((gdouble) light[2] * frac +
|
|
(gdouble) opaque[2] * (1.0 - frac)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((gint) frac)
|
|
{
|
|
PIKA_CAIRO_RGB24_SET_PIXEL (p,
|
|
dark[0],
|
|
dark[1],
|
|
dark[2]);
|
|
}
|
|
else
|
|
{
|
|
PIKA_CAIRO_RGB24_SET_PIXEL (p,
|
|
((gdouble) dark[0] * frac +
|
|
(gdouble) opaque[0] * (1.0 - frac)),
|
|
((gdouble) dark[1] * frac +
|
|
(gdouble) opaque[1] * (1.0 - frac)),
|
|
((gdouble) dark[2] * frac +
|
|
(gdouble) opaque[2] * (1.0 - frac)));
|
|
}
|
|
}
|
|
|
|
p += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_color_area_render (PikaColorArea *area)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (area);
|
|
|
|
if (! priv->buf)
|
|
return;
|
|
|
|
pika_color_area_render_buf (GTK_WIDGET (area),
|
|
priv->type,
|
|
priv->buf,
|
|
priv->width, priv->height, priv->rowstride,
|
|
&priv->color);
|
|
|
|
priv->needs_render = FALSE;
|
|
}
|
|
|
|
static void
|
|
pika_color_area_drag_begin (GtkWidget *widget,
|
|
GdkDragContext *context)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (widget);
|
|
PikaRGB color;
|
|
GtkWidget *window;
|
|
GtkWidget *frame;
|
|
GtkWidget *color_area;
|
|
|
|
window = gtk_window_new (GTK_WINDOW_POPUP);
|
|
gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND);
|
|
gtk_window_set_screen (GTK_WINDOW (window), gtk_widget_get_screen (widget));
|
|
|
|
gtk_widget_realize (window);
|
|
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
|
|
gtk_container_add (GTK_CONTAINER (window), frame);
|
|
|
|
pika_color_area_get_color (PIKA_COLOR_AREA (widget), &color);
|
|
|
|
color_area = pika_color_area_new (&color, priv->type, 0);
|
|
|
|
gtk_widget_set_size_request (color_area,
|
|
DRAG_PREVIEW_SIZE, DRAG_PREVIEW_SIZE);
|
|
gtk_container_add (GTK_CONTAINER (frame), color_area);
|
|
gtk_widget_show (color_area);
|
|
gtk_widget_show (frame);
|
|
|
|
g_object_set_data_full (G_OBJECT (widget),
|
|
"pika-color-area-drag-window",
|
|
window,
|
|
(GDestroyNotify) gtk_widget_destroy);
|
|
|
|
gtk_drag_set_icon_widget (context, window,
|
|
DRAG_ICON_OFFSET, DRAG_ICON_OFFSET);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_drag_end (GtkWidget *widget,
|
|
GdkDragContext *context)
|
|
{
|
|
g_object_set_data (G_OBJECT (widget),
|
|
"pika-color-area-drag-window", NULL);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_drag_data_received (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint time)
|
|
{
|
|
PikaColorArea *area = PIKA_COLOR_AREA (widget);
|
|
const guint16 *vals;
|
|
PikaRGB color;
|
|
|
|
if (gtk_selection_data_get_length (selection_data) != 8 ||
|
|
gtk_selection_data_get_format (selection_data) != 16)
|
|
{
|
|
g_warning ("%s: received invalid color data", G_STRFUNC);
|
|
return;
|
|
}
|
|
|
|
vals = (const guint16 *) gtk_selection_data_get_data (selection_data);
|
|
|
|
pika_rgba_set (&color,
|
|
(gdouble) vals[0] / 0xffff,
|
|
(gdouble) vals[1] / 0xffff,
|
|
(gdouble) vals[2] / 0xffff,
|
|
(gdouble) vals[3] / 0xffff);
|
|
|
|
pika_color_area_set_color (area, &color);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_drag_data_get (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint time)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (widget);
|
|
guint16 vals[4];
|
|
|
|
vals[0] = priv->color.r * 0xffff;
|
|
vals[1] = priv->color.g * 0xffff;
|
|
vals[2] = priv->color.b * 0xffff;
|
|
|
|
if (priv->type == PIKA_COLOR_AREA_FLAT)
|
|
vals[3] = 0xffff;
|
|
else
|
|
vals[3] = priv->color.a * 0xffff;
|
|
|
|
gtk_selection_data_set (selection_data,
|
|
gdk_atom_intern ("application/x-color", FALSE),
|
|
16, (guchar *) vals, 8);
|
|
}
|
|
|
|
static void
|
|
pika_color_area_create_transform (PikaColorArea *area)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (area);
|
|
|
|
if (priv->config)
|
|
{
|
|
static PikaColorProfile *profile = NULL;
|
|
|
|
const Babl *format = babl_format ("cairo-RGB24");
|
|
|
|
if (G_UNLIKELY (! profile))
|
|
profile = pika_color_profile_new_rgb_srgb ();
|
|
|
|
priv->transform = pika_widget_get_color_transform (GTK_WIDGET (area),
|
|
priv->config,
|
|
profile,
|
|
format,
|
|
format,
|
|
NULL,
|
|
PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
FALSE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_color_area_destroy_transform (PikaColorArea *area)
|
|
{
|
|
PikaColorAreaPrivate *priv = GET_PRIVATE (area);
|
|
|
|
g_clear_object (&priv->transform);
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
}
|