943 lines
31 KiB
C
943 lines
31 KiB
C
|
/* PIKA - Photo and Image Kooker Application
|
|||
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|||
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|||
|
*
|
|||
|
* Original copyright, applying to most contents (license remains unchanged):
|
|||
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|||
|
*
|
|||
|
* This program is free software: you can redistribute it and/or modify
|
|||
|
* it under the terms of the GNU General Public License as published by
|
|||
|
* the Free Software Foundation; either version 3 of the License, or
|
|||
|
* (at your option) any later version.
|
|||
|
*
|
|||
|
* This program is distributed in the hope that it will be useful,
|
|||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
* GNU General Public License for more details.
|
|||
|
*
|
|||
|
* You should have received a copy of the GNU General Public License
|
|||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
*/
|
|||
|
|
|||
|
#include "config.h"
|
|||
|
|
|||
|
#include <cairo.h>
|
|||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|||
|
#include <gegl.h>
|
|||
|
|
|||
|
#include "libpikabase/pikabase.h"
|
|||
|
#include "libpikamath/pikamath.h"
|
|||
|
|
|||
|
#include "core-types.h"
|
|||
|
|
|||
|
#include "pikabezierdesc.h"
|
|||
|
#include "pikabrush.h"
|
|||
|
#include "pikabrush-boundary.h"
|
|||
|
#include "pikabrush-load.h"
|
|||
|
#include "pikabrush-mipmap.h"
|
|||
|
#include "pikabrush-private.h"
|
|||
|
#include "pikabrush-save.h"
|
|||
|
#include "pikabrush-transform.h"
|
|||
|
#include "pikabrushcache.h"
|
|||
|
#include "pikabrushgenerated.h"
|
|||
|
#include "pikabrushpipe.h"
|
|||
|
#include "pikatagged.h"
|
|||
|
#include "pikatempbuf.h"
|
|||
|
|
|||
|
#include "pika-intl.h"
|
|||
|
|
|||
|
|
|||
|
enum
|
|||
|
{
|
|||
|
SPACING_CHANGED,
|
|||
|
LAST_SIGNAL
|
|||
|
};
|
|||
|
|
|||
|
enum
|
|||
|
{
|
|||
|
PROP_0,
|
|||
|
PROP_SPACING
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
static void pika_brush_tagged_iface_init (PikaTaggedInterface *iface);
|
|||
|
|
|||
|
static void pika_brush_finalize (GObject *object);
|
|||
|
static void pika_brush_set_property (GObject *object,
|
|||
|
guint property_id,
|
|||
|
const GValue *value,
|
|||
|
GParamSpec *pspec);
|
|||
|
static void pika_brush_get_property (GObject *object,
|
|||
|
guint property_id,
|
|||
|
GValue *value,
|
|||
|
GParamSpec *pspec);
|
|||
|
|
|||
|
static gint64 pika_brush_get_memsize (PikaObject *object,
|
|||
|
gint64 *gui_size);
|
|||
|
|
|||
|
static gboolean pika_brush_get_size (PikaViewable *viewable,
|
|||
|
gint *width,
|
|||
|
gint *height);
|
|||
|
static PikaTempBuf * pika_brush_get_new_preview (PikaViewable *viewable,
|
|||
|
PikaContext *context,
|
|||
|
gint width,
|
|||
|
gint height);
|
|||
|
static gchar * pika_brush_get_description (PikaViewable *viewable,
|
|||
|
gchar **tooltip);
|
|||
|
|
|||
|
static void pika_brush_dirty (PikaData *data);
|
|||
|
static const gchar * pika_brush_get_extension (PikaData *data);
|
|||
|
static void pika_brush_copy (PikaData *data,
|
|||
|
PikaData *src_data);
|
|||
|
|
|||
|
static void pika_brush_real_begin_use (PikaBrush *brush);
|
|||
|
static void pika_brush_real_end_use (PikaBrush *brush);
|
|||
|
static PikaBrush * pika_brush_real_select_brush (PikaBrush *brush,
|
|||
|
const PikaCoords *last_coords,
|
|||
|
const PikaCoords *current_coords);
|
|||
|
static gboolean pika_brush_real_want_null_motion (PikaBrush *brush,
|
|||
|
const PikaCoords *last_coords,
|
|||
|
const PikaCoords *current_coords);
|
|||
|
|
|||
|
static gchar * pika_brush_get_checksum (PikaTagged *tagged);
|
|||
|
|
|||
|
|
|||
|
G_DEFINE_TYPE_WITH_CODE (PikaBrush, pika_brush, PIKA_TYPE_DATA,
|
|||
|
G_ADD_PRIVATE (PikaBrush)
|
|||
|
G_IMPLEMENT_INTERFACE (PIKA_TYPE_TAGGED,
|
|||
|
pika_brush_tagged_iface_init))
|
|||
|
|
|||
|
#define parent_class pika_brush_parent_class
|
|||
|
|
|||
|
static guint brush_signals[LAST_SIGNAL] = { 0 };
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_class_init (PikaBrushClass *klass)
|
|||
|
{
|
|||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|||
|
PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass);
|
|||
|
PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass);
|
|||
|
PikaDataClass *data_class = PIKA_DATA_CLASS (klass);
|
|||
|
|
|||
|
brush_signals[SPACING_CHANGED] =
|
|||
|
g_signal_new ("spacing-changed",
|
|||
|
G_TYPE_FROM_CLASS (klass),
|
|||
|
G_SIGNAL_RUN_FIRST,
|
|||
|
G_STRUCT_OFFSET (PikaBrushClass, spacing_changed),
|
|||
|
NULL, NULL, NULL,
|
|||
|
G_TYPE_NONE, 0);
|
|||
|
|
|||
|
object_class->finalize = pika_brush_finalize;
|
|||
|
object_class->get_property = pika_brush_get_property;
|
|||
|
object_class->set_property = pika_brush_set_property;
|
|||
|
|
|||
|
pika_object_class->get_memsize = pika_brush_get_memsize;
|
|||
|
|
|||
|
viewable_class->default_icon_name = "pika-tool-paintbrush";
|
|||
|
viewable_class->get_size = pika_brush_get_size;
|
|||
|
viewable_class->get_new_preview = pika_brush_get_new_preview;
|
|||
|
viewable_class->get_description = pika_brush_get_description;
|
|||
|
|
|||
|
data_class->dirty = pika_brush_dirty;
|
|||
|
data_class->save = pika_brush_save;
|
|||
|
data_class->get_extension = pika_brush_get_extension;
|
|||
|
data_class->copy = pika_brush_copy;
|
|||
|
|
|||
|
klass->begin_use = pika_brush_real_begin_use;
|
|||
|
klass->end_use = pika_brush_real_end_use;
|
|||
|
klass->select_brush = pika_brush_real_select_brush;
|
|||
|
klass->want_null_motion = pika_brush_real_want_null_motion;
|
|||
|
klass->transform_size = pika_brush_real_transform_size;
|
|||
|
klass->transform_mask = pika_brush_real_transform_mask;
|
|||
|
klass->transform_pixmap = pika_brush_real_transform_pixmap;
|
|||
|
klass->transform_boundary = pika_brush_real_transform_boundary;
|
|||
|
klass->spacing_changed = NULL;
|
|||
|
|
|||
|
g_object_class_install_property (object_class, PROP_SPACING,
|
|||
|
g_param_spec_double ("spacing", NULL,
|
|||
|
_("Brush Spacing"),
|
|||
|
1.0, 5000.0, 20.0,
|
|||
|
PIKA_PARAM_READWRITE |
|
|||
|
G_PARAM_CONSTRUCT));
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_tagged_iface_init (PikaTaggedInterface *iface)
|
|||
|
{
|
|||
|
iface->get_checksum = pika_brush_get_checksum;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_init (PikaBrush *brush)
|
|||
|
{
|
|||
|
brush->priv = pika_brush_get_instance_private (brush);
|
|||
|
|
|||
|
brush->priv->spacing = 20;
|
|||
|
brush->priv->x_axis.x = 15.0;
|
|||
|
brush->priv->x_axis.y = 0.0;
|
|||
|
brush->priv->y_axis.x = 0.0;
|
|||
|
brush->priv->y_axis.y = 15.0;
|
|||
|
|
|||
|
brush->priv->blur_hardness = 1.0;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_finalize (GObject *object)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (object);
|
|||
|
|
|||
|
g_clear_pointer (&brush->priv->mask, pika_temp_buf_unref);
|
|||
|
g_clear_pointer (&brush->priv->pixmap, pika_temp_buf_unref);
|
|||
|
g_clear_pointer (&brush->priv->blurred_mask, pika_temp_buf_unref);
|
|||
|
g_clear_pointer (&brush->priv->blurred_pixmap, pika_temp_buf_unref);
|
|||
|
|
|||
|
pika_brush_mipmap_clear (brush);
|
|||
|
|
|||
|
g_clear_object (&brush->priv->mask_cache);
|
|||
|
g_clear_object (&brush->priv->pixmap_cache);
|
|||
|
g_clear_object (&brush->priv->boundary_cache);
|
|||
|
|
|||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_set_property (GObject *object,
|
|||
|
guint property_id,
|
|||
|
const GValue *value,
|
|||
|
GParamSpec *pspec)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (object);
|
|||
|
|
|||
|
switch (property_id)
|
|||
|
{
|
|||
|
case PROP_SPACING:
|
|||
|
pika_brush_set_spacing (brush, g_value_get_double (value));
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_get_property (GObject *object,
|
|||
|
guint property_id,
|
|||
|
GValue *value,
|
|||
|
GParamSpec *pspec)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (object);
|
|||
|
|
|||
|
switch (property_id)
|
|||
|
{
|
|||
|
case PROP_SPACING:
|
|||
|
g_value_set_double (value, pika_brush_get_spacing (brush));
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static gint64
|
|||
|
pika_brush_get_memsize (PikaObject *object,
|
|||
|
gint64 *gui_size)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (object);
|
|||
|
gint64 memsize = 0;
|
|||
|
|
|||
|
memsize += pika_temp_buf_get_memsize (brush->priv->mask);
|
|||
|
memsize += pika_temp_buf_get_memsize (brush->priv->pixmap);
|
|||
|
|
|||
|
memsize += pika_brush_mipmap_get_memsize (brush);
|
|||
|
|
|||
|
return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
|
|||
|
gui_size);
|
|||
|
}
|
|||
|
|
|||
|
static gboolean
|
|||
|
pika_brush_get_size (PikaViewable *viewable,
|
|||
|
gint *width,
|
|||
|
gint *height)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (viewable);
|
|||
|
|
|||
|
*width = pika_temp_buf_get_width (brush->priv->mask);
|
|||
|
*height = pika_temp_buf_get_height (brush->priv->mask);
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
static PikaTempBuf *
|
|||
|
pika_brush_get_new_preview (PikaViewable *viewable,
|
|||
|
PikaContext *context,
|
|||
|
gint width,
|
|||
|
gint height)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (viewable);
|
|||
|
const PikaTempBuf *mask_buf = brush->priv->mask;
|
|||
|
const PikaTempBuf *pixmap_buf = brush->priv->pixmap;
|
|||
|
PikaTempBuf *return_buf = NULL;
|
|||
|
gint mask_width;
|
|||
|
gint mask_height;
|
|||
|
guchar *mask_data;
|
|||
|
guchar *mask;
|
|||
|
guchar *buf;
|
|||
|
gint x, y;
|
|||
|
gboolean scaled = FALSE;
|
|||
|
|
|||
|
mask_width = pika_temp_buf_get_width (mask_buf);
|
|||
|
mask_height = pika_temp_buf_get_height (mask_buf);
|
|||
|
|
|||
|
if (mask_width > width || mask_height > height)
|
|||
|
{
|
|||
|
gdouble ratio_x = (gdouble) width / (gdouble) mask_width;
|
|||
|
gdouble ratio_y = (gdouble) height / (gdouble) mask_height;
|
|||
|
gdouble scale = MIN (ratio_x, ratio_y);
|
|||
|
|
|||
|
if (scale != 1.0)
|
|||
|
{
|
|||
|
pika_brush_begin_use (brush);
|
|||
|
|
|||
|
if (PIKA_IS_BRUSH_GENERATED (brush))
|
|||
|
{
|
|||
|
PikaBrushGenerated *gen_brush = PIKA_BRUSH_GENERATED (brush);
|
|||
|
|
|||
|
mask_buf = pika_brush_transform_mask (brush, scale,
|
|||
|
(pika_brush_generated_get_aspect_ratio (gen_brush) - 1.0) * 20.0 / 19.0,
|
|||
|
pika_brush_generated_get_angle (gen_brush) / 360.0,
|
|||
|
FALSE,
|
|||
|
pika_brush_generated_get_hardness (gen_brush));
|
|||
|
}
|
|||
|
else
|
|||
|
mask_buf = pika_brush_transform_mask (brush, scale,
|
|||
|
0.0, 0.0, FALSE, 1.0);
|
|||
|
|
|||
|
if (! mask_buf)
|
|||
|
{
|
|||
|
mask_buf = pika_temp_buf_new (1, 1, babl_format ("Y u8"));
|
|||
|
pika_temp_buf_data_clear ((PikaTempBuf *) mask_buf);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
pika_temp_buf_ref ((PikaTempBuf *) mask_buf);
|
|||
|
}
|
|||
|
|
|||
|
if (pixmap_buf)
|
|||
|
pixmap_buf = pika_brush_transform_pixmap (brush, scale,
|
|||
|
0.0, 0.0, FALSE, 1.0);
|
|||
|
|
|||
|
mask_width = pika_temp_buf_get_width (mask_buf);
|
|||
|
mask_height = pika_temp_buf_get_height (mask_buf);
|
|||
|
|
|||
|
scaled = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return_buf = pika_temp_buf_new (mask_width, mask_height,
|
|||
|
babl_format ("R'G'B'A u8"));
|
|||
|
|
|||
|
mask = mask_data = pika_temp_buf_lock (mask_buf, babl_format ("Y u8"),
|
|||
|
GEGL_ACCESS_READ);
|
|||
|
buf = pika_temp_buf_get_data (return_buf);
|
|||
|
|
|||
|
if (pixmap_buf)
|
|||
|
{
|
|||
|
guchar *pixmap_data;
|
|||
|
guchar *pixmap;
|
|||
|
|
|||
|
pixmap = pixmap_data = pika_temp_buf_lock (pixmap_buf,
|
|||
|
babl_format ("R'G'B' u8"),
|
|||
|
GEGL_ACCESS_READ);
|
|||
|
|
|||
|
for (y = 0; y < mask_height; y++)
|
|||
|
{
|
|||
|
for (x = 0; x < mask_width ; x++)
|
|||
|
{
|
|||
|
*buf++ = *pixmap++;
|
|||
|
*buf++ = *pixmap++;
|
|||
|
*buf++ = *pixmap++;
|
|||
|
*buf++ = *mask++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
pika_temp_buf_unlock (pixmap_buf, pixmap_data);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
for (y = 0; y < mask_height; y++)
|
|||
|
{
|
|||
|
for (x = 0; x < mask_width ; x++)
|
|||
|
{
|
|||
|
*buf++ = 0;
|
|||
|
*buf++ = 0;
|
|||
|
*buf++ = 0;
|
|||
|
*buf++ = *mask++;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
pika_temp_buf_unlock (mask_buf, mask_data);
|
|||
|
|
|||
|
if (scaled)
|
|||
|
{
|
|||
|
pika_temp_buf_unref ((PikaTempBuf *) mask_buf);
|
|||
|
|
|||
|
pika_brush_end_use (brush);
|
|||
|
}
|
|||
|
|
|||
|
return return_buf;
|
|||
|
}
|
|||
|
|
|||
|
static gchar *
|
|||
|
pika_brush_get_description (PikaViewable *viewable,
|
|||
|
gchar **tooltip)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (viewable);
|
|||
|
|
|||
|
return g_strdup_printf ("%s (%d × %d)",
|
|||
|
pika_object_get_name (brush),
|
|||
|
pika_temp_buf_get_width (brush->priv->mask),
|
|||
|
pika_temp_buf_get_height (brush->priv->mask));
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_dirty (PikaData *data)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (data);
|
|||
|
|
|||
|
if (brush->priv->mask_cache)
|
|||
|
pika_brush_cache_clear (brush->priv->mask_cache);
|
|||
|
|
|||
|
if (brush->priv->pixmap_cache)
|
|||
|
pika_brush_cache_clear (brush->priv->pixmap_cache);
|
|||
|
|
|||
|
if (brush->priv->boundary_cache)
|
|||
|
pika_brush_cache_clear (brush->priv->boundary_cache);
|
|||
|
|
|||
|
pika_brush_mipmap_clear (brush);
|
|||
|
|
|||
|
g_clear_pointer (&brush->priv->blurred_mask, pika_temp_buf_unref);
|
|||
|
g_clear_pointer (&brush->priv->blurred_pixmap, pika_temp_buf_unref);
|
|||
|
|
|||
|
PIKA_DATA_CLASS (parent_class)->dirty (data);
|
|||
|
}
|
|||
|
|
|||
|
static const gchar *
|
|||
|
pika_brush_get_extension (PikaData *data)
|
|||
|
{
|
|||
|
return PIKA_BRUSH_FILE_EXTENSION;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_copy (PikaData *data,
|
|||
|
PikaData *src_data)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (data);
|
|||
|
PikaBrush *src_brush = PIKA_BRUSH (src_data);
|
|||
|
|
|||
|
g_clear_pointer (&brush->priv->mask, pika_temp_buf_unref);
|
|||
|
if (src_brush->priv->mask)
|
|||
|
brush->priv->mask = pika_temp_buf_copy (src_brush->priv->mask);
|
|||
|
|
|||
|
g_clear_pointer (&brush->priv->pixmap, pika_temp_buf_unref);
|
|||
|
if (src_brush->priv->pixmap)
|
|||
|
brush->priv->pixmap = pika_temp_buf_copy (src_brush->priv->pixmap);
|
|||
|
|
|||
|
brush->priv->spacing = src_brush->priv->spacing;
|
|||
|
brush->priv->x_axis = src_brush->priv->x_axis;
|
|||
|
brush->priv->y_axis = src_brush->priv->y_axis;
|
|||
|
|
|||
|
pika_data_dirty (data);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_real_begin_use (PikaBrush *brush)
|
|||
|
{
|
|||
|
brush->priv->mask_cache =
|
|||
|
pika_brush_cache_new ((GDestroyNotify) pika_temp_buf_unref, 'M', 'm');
|
|||
|
|
|||
|
brush->priv->pixmap_cache =
|
|||
|
pika_brush_cache_new ((GDestroyNotify) pika_temp_buf_unref, 'P', 'p');
|
|||
|
|
|||
|
brush->priv->boundary_cache =
|
|||
|
pika_brush_cache_new ((GDestroyNotify) pika_bezier_desc_free, 'B', 'b');
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pika_brush_real_end_use (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_clear_object (&brush->priv->mask_cache);
|
|||
|
g_clear_object (&brush->priv->pixmap_cache);
|
|||
|
g_clear_object (&brush->priv->boundary_cache);
|
|||
|
|
|||
|
g_clear_pointer (&brush->priv->blurred_mask, pika_temp_buf_unref);
|
|||
|
g_clear_pointer (&brush->priv->blurred_pixmap, pika_temp_buf_unref);
|
|||
|
}
|
|||
|
|
|||
|
static PikaBrush *
|
|||
|
pika_brush_real_select_brush (PikaBrush *brush,
|
|||
|
const PikaCoords *last_coords,
|
|||
|
const PikaCoords *current_coords)
|
|||
|
{
|
|||
|
return brush;
|
|||
|
}
|
|||
|
|
|||
|
static gboolean
|
|||
|
pika_brush_real_want_null_motion (PikaBrush *brush,
|
|||
|
const PikaCoords *last_coords,
|
|||
|
const PikaCoords *current_coords)
|
|||
|
{
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
static gchar *
|
|||
|
pika_brush_get_checksum (PikaTagged *tagged)
|
|||
|
{
|
|||
|
PikaBrush *brush = PIKA_BRUSH (tagged);
|
|||
|
gchar *checksum_string = NULL;
|
|||
|
|
|||
|
if (brush->priv->mask)
|
|||
|
{
|
|||
|
GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
|
|||
|
|
|||
|
g_checksum_update (checksum,
|
|||
|
pika_temp_buf_get_data (brush->priv->mask),
|
|||
|
pika_temp_buf_get_data_size (brush->priv->mask));
|
|||
|
if (brush->priv->pixmap)
|
|||
|
g_checksum_update (checksum,
|
|||
|
pika_temp_buf_get_data (brush->priv->pixmap),
|
|||
|
pika_temp_buf_get_data_size (brush->priv->pixmap));
|
|||
|
g_checksum_update (checksum,
|
|||
|
(const guchar *) &brush->priv->spacing,
|
|||
|
sizeof (brush->priv->spacing));
|
|||
|
g_checksum_update (checksum,
|
|||
|
(const guchar *) &brush->priv->x_axis,
|
|||
|
sizeof (brush->priv->x_axis));
|
|||
|
g_checksum_update (checksum,
|
|||
|
(const guchar *) &brush->priv->y_axis,
|
|||
|
sizeof (brush->priv->y_axis));
|
|||
|
|
|||
|
checksum_string = g_strdup (g_checksum_get_string (checksum));
|
|||
|
|
|||
|
g_checksum_free (checksum);
|
|||
|
}
|
|||
|
|
|||
|
return checksum_string;
|
|||
|
}
|
|||
|
|
|||
|
/* public functions */
|
|||
|
|
|||
|
PikaData *
|
|||
|
pika_brush_new (PikaContext *context,
|
|||
|
const gchar *name)
|
|||
|
{
|
|||
|
g_return_val_if_fail (name != NULL, NULL);
|
|||
|
|
|||
|
return pika_brush_generated_new (name,
|
|||
|
PIKA_BRUSH_GENERATED_CIRCLE,
|
|||
|
5.0, 2, 0.5, 1.0, 0.0);
|
|||
|
}
|
|||
|
|
|||
|
PikaData *
|
|||
|
pika_brush_get_standard (PikaContext *context)
|
|||
|
{
|
|||
|
static PikaData *standard_brush = NULL;
|
|||
|
|
|||
|
if (! standard_brush)
|
|||
|
{
|
|||
|
g_set_weak_pointer (&standard_brush,
|
|||
|
pika_brush_new (context, "Standard"));
|
|||
|
|
|||
|
pika_data_clean (standard_brush);
|
|||
|
pika_data_make_internal (standard_brush, "pika-brush-standard");
|
|||
|
}
|
|||
|
|
|||
|
return standard_brush;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
pika_brush_begin_use (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_if_fail (PIKA_IS_BRUSH (brush));
|
|||
|
|
|||
|
brush->priv->use_count++;
|
|||
|
|
|||
|
if (brush->priv->use_count == 1)
|
|||
|
PIKA_BRUSH_GET_CLASS (brush)->begin_use (brush);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
pika_brush_end_use (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_if_fail (PIKA_IS_BRUSH (brush));
|
|||
|
g_return_if_fail (brush->priv->use_count > 0);
|
|||
|
|
|||
|
brush->priv->use_count--;
|
|||
|
|
|||
|
if (brush->priv->use_count == 0)
|
|||
|
PIKA_BRUSH_GET_CLASS (brush)->end_use (brush);
|
|||
|
}
|
|||
|
|
|||
|
PikaBrush *
|
|||
|
pika_brush_select_brush (PikaBrush *brush,
|
|||
|
const PikaCoords *last_coords,
|
|||
|
const PikaCoords *current_coords)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), NULL);
|
|||
|
g_return_val_if_fail (last_coords != NULL, NULL);
|
|||
|
g_return_val_if_fail (current_coords != NULL, NULL);
|
|||
|
|
|||
|
return PIKA_BRUSH_GET_CLASS (brush)->select_brush (brush,
|
|||
|
last_coords,
|
|||
|
current_coords);
|
|||
|
}
|
|||
|
|
|||
|
gboolean
|
|||
|
pika_brush_want_null_motion (PikaBrush *brush,
|
|||
|
const PikaCoords *last_coords,
|
|||
|
const PikaCoords *current_coords)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), FALSE);
|
|||
|
g_return_val_if_fail (last_coords != NULL, FALSE);
|
|||
|
g_return_val_if_fail (current_coords != NULL, FALSE);
|
|||
|
|
|||
|
return PIKA_BRUSH_GET_CLASS (brush)->want_null_motion (brush,
|
|||
|
last_coords,
|
|||
|
current_coords);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
pika_brush_transform_size (PikaBrush *brush,
|
|||
|
gdouble scale,
|
|||
|
gdouble aspect_ratio,
|
|||
|
gdouble angle,
|
|||
|
gboolean reflect,
|
|||
|
gint *width,
|
|||
|
gint *height)
|
|||
|
{
|
|||
|
g_return_if_fail (PIKA_IS_BRUSH (brush));
|
|||
|
g_return_if_fail (scale > 0.0);
|
|||
|
g_return_if_fail (width != NULL);
|
|||
|
g_return_if_fail (height != NULL);
|
|||
|
|
|||
|
if (scale == 1.0 &&
|
|||
|
aspect_ratio == 0.0 &&
|
|||
|
fmod (angle, 0.5) == 0.0)
|
|||
|
{
|
|||
|
*width = pika_temp_buf_get_width (brush->priv->mask);
|
|||
|
*height = pika_temp_buf_get_height (brush->priv->mask);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
PIKA_BRUSH_GET_CLASS (brush)->transform_size (brush,
|
|||
|
scale, aspect_ratio, angle, reflect,
|
|||
|
width, height);
|
|||
|
}
|
|||
|
|
|||
|
const PikaTempBuf *
|
|||
|
pika_brush_transform_mask (PikaBrush *brush,
|
|||
|
gdouble scale,
|
|||
|
gdouble aspect_ratio,
|
|||
|
gdouble angle,
|
|||
|
gboolean reflect,
|
|||
|
gdouble hardness)
|
|||
|
{
|
|||
|
const PikaTempBuf *mask;
|
|||
|
gint width;
|
|||
|
gint height;
|
|||
|
gdouble effective_hardness = hardness;
|
|||
|
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), NULL);
|
|||
|
g_return_val_if_fail (scale > 0.0, NULL);
|
|||
|
|
|||
|
pika_brush_transform_size (brush,
|
|||
|
scale, aspect_ratio, angle, reflect,
|
|||
|
&width, &height);
|
|||
|
|
|||
|
mask = pika_brush_cache_get (brush->priv->mask_cache,
|
|||
|
width, height,
|
|||
|
scale, aspect_ratio, angle, reflect, hardness);
|
|||
|
|
|||
|
if (! mask)
|
|||
|
{
|
|||
|
#if 0
|
|||
|
/* This code makes sure that brushes using blur for hardness
|
|||
|
* (all of them but generated) are blurred once and no more.
|
|||
|
* It also makes hardnes dynamics not work for these brushes.
|
|||
|
* This is intentional. Confoliving for each stamp is too expensive.*/
|
|||
|
if (! brush->priv->blurred_mask &&
|
|||
|
! PIKA_IS_BRUSH_GENERATED(brush) &&
|
|||
|
! PIKA_IS_BRUSH_PIPE(brush) && /*Can't cache pipes. Sanely anyway*/
|
|||
|
hardness < 1.0)
|
|||
|
{
|
|||
|
brush->priv->blurred_mask = PIKA_BRUSH_GET_CLASS (brush)->transform_mask (brush,
|
|||
|
1.0,
|
|||
|
0.0,
|
|||
|
0.0,
|
|||
|
FALSE,
|
|||
|
hardness);
|
|||
|
brush->priv->blur_hardness = hardness;
|
|||
|
}
|
|||
|
|
|||
|
if (brush->priv->blurred_mask)
|
|||
|
{
|
|||
|
effective_hardness = 1.0; /*Hardness has already been applied*/
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
mask = PIKA_BRUSH_GET_CLASS (brush)->transform_mask (brush,
|
|||
|
scale,
|
|||
|
aspect_ratio,
|
|||
|
angle,
|
|||
|
reflect,
|
|||
|
effective_hardness);
|
|||
|
|
|||
|
pika_brush_cache_add (brush->priv->mask_cache,
|
|||
|
(gpointer) mask,
|
|||
|
width, height,
|
|||
|
scale, aspect_ratio, angle, reflect, effective_hardness);
|
|||
|
}
|
|||
|
|
|||
|
return mask;
|
|||
|
}
|
|||
|
|
|||
|
const PikaTempBuf *
|
|||
|
pika_brush_transform_pixmap (PikaBrush *brush,
|
|||
|
gdouble scale,
|
|||
|
gdouble aspect_ratio,
|
|||
|
gdouble angle,
|
|||
|
gboolean reflect,
|
|||
|
gdouble hardness)
|
|||
|
{
|
|||
|
const PikaTempBuf *pixmap;
|
|||
|
gint width;
|
|||
|
gint height;
|
|||
|
gdouble effective_hardness = hardness;
|
|||
|
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), NULL);
|
|||
|
g_return_val_if_fail (brush->priv->pixmap != NULL, NULL);
|
|||
|
g_return_val_if_fail (scale > 0.0, NULL);
|
|||
|
|
|||
|
pika_brush_transform_size (brush,
|
|||
|
scale, aspect_ratio, angle, reflect,
|
|||
|
&width, &height);
|
|||
|
|
|||
|
pixmap = pika_brush_cache_get (brush->priv->pixmap_cache,
|
|||
|
width, height,
|
|||
|
scale, aspect_ratio, angle, reflect, hardness);
|
|||
|
|
|||
|
if (! pixmap)
|
|||
|
{
|
|||
|
#if 0
|
|||
|
if (! brush->priv->blurred_pixmap &&
|
|||
|
! PIKA_IS_BRUSH_GENERATED(brush) &&
|
|||
|
! PIKA_IS_BRUSH_PIPE(brush) /*Can't cache pipes. Sanely anyway*/
|
|||
|
&& hardness < 1.0)
|
|||
|
{
|
|||
|
brush->priv->blurred_pixmap = PIKA_BRUSH_GET_CLASS (brush)->transform_pixmap (brush,
|
|||
|
1.0,
|
|||
|
0.0,
|
|||
|
0.0,
|
|||
|
FALSE,
|
|||
|
hardness);
|
|||
|
brush->priv->blur_hardness = hardness;
|
|||
|
}
|
|||
|
|
|||
|
if (brush->priv->blurred_pixmap) {
|
|||
|
effective_hardness = 1.0; /*Hardness has already been applied*/
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
pixmap = PIKA_BRUSH_GET_CLASS (brush)->transform_pixmap (brush,
|
|||
|
scale,
|
|||
|
aspect_ratio,
|
|||
|
angle,
|
|||
|
reflect,
|
|||
|
effective_hardness);
|
|||
|
|
|||
|
pika_brush_cache_add (brush->priv->pixmap_cache,
|
|||
|
(gpointer) pixmap,
|
|||
|
width, height,
|
|||
|
scale, aspect_ratio, angle, reflect, effective_hardness);
|
|||
|
}
|
|||
|
|
|||
|
return pixmap;
|
|||
|
}
|
|||
|
|
|||
|
const PikaBezierDesc *
|
|||
|
pika_brush_transform_boundary (PikaBrush *brush,
|
|||
|
gdouble scale,
|
|||
|
gdouble aspect_ratio,
|
|||
|
gdouble angle,
|
|||
|
gboolean reflect,
|
|||
|
gdouble hardness,
|
|||
|
gint *width,
|
|||
|
gint *height)
|
|||
|
{
|
|||
|
const PikaBezierDesc *boundary;
|
|||
|
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), NULL);
|
|||
|
g_return_val_if_fail (scale > 0.0, NULL);
|
|||
|
g_return_val_if_fail (width != NULL, NULL);
|
|||
|
g_return_val_if_fail (height != NULL, NULL);
|
|||
|
|
|||
|
pika_brush_transform_size (brush,
|
|||
|
scale, aspect_ratio, angle, reflect,
|
|||
|
width, height);
|
|||
|
|
|||
|
boundary = pika_brush_cache_get (brush->priv->boundary_cache,
|
|||
|
*width, *height,
|
|||
|
scale, aspect_ratio, angle, reflect, hardness);
|
|||
|
|
|||
|
if (! boundary)
|
|||
|
{
|
|||
|
boundary = PIKA_BRUSH_GET_CLASS (brush)->transform_boundary (brush,
|
|||
|
scale,
|
|||
|
aspect_ratio,
|
|||
|
angle,
|
|||
|
reflect,
|
|||
|
hardness,
|
|||
|
width,
|
|||
|
height);
|
|||
|
|
|||
|
/* while the brush mask is always at least 1x1 pixels, its
|
|||
|
* outline can correctly be NULL
|
|||
|
*
|
|||
|
* FIXME: make the cache handle NULL things when it is
|
|||
|
* properly implemented
|
|||
|
*/
|
|||
|
if (boundary)
|
|||
|
pika_brush_cache_add (brush->priv->boundary_cache,
|
|||
|
(gpointer) boundary,
|
|||
|
*width, *height,
|
|||
|
scale, aspect_ratio, angle, reflect, hardness);
|
|||
|
}
|
|||
|
|
|||
|
return boundary;
|
|||
|
}
|
|||
|
|
|||
|
PikaTempBuf *
|
|||
|
pika_brush_get_mask (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (brush != NULL, NULL);
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), NULL);
|
|||
|
|
|||
|
if (brush->priv->blurred_mask)
|
|||
|
{
|
|||
|
return brush->priv->blurred_mask;
|
|||
|
}
|
|||
|
return brush->priv->mask;
|
|||
|
}
|
|||
|
|
|||
|
PikaTempBuf *
|
|||
|
pika_brush_get_pixmap (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (brush != NULL, NULL);
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), NULL);
|
|||
|
|
|||
|
if(brush->priv->blurred_pixmap)
|
|||
|
{
|
|||
|
return brush->priv->blurred_pixmap;
|
|||
|
}
|
|||
|
return brush->priv->pixmap;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
pika_brush_flush_blur_caches (PikaBrush *brush)
|
|||
|
{
|
|||
|
#if 0
|
|||
|
g_clear_pointer (&brush->priv->blurred_mask, pika_temp_buf_unref);
|
|||
|
g_clear_pointer (&brush->priv->blurred_pixmap, pika_temp_buf_unref);
|
|||
|
|
|||
|
if (brush->priv->mask_cache)
|
|||
|
pika_brush_cache_clear (brush->priv->mask_cache);
|
|||
|
|
|||
|
if (brush->priv->pixmap_cache)
|
|||
|
pika_brush_cache_clear (brush->priv->pixmap_cache);
|
|||
|
|
|||
|
if (brush->priv->boundary_cache)
|
|||
|
pika_brush_cache_clear (brush->priv->boundary_cache);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
gdouble
|
|||
|
pika_brush_get_blur_hardness (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), 0);
|
|||
|
|
|||
|
return brush->priv->blur_hardness;
|
|||
|
}
|
|||
|
|
|||
|
gint
|
|||
|
pika_brush_get_width (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), 0);
|
|||
|
|
|||
|
if (brush->priv->blurred_mask)
|
|||
|
return pika_temp_buf_get_width (brush->priv->blurred_mask);
|
|||
|
|
|||
|
if (brush->priv->blurred_pixmap)
|
|||
|
return pika_temp_buf_get_width (brush->priv->blurred_pixmap);
|
|||
|
|
|||
|
return pika_temp_buf_get_width (brush->priv->mask);
|
|||
|
}
|
|||
|
|
|||
|
gint
|
|||
|
pika_brush_get_height (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), 0);
|
|||
|
|
|||
|
if (brush->priv->blurred_mask)
|
|||
|
return pika_temp_buf_get_height (brush->priv->blurred_mask);
|
|||
|
|
|||
|
if (brush->priv->blurred_pixmap)
|
|||
|
return pika_temp_buf_get_height (brush->priv->blurred_pixmap);
|
|||
|
|
|||
|
return pika_temp_buf_get_height (brush->priv->mask);
|
|||
|
}
|
|||
|
|
|||
|
gint
|
|||
|
pika_brush_get_spacing (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), 0);
|
|||
|
|
|||
|
return brush->priv->spacing;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
pika_brush_set_spacing (PikaBrush *brush,
|
|||
|
gint spacing)
|
|||
|
{
|
|||
|
g_return_if_fail (PIKA_IS_BRUSH (brush));
|
|||
|
|
|||
|
if (brush->priv->spacing != spacing)
|
|||
|
{
|
|||
|
brush->priv->spacing = spacing;
|
|||
|
|
|||
|
g_signal_emit (brush, brush_signals[SPACING_CHANGED], 0);
|
|||
|
g_object_notify (G_OBJECT (brush), "spacing");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static const PikaVector2 fail = { 0.0, 0.0 };
|
|||
|
|
|||
|
PikaVector2
|
|||
|
pika_brush_get_x_axis (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), fail);
|
|||
|
|
|||
|
return brush->priv->x_axis;
|
|||
|
}
|
|||
|
|
|||
|
PikaVector2
|
|||
|
pika_brush_get_y_axis (PikaBrush *brush)
|
|||
|
{
|
|||
|
g_return_val_if_fail (PIKA_IS_BRUSH (brush), fail);
|
|||
|
|
|||
|
return brush->priv->y_axis;
|
|||
|
}
|