/* 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 . */ #include "config.h" #include #include #include #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 free_mask = FALSE; gdouble scale = 1.0; 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; scale = MIN (ratio_x, ratio_y); } if (PIKA_IS_BRUSH_GENERATED (brush) || 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); free_mask = 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 (free_mask) { 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; }