2018-10-11 02:28:47 +08:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
|
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
|
|
*
|
|
|
|
* Copyright (C) 2017 Sébastien Fourey & David Tchumperlé
|
|
|
|
* Copyright (C) 2018 Jehan
|
|
|
|
*
|
|
|
|
* 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"
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
2018-10-11 02:28:47 +08:00
|
|
|
#include <gegl.h>
|
|
|
|
|
2019-07-31 16:16:21 +08:00
|
|
|
#include "libgimpbase/gimpbase.h"
|
2018-10-11 02:28:47 +08:00
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
|
|
|
|
#include "core-types.h"
|
|
|
|
|
2019-03-06 17:50:03 +08:00
|
|
|
#include "gegl/gimp-gegl-loops.h"
|
2019-07-31 01:25:54 +08:00
|
|
|
#include "gegl/gimp-gegl-utils.h"
|
2019-03-06 17:50:03 +08:00
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
#include "gimp-parallel.h"
|
2018-12-28 06:07:46 +08:00
|
|
|
#include "gimp-priorities.h"
|
2018-12-02 01:33:51 +08:00
|
|
|
#include "gimp-utils.h" /* GIMP_TIMER */
|
|
|
|
#include "gimpasync.h"
|
|
|
|
#include "gimpcancelable.h"
|
|
|
|
#include "gimpdrawable.h"
|
|
|
|
#include "gimpimage.h"
|
2018-10-11 02:28:47 +08:00
|
|
|
#include "gimplineart.h"
|
2019-03-04 23:06:59 +08:00
|
|
|
#include "gimpmarshal.h"
|
2018-12-02 01:33:51 +08:00
|
|
|
#include "gimppickable.h"
|
|
|
|
#include "gimpprojection.h"
|
2018-12-28 06:07:46 +08:00
|
|
|
#include "gimpviewable.h"
|
2018-12-02 01:33:51 +08:00
|
|
|
#include "gimpwaitable.h"
|
|
|
|
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
2019-03-04 23:06:59 +08:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
COMPUTING_START,
|
|
|
|
COMPUTING_END,
|
|
|
|
LAST_SIGNAL,
|
|
|
|
};
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
PROP_0,
|
|
|
|
PROP_SELECT_TRANSPARENT,
|
|
|
|
PROP_MAX_GROW,
|
|
|
|
PROP_THRESHOLD,
|
|
|
|
PROP_SPLINE_MAX_LEN,
|
|
|
|
PROP_SEGMENT_MAX_LEN,
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct _GimpLineArtPrivate GimpLineArtPrivate;
|
|
|
|
|
|
|
|
struct _GimpLineArtPrivate
|
|
|
|
{
|
|
|
|
gboolean frozen;
|
|
|
|
gboolean compute_after_thaw;
|
|
|
|
|
|
|
|
GimpAsync *async;
|
|
|
|
|
2018-12-28 06:07:46 +08:00
|
|
|
gint idle_id;
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
GimpPickable *input;
|
|
|
|
GeglBuffer *closed;
|
|
|
|
gfloat *distmap;
|
|
|
|
|
|
|
|
/* Used in the closing step. */
|
|
|
|
gboolean select_transparent;
|
|
|
|
gdouble threshold;
|
|
|
|
gint spline_max_len;
|
|
|
|
gint segment_max_len;
|
2019-03-04 23:43:19 +08:00
|
|
|
gboolean max_len_bound;
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
/* Used in the grow step. */
|
|
|
|
gint max_grow;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
GeglBuffer *buffer;
|
|
|
|
|
|
|
|
gboolean select_transparent;
|
|
|
|
gdouble threshold;
|
|
|
|
gint spline_max_len;
|
|
|
|
gint segment_max_len;
|
|
|
|
} LineArtData;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
GeglBuffer *closed;
|
|
|
|
gfloat *distmap;
|
|
|
|
} LineArtResult;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
static int DeltaX[4] = {+1, -1, 0, 0};
|
|
|
|
static int DeltaY[4] = {0, 0, +1, -1};
|
|
|
|
|
|
|
|
static const GimpVector2 Direction2Normal[4] =
|
|
|
|
{
|
|
|
|
{ 1.0f, 0.0f },
|
|
|
|
{ -1.0f, 0.0f },
|
|
|
|
{ 0.0f, 1.0f },
|
|
|
|
{ 0.0f, -1.0f }
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef enum _Direction
|
|
|
|
{
|
|
|
|
XPlusDirection = 0,
|
|
|
|
XMinusDirection = 1,
|
|
|
|
YPlusDirection = 2,
|
|
|
|
YMinusDirection = 3
|
|
|
|
} Direction;
|
|
|
|
|
|
|
|
typedef GimpVector2 Pixel;
|
|
|
|
|
|
|
|
typedef struct _SplineCandidate
|
|
|
|
{
|
|
|
|
Pixel p1;
|
|
|
|
Pixel p2;
|
|
|
|
float quality;
|
|
|
|
} SplineCandidate;
|
|
|
|
|
|
|
|
typedef struct _Edgel
|
|
|
|
{
|
|
|
|
gint x, y;
|
|
|
|
Direction direction;
|
|
|
|
|
|
|
|
gfloat x_normal;
|
|
|
|
gfloat y_normal;
|
|
|
|
gfloat curvature;
|
|
|
|
guint next, previous;
|
|
|
|
} Edgel;
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
static void gimp_line_art_finalize (GObject *object);
|
|
|
|
static void gimp_line_art_set_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
const GValue *value,
|
|
|
|
GParamSpec *pspec);
|
|
|
|
static void gimp_line_art_get_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
GValue *value,
|
|
|
|
GParamSpec *pspec);
|
|
|
|
|
|
|
|
/* Functions for asynchronous computation. */
|
|
|
|
|
|
|
|
static void gimp_line_art_compute (GimpLineArt *line_art);
|
|
|
|
static void gimp_line_art_compute_cb (GimpAsync *async,
|
|
|
|
GimpLineArt *line_art);
|
|
|
|
|
|
|
|
static GimpAsync * gimp_line_art_prepare_async (GimpLineArt *line_art,
|
|
|
|
gint priority);
|
|
|
|
static void gimp_line_art_prepare_async_func (GimpAsync *async,
|
|
|
|
LineArtData *data);
|
|
|
|
static LineArtData * line_art_data_new (GeglBuffer *buffer,
|
|
|
|
GimpLineArt *line_art);
|
|
|
|
static void line_art_data_free (LineArtData *data);
|
|
|
|
static LineArtResult * line_art_result_new (GeglBuffer *line_art,
|
|
|
|
gfloat *distmap);
|
|
|
|
static void line_art_result_free (LineArtResult *result);
|
|
|
|
|
2018-12-28 06:07:46 +08:00
|
|
|
static gboolean gimp_line_art_idle (GimpLineArt *line_art);
|
|
|
|
static void gimp_line_art_input_invalidate_preview (GimpViewable *viewable,
|
2018-12-02 01:33:51 +08:00
|
|
|
GimpLineArt *line_art);
|
|
|
|
|
|
|
|
|
|
|
|
/* All actual computation functions. */
|
|
|
|
|
|
|
|
static GeglBuffer * gimp_line_art_close (GeglBuffer *buffer,
|
|
|
|
gboolean select_transparent,
|
|
|
|
gdouble stroke_threshold,
|
|
|
|
gint spline_max_length,
|
|
|
|
gint segment_max_length,
|
|
|
|
gint minimal_lineart_area,
|
|
|
|
gint normal_estimate_mask_size,
|
|
|
|
gfloat end_point_rate,
|
|
|
|
gfloat spline_max_angle,
|
|
|
|
gint end_point_connectivity,
|
|
|
|
gfloat spline_roundness,
|
|
|
|
gboolean allow_self_intersections,
|
|
|
|
gint created_regions_significant_area,
|
|
|
|
gint created_regions_minimum_area,
|
|
|
|
gboolean small_segments_from_spline_sources,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gfloat **lineart_distmap,
|
|
|
|
GimpAsync *async);
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
static void gimp_lineart_denoise (GeglBuffer *buffer,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
int size,
|
|
|
|
GimpAsync *async);
|
2018-12-02 01:33:51 +08:00
|
|
|
static void gimp_lineart_compute_normals_curvatures (GeglBuffer *mask,
|
|
|
|
gfloat *normals,
|
|
|
|
gfloat *curvatures,
|
|
|
|
gfloat *smoothed_curvatures,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
int normal_estimate_mask_size,
|
|
|
|
GimpAsync *async);
|
|
|
|
static gfloat * gimp_lineart_get_smooth_curvatures (GArray *edgelset,
|
|
|
|
GimpAsync *async);
|
2018-12-02 01:33:51 +08:00
|
|
|
static GArray * gimp_lineart_curvature_extremums (gfloat *curvatures,
|
|
|
|
gfloat *smoothed_curvatures,
|
|
|
|
gint curvatures_width,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gint curvatures_height,
|
|
|
|
GimpAsync *async);
|
2018-12-02 01:33:51 +08:00
|
|
|
static gint gimp_spline_candidate_cmp (const SplineCandidate *a,
|
|
|
|
const SplineCandidate *b,
|
|
|
|
gpointer user_data);
|
|
|
|
static GList * gimp_lineart_find_spline_candidates (GArray *max_positions,
|
|
|
|
gfloat *normals,
|
|
|
|
gint width,
|
|
|
|
gint distance_threshold,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gfloat max_angle_deg,
|
|
|
|
GimpAsync *async);
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
static GArray * gimp_lineart_discrete_spline (Pixel p0,
|
|
|
|
GimpVector2 n0,
|
|
|
|
Pixel p1,
|
|
|
|
GimpVector2 n1);
|
|
|
|
|
|
|
|
static gint gimp_number_of_transitions (GArray *pixels,
|
|
|
|
GeglBuffer *buffer);
|
2019-02-10 06:40:47 +08:00
|
|
|
static gboolean gimp_line_art_allow_closure (GeglBuffer *mask,
|
2018-12-02 01:33:51 +08:00
|
|
|
GArray *pixels,
|
2019-02-08 00:44:35 +08:00
|
|
|
GList **fill_pixels,
|
2019-02-10 06:40:47 +08:00
|
|
|
int significant_size,
|
|
|
|
int minimum_size);
|
2018-12-02 01:33:51 +08:00
|
|
|
static GArray * gimp_lineart_line_segment_until_hit (const GeglBuffer *buffer,
|
|
|
|
Pixel start,
|
|
|
|
GimpVector2 direction,
|
|
|
|
int size);
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
static gfloat * gimp_lineart_estimate_strokes_radii (GeglBuffer *mask,
|
|
|
|
GimpAsync *async);
|
2019-02-08 00:44:35 +08:00
|
|
|
static void gimp_line_art_simple_fill (GeglBuffer *buffer,
|
|
|
|
gint x,
|
2019-02-16 19:27:32 +08:00
|
|
|
gint y,
|
|
|
|
gint *counter);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
/* Some callback-type functions. */
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
static guint visited_hash_fun (Pixel *key);
|
|
|
|
static gboolean visited_equal_fun (Pixel *e1,
|
|
|
|
Pixel *e2);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
static inline gboolean border_in_direction (GeglBuffer *mask,
|
|
|
|
Pixel p,
|
|
|
|
int direction);
|
|
|
|
static inline GimpVector2 pair2normal (Pixel p,
|
|
|
|
gfloat *normals,
|
|
|
|
gint width);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
/* Edgel */
|
|
|
|
|
|
|
|
static Edgel * gimp_edgel_new (int x,
|
|
|
|
int y,
|
|
|
|
Direction direction);
|
|
|
|
static void gimp_edgel_init (Edgel *edgel);
|
|
|
|
static void gimp_edgel_clear (Edgel **edgel);
|
|
|
|
static int gimp_edgel_cmp (const Edgel *e1,
|
|
|
|
const Edgel *e2);
|
|
|
|
static guint edgel2index_hash_fun (Edgel *key);
|
|
|
|
static gboolean edgel2index_equal_fun (Edgel *e1,
|
|
|
|
Edgel *e2);
|
|
|
|
|
|
|
|
static glong gimp_edgel_track_mark (GeglBuffer *mask,
|
|
|
|
Edgel edgel,
|
|
|
|
long size_limit);
|
|
|
|
static glong gimp_edgel_region_area (const GeglBuffer *mask,
|
2019-02-10 06:13:58 +08:00
|
|
|
Edgel start_edgel);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
/* Edgel set */
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
static GArray * gimp_edgelset_new (GeglBuffer *buffer,
|
|
|
|
GimpAsync *async);
|
2018-10-11 02:28:47 +08:00
|
|
|
static void gimp_edgelset_add (GArray *set,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
Direction direction,
|
|
|
|
GHashTable *edgel2index);
|
|
|
|
static void gimp_edgelset_init_normals (GArray *set);
|
|
|
|
static void gimp_edgelset_smooth_normals (GArray *set,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
int mask_size,
|
|
|
|
GimpAsync *async);
|
|
|
|
static void gimp_edgelset_compute_curvature (GArray *set,
|
|
|
|
GimpAsync *async);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
static void gimp_edgelset_build_graph (GArray *set,
|
|
|
|
GeglBuffer *buffer,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GHashTable *edgel2index,
|
|
|
|
GimpAsync *async);
|
2018-10-11 02:28:47 +08:00
|
|
|
static void gimp_edgelset_next8 (const GeglBuffer *buffer,
|
|
|
|
Edgel *it,
|
|
|
|
Edgel *n);
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
G_DEFINE_TYPE_WITH_CODE (GimpLineArt, gimp_line_art, GIMP_TYPE_OBJECT,
|
|
|
|
G_ADD_PRIVATE (GimpLineArt))
|
|
|
|
|
2019-03-04 23:06:59 +08:00
|
|
|
static guint gimp_line_art_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
static void
|
|
|
|
gimp_line_art_class_init (GimpLineArtClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
|
2019-03-04 23:06:59 +08:00
|
|
|
gimp_line_art_signals[COMPUTING_START] =
|
|
|
|
g_signal_new ("computing-start",
|
|
|
|
G_TYPE_FROM_CLASS (klass),
|
|
|
|
G_SIGNAL_RUN_FIRST,
|
|
|
|
G_STRUCT_OFFSET (GimpLineArtClass, computing_start),
|
2020-01-12 18:06:05 +08:00
|
|
|
NULL, NULL, NULL,
|
2019-03-04 23:06:59 +08:00
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
gimp_line_art_signals[COMPUTING_END] =
|
|
|
|
g_signal_new ("computing-end",
|
|
|
|
G_TYPE_FROM_CLASS (klass),
|
|
|
|
G_SIGNAL_RUN_FIRST,
|
|
|
|
G_STRUCT_OFFSET (GimpLineArtClass, computing_end),
|
2020-01-12 18:06:05 +08:00
|
|
|
NULL, NULL, NULL,
|
2019-03-04 23:06:59 +08:00
|
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
object_class->finalize = gimp_line_art_finalize;
|
|
|
|
object_class->set_property = gimp_line_art_set_property;
|
|
|
|
object_class->get_property = gimp_line_art_get_property;
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_SELECT_TRANSPARENT,
|
|
|
|
g_param_spec_boolean ("select-transparent",
|
|
|
|
_("Select transparent pixels instead of gray ones"),
|
|
|
|
_("Select transparent pixels instead of gray ones"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_THRESHOLD,
|
|
|
|
g_param_spec_double ("threshold",
|
|
|
|
_("Line art detection threshold"),
|
|
|
|
_("Threshold to detect contour (higher values will include more pixels)"),
|
|
|
|
0.0, 1.0, 0.92,
|
|
|
|
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_MAX_GROW,
|
|
|
|
g_param_spec_int ("max-grow",
|
|
|
|
_("Maximum growing size"),
|
|
|
|
_("Maximum number of pixels grown under the line art"),
|
|
|
|
1, 100, 3,
|
|
|
|
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_SPLINE_MAX_LEN,
|
|
|
|
g_param_spec_int ("spline-max-length",
|
|
|
|
_("Maximum curved closing length"),
|
|
|
|
_("Maximum curved length (in pixels) to close the line art"),
|
2018-12-24 20:21:12 +08:00
|
|
|
0, 1000, 100,
|
2018-12-02 01:33:51 +08:00
|
|
|
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
|
|
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_SEGMENT_MAX_LEN,
|
|
|
|
g_param_spec_int ("segment-max-length",
|
|
|
|
_("Maximum straight closing length"),
|
|
|
|
_("Maximum straight length (in pixels) to close the line art"),
|
2018-12-24 20:21:12 +08:00
|
|
|
0, 1000, 100,
|
2018-12-02 01:33:51 +08:00
|
|
|
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_init (GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
line_art->priv = gimp_line_art_get_instance_private (line_art);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_finalize (GObject *object)
|
|
|
|
{
|
|
|
|
GimpLineArt *line_art = GIMP_LINE_ART (object);
|
|
|
|
|
2018-12-28 06:07:46 +08:00
|
|
|
line_art->priv->frozen = FALSE;
|
2018-12-02 01:33:51 +08:00
|
|
|
|
2018-12-28 06:07:46 +08:00
|
|
|
gimp_line_art_set_input (line_art, NULL);
|
2018-12-02 01:33:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_set_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
const GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GimpLineArt *line_art = GIMP_LINE_ART (object);
|
|
|
|
|
|
|
|
switch (property_id)
|
|
|
|
{
|
|
|
|
case PROP_SELECT_TRANSPARENT:
|
2019-03-04 23:43:19 +08:00
|
|
|
if (line_art->priv->select_transparent != g_value_get_boolean (value))
|
|
|
|
{
|
|
|
|
line_art->priv->select_transparent = g_value_get_boolean (value);
|
|
|
|
gimp_line_art_compute (line_art);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
break;
|
|
|
|
case PROP_MAX_GROW:
|
|
|
|
line_art->priv->max_grow = g_value_get_int (value);
|
|
|
|
break;
|
|
|
|
case PROP_THRESHOLD:
|
2019-03-04 23:43:19 +08:00
|
|
|
if (line_art->priv->threshold != g_value_get_double (value))
|
|
|
|
{
|
|
|
|
line_art->priv->threshold = g_value_get_double (value);
|
|
|
|
gimp_line_art_compute (line_art);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
break;
|
|
|
|
case PROP_SPLINE_MAX_LEN:
|
2019-03-04 23:43:19 +08:00
|
|
|
if (line_art->priv->spline_max_len != g_value_get_int (value))
|
|
|
|
{
|
|
|
|
line_art->priv->spline_max_len = g_value_get_int (value);
|
|
|
|
if (line_art->priv->max_len_bound)
|
|
|
|
line_art->priv->segment_max_len = line_art->priv->spline_max_len;
|
|
|
|
gimp_line_art_compute (line_art);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
break;
|
|
|
|
case PROP_SEGMENT_MAX_LEN:
|
2019-03-04 23:43:19 +08:00
|
|
|
if (line_art->priv->segment_max_len != g_value_get_int (value))
|
|
|
|
{
|
|
|
|
line_art->priv->segment_max_len = g_value_get_int (value);
|
|
|
|
if (line_art->priv->max_len_bound)
|
|
|
|
line_art->priv->spline_max_len = line_art->priv->segment_max_len;
|
|
|
|
gimp_line_art_compute (line_art);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_get_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GimpLineArt *line_art = GIMP_LINE_ART (object);
|
|
|
|
|
|
|
|
switch (property_id)
|
|
|
|
{
|
|
|
|
case PROP_SELECT_TRANSPARENT:
|
|
|
|
g_value_set_boolean (value, line_art->priv->select_transparent);
|
|
|
|
break;
|
|
|
|
case PROP_MAX_GROW:
|
|
|
|
g_value_set_int (value, line_art->priv->max_grow);
|
|
|
|
break;
|
|
|
|
case PROP_THRESHOLD:
|
|
|
|
g_value_set_double (value, line_art->priv->threshold);
|
|
|
|
break;
|
|
|
|
case PROP_SPLINE_MAX_LEN:
|
|
|
|
g_value_set_int (value, line_art->priv->spline_max_len);
|
|
|
|
break;
|
|
|
|
case PROP_SEGMENT_MAX_LEN:
|
|
|
|
g_value_set_int (value, line_art->priv->segment_max_len);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
/* Public functions */
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
GimpLineArt *
|
|
|
|
gimp_line_art_new (void)
|
|
|
|
{
|
|
|
|
return g_object_new (GIMP_TYPE_LINE_ART,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
|
2019-03-04 23:43:19 +08:00
|
|
|
void
|
|
|
|
gimp_line_art_bind_gap_length (GimpLineArt *line_art,
|
|
|
|
gboolean bound)
|
|
|
|
{
|
|
|
|
line_art->priv->max_len_bound = bound;
|
|
|
|
}
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
void
|
|
|
|
gimp_line_art_set_input (GimpLineArt *line_art,
|
|
|
|
GimpPickable *pickable)
|
|
|
|
{
|
2018-12-28 06:07:46 +08:00
|
|
|
g_return_if_fail (pickable == NULL || GIMP_IS_VIEWABLE (pickable));
|
|
|
|
|
2019-01-07 05:26:35 +08:00
|
|
|
if (pickable != line_art->priv->input)
|
|
|
|
{
|
|
|
|
if (line_art->priv->input)
|
|
|
|
g_signal_handlers_disconnect_by_data (line_art->priv->input, line_art);
|
2018-12-02 01:33:51 +08:00
|
|
|
|
2019-11-04 18:51:09 +08:00
|
|
|
g_set_object (&line_art->priv->input, pickable);
|
2018-12-02 01:33:51 +08:00
|
|
|
|
2019-01-07 05:26:35 +08:00
|
|
|
gimp_line_art_compute (line_art);
|
2018-12-28 06:07:46 +08:00
|
|
|
|
2019-01-07 05:26:35 +08:00
|
|
|
if (pickable)
|
|
|
|
{
|
|
|
|
g_signal_connect (pickable, "invalidate-preview",
|
|
|
|
G_CALLBACK (gimp_line_art_input_invalidate_preview),
|
|
|
|
line_art);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 23:06:50 +08:00
|
|
|
GimpPickable *
|
|
|
|
gimp_line_art_get_input (GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
return line_art->priv->input;
|
|
|
|
}
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
void
|
|
|
|
gimp_line_art_freeze (GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
g_return_if_fail (! line_art->priv->frozen);
|
|
|
|
|
|
|
|
line_art->priv->frozen = TRUE;
|
|
|
|
line_art->priv->compute_after_thaw = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gimp_line_art_thaw (GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
g_return_if_fail (line_art->priv->frozen);
|
|
|
|
|
|
|
|
line_art->priv->frozen = FALSE;
|
|
|
|
if (line_art->priv->compute_after_thaw)
|
|
|
|
{
|
|
|
|
gimp_line_art_compute (line_art);
|
|
|
|
line_art->priv->compute_after_thaw = FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 18:41:49 +08:00
|
|
|
gboolean
|
|
|
|
gimp_line_art_is_frozen (GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
return line_art->priv->frozen;
|
|
|
|
}
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
GeglBuffer *
|
|
|
|
gimp_line_art_get (GimpLineArt *line_art,
|
|
|
|
gfloat **distmap)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (line_art->priv->input, NULL);
|
|
|
|
|
|
|
|
if (line_art->priv->async)
|
|
|
|
{
|
|
|
|
gimp_waitable_wait (GIMP_WAITABLE (line_art->priv->async));
|
|
|
|
}
|
|
|
|
else if (! line_art->priv->closed)
|
|
|
|
{
|
|
|
|
gimp_line_art_compute (line_art);
|
|
|
|
if (line_art->priv->async)
|
|
|
|
gimp_waitable_wait (GIMP_WAITABLE (line_art->priv->async));
|
|
|
|
}
|
|
|
|
|
|
|
|
g_return_val_if_fail (line_art->priv->closed, NULL);
|
|
|
|
|
|
|
|
if (distmap)
|
|
|
|
*distmap = line_art->priv->distmap;
|
|
|
|
|
|
|
|
return line_art->priv->closed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Functions for asynchronous computation. */
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_compute (GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
if (line_art->priv->frozen)
|
|
|
|
{
|
|
|
|
line_art->priv->compute_after_thaw = TRUE;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (line_art->priv->async)
|
|
|
|
{
|
2018-12-28 06:07:46 +08:00
|
|
|
/* we cancel the async, but don't wait for it to finish, since
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
* it might take a while to respond. instead gimp_line_art_compute_cb()
|
2018-12-28 06:07:46 +08:00
|
|
|
* bails if the async has been canceled, to avoid accessing the line art.
|
|
|
|
*/
|
2019-03-04 23:55:51 +08:00
|
|
|
g_signal_emit (line_art, gimp_line_art_signals[COMPUTING_END], 0);
|
2018-12-02 01:33:51 +08:00
|
|
|
gimp_cancelable_cancel (GIMP_CANCELABLE (line_art->priv->async));
|
|
|
|
g_clear_object (&line_art->priv->async);
|
|
|
|
}
|
|
|
|
|
2018-12-28 06:07:46 +08:00
|
|
|
if (line_art->priv->idle_id)
|
|
|
|
{
|
|
|
|
g_source_remove (line_art->priv->idle_id);
|
|
|
|
line_art->priv->idle_id = 0;
|
|
|
|
}
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
g_clear_object (&line_art->priv->closed);
|
|
|
|
g_clear_pointer (&line_art->priv->distmap, g_free);
|
|
|
|
|
|
|
|
if (line_art->priv->input)
|
|
|
|
{
|
|
|
|
/* gimp_line_art_prepare_async() will flush the pickable, which
|
|
|
|
* may trigger this signal handler, and will leak a line art (as
|
|
|
|
* line_art->priv->async has not been set yet).
|
|
|
|
*/
|
2018-12-28 06:07:46 +08:00
|
|
|
g_signal_handlers_block_by_func (
|
|
|
|
line_art->priv->input,
|
|
|
|
G_CALLBACK (gimp_line_art_input_invalidate_preview),
|
|
|
|
line_art);
|
2018-12-02 01:33:51 +08:00
|
|
|
line_art->priv->async = gimp_line_art_prepare_async (line_art, +1);
|
2019-03-04 23:06:59 +08:00
|
|
|
g_signal_emit (line_art, gimp_line_art_signals[COMPUTING_START], 0);
|
2018-12-28 06:07:46 +08:00
|
|
|
g_signal_handlers_unblock_by_func (
|
|
|
|
line_art->priv->input,
|
|
|
|
G_CALLBACK (gimp_line_art_input_invalidate_preview),
|
|
|
|
line_art);
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
gimp_async_add_callback_for_object (line_art->priv->async,
|
|
|
|
(GimpAsyncCallback) gimp_line_art_compute_cb,
|
|
|
|
line_art, line_art);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_compute_cb (GimpAsync *async,
|
|
|
|
GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (gimp_async_is_finished (async))
|
|
|
|
{
|
|
|
|
LineArtResult *result;
|
|
|
|
|
|
|
|
result = gimp_async_get_result (async);
|
|
|
|
|
|
|
|
line_art->priv->closed = g_object_ref (result->closed);
|
|
|
|
line_art->priv->distmap = result->distmap;
|
|
|
|
result->distmap = NULL;
|
2019-03-04 23:55:51 +08:00
|
|
|
g_signal_emit (line_art, gimp_line_art_signals[COMPUTING_END], 0);
|
2018-12-02 01:33:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
g_clear_object (&line_art->priv->async);
|
|
|
|
}
|
|
|
|
|
|
|
|
static GimpAsync *
|
|
|
|
gimp_line_art_prepare_async (GimpLineArt *line_art,
|
|
|
|
gint priority)
|
|
|
|
{
|
|
|
|
GeglBuffer *buffer;
|
|
|
|
GimpAsync *async;
|
|
|
|
LineArtData *data;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GIMP_IS_PICKABLE (line_art->priv->input), NULL);
|
|
|
|
|
|
|
|
gimp_pickable_flush (line_art->priv->input);
|
|
|
|
|
2019-07-31 01:25:54 +08:00
|
|
|
buffer = gimp_gegl_buffer_dup (
|
|
|
|
gimp_pickable_get_buffer (line_art->priv->input));
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
data = line_art_data_new (buffer, line_art);
|
|
|
|
|
|
|
|
g_object_unref (buffer);
|
|
|
|
|
|
|
|
async = gimp_parallel_run_async_full (
|
|
|
|
priority,
|
2020-03-14 06:10:20 +08:00
|
|
|
(GimpRunAsyncFunc) gimp_line_art_prepare_async_func,
|
2018-12-02 01:33:51 +08:00
|
|
|
data, (GDestroyNotify) line_art_data_free);
|
|
|
|
|
|
|
|
return async;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_line_art_prepare_async_func (GimpAsync *async,
|
|
|
|
LineArtData *data)
|
|
|
|
{
|
2019-11-04 18:46:42 +08:00
|
|
|
GeglBuffer *buffer;
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GeglBuffer *closed = NULL;
|
|
|
|
gfloat *distmap = NULL;
|
2019-11-04 18:46:42 +08:00
|
|
|
gint buffer_x;
|
|
|
|
gint buffer_y;
|
2018-12-02 01:33:51 +08:00
|
|
|
gboolean has_alpha;
|
|
|
|
gboolean select_transparent = FALSE;
|
|
|
|
|
|
|
|
has_alpha = babl_format_has_alpha (gegl_buffer_get_format (data->buffer));
|
|
|
|
|
|
|
|
if (has_alpha)
|
|
|
|
{
|
|
|
|
if (data->select_transparent)
|
|
|
|
{
|
|
|
|
/* don't select transparent regions if there are no fully
|
|
|
|
* transparent pixels.
|
|
|
|
*/
|
|
|
|
GeglBufferIterator *gi;
|
|
|
|
|
|
|
|
gi = gegl_buffer_iterator_new (data->buffer, NULL, 0,
|
|
|
|
babl_format ("A u8"),
|
|
|
|
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
|
|
|
|
while (gegl_buffer_iterator_next (gi))
|
|
|
|
{
|
|
|
|
guint8 *p = (guint8*) gi->items[0].data;
|
|
|
|
gint k;
|
|
|
|
|
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gegl_buffer_iterator_stop (gi);
|
|
|
|
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
line_art_data_free (data);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (k = 0; k < gi->length; k++)
|
|
|
|
{
|
|
|
|
if (! *p)
|
|
|
|
{
|
|
|
|
select_transparent = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
if (select_transparent)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (select_transparent)
|
|
|
|
gegl_buffer_iterator_stop (gi);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 18:46:42 +08:00
|
|
|
buffer = data->buffer;
|
|
|
|
buffer_x = gegl_buffer_get_x (data->buffer);
|
|
|
|
buffer_y = gegl_buffer_get_y (data->buffer);
|
|
|
|
|
|
|
|
if (buffer_x != 0 || buffer_y != 0)
|
|
|
|
{
|
|
|
|
buffer = g_object_new (GEGL_TYPE_BUFFER,
|
|
|
|
"source", buffer,
|
|
|
|
"shift-x", buffer_x,
|
|
|
|
"shift-y", buffer_y,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
/* For smart selection, we generate a binarized image with close
|
|
|
|
* regions, then run a composite selection with no threshold on
|
|
|
|
* this intermediate buffer.
|
|
|
|
*/
|
|
|
|
GIMP_TIMER_START();
|
|
|
|
|
2019-11-04 18:46:42 +08:00
|
|
|
closed = gimp_line_art_close (buffer,
|
2018-12-02 01:33:51 +08:00
|
|
|
select_transparent,
|
|
|
|
data->threshold,
|
|
|
|
data->spline_max_len,
|
|
|
|
data->segment_max_len,
|
|
|
|
/*minimal_lineart_area,*/
|
|
|
|
5,
|
|
|
|
/*normal_estimate_mask_size,*/
|
|
|
|
5,
|
|
|
|
/*end_point_rate,*/
|
|
|
|
0.85,
|
|
|
|
/*spline_max_angle,*/
|
|
|
|
90.0,
|
|
|
|
/*end_point_connectivity,*/
|
|
|
|
2,
|
|
|
|
/*spline_roundness,*/
|
|
|
|
1.0,
|
|
|
|
/*allow_self_intersections,*/
|
|
|
|
TRUE,
|
|
|
|
/*created_regions_significant_area,*/
|
|
|
|
4,
|
|
|
|
/*created_regions_minimum_area,*/
|
|
|
|
100,
|
|
|
|
/*small_segments_from_spline_sources,*/
|
|
|
|
TRUE,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
&distmap,
|
|
|
|
async);
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
GIMP_TIMER_END("close line-art");
|
|
|
|
|
2019-11-04 18:46:42 +08:00
|
|
|
if (buffer != data->buffer)
|
|
|
|
g_object_unref (buffer);
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (! gimp_async_is_stopped (async))
|
|
|
|
{
|
2019-11-04 18:46:42 +08:00
|
|
|
if (buffer_x != 0 || buffer_y != 0)
|
|
|
|
{
|
|
|
|
buffer = g_object_new (GEGL_TYPE_BUFFER,
|
|
|
|
"source", closed,
|
|
|
|
"shift-x", -buffer_x,
|
|
|
|
"shift-y", -buffer_y,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
g_object_unref (closed);
|
|
|
|
|
|
|
|
closed = buffer;
|
|
|
|
}
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_async_finish_full (async,
|
|
|
|
line_art_result_new (closed, distmap),
|
|
|
|
(GDestroyNotify) line_art_result_free);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
|
|
|
|
line_art_data_free (data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static LineArtData *
|
|
|
|
line_art_data_new (GeglBuffer *buffer,
|
|
|
|
GimpLineArt *line_art)
|
|
|
|
{
|
|
|
|
LineArtData *data = g_slice_new (LineArtData);
|
|
|
|
|
|
|
|
data->buffer = g_object_ref (buffer);
|
|
|
|
data->select_transparent = line_art->priv->select_transparent;
|
|
|
|
data->threshold = line_art->priv->threshold;
|
|
|
|
data->spline_max_len = line_art->priv->spline_max_len;
|
|
|
|
data->segment_max_len = line_art->priv->segment_max_len;
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
line_art_data_free (LineArtData *data)
|
|
|
|
{
|
|
|
|
g_object_unref (data->buffer);
|
|
|
|
|
|
|
|
g_slice_free (LineArtData, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static LineArtResult *
|
|
|
|
line_art_result_new (GeglBuffer *closed,
|
|
|
|
gfloat *distmap)
|
|
|
|
{
|
|
|
|
LineArtResult *data;
|
|
|
|
|
|
|
|
data = g_slice_new (LineArtResult);
|
|
|
|
data->closed = closed;
|
|
|
|
data->distmap = distmap;
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
line_art_result_free (LineArtResult *data)
|
|
|
|
{
|
|
|
|
g_object_unref (data->closed);
|
|
|
|
g_clear_pointer (&data->distmap, g_free);
|
|
|
|
|
|
|
|
g_slice_free (LineArtResult, data);
|
|
|
|
}
|
|
|
|
|
2018-12-28 06:07:46 +08:00
|
|
|
static gboolean
|
|
|
|
gimp_line_art_idle (GimpLineArt *line_art)
|
2018-12-02 01:33:51 +08:00
|
|
|
{
|
2018-12-28 06:07:46 +08:00
|
|
|
line_art->priv->idle_id = 0;
|
|
|
|
|
2018-12-02 01:33:51 +08:00
|
|
|
gimp_line_art_compute (line_art);
|
2018-12-28 06:07:46 +08:00
|
|
|
|
|
|
|
return G_SOURCE_REMOVE;
|
2018-12-02 01:33:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-12-28 06:07:46 +08:00
|
|
|
gimp_line_art_input_invalidate_preview (GimpViewable *viewable,
|
|
|
|
GimpLineArt *line_art)
|
2018-12-02 01:33:51 +08:00
|
|
|
{
|
2018-12-28 06:07:46 +08:00
|
|
|
if (! line_art->priv->idle_id)
|
|
|
|
{
|
|
|
|
line_art->priv->idle_id = g_idle_add_full (
|
|
|
|
GIMP_PRIORITY_VIEWABLE_IDLE,
|
|
|
|
(GSourceFunc) gimp_line_art_idle,
|
|
|
|
line_art, NULL);
|
|
|
|
}
|
2018-12-02 01:33:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* All actual computation functions. */
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
/**
|
2018-12-02 01:33:51 +08:00
|
|
|
* gimp_line_art_close:
|
2018-11-21 22:52:51 +08:00
|
|
|
* @buffer: the input #GeglBuffer.
|
2018-10-11 02:28:47 +08:00
|
|
|
* @select_transparent: whether we binarize the alpha channel or the
|
|
|
|
* luminosity.
|
|
|
|
* @stroke_threshold: [0-1] threshold value for detecting stroke pixels
|
|
|
|
* (higher values will detect more stroke pixels).
|
2018-12-02 01:33:51 +08:00
|
|
|
* @spline_max_length: the maximum length for creating splines between
|
|
|
|
* end points.
|
|
|
|
* @segment_max_length: the maximum length for creating segments
|
|
|
|
* between end points. Unlike splines, segments
|
|
|
|
* are straight lines.
|
2018-11-07 21:22:10 +08:00
|
|
|
* @minimal_lineart_area: the minimum size in number pixels for area to
|
|
|
|
* be considered as line art.
|
2018-10-11 02:28:47 +08:00
|
|
|
* @normal_estimate_mask_size:
|
2018-11-07 21:22:10 +08:00
|
|
|
* @end_point_rate: threshold to estimate if a curvature is an end-point
|
|
|
|
* in [0-1] range value.
|
|
|
|
* @spline_max_angle: the maximum angle between end point normals for
|
|
|
|
* creating splines between them.
|
2018-10-11 02:28:47 +08:00
|
|
|
* @end_point_connectivity:
|
|
|
|
* @spline_roundness:
|
2018-11-07 21:22:10 +08:00
|
|
|
* @allow_self_intersections: whether to allow created splines and
|
|
|
|
* segments to intersect.
|
2018-10-11 02:28:47 +08:00
|
|
|
* @created_regions_significant_area:
|
|
|
|
* @created_regions_minimum_area:
|
|
|
|
* @small_segments_from_spline_sources:
|
2018-11-22 21:32:59 +08:00
|
|
|
* @closed_distmap: a distance map of the closed line art pixels.
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
* @async: the #GimpAsync associated with the computation
|
2018-10-11 02:28:47 +08:00
|
|
|
*
|
2018-11-21 22:52:51 +08:00
|
|
|
* Creates a binarized version of the strokes of @buffer, detected either
|
2018-10-11 02:28:47 +08:00
|
|
|
* with luminosity (light means background) or alpha values depending on
|
|
|
|
* @select_transparent. This binary version of the strokes will have closed
|
|
|
|
* regions allowing adequate selection of "nearly closed regions".
|
|
|
|
* This algorithm is meant for digital painting (and in particular on the
|
|
|
|
* sketch-only step), and therefore will likely produce unexpected results on
|
|
|
|
* other types of input.
|
|
|
|
*
|
|
|
|
* The algorithm is the first step from the research paper "A Fast and
|
|
|
|
* Efficient Semi-guided Algorithm for Flat Coloring Line-arts", by Sébastian
|
|
|
|
* Fourey, David Tschumperlé, David Revoy.
|
2019-01-12 07:26:19 +08:00
|
|
|
* https://hal.archives-ouvertes.fr/hal-01891876
|
2018-10-11 02:28:47 +08:00
|
|
|
*
|
|
|
|
* Returns: a new #GeglBuffer of format "Y u8" representing the
|
2019-08-03 06:04:28 +08:00
|
|
|
* binarized @line_art. If @lineart_distmap is not %NULL, a
|
2018-11-27 21:59:35 +08:00
|
|
|
* newly allocated float buffer is returned, which can be used
|
|
|
|
* for overflowing created masks later.
|
2018-10-11 02:28:47 +08:00
|
|
|
*/
|
2018-12-02 01:33:51 +08:00
|
|
|
static GeglBuffer *
|
|
|
|
gimp_line_art_close (GeglBuffer *buffer,
|
|
|
|
gboolean select_transparent,
|
|
|
|
gdouble stroke_threshold,
|
|
|
|
gint spline_max_length,
|
|
|
|
gint segment_max_length,
|
|
|
|
gint minimal_lineart_area,
|
|
|
|
gint normal_estimate_mask_size,
|
|
|
|
gfloat end_point_rate,
|
|
|
|
gfloat spline_max_angle,
|
|
|
|
gint end_point_connectivity,
|
|
|
|
gfloat spline_roundness,
|
|
|
|
gboolean allow_self_intersections,
|
|
|
|
gint created_regions_significant_area,
|
|
|
|
gint created_regions_minimum_area,
|
|
|
|
gboolean small_segments_from_spline_sources,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gfloat **closed_distmap,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-10-17 21:53:03 +08:00
|
|
|
const Babl *gray_format;
|
|
|
|
GeglBufferIterator *gi;
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GeglBuffer *closed = NULL;
|
|
|
|
GeglBuffer *strokes = NULL;
|
2018-10-17 21:53:03 +08:00
|
|
|
guchar max_value = 0;
|
2018-11-21 22:52:51 +08:00
|
|
|
gint width = gegl_buffer_get_width (buffer);
|
|
|
|
gint height = gegl_buffer_get_height (buffer);
|
2018-10-17 21:53:03 +08:00
|
|
|
gint i;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
if (select_transparent)
|
|
|
|
/* Keep alpha channel as gray levels */
|
|
|
|
gray_format = babl_format ("A u8");
|
|
|
|
else
|
|
|
|
/* Keep luminance */
|
|
|
|
gray_format = babl_format ("Y' u8");
|
|
|
|
|
|
|
|
/* Transform the line art from any format to gray. */
|
2018-11-21 22:52:51 +08:00
|
|
|
strokes = gegl_buffer_new (gegl_buffer_get_extent (buffer),
|
2018-10-11 02:28:47 +08:00
|
|
|
gray_format);
|
2019-03-06 17:50:03 +08:00
|
|
|
gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, strokes, NULL);
|
2018-10-11 02:28:47 +08:00
|
|
|
gegl_buffer_set_format (strokes, babl_format ("Y' u8"));
|
|
|
|
|
|
|
|
if (! select_transparent)
|
|
|
|
{
|
|
|
|
/* Compute the biggest value */
|
|
|
|
gi = gegl_buffer_iterator_new (strokes, NULL, 0, NULL,
|
|
|
|
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
|
|
|
|
while (gegl_buffer_iterator_next (gi))
|
|
|
|
{
|
|
|
|
guchar *data = (guchar*) gi->items[0].data;
|
|
|
|
gint k;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gegl_buffer_iterator_stop (gi);
|
|
|
|
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end1;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
for (k = 0; k < gi->length; k++)
|
|
|
|
{
|
|
|
|
if (*data > max_value)
|
|
|
|
max_value = *data;
|
|
|
|
data++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make the image binary: 1 is stroke, 0 background */
|
|
|
|
gi = gegl_buffer_iterator_new (strokes, NULL, 0, NULL,
|
|
|
|
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
|
|
|
|
while (gegl_buffer_iterator_next (gi))
|
|
|
|
{
|
|
|
|
guchar *data = (guchar*) gi->items[0].data;
|
|
|
|
gint k;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gegl_buffer_iterator_stop (gi);
|
|
|
|
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end1;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
for (k = 0; k < gi->length; k++)
|
|
|
|
{
|
|
|
|
if (! select_transparent)
|
|
|
|
/* Negate the value. */
|
|
|
|
*data = max_value - *data;
|
|
|
|
/* Apply a threshold. */
|
|
|
|
if (*data > (guchar) (255.0f * (1.0f - stroke_threshold)))
|
|
|
|
*data = 1;
|
|
|
|
else
|
|
|
|
*data = 0;
|
|
|
|
data++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Denoise (remove small connected components) */
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_lineart_denoise (strokes, minimal_lineart_area, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end1;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
closed = g_object_ref (strokes);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2018-12-17 22:38:15 +08:00
|
|
|
if (spline_max_length > 0 || segment_max_length > 0)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GArray *keypoints = NULL;
|
|
|
|
GHashTable *visited = NULL;
|
|
|
|
gfloat *radii = NULL;
|
|
|
|
gfloat *normals = NULL;
|
|
|
|
gfloat *curvatures = NULL;
|
|
|
|
gfloat *smoothed_curvatures = NULL;
|
2018-12-17 22:38:15 +08:00
|
|
|
gfloat threshold;
|
|
|
|
gfloat clamped_threshold;
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GList *fill_pixels = NULL;
|
2019-02-08 00:44:35 +08:00
|
|
|
GList *iter;
|
2018-12-17 22:38:15 +08:00
|
|
|
|
|
|
|
normals = g_new0 (gfloat, width * height * 2);
|
|
|
|
curvatures = g_new0 (gfloat, width * height);
|
|
|
|
smoothed_curvatures = g_new0 (gfloat, width * height);
|
|
|
|
|
|
|
|
/* Estimate normals & curvature */
|
|
|
|
gimp_lineart_compute_normals_curvatures (strokes, normals, curvatures,
|
|
|
|
smoothed_curvatures,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
normal_estimate_mask_size,
|
|
|
|
async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end2;
|
|
|
|
|
|
|
|
radii = gimp_lineart_estimate_strokes_radii (strokes, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end2;
|
2018-12-17 22:38:15 +08:00
|
|
|
threshold = 1.0f - end_point_rate;
|
|
|
|
clamped_threshold = MAX (0.25f, threshold);
|
|
|
|
for (i = 0; i < width; i++)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-12-17 22:38:15 +08:00
|
|
|
gint j;
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
|
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end2;
|
|
|
|
}
|
|
|
|
|
2018-12-17 22:38:15 +08:00
|
|
|
for (j = 0; j < height; j++)
|
|
|
|
{
|
|
|
|
if (smoothed_curvatures[i + j * width] >= (threshold / MAX (1.0f, radii[i + j * width])) ||
|
|
|
|
curvatures[i + j * width] >= clamped_threshold)
|
|
|
|
curvatures[i + j * width] = 1.0;
|
|
|
|
else
|
|
|
|
curvatures[i + j * width] = 0.0;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
g_clear_pointer (&radii, g_free);
|
2018-12-17 22:38:15 +08:00
|
|
|
|
|
|
|
keypoints = gimp_lineart_curvature_extremums (curvatures, smoothed_curvatures,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
width, height, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end2;
|
|
|
|
|
2018-12-17 22:38:15 +08:00
|
|
|
visited = g_hash_table_new_full ((GHashFunc) visited_hash_fun,
|
|
|
|
(GEqualFunc) visited_equal_fun,
|
|
|
|
(GDestroyNotify) g_free, NULL);
|
|
|
|
|
|
|
|
if (spline_max_length > 0)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-12-17 22:38:15 +08:00
|
|
|
GList *candidates;
|
|
|
|
SplineCandidate *candidate;
|
|
|
|
|
|
|
|
candidates = gimp_lineart_find_spline_candidates (keypoints, normals, width,
|
|
|
|
spline_max_length,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
spline_max_angle,
|
|
|
|
async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end3;
|
|
|
|
|
|
|
|
g_object_unref (closed);
|
2019-07-31 01:25:54 +08:00
|
|
|
closed = gimp_gegl_buffer_dup (strokes);
|
2018-12-17 22:38:15 +08:00
|
|
|
|
|
|
|
/* Draw splines */
|
|
|
|
while (candidates)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
Pixel *p1;
|
|
|
|
Pixel *p2;
|
2018-12-17 22:38:15 +08:00
|
|
|
gboolean inserted = FALSE;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end3;
|
|
|
|
}
|
|
|
|
|
|
|
|
p1 = g_new (Pixel, 1);
|
|
|
|
p2 = g_new (Pixel, 1);
|
|
|
|
|
2018-12-17 22:38:15 +08:00
|
|
|
candidate = (SplineCandidate *) candidates->data;
|
|
|
|
p1->x = candidate->p1.x;
|
|
|
|
p1->y = candidate->p1.y;
|
|
|
|
p2->x = candidate->p2.x;
|
|
|
|
p2->y = candidate->p2.y;
|
|
|
|
|
|
|
|
g_free (candidate);
|
|
|
|
candidates = g_list_delete_link (candidates, candidates);
|
|
|
|
|
|
|
|
if ((! g_hash_table_contains (visited, p1) ||
|
|
|
|
GPOINTER_TO_INT (g_hash_table_lookup (visited, p1)) < end_point_connectivity) &&
|
|
|
|
(! g_hash_table_contains (visited, p2) ||
|
|
|
|
GPOINTER_TO_INT (g_hash_table_lookup (visited, p2)) < end_point_connectivity))
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-12-17 22:38:15 +08:00
|
|
|
GArray *discrete_curve;
|
|
|
|
GimpVector2 vect1 = pair2normal (*p1, normals, width);
|
|
|
|
GimpVector2 vect2 = pair2normal (*p2, normals, width);
|
|
|
|
gfloat distance = gimp_vector2_length_val (gimp_vector2_sub_val (*p1, *p2));
|
|
|
|
gint transitions;
|
|
|
|
|
|
|
|
gimp_vector2_mul (&vect1, distance);
|
|
|
|
gimp_vector2_mul (&vect1, spline_roundness);
|
|
|
|
gimp_vector2_mul (&vect2, distance);
|
|
|
|
gimp_vector2_mul (&vect2, spline_roundness);
|
|
|
|
|
|
|
|
discrete_curve = gimp_lineart_discrete_spline (*p1, vect1, *p2, vect2);
|
|
|
|
|
|
|
|
transitions = allow_self_intersections ?
|
|
|
|
gimp_number_of_transitions (discrete_curve, strokes) :
|
|
|
|
gimp_number_of_transitions (discrete_curve, closed);
|
|
|
|
|
|
|
|
if (transitions == 2 &&
|
2019-02-10 06:40:47 +08:00
|
|
|
gimp_line_art_allow_closure (closed, discrete_curve,
|
|
|
|
&fill_pixels,
|
|
|
|
created_regions_significant_area,
|
2019-02-16 19:27:32 +08:00
|
|
|
created_regions_minimum_area))
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-12-17 22:38:15 +08:00
|
|
|
for (i = 0; i < discrete_curve->len; i++)
|
|
|
|
{
|
|
|
|
Pixel p = g_array_index (discrete_curve, Pixel, i);
|
|
|
|
|
|
|
|
if (p.x >= 0 && p.x < gegl_buffer_get_width (closed) &&
|
|
|
|
p.y >= 0 && p.y < gegl_buffer_get_height (closed))
|
|
|
|
{
|
2019-03-05 01:28:11 +08:00
|
|
|
guchar val = 2;
|
2018-12-17 22:38:15 +08:00
|
|
|
|
|
|
|
gegl_buffer_set (closed, GEGL_RECTANGLE ((gint) p.x, (gint) p.y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_hash_table_replace (visited, p1,
|
|
|
|
GINT_TO_POINTER (GPOINTER_TO_INT (g_hash_table_lookup (visited, p1)) + 1));
|
|
|
|
g_hash_table_replace (visited, p2,
|
|
|
|
GINT_TO_POINTER (GPOINTER_TO_INT (g_hash_table_lookup (visited, p2)) + 1));
|
|
|
|
inserted = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
2018-12-17 22:38:15 +08:00
|
|
|
g_array_free (discrete_curve, TRUE);
|
|
|
|
}
|
|
|
|
if (! inserted)
|
|
|
|
{
|
|
|
|
g_free (p1);
|
|
|
|
g_free (p2);
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
end3:
|
2018-12-17 22:38:15 +08:00
|
|
|
g_list_free_full (candidates, g_free);
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end2;
|
2018-12-17 22:38:15 +08:00
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2019-03-07 04:39:29 +08:00
|
|
|
g_clear_object (&strokes);
|
|
|
|
|
2018-12-17 22:38:15 +08:00
|
|
|
/* Draw straight line segments */
|
|
|
|
if (segment_max_length > 0)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-12-17 22:38:15 +08:00
|
|
|
Pixel *point;
|
|
|
|
|
|
|
|
point = (Pixel *) keypoints->data;
|
|
|
|
for (i = 0; i < keypoints->len; i++)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
Pixel *p;
|
2018-12-17 22:38:15 +08:00
|
|
|
gboolean inserted = FALSE;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end2;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = g_new (Pixel, 1);
|
2018-12-17 22:38:15 +08:00
|
|
|
*p = *point;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2018-12-17 22:38:15 +08:00
|
|
|
if (! g_hash_table_contains (visited, p) ||
|
|
|
|
(small_segments_from_spline_sources &&
|
|
|
|
GPOINTER_TO_INT (g_hash_table_lookup (visited, p)) < end_point_connectivity))
|
|
|
|
{
|
|
|
|
GArray *segment = gimp_lineart_line_segment_until_hit (closed, *point,
|
|
|
|
pair2normal (*point, normals, width),
|
|
|
|
segment_max_length);
|
|
|
|
|
|
|
|
if (segment->len &&
|
2019-02-10 06:40:47 +08:00
|
|
|
gimp_line_art_allow_closure (closed, segment, &fill_pixels,
|
|
|
|
created_regions_significant_area,
|
2019-02-16 19:27:32 +08:00
|
|
|
created_regions_minimum_area))
|
2018-12-17 22:38:15 +08:00
|
|
|
{
|
|
|
|
gint j;
|
|
|
|
|
|
|
|
for (j = 0; j < segment->len; j++)
|
|
|
|
{
|
|
|
|
Pixel p2 = g_array_index (segment, Pixel, j);
|
2019-03-05 01:28:11 +08:00
|
|
|
guchar val = 2;
|
2018-12-17 22:38:15 +08:00
|
|
|
|
|
|
|
gegl_buffer_set (closed, GEGL_RECTANGLE ((gint) p2.x, (gint) p2.y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
}
|
|
|
|
g_hash_table_replace (visited, p,
|
|
|
|
GINT_TO_POINTER (GPOINTER_TO_INT (g_hash_table_lookup (visited, p)) + 1));
|
|
|
|
inserted = TRUE;
|
|
|
|
}
|
|
|
|
g_array_free (segment, TRUE);
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
2018-12-17 22:38:15 +08:00
|
|
|
if (! inserted)
|
|
|
|
g_free (p);
|
|
|
|
point++;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
2018-12-17 22:38:15 +08:00
|
|
|
|
2019-02-08 00:44:35 +08:00
|
|
|
for (iter = fill_pixels; iter; iter = iter->next)
|
|
|
|
{
|
2019-02-16 19:27:32 +08:00
|
|
|
Pixel *p = iter->data;
|
|
|
|
gint fill_max = created_regions_significant_area - 1;
|
2019-02-08 00:44:35 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end2;
|
|
|
|
}
|
|
|
|
|
2019-02-08 00:44:35 +08:00
|
|
|
/* XXX A best approach would be to generalize
|
|
|
|
* gimp_drawable_bucket_fill() to work on any buffer (the code
|
|
|
|
* is already mostly there) rather than reimplementing a naive
|
|
|
|
* bucket fill.
|
|
|
|
* This is mostly a quick'n dirty first implementation which I
|
|
|
|
* will improve later.
|
|
|
|
*/
|
2019-02-16 19:27:32 +08:00
|
|
|
gimp_line_art_simple_fill (closed, (gint) p->x, (gint) p->y, &fill_max);
|
2019-02-08 00:44:35 +08:00
|
|
|
}
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
end2:
|
2019-02-08 00:44:35 +08:00
|
|
|
g_list_free_full (fill_pixels, g_free);
|
2018-12-17 22:38:15 +08:00
|
|
|
g_free (normals);
|
|
|
|
g_free (curvatures);
|
|
|
|
g_free (smoothed_curvatures);
|
2019-03-07 04:39:29 +08:00
|
|
|
g_clear_pointer (&radii, g_free);
|
|
|
|
if (keypoints)
|
|
|
|
g_array_free (keypoints, TRUE);
|
|
|
|
g_clear_pointer (&visited, g_hash_table_destroy);
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end1;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
2019-03-07 04:39:29 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
g_clear_object (&strokes);
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2018-11-22 21:32:59 +08:00
|
|
|
if (closed_distmap)
|
|
|
|
{
|
|
|
|
GeglNode *graph;
|
|
|
|
GeglNode *input;
|
|
|
|
GeglNode *op;
|
|
|
|
|
|
|
|
/* Flooding needs a distance map for closed line art. */
|
|
|
|
*closed_distmap = g_new (gfloat, width * height);
|
|
|
|
|
|
|
|
graph = gegl_node_new ();
|
|
|
|
input = gegl_node_new_child (graph,
|
|
|
|
"operation", "gegl:buffer-source",
|
|
|
|
"buffer", closed,
|
|
|
|
NULL);
|
|
|
|
op = gegl_node_new_child (graph,
|
|
|
|
"operation", "gegl:distance-transform",
|
|
|
|
"metric", GEGL_DISTANCE_METRIC_EUCLIDEAN,
|
|
|
|
"normalize", FALSE,
|
|
|
|
NULL);
|
|
|
|
gegl_node_connect_to (input, "output",
|
|
|
|
op, "input");
|
|
|
|
gegl_node_blit (op, 1.0, gegl_buffer_get_extent (closed),
|
|
|
|
NULL, *closed_distmap,
|
|
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_BLIT_DEFAULT);
|
|
|
|
g_object_unref (graph);
|
|
|
|
}
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
end1:
|
|
|
|
g_clear_object (&strokes);
|
|
|
|
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
g_clear_object (&closed);
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
return closed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_lineart_denoise (GeglBuffer *buffer,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
int minimum_area,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
/* Keep connected regions with significant area. */
|
|
|
|
GArray *region;
|
2018-10-17 21:04:30 +08:00
|
|
|
GQueue *q = g_queue_new ();
|
|
|
|
gint width = gegl_buffer_get_width (buffer);
|
|
|
|
gint height = gegl_buffer_get_height (buffer);
|
|
|
|
gboolean *visited = g_new0 (gboolean, width * height);
|
|
|
|
gint x, y;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
region = g_array_sized_new (TRUE, TRUE, sizeof (Pixel *), minimum_area);
|
|
|
|
|
2018-10-17 21:04:30 +08:00
|
|
|
for (y = 0; y < height; ++y)
|
|
|
|
for (x = 0; x < width; ++x)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
guchar has_stroke;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
gegl_buffer_sample (buffer, x, y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[x + y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p = g_new (Pixel, 1);
|
2018-10-17 21:04:30 +08:00
|
|
|
gint regionSize = 0;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
p->x = x;
|
|
|
|
p->y = y;
|
|
|
|
|
|
|
|
g_queue_push_tail (q, p);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[x + y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
while (! g_queue_is_empty (q))
|
|
|
|
{
|
2019-03-07 04:39:29 +08:00
|
|
|
Pixel *p;
|
2018-10-11 02:28:47 +08:00
|
|
|
gint p2x;
|
|
|
|
gint p2y;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:39:29 +08:00
|
|
|
p = (Pixel *) g_queue_pop_head (q);
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
p2x = p->x + 1;
|
|
|
|
p2y = p->y;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x +p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x - 1;
|
|
|
|
p2y = p->y;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x;
|
|
|
|
p2y = p->y - 1;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x;
|
|
|
|
p2y = p->y + 1;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x + 1;
|
|
|
|
p2y = p->y + 1;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x - 1;
|
|
|
|
p2y = p->y - 1;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x - 1;
|
|
|
|
p2y = p->y + 1;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p2x = p->x + 1;
|
|
|
|
p2y = p->y - 1;
|
|
|
|
if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
|
|
|
|
{
|
|
|
|
gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
2018-10-17 21:04:30 +08:00
|
|
|
if (has_stroke && ! visited[p2x + p2y * width])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
2018-10-17 21:04:30 +08:00
|
|
|
visited[p2x + p2y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++regionSize;
|
|
|
|
if (regionSize < minimum_area)
|
|
|
|
g_array_append_val (region, *p);
|
|
|
|
g_free (p);
|
|
|
|
}
|
|
|
|
if (regionSize < minimum_area)
|
|
|
|
{
|
|
|
|
Pixel *pixel = (Pixel *) region->data;
|
|
|
|
gint i = 0;
|
|
|
|
|
|
|
|
for (; i < region->len; i++)
|
|
|
|
{
|
|
|
|
guchar val = 0;
|
|
|
|
gegl_buffer_set (buffer, GEGL_RECTANGLE (pixel->x, pixel->y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
pixel++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_array_remove_range (region, 0, region->len);
|
|
|
|
}
|
|
|
|
}
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
|
|
|
|
end:
|
2018-10-11 02:28:47 +08:00
|
|
|
g_array_free (region, TRUE);
|
|
|
|
g_queue_free_full (q, g_free);
|
2018-10-17 21:04:30 +08:00
|
|
|
g_free (visited);
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-10-17 21:53:03 +08:00
|
|
|
gimp_lineart_compute_normals_curvatures (GeglBuffer *mask,
|
|
|
|
gfloat *normals,
|
|
|
|
gfloat *curvatures,
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
gfloat *smoothed_curvatures,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
int normal_estimate_mask_size,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gfloat *edgels_curvatures = NULL;
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
gfloat *smoothed_curvature;
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GArray *es = NULL;
|
|
|
|
Edgel **e;
|
|
|
|
gint width = gegl_buffer_get_width (mask);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
es = gimp_edgelset_new (mask, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
e = (Edgel **) es->data;
|
|
|
|
|
|
|
|
gimp_edgelset_smooth_normals (es, normal_estimate_mask_size, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
gimp_edgelset_compute_curvature (es, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
while (*e)
|
|
|
|
{
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
const float curvature = ((*e)->curvature > 0.0f) ? (*e)->curvature : 0.0f;
|
|
|
|
const float w = MAX (1e-8f, curvature * curvature);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2018-10-17 21:53:03 +08:00
|
|
|
normals[((*e)->x + (*e)->y * width) * 2] += w * (*e)->x_normal;
|
|
|
|
normals[((*e)->x + (*e)->y * width) * 2 + 1] += w * (*e)->y_normal;
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
curvatures[(*e)->x + (*e)->y * width] = MAX (curvature,
|
2018-10-17 21:53:03 +08:00
|
|
|
curvatures[(*e)->x + (*e)->y * width]);
|
2018-10-11 02:28:47 +08:00
|
|
|
e++;
|
|
|
|
}
|
|
|
|
for (int y = 0; y < gegl_buffer_get_height (mask); ++y)
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
{
|
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int x = 0; x < gegl_buffer_get_width (mask); ++x)
|
|
|
|
{
|
|
|
|
const float _angle = atan2f (normals[(x + y * width) * 2 + 1],
|
|
|
|
normals[(x + y * width) * 2]);
|
|
|
|
normals[(x + y * width) * 2] = cosf (_angle);
|
|
|
|
normals[(x + y * width) * 2 + 1] = sinf (_angle);
|
|
|
|
}
|
|
|
|
}
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
|
|
|
|
/* Smooth curvatures on edgels, then take maximum on each pixel. */
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
edgels_curvatures = gimp_lineart_get_smooth_curvatures (es, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end;
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
smoothed_curvature = edgels_curvatures;
|
|
|
|
|
|
|
|
e = (Edgel **) es->data;
|
|
|
|
while (*e)
|
|
|
|
{
|
|
|
|
gfloat *pixel_curvature = &smoothed_curvatures[(*e)->x + (*e)->y * width];
|
|
|
|
|
|
|
|
if (*pixel_curvature < *smoothed_curvature)
|
|
|
|
*pixel_curvature = *smoothed_curvature;
|
|
|
|
|
|
|
|
++smoothed_curvature;
|
|
|
|
e++;
|
|
|
|
}
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
|
|
|
|
end:
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
g_free (edgels_curvatures);
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (es)
|
|
|
|
g_array_free (es, TRUE);
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
static gfloat *
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_lineart_get_smooth_curvatures (GArray *edgelset,
|
|
|
|
GimpAsync *async)
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
{
|
|
|
|
Edgel **e;
|
|
|
|
gfloat *smoothed_curvatures = g_new0 (gfloat, edgelset->len);
|
|
|
|
gfloat weights[9];
|
|
|
|
gfloat smoothed_curvature;
|
|
|
|
gfloat weights_sum;
|
|
|
|
gint idx = 0;
|
|
|
|
|
|
|
|
weights[0] = 1.0f;
|
|
|
|
for (int i = 1; i <= 8; ++i)
|
|
|
|
weights[i] = expf (-(i * i) / 30.0f);
|
|
|
|
|
|
|
|
e = (Edgel **) edgelset->data;
|
|
|
|
while (*e)
|
|
|
|
{
|
|
|
|
Edgel *edgel_before = g_array_index (edgelset, Edgel*, (*e)->previous);
|
|
|
|
Edgel *edgel_after = g_array_index (edgelset, Edgel*, (*e)->next);
|
|
|
|
int n = 5;
|
|
|
|
int i = 1;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
g_free (smoothed_curvatures);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
smoothed_curvature = (*e)->curvature;
|
|
|
|
weights_sum = weights[0];
|
|
|
|
while (n-- && (edgel_after != edgel_before))
|
|
|
|
{
|
|
|
|
smoothed_curvature += weights[i] * edgel_before->curvature;
|
|
|
|
smoothed_curvature += weights[i] * edgel_after->curvature;
|
|
|
|
edgel_before = g_array_index (edgelset, Edgel*, edgel_before->previous);
|
|
|
|
edgel_after = g_array_index (edgelset, Edgel*, edgel_after->next);
|
|
|
|
weights_sum += 2 * weights[i];
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
smoothed_curvature /= weights_sum;
|
|
|
|
smoothed_curvatures[idx++] = smoothed_curvature;
|
|
|
|
|
|
|
|
e++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return smoothed_curvatures;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
/**
|
|
|
|
* Keep one pixel per connected component of curvature extremums.
|
|
|
|
*/
|
|
|
|
static GArray *
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_lineart_curvature_extremums (gfloat *curvatures,
|
|
|
|
gfloat *smoothed_curvatures,
|
|
|
|
gint width,
|
|
|
|
gint height,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-10-17 21:04:30 +08:00
|
|
|
gboolean *visited = g_new0 (gboolean, width * height);
|
|
|
|
GQueue *q = g_queue_new ();
|
2018-10-11 02:28:47 +08:00
|
|
|
GArray *max_positions;
|
|
|
|
|
|
|
|
max_positions = g_array_new (FALSE, TRUE, sizeof (Pixel));
|
|
|
|
|
|
|
|
for (int y = 0; y < height; ++y)
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
{
|
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
2019-01-17 20:21:18 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
goto end;
|
|
|
|
}
|
2019-01-17 20:21:18 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
for (int x = 0; x < width; ++x)
|
|
|
|
{
|
|
|
|
if ((curvatures[x + y * width] > 0.0) && ! visited[x + y * width])
|
|
|
|
{
|
|
|
|
Pixel *p = g_new (Pixel, 1);
|
|
|
|
Pixel max_smoothed_curvature_pixel;
|
|
|
|
Pixel max_raw_curvature_pixel;
|
|
|
|
gfloat max_smoothed_curvature;
|
|
|
|
gfloat max_raw_curvature;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
max_smoothed_curvature_pixel = gimp_vector2_new (-1.0, -1.0);
|
|
|
|
max_smoothed_curvature = 0.0f;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
max_raw_curvature_pixel = gimp_vector2_new (x, y);
|
|
|
|
max_raw_curvature = curvatures[x + y * width];
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p->x = x;
|
|
|
|
p->y = y;
|
|
|
|
g_queue_push_tail (q, p);
|
|
|
|
visited[x + y * width] = TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
while (! g_queue_is_empty (q))
|
|
|
|
{
|
|
|
|
gfloat sc;
|
|
|
|
gfloat c;
|
|
|
|
gint p2x;
|
|
|
|
gint p2y;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
goto end;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p = (Pixel *) g_queue_pop_head (q);
|
|
|
|
sc = smoothed_curvatures[(gint) p->x + (gint) p->y * width];
|
|
|
|
c = curvatures[(gint) p->x + (gint) p->y * width];
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
curvatures[(gint) p->x + (gint) p->y * width] = 0.0f;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = (gint) p->x + 1;
|
|
|
|
p2y = (gint) p->y;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = p->x - 1;
|
|
|
|
p2y = p->y;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = p->x;
|
|
|
|
p2y = p->y - 1;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = p->x;
|
|
|
|
p2y = p->y + 1;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = p->x + 1;
|
|
|
|
p2y = p->y + 1;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = p->x - 1;
|
|
|
|
p2y = p->y - 1;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
p2x = p->x - 1;
|
|
|
|
p2y = p->y + 1;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
p2x = p->x + 1;
|
|
|
|
p2y = p->y - 1;
|
|
|
|
if (p2x >= 0 && p2x < width &&
|
|
|
|
p2y >= 0 && p2y < height &&
|
|
|
|
curvatures[p2x + p2y * width] > 0.0 &&
|
|
|
|
! visited[p2x + p2y * width])
|
|
|
|
{
|
|
|
|
Pixel *p2 = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
p2->x = p2x;
|
|
|
|
p2->y = p2y;
|
|
|
|
g_queue_push_tail (q, p2);
|
|
|
|
visited[p2x + p2y * width] = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sc > max_smoothed_curvature)
|
|
|
|
{
|
|
|
|
max_smoothed_curvature_pixel = *p;
|
|
|
|
max_smoothed_curvature = sc;
|
|
|
|
}
|
|
|
|
if (c > max_raw_curvature)
|
|
|
|
{
|
|
|
|
max_raw_curvature_pixel = *p;
|
|
|
|
max_raw_curvature = c;
|
|
|
|
}
|
|
|
|
g_free (p);
|
|
|
|
}
|
|
|
|
if (max_smoothed_curvature > 0.0f)
|
|
|
|
{
|
|
|
|
curvatures[(gint) max_smoothed_curvature_pixel.x + (gint) max_smoothed_curvature_pixel.y * width] = max_smoothed_curvature;
|
|
|
|
g_array_append_val (max_positions, max_smoothed_curvature_pixel);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
curvatures[(gint) max_raw_curvature_pixel.x + (gint) max_raw_curvature_pixel.y * width] = max_raw_curvature;
|
|
|
|
g_array_append_val (max_positions, max_raw_curvature_pixel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
2018-10-11 02:28:47 +08:00
|
|
|
g_queue_free_full (q, g_free);
|
2018-10-17 21:04:30 +08:00
|
|
|
g_free (visited);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
{
|
|
|
|
g_array_free (max_positions, TRUE);
|
|
|
|
max_positions = NULL;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
return max_positions;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gint
|
|
|
|
gimp_spline_candidate_cmp (const SplineCandidate *a,
|
|
|
|
const SplineCandidate *b,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
/* This comparison actually returns the opposite of common comparison
|
|
|
|
* functions on purpose, as we want the first element on the list to
|
|
|
|
* be the "bigger".
|
|
|
|
*/
|
|
|
|
if (a->quality < b->quality)
|
|
|
|
return 1;
|
|
|
|
else if (a->quality > b->quality)
|
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GList *
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_lineart_find_spline_candidates (GArray *max_positions,
|
|
|
|
gfloat *normals,
|
|
|
|
gint width,
|
|
|
|
gint distance_threshold,
|
|
|
|
gfloat max_angle_deg,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
GList *candidates = NULL;
|
|
|
|
const float CosMin = cosf (M_PI * (max_angle_deg / 180.0));
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
for (i = 0; i < max_positions->len; i++)
|
|
|
|
{
|
|
|
|
Pixel p1 = g_array_index (max_positions, Pixel, i);
|
|
|
|
gint j;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
g_list_free_full (candidates, g_free);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
for (j = i + 1; j < max_positions->len; j++)
|
|
|
|
{
|
|
|
|
Pixel p2 = g_array_index (max_positions, Pixel, j);
|
|
|
|
const float distance = gimp_vector2_length_val (gimp_vector2_sub_val (p1, p2));
|
|
|
|
|
|
|
|
if (distance <= distance_threshold)
|
|
|
|
{
|
|
|
|
GimpVector2 normalP1;
|
|
|
|
GimpVector2 normalP2;
|
|
|
|
GimpVector2 p1f;
|
|
|
|
GimpVector2 p2f;
|
|
|
|
GimpVector2 p1p2;
|
|
|
|
float cosN;
|
|
|
|
float qualityA;
|
|
|
|
float qualityB;
|
|
|
|
float qualityC;
|
|
|
|
float quality;
|
|
|
|
|
2018-10-17 21:53:03 +08:00
|
|
|
normalP1 = gimp_vector2_new (normals[((gint) p1.x + (gint) p1.y * width) * 2],
|
|
|
|
normals[((gint) p1.x + (gint) p1.y * width) * 2 + 1]);
|
|
|
|
normalP2 = gimp_vector2_new (normals[((gint) p2.x + (gint) p2.y * width) * 2],
|
|
|
|
normals[((gint) p2.x + (gint) p2.y * width) * 2 + 1]);
|
2018-10-11 02:28:47 +08:00
|
|
|
p1f = gimp_vector2_new (p1.x, p1.y);
|
|
|
|
p2f = gimp_vector2_new (p2.x, p2.y);
|
|
|
|
p1p2 = gimp_vector2_sub_val (p2f, p1f);
|
|
|
|
|
|
|
|
cosN = gimp_vector2_inner_product_val (normalP1, (gimp_vector2_neg_val (normalP2)));
|
|
|
|
qualityA = MAX (0.0f, 1 - distance / distance_threshold);
|
|
|
|
qualityB = MAX (0.0f,
|
|
|
|
(float) (gimp_vector2_inner_product_val (normalP1, p1p2) - gimp_vector2_inner_product_val (normalP2, p1p2)) /
|
|
|
|
distance);
|
|
|
|
qualityC = MAX (0.0f, cosN - CosMin);
|
|
|
|
quality = qualityA * qualityB * qualityC;
|
|
|
|
if (quality > 0)
|
|
|
|
{
|
|
|
|
SplineCandidate *candidate = g_new (SplineCandidate, 1);
|
|
|
|
|
|
|
|
candidate->p1 = p1;
|
|
|
|
candidate->p2 = p2;
|
|
|
|
candidate->quality = quality;
|
|
|
|
|
|
|
|
candidates = g_list_insert_sorted_with_data (candidates, candidate,
|
|
|
|
(GCompareDataFunc) gimp_spline_candidate_cmp,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return candidates;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GArray *
|
|
|
|
gimp_lineart_discrete_spline (Pixel p0,
|
|
|
|
GimpVector2 n0,
|
|
|
|
Pixel p1,
|
|
|
|
GimpVector2 n1)
|
|
|
|
{
|
|
|
|
GArray *points = g_array_new (FALSE, TRUE, sizeof (Pixel));
|
|
|
|
const double a0 = 2 * p0.x - 2 * p1.x + n0.x - n1.x;
|
|
|
|
const double b0 = -3 * p0.x + 3 * p1.x - 2 * n0.x + n1.x;
|
|
|
|
const double c0 = n0.x;
|
|
|
|
const double d0 = p0.x;
|
|
|
|
const double a1 = 2 * p0.y - 2 * p1.y + n0.y - n1.y;
|
|
|
|
const double b1 = -3 * p0.y + 3 * p1.y - 2 * n0.y + n1.y;
|
|
|
|
const double c1 = n0.y;
|
|
|
|
const double d1 = p0.y;
|
|
|
|
|
|
|
|
double t = 0.0;
|
|
|
|
const double dtMin = 1.0 / MAX (fabs (p0.x - p1.x), fabs (p0.y - p1.y));
|
|
|
|
Pixel point = gimp_vector2_new ((gint) round (d0), (gint) round (d1));
|
|
|
|
|
|
|
|
g_array_append_val (points, point);
|
|
|
|
|
|
|
|
while (t <= 1.0)
|
|
|
|
{
|
|
|
|
const double t2 = t * t;
|
|
|
|
const double t3 = t * t2;
|
|
|
|
double dx;
|
|
|
|
double dy;
|
|
|
|
Pixel p = gimp_vector2_new ((gint) round (a0 * t3 + b0 * t2 + c0 * t + d0),
|
|
|
|
(gint) round (a1 * t3 + b1 * t2 + c1 * t + d1));
|
|
|
|
|
|
|
|
/* create gimp_vector2_neq () ? */
|
|
|
|
if (g_array_index (points, Pixel, points->len - 1).x != p.x ||
|
|
|
|
g_array_index (points, Pixel, points->len - 1).y != p.y)
|
|
|
|
{
|
|
|
|
g_array_append_val (points, p);
|
|
|
|
}
|
|
|
|
dx = fabs (3 * a0 * t * t + 2 * b0 * t + c0) + 1e-8;
|
|
|
|
dy = fabs (3 * a1 * t * t + 2 * b1 * t + c1) + 1e-8;
|
|
|
|
t += MIN (dtMin, 0.75 / MAX (dx, dy));
|
|
|
|
}
|
|
|
|
if (g_array_index (points, Pixel, points->len - 1).x != p1.x ||
|
|
|
|
g_array_index (points, Pixel, points->len - 1).y != p1.y)
|
|
|
|
{
|
|
|
|
g_array_append_val (points, p1);
|
|
|
|
}
|
|
|
|
return points;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gint
|
|
|
|
gimp_number_of_transitions (GArray *pixels,
|
2018-11-29 21:12:39 +08:00
|
|
|
GeglBuffer *buffer)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
int result = 0;
|
|
|
|
|
|
|
|
if (pixels->len > 0)
|
|
|
|
{
|
|
|
|
Pixel it = g_array_index (pixels, Pixel, 0);
|
|
|
|
guchar value;
|
|
|
|
gboolean previous;
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
gegl_buffer_sample (buffer, (gint) it.x, (gint) it.y, NULL, &value, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
previous = (gboolean) value;
|
|
|
|
|
|
|
|
/* Starts at the second element. */
|
|
|
|
for (i = 1; i < pixels->len; i++)
|
|
|
|
{
|
|
|
|
it = g_array_index (pixels, Pixel, i);
|
|
|
|
|
2018-11-29 21:12:39 +08:00
|
|
|
gegl_buffer_sample (buffer, (gint) it.x, (gint) it.y, NULL, &value, NULL,
|
|
|
|
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
result += ((gboolean) value != previous);
|
|
|
|
previous = (gboolean) value;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-02-10 06:40:47 +08:00
|
|
|
* gimp_line_art_allow_closure:
|
|
|
|
* @mask: the current state of line art closure.
|
|
|
|
* @pixels: the pixels of a candidate closure (spline or segment).
|
2019-08-07 08:06:52 +08:00
|
|
|
* @fill_pixels: #GList of insignificant pixels to bucket fill.
|
2019-02-10 06:40:47 +08:00
|
|
|
* @significant_size: number of pixels for area to be considered
|
|
|
|
* "significant".
|
|
|
|
* @minimum_size: number of pixels for area to be allowed.
|
|
|
|
*
|
|
|
|
* Checks whether adding the set of points @pixels to @mask will create
|
|
|
|
* 4-connected background regions whose size (i.e. number of pixels)
|
|
|
|
* will be below @minimum_size. If it creates such small areas, the
|
|
|
|
* function will refuse this candidate spline/segment, with the
|
|
|
|
* exception of very small areas under @significant_size. These
|
2019-08-07 08:06:52 +08:00
|
|
|
* micro-area are considered "insignificant" and accepted (because they
|
2019-02-10 06:40:47 +08:00
|
|
|
* can be created in some conditions, for instance when created curves
|
|
|
|
* cross or start from a same endpoint), and one pixel for each
|
|
|
|
* micro-area will be added to @fill_pixels to be later filled along
|
|
|
|
* with the candidate pixels.
|
|
|
|
*
|
2019-08-03 06:04:28 +08:00
|
|
|
* Returns: %TRUE if @pixels should be added to @mask, %FALSE otherwise.
|
2018-10-11 02:28:47 +08:00
|
|
|
*/
|
|
|
|
static gboolean
|
2019-02-10 06:40:47 +08:00
|
|
|
gimp_line_art_allow_closure (GeglBuffer *mask,
|
|
|
|
GArray *pixels,
|
|
|
|
GList **fill_pixels,
|
|
|
|
int significant_size,
|
|
|
|
int minimum_size)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2019-02-10 06:40:47 +08:00
|
|
|
/* A theorem from the paper is that a zone with more than
|
|
|
|
* `2 * (@minimum_size - 1)` edgels (border pixels) will have more
|
|
|
|
* than @minimum_size pixels.
|
|
|
|
* Since we are following the edges of the area, we can therefore stop
|
|
|
|
* earlier if we reach this number of edgels.
|
|
|
|
*/
|
2019-02-16 19:27:32 +08:00
|
|
|
const glong max_edgel_count = 2 * minimum_size;
|
2019-02-10 06:40:47 +08:00
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
Pixel *p = (Pixel*) pixels->data;
|
2019-02-08 00:44:35 +08:00
|
|
|
GList *fp = NULL;
|
2018-10-11 02:28:47 +08:00
|
|
|
gint i;
|
|
|
|
|
|
|
|
/* Mark pixels */
|
|
|
|
for (i = 0; i < pixels->len; i++)
|
|
|
|
{
|
|
|
|
if (p->x >= 0 && p->x < gegl_buffer_get_width (mask) &&
|
|
|
|
p->y >= 0 && p->y < gegl_buffer_get_height (mask))
|
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
|
|
|
|
gegl_buffer_sample (mask, (gint) p->x, (gint) p->y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
val = val ? 3 : 2;
|
|
|
|
gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p->x, (gint) p->y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < pixels->len; i++)
|
|
|
|
{
|
|
|
|
Pixel p = g_array_index (pixels, Pixel, i);
|
|
|
|
|
|
|
|
for (int direction = 0; direction < 4; ++direction)
|
|
|
|
{
|
|
|
|
if (p.x >= 0 && p.x < gegl_buffer_get_width (mask) &&
|
|
|
|
p.y >= 0 && p.y < gegl_buffer_get_height (mask) &&
|
|
|
|
border_in_direction (mask, p, direction))
|
|
|
|
{
|
|
|
|
Edgel e;
|
|
|
|
guchar val;
|
|
|
|
glong count;
|
|
|
|
glong area;
|
|
|
|
|
|
|
|
gegl_buffer_sample (mask, (gint) p.x, (gint) p.y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
if ((gboolean) (val & (4 << direction)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
gimp_edgel_init (&e);
|
|
|
|
e.x = p.x;
|
|
|
|
e.y = p.y;
|
|
|
|
e.direction = direction;
|
|
|
|
|
|
|
|
count = gimp_edgel_track_mark (mask, e, max_edgel_count);
|
2019-02-08 00:44:35 +08:00
|
|
|
if ((count != -1) && (count <= max_edgel_count))
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2019-02-10 06:13:58 +08:00
|
|
|
area = gimp_edgel_region_area (mask, e);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2019-02-16 19:27:32 +08:00
|
|
|
if (area >= significant_size && area < minimum_size)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2019-02-08 00:44:35 +08:00
|
|
|
gint j;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2019-02-08 00:44:35 +08:00
|
|
|
/* Remove marks */
|
|
|
|
for (j = 0; j < pixels->len; j++)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2019-02-08 00:44:35 +08:00
|
|
|
Pixel p2 = g_array_index (pixels, Pixel, j);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
2019-02-08 00:44:35 +08:00
|
|
|
if (p2.x >= 0 && p2.x < gegl_buffer_get_width (mask) &&
|
|
|
|
p2.y >= 0 && p2.y < gegl_buffer_get_height (mask))
|
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
|
|
|
|
gegl_buffer_sample (mask, (gint) p2.x, (gint) p2.y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
val &= 1;
|
|
|
|
gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p2.x, (gint) p2.y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
2019-02-08 00:44:35 +08:00
|
|
|
g_list_free_full (fp, g_free);
|
|
|
|
|
2019-02-10 06:40:47 +08:00
|
|
|
return FALSE;
|
2019-02-08 00:44:35 +08:00
|
|
|
}
|
2019-02-14 20:46:09 +08:00
|
|
|
else if (area > 0 && area < significant_size)
|
2019-02-08 00:44:35 +08:00
|
|
|
{
|
|
|
|
Pixel *np = g_new (Pixel, 1);
|
|
|
|
|
|
|
|
np->x = direction == XPlusDirection ? p.x + 1 : (direction == XMinusDirection ? p.x - 1 : p.x);
|
|
|
|
np->y = direction == YPlusDirection ? p.y + 1 : (direction == YMinusDirection ? p.y - 1 : p.y);
|
|
|
|
|
|
|
|
if (np->x >= 0 && np->x < gegl_buffer_get_width (mask) &&
|
|
|
|
np->y >= 0 && np->y < gegl_buffer_get_height (mask))
|
|
|
|
fp = g_list_prepend (fp, np);
|
|
|
|
else
|
|
|
|
g_free (np);
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-08 00:44:35 +08:00
|
|
|
*fill_pixels = g_list_concat (*fill_pixels, fp);
|
2018-10-11 02:28:47 +08:00
|
|
|
/* Remove marks */
|
|
|
|
for (i = 0; i < pixels->len; i++)
|
|
|
|
{
|
|
|
|
Pixel p = g_array_index (pixels, Pixel, i);
|
|
|
|
|
|
|
|
if (p.x >= 0 && p.x < gegl_buffer_get_width (mask) &&
|
|
|
|
p.y >= 0 && p.y < gegl_buffer_get_height (mask))
|
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
|
|
|
|
gegl_buffer_sample (mask, (gint) p.x, (gint) p.y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
val &= 1;
|
|
|
|
gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p.x, (gint) p.y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
}
|
|
|
|
}
|
2019-02-10 06:40:47 +08:00
|
|
|
return TRUE;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static GArray *
|
|
|
|
gimp_lineart_line_segment_until_hit (const GeglBuffer *mask,
|
|
|
|
Pixel start,
|
|
|
|
GimpVector2 direction,
|
|
|
|
int size)
|
|
|
|
{
|
|
|
|
GeglBuffer *buffer = (GeglBuffer *) mask;
|
|
|
|
gboolean out = FALSE;
|
|
|
|
GArray *points = g_array_new (FALSE, TRUE, sizeof (Pixel));
|
|
|
|
int tmax;
|
|
|
|
GimpVector2 p0 = gimp_vector2_new (start.x, start.y);
|
|
|
|
|
|
|
|
gimp_vector2_mul (&direction, (gdouble) size);
|
|
|
|
direction.x = round (direction.x);
|
|
|
|
direction.y = round (direction.y);
|
|
|
|
|
|
|
|
tmax = MAX (abs ((int) direction.x), abs ((int) direction.y));
|
|
|
|
|
|
|
|
for (int t = 0; t <= tmax; ++t)
|
|
|
|
{
|
|
|
|
GimpVector2 v = gimp_vector2_add_val (p0, gimp_vector2_mul_val (direction, (float)t / tmax));
|
|
|
|
Pixel p;
|
|
|
|
|
|
|
|
p.x = (gint) round (v.x);
|
|
|
|
p.y = (gint) round (v.y);
|
|
|
|
if (p.x >= 0 && p.x < gegl_buffer_get_width (buffer) &&
|
|
|
|
p.y >= 0 && p.y < gegl_buffer_get_height (buffer))
|
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
gegl_buffer_sample (buffer, p.x, p.y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
if (out && val)
|
|
|
|
{
|
|
|
|
return points;
|
|
|
|
}
|
|
|
|
out = ! val;
|
|
|
|
}
|
|
|
|
else if (out)
|
|
|
|
{
|
|
|
|
return points;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_array_free (points, TRUE);
|
|
|
|
return g_array_new (FALSE, TRUE, sizeof (Pixel));
|
|
|
|
}
|
|
|
|
g_array_append_val (points, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_array_free (points, TRUE);
|
|
|
|
return g_array_new (FALSE, TRUE, sizeof (Pixel));
|
|
|
|
}
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
static gfloat *
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_lineart_estimate_strokes_radii (GeglBuffer *mask,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
GeglBufferIterator *gi;
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
gfloat *dist;
|
|
|
|
gfloat *thickness;
|
2018-10-11 02:28:47 +08:00
|
|
|
GeglNode *graph;
|
|
|
|
GeglNode *input;
|
|
|
|
GeglNode *op;
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
gint width = gegl_buffer_get_width (mask);
|
|
|
|
gint height = gegl_buffer_get_height (mask);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
/* Compute a distance map for the line art. */
|
2018-11-26 19:26:54 +08:00
|
|
|
dist = g_new (gfloat, width * height);
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
graph = gegl_node_new ();
|
|
|
|
input = gegl_node_new_child (graph,
|
|
|
|
"operation", "gegl:buffer-source",
|
|
|
|
"buffer", mask,
|
|
|
|
NULL);
|
|
|
|
op = gegl_node_new_child (graph,
|
|
|
|
"operation", "gegl:distance-transform",
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
"metric", GEGL_DISTANCE_METRIC_EUCLIDEAN,
|
2018-10-11 02:28:47 +08:00
|
|
|
"normalize", FALSE,
|
|
|
|
NULL);
|
2018-11-26 19:26:54 +08:00
|
|
|
gegl_node_connect_to (input, "output", op, "input");
|
|
|
|
gegl_node_blit (op, 1.0, gegl_buffer_get_extent (mask),
|
|
|
|
NULL, dist, GEGL_AUTO_ROWSTRIDE, GEGL_BLIT_DEFAULT);
|
2018-10-11 02:28:47 +08:00
|
|
|
g_object_unref (graph);
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
thickness = g_new0 (gfloat, width * height);
|
2018-10-12 00:18:52 +08:00
|
|
|
gi = gegl_buffer_iterator_new (mask, NULL, 0, NULL,
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
|
2018-10-12 00:18:52 +08:00
|
|
|
while (gegl_buffer_iterator_next (gi))
|
|
|
|
{
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
guint8 *m = (guint8*) gi->items[0].data;
|
|
|
|
gint startx = gi->items[0].roi.x;
|
|
|
|
gint starty = gi->items[0].roi.y;
|
|
|
|
gint endy = starty + gi->items[0].roi.height;
|
|
|
|
gint endx = startx + gi->items[0].roi.width;
|
|
|
|
gint x;
|
|
|
|
gint y;
|
2018-10-14 22:51:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gegl_buffer_iterator_stop (gi);
|
|
|
|
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
for (y = starty; y < endy; y++)
|
|
|
|
for (x = startx; x < endx; x++)
|
|
|
|
{
|
|
|
|
if (*m && dist[x + y * width] == 1.0)
|
|
|
|
{
|
|
|
|
gint dx = x;
|
|
|
|
gint dy = y;
|
|
|
|
gfloat d = 1.0;
|
|
|
|
gfloat nd;
|
|
|
|
gboolean neighbour_thicker = TRUE;
|
2018-10-12 00:18:52 +08:00
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
while (neighbour_thicker)
|
|
|
|
{
|
|
|
|
gint px = dx - 1;
|
|
|
|
gint py = dy - 1;
|
|
|
|
gint nx = dx + 1;
|
|
|
|
gint ny = dy + 1;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
neighbour_thicker = FALSE;
|
|
|
|
if (px >= 0)
|
|
|
|
{
|
|
|
|
if ((nd = dist[px + dy * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dx = px;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (py >= 0 && (nd = dist[px + py * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dx = px;
|
|
|
|
dy = py;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (ny < height && (nd = dist[px + ny * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dx = px;
|
|
|
|
dy = ny;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nx < width)
|
|
|
|
{
|
|
|
|
if ((nd = dist[nx + dy * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dx = nx;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (py >= 0 && (nd = dist[nx + py * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dx = nx;
|
|
|
|
dy = py;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (ny < height && (nd = dist[nx + ny * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dx = nx;
|
|
|
|
dy = ny;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (py > 0 && (nd = dist[dx + py * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dy = py;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (ny < height && (nd = dist[dx + ny * width]) > d)
|
|
|
|
{
|
|
|
|
d = nd;
|
|
|
|
dy = ny;
|
|
|
|
neighbour_thicker = TRUE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
thickness[(gint) x + (gint) y * width] = d;
|
|
|
|
}
|
|
|
|
m++;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
end:
|
2018-11-22 21:32:59 +08:00
|
|
|
g_free (dist);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
g_clear_pointer (&thickness, g_free);
|
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
return thickness;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
|
2019-02-08 00:44:35 +08:00
|
|
|
static void
|
|
|
|
gimp_line_art_simple_fill (GeglBuffer *buffer,
|
|
|
|
gint x,
|
2019-02-16 19:27:32 +08:00
|
|
|
gint y,
|
|
|
|
gint *counter)
|
2019-02-08 00:44:35 +08:00
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
|
2019-02-16 19:27:32 +08:00
|
|
|
if (x < 0 || x >= gegl_buffer_get_width (buffer) ||
|
|
|
|
y < 0 || y >= gegl_buffer_get_height (buffer) ||
|
2020-04-30 20:56:30 +08:00
|
|
|
*counter <= 0)
|
2019-02-08 00:44:35 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
gegl_buffer_sample (buffer, x, y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
|
|
|
|
if (! val)
|
|
|
|
{
|
|
|
|
val = 1;
|
|
|
|
gegl_buffer_set (buffer, GEGL_RECTANGLE (x, y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
2019-02-16 19:27:32 +08:00
|
|
|
(*counter)--;
|
|
|
|
gimp_line_art_simple_fill (buffer, x + 1, y, counter);
|
|
|
|
gimp_line_art_simple_fill (buffer, x - 1, y, counter);
|
|
|
|
gimp_line_art_simple_fill (buffer, x, y + 1, counter);
|
|
|
|
gimp_line_art_simple_fill (buffer, x, y - 1, counter);
|
2019-02-08 00:44:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
static guint
|
|
|
|
visited_hash_fun (Pixel *key)
|
|
|
|
{
|
|
|
|
/* Cantor pairing function. */
|
|
|
|
return (key->x + key->y) * (key->x + key->y + 1) / 2 + key->y;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
visited_equal_fun (Pixel *e1,
|
|
|
|
Pixel *e2)
|
|
|
|
{
|
|
|
|
return (e1->x == e2->x && e1->y == e2->y);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline gboolean
|
|
|
|
border_in_direction (GeglBuffer *mask,
|
|
|
|
Pixel p,
|
|
|
|
int direction)
|
|
|
|
{
|
|
|
|
gint px = (gint) p.x + DeltaX[direction];
|
|
|
|
gint py = (gint) p.y + DeltaY[direction];
|
|
|
|
|
|
|
|
if (px >= 0 && px < gegl_buffer_get_width (mask) &&
|
|
|
|
py >= 0 && py < gegl_buffer_get_height (mask))
|
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
|
|
|
|
gegl_buffer_sample (mask, px, py, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
return ! ((gboolean) val);
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline GimpVector2
|
2018-10-17 21:53:03 +08:00
|
|
|
pair2normal (Pixel p,
|
|
|
|
gfloat *normals,
|
|
|
|
gint width)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-10-17 21:53:03 +08:00
|
|
|
return gimp_vector2_new (normals[((gint) p.x + (gint) p.y * width) * 2],
|
|
|
|
normals[((gint) p.x + (gint) p.y * width) * 2 + 1]);
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
/* Edgel functions */
|
|
|
|
|
|
|
|
static Edgel *
|
|
|
|
gimp_edgel_new (int x,
|
|
|
|
int y,
|
|
|
|
Direction direction)
|
|
|
|
{
|
|
|
|
Edgel *edgel = g_new (Edgel, 1);
|
|
|
|
|
|
|
|
edgel->x = x;
|
|
|
|
edgel->y = y;
|
|
|
|
edgel->direction = direction;
|
|
|
|
|
|
|
|
gimp_edgel_init (edgel);
|
|
|
|
|
|
|
|
return edgel;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_edgel_init (Edgel *edgel)
|
|
|
|
{
|
|
|
|
edgel->x_normal = 0;
|
|
|
|
edgel->y_normal = 0;
|
|
|
|
edgel->curvature = 0;
|
|
|
|
edgel->next = edgel->previous = G_MAXUINT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_edgel_clear (Edgel **edgel)
|
|
|
|
{
|
|
|
|
g_clear_pointer (edgel, g_free);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
gimp_edgel_cmp (const Edgel* e1,
|
|
|
|
const Edgel* e2)
|
|
|
|
{
|
|
|
|
gimp_assert (e1 && e2);
|
|
|
|
|
|
|
|
if ((e1->x == e2->x) && (e1->y == e2->y) &&
|
|
|
|
(e1->direction == e2->direction))
|
|
|
|
return 0;
|
|
|
|
else if ((e1->y < e2->y) || (e1->y == e2->y && e1->x < e2->x) ||
|
|
|
|
(e1->y == e2->y && e1->x == e2->x && e1->direction < e2->direction))
|
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static guint
|
|
|
|
edgel2index_hash_fun (Edgel *key)
|
|
|
|
{
|
|
|
|
/* Cantor pairing function.
|
|
|
|
* Was not sure how to use the direction though. :-/
|
|
|
|
*/
|
|
|
|
return (key->x + key->y) * (key->x + key->y + 1) / 2 + key->y * key->direction;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
edgel2index_equal_fun (Edgel *e1,
|
|
|
|
Edgel *e2)
|
|
|
|
{
|
|
|
|
return (e1->x == e2->x && e1->y == e2->y &&
|
|
|
|
e1->direction == e2->direction);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mask;
|
|
|
|
* @edgel:
|
|
|
|
* @size_limit:
|
|
|
|
*
|
|
|
|
* Track a border, marking inner pixels with a bit corresponding to the
|
|
|
|
* edgel traversed (4 << direction) for direction in {0,1,2,3}.
|
|
|
|
* Stop tracking after @size_limit edgels have been visited.
|
|
|
|
*
|
|
|
|
* Returns: Number of visited edgels, or -1 if an already visited edgel
|
|
|
|
* has been encountered.
|
|
|
|
*/
|
|
|
|
static glong
|
|
|
|
gimp_edgel_track_mark (GeglBuffer *mask,
|
|
|
|
Edgel edgel,
|
|
|
|
long size_limit)
|
|
|
|
{
|
|
|
|
Edgel start = edgel;
|
|
|
|
long count = 1;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
guchar val;
|
|
|
|
|
|
|
|
gimp_edgelset_next8 (mask, &edgel, &edgel);
|
|
|
|
gegl_buffer_sample (mask, edgel.x, edgel.y, NULL, &val,
|
|
|
|
NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
|
|
|
|
if (val & 2)
|
|
|
|
{
|
|
|
|
/* Only mark pixels of the spline/segment */
|
|
|
|
if (val & (4 << edgel.direction))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Mark edgel in pixel (1 == In Mask, 2 == Spline/Segment) */
|
|
|
|
val |= (4 << edgel.direction);
|
|
|
|
gegl_buffer_set (mask, GEGL_RECTANGLE (edgel.x, edgel.y, 1, 1), 0,
|
|
|
|
NULL, &val, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
}
|
|
|
|
if (gimp_edgel_cmp (&edgel, &start) != 0)
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
while (gimp_edgel_cmp (&edgel, &start) != 0 && count <= size_limit);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2019-02-14 20:46:09 +08:00
|
|
|
/**
|
|
|
|
* gimp_edgel_region_area:
|
|
|
|
* @mask: current state of closed line art buffer.
|
|
|
|
* @start_edgel: edgel to follow.
|
|
|
|
*
|
|
|
|
* Follows a line border, starting from @start_edgel to compute the area
|
|
|
|
* enclosed by this border.
|
|
|
|
* Unfortunately this may return a negative area when the line does not
|
2019-10-11 03:39:09 +08:00
|
|
|
* close a zone. In this case, there is an uncertainty on the size of
|
2019-02-14 20:46:09 +08:00
|
|
|
* the created zone, and we should consider it a big size.
|
|
|
|
*
|
|
|
|
* Returns: the area enclosed by the followed line, or a negative value
|
|
|
|
* if the zone is not closed (hence actual area unknown).
|
|
|
|
*/
|
2018-10-11 02:28:47 +08:00
|
|
|
static glong
|
|
|
|
gimp_edgel_region_area (const GeglBuffer *mask,
|
|
|
|
Edgel start_edgel)
|
|
|
|
{
|
|
|
|
Edgel edgel = start_edgel;
|
2019-02-10 06:13:58 +08:00
|
|
|
glong area = 0;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (edgel.direction == XPlusDirection)
|
2019-02-10 06:13:58 +08:00
|
|
|
area -= edgel.x;
|
2018-10-11 02:28:47 +08:00
|
|
|
else if (edgel.direction == XMinusDirection)
|
2019-02-10 06:13:58 +08:00
|
|
|
area += edgel.x - 1;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
gimp_edgelset_next8 (mask, &edgel, &edgel);
|
|
|
|
}
|
|
|
|
while (gimp_edgel_cmp (&edgel, &start_edgel) != 0);
|
|
|
|
|
|
|
|
return area;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Edgel sets */
|
|
|
|
|
|
|
|
static GArray *
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_edgelset_new (GeglBuffer *buffer,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-10-14 21:30:38 +08:00
|
|
|
GeglBufferIterator *gi;
|
|
|
|
GArray *set;
|
|
|
|
GHashTable *edgel2index;
|
|
|
|
gint width = gegl_buffer_get_width (buffer);
|
|
|
|
gint height = gegl_buffer_get_height (buffer);
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
set = g_array_new (TRUE, TRUE, sizeof (Edgel *));
|
|
|
|
g_array_set_clear_func (set, (GDestroyNotify) gimp_edgel_clear);
|
|
|
|
|
2018-10-14 21:30:38 +08:00
|
|
|
if (width <= 1 || height <= 1)
|
|
|
|
return set;
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
edgel2index = g_hash_table_new ((GHashFunc) edgel2index_hash_fun,
|
|
|
|
(GEqualFunc) edgel2index_equal_fun);
|
|
|
|
|
2018-10-27 17:29:55 +08:00
|
|
|
gi = gegl_buffer_iterator_new (buffer, GEGL_RECTANGLE (0, 0, width, height),
|
2018-10-14 21:30:38 +08:00
|
|
|
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 5);
|
2018-10-27 17:29:55 +08:00
|
|
|
gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (0, -1, width, height),
|
2018-10-14 21:30:38 +08:00
|
|
|
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
|
2018-10-27 17:29:55 +08:00
|
|
|
gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (0, 1, width, height),
|
2018-10-14 21:30:38 +08:00
|
|
|
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
|
2018-10-27 17:29:55 +08:00
|
|
|
gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (-1, 0, width, height),
|
2018-10-14 21:30:38 +08:00
|
|
|
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
|
2018-10-27 17:29:55 +08:00
|
|
|
gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (1, 0, width, height),
|
2018-10-14 21:30:38 +08:00
|
|
|
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
|
|
|
|
while (gegl_buffer_iterator_next (gi))
|
|
|
|
{
|
|
|
|
guint8 *p = (guint8*) gi->items[0].data;
|
|
|
|
guint8 *prevy = (guint8*) gi->items[1].data;
|
|
|
|
guint8 *nexty = (guint8*) gi->items[2].data;
|
|
|
|
guint8 *prevx = (guint8*) gi->items[3].data;
|
|
|
|
guint8 *nextx = (guint8*) gi->items[4].data;
|
|
|
|
gint startx = gi->items[0].roi.x;
|
|
|
|
gint starty = gi->items[0].roi.y;
|
|
|
|
gint endy = starty + gi->items[0].roi.height;
|
|
|
|
gint endx = startx + gi->items[0].roi.width;
|
|
|
|
gint x;
|
|
|
|
gint y;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
2019-03-07 04:39:29 +08:00
|
|
|
gegl_buffer_iterator_stop (gi);
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2018-10-14 21:30:38 +08:00
|
|
|
for (y = starty; y < endy; y++)
|
|
|
|
for (x = startx; x < endx; x++)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
2018-10-14 21:30:38 +08:00
|
|
|
if (*(p++))
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
if (! *prevy)
|
2018-10-14 21:30:38 +08:00
|
|
|
gimp_edgelset_add (set, x, y, YMinusDirection, edgel2index);
|
2018-11-29 21:12:39 +08:00
|
|
|
if (! *nexty)
|
2018-10-14 21:30:38 +08:00
|
|
|
gimp_edgelset_add (set, x, y, YPlusDirection, edgel2index);
|
2018-11-29 21:12:39 +08:00
|
|
|
if (! *prevx)
|
2018-10-14 21:30:38 +08:00
|
|
|
gimp_edgelset_add (set, x, y, XMinusDirection, edgel2index);
|
2018-11-29 21:12:39 +08:00
|
|
|
if (! *nextx)
|
2018-10-14 21:30:38 +08:00
|
|
|
gimp_edgelset_add (set, x, y, XPlusDirection, edgel2index);
|
|
|
|
}
|
|
|
|
prevy++;
|
|
|
|
nexty++;
|
|
|
|
prevx++;
|
|
|
|
nextx++;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
2018-10-14 21:30:38 +08:00
|
|
|
}
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_edgelset_build_graph (set, buffer, edgel2index, async);
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
goto end;
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
gimp_edgelset_init_normals (set);
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
end:
|
|
|
|
g_hash_table_destroy (edgel2index);
|
|
|
|
|
|
|
|
if (gimp_async_is_stopped (async))
|
|
|
|
{
|
|
|
|
g_array_free (set, TRUE);
|
|
|
|
set = NULL;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
return set;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_edgelset_add (GArray *set,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
Direction direction,
|
|
|
|
GHashTable *edgel2index)
|
|
|
|
{
|
|
|
|
Edgel *edgel = gimp_edgel_new (x, y, direction);
|
|
|
|
unsigned long position = set->len;
|
|
|
|
|
|
|
|
g_array_append_val (set, edgel);
|
|
|
|
g_hash_table_insert (edgel2index, edgel, GUINT_TO_POINTER (position));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_edgelset_init_normals (GArray *set)
|
|
|
|
{
|
|
|
|
Edgel **e = (Edgel**) set->data;
|
|
|
|
|
|
|
|
while (*e)
|
|
|
|
{
|
|
|
|
GimpVector2 n = Direction2Normal[(*e)->direction];
|
|
|
|
|
|
|
|
(*e)->x_normal = n.x;
|
|
|
|
(*e)->y_normal = n.y;
|
|
|
|
e++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_edgelset_smooth_normals (GArray *set,
|
|
|
|
int mask_size,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
const gfloat sigma = mask_size * 0.775;
|
|
|
|
const gfloat den = 2 * sigma * sigma;
|
|
|
|
gfloat weights[65];
|
|
|
|
GimpVector2 smoothed_normal;
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
gimp_assert (mask_size <= 65);
|
|
|
|
|
|
|
|
weights[0] = 1.0f;
|
|
|
|
for (int i = 1; i <= mask_size; ++i)
|
|
|
|
weights[i] = expf (-(i * i) / den);
|
|
|
|
|
|
|
|
for (i = 0; i < set->len; i++)
|
|
|
|
{
|
|
|
|
Edgel *it = g_array_index (set, Edgel*, i);
|
|
|
|
Edgel *edgel_before = g_array_index (set, Edgel*, it->previous);
|
|
|
|
Edgel *edgel_after = g_array_index (set, Edgel*, it->next);
|
|
|
|
int n = mask_size;
|
|
|
|
int i = 1;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
smoothed_normal = Direction2Normal[it->direction];
|
|
|
|
while (n-- && (edgel_after != edgel_before))
|
|
|
|
{
|
|
|
|
smoothed_normal = gimp_vector2_add_val (smoothed_normal,
|
|
|
|
gimp_vector2_mul_val (Direction2Normal[edgel_before->direction], weights[i]));
|
|
|
|
smoothed_normal = gimp_vector2_add_val (smoothed_normal,
|
|
|
|
gimp_vector2_mul_val (Direction2Normal[edgel_after->direction], weights[i]));
|
|
|
|
edgel_before = g_array_index (set, Edgel *, edgel_before->previous);
|
|
|
|
edgel_after = g_array_index (set, Edgel *, edgel_after->next);
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
gimp_vector2_normalize (&smoothed_normal);
|
|
|
|
it->x_normal = smoothed_normal.x;
|
|
|
|
it->y_normal = smoothed_normal.y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
gimp_edgelset_compute_curvature (GArray *set,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
for (i = 0; i < set->len; i++)
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
Edgel *it = g_array_index (set, Edgel*, i);
|
|
|
|
Edgel *previous = g_array_index (set, Edgel *, it->previous);
|
|
|
|
Edgel *next = g_array_index (set, Edgel *, it->next);
|
|
|
|
GimpVector2 n_prev = gimp_vector2_new (previous->x_normal, previous->y_normal);
|
|
|
|
GimpVector2 n_next = gimp_vector2_new (next->x_normal, next->y_normal);
|
|
|
|
GimpVector2 diff = gimp_vector2_mul_val (gimp_vector2_sub_val (n_next, n_prev),
|
|
|
|
0.5);
|
|
|
|
const float c = gimp_vector2_length_val (diff);
|
|
|
|
const float crossp = n_prev.x * n_next.y - n_prev.y * n_next.x;
|
2018-10-11 02:28:47 +08:00
|
|
|
|
app: improve end point detection for smart colorization.
Previous algorithm was relying on strokes of small radius to detect
points of interest. In order to work with various sizes of strokes, we
were computing an approximate median stroke thickness, then using this
median value to erode the binary line art.
Unfortunately this was not working that well for very fat strokes, and
also it was potentially opening holes in the line art. These holes were
usually filled back later during the spline and segment creations. Yet
it could not be totally assured, and we had some experience where color
filling would leak out of line art zones without any holes from the
start (which is the opposite of where this new feature is supposed to
go)!
This updated code computes instead some radius estimate for every border
point of strokes, and the detection of end points uses this information
of local thickness. Using local approximation is obviously much more
accurate than a single thickness approximation for the whole drawing,
while not making the processing slower (in particular since we got rid
of the quite expensive erosion step).
This fixes the aforementionned issues (i.e. work better with fat strokes
and do not create invisible holes in closed lines), and also is not
subject to the problem of mistakenly increasing median radius when you
color fill in sample merge mode (i.e. using also the color data in the
input)!
Also it is algorithmically less intensive, which is obviously very good.
This new version of the algorithm is a reimplementation in GIMP of new
code by Sébastien Fourey and David Tschumperlé, as a result of our many
discussions and tests with the previous algorithm.
Note that we had various tests, experiments and propositions to try and
improve these issues. Skeletonization was evoked, but would have been
most likely much slower. Simpler erosion based solely on local radius
was also a possibility but it may have created too much noise (skeleton
barbs), with high curvature, hence may have created too many new
artificial endpoints.
This new version also creates more endpoints though (and does not seem
to lose any previously detected endpoints), which may be a bit annoying
yet acceptable with the new bucket fill stroking interaction. In any
case, on simple examples, it seems to do the job quite well.
2018-11-17 02:54:38 +08:00
|
|
|
it->curvature = (crossp > 0.0f) ? c : -c;
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
|
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_edgelset_build_graph (GArray *set,
|
|
|
|
GeglBuffer *buffer,
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
GHashTable *edgel2index,
|
|
|
|
GimpAsync *async)
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
Edgel edgel;
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
for (i = 0; i < set->len; i++)
|
|
|
|
{
|
|
|
|
Edgel *neighbor;
|
|
|
|
Edgel *it = g_array_index (set, Edgel *, i);
|
|
|
|
guint neighbor_pos;
|
|
|
|
|
app: allow canceling line-art computation
Line-art computation can take a long time, and it's therefore
desirable for it to be interruptable. While we do cancel the line-
art async when its result is no longer needed, most parts of the
computation don't respond to the cancelation request, leaving the
async operation running in the background, blocking subsequent
async operations.
Implement cancelation support of line-art computation, by passing
down the async object to the various functions, and periodically
checking for its cancelation at various points. When the async is
canceled, we quickly abort the operation.
Even though cancelation now happens relatively quickly, some parts
of the computation are still uninterruptable and may incur some
latency, so we avoid waiting for the async opration to be aborted
after cancelation, as we did before.
2019-03-07 04:04:39 +08:00
|
|
|
if (gimp_async_is_canceled (async))
|
|
|
|
{
|
|
|
|
gimp_async_abort (async);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-11 02:28:47 +08:00
|
|
|
gimp_edgelset_next8 (buffer, it, &edgel);
|
|
|
|
|
|
|
|
gimp_assert (g_hash_table_contains (edgel2index, &edgel));
|
|
|
|
neighbor_pos = GPOINTER_TO_UINT (g_hash_table_lookup (edgel2index, &edgel));
|
|
|
|
it->next = neighbor_pos;
|
|
|
|
neighbor = g_array_index (set, Edgel *, neighbor_pos);
|
|
|
|
neighbor->previous = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_edgelset_next8 (const GeglBuffer *buffer,
|
|
|
|
Edgel *it,
|
|
|
|
Edgel *n)
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
guint8 pixels[9];
|
2018-10-11 02:28:47 +08:00
|
|
|
|
|
|
|
n->x = it->x;
|
|
|
|
n->y = it->y;
|
|
|
|
n->direction = it->direction;
|
2018-11-29 21:12:39 +08:00
|
|
|
|
|
|
|
gegl_buffer_get ((GeglBuffer *) buffer,
|
|
|
|
GEGL_RECTANGLE (n->x - 1, n->y - 1, 3, 3),
|
|
|
|
1.0, NULL, pixels, GEGL_AUTO_ROWSTRIDE,
|
|
|
|
GEGL_ABYSS_NONE);
|
2018-10-11 02:28:47 +08:00
|
|
|
switch (n->direction)
|
|
|
|
{
|
|
|
|
case XPlusDirection:
|
2018-11-29 21:12:39 +08:00
|
|
|
if (pixels[8])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
++(n->y);
|
|
|
|
++(n->x);
|
|
|
|
n->direction = YMinusDirection;
|
|
|
|
}
|
2018-11-29 21:12:39 +08:00
|
|
|
else if (pixels[7])
|
|
|
|
{
|
|
|
|
++(n->y);
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
else
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
n->direction = YPlusDirection;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YMinusDirection:
|
2018-11-29 21:12:39 +08:00
|
|
|
if (pixels[2])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
++(n->x);
|
|
|
|
--(n->y);
|
|
|
|
n->direction = XMinusDirection;
|
|
|
|
}
|
2018-11-29 21:12:39 +08:00
|
|
|
else if (pixels[5])
|
|
|
|
{
|
|
|
|
++(n->x);
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
else
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
n->direction = XPlusDirection;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XMinusDirection:
|
2018-11-29 21:12:39 +08:00
|
|
|
if (pixels[0])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
--(n->x);
|
|
|
|
--(n->y);
|
|
|
|
n->direction = YPlusDirection;
|
|
|
|
}
|
2018-11-29 21:12:39 +08:00
|
|
|
else if (pixels[1])
|
|
|
|
{
|
|
|
|
--(n->y);
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
else
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
n->direction = YMinusDirection;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case YPlusDirection:
|
2018-11-29 21:12:39 +08:00
|
|
|
if (pixels[6])
|
2018-10-11 02:28:47 +08:00
|
|
|
{
|
|
|
|
--(n->x);
|
|
|
|
++(n->y);
|
|
|
|
n->direction = XPlusDirection;
|
|
|
|
}
|
2018-11-29 21:12:39 +08:00
|
|
|
else if (pixels[3])
|
|
|
|
{
|
|
|
|
--(n->x);
|
|
|
|
}
|
2018-10-11 02:28:47 +08:00
|
|
|
else
|
|
|
|
{
|
2018-11-29 21:12:39 +08:00
|
|
|
n->direction = XMinusDirection;
|
2018-10-11 02:28:47 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2018-11-29 21:12:39 +08:00
|
|
|
g_return_if_reached ();
|
2018-10-11 02:28:47 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|