1. 19 Dec, 2018 40 commits
    • Jehan's avatar
      app: flood isolated pixels in smart colorization fill. · 439a44a6
      Jehan authored
      The smart colorization was leaving irritating single pixels in between
      colorized regions, after growing and combining. So let's just flood
      these. We don't flood bigger regions (and in particular don't use
      gimp_gegl_apply_flood()) on purpose, because there may be small yet
      actual regions inside regions which we'd want in other colors. 1-pixel
      regions is the extreme case where chances that one wanted it filled are
      just higher.
      (cherry picked from commit 744d6793)
    • Jehan's avatar
      app: radius map actually not useful during smart colorization grow step. · 4fe5dc5d
      Jehan authored
      The distance map has all the information we need already. Also we will
      actually grow up to the max radius pixel (middle pixel of a stroke).
      After discussing with Aryeom, we realized it was better to fill a stroke
      fully (for cases of overflowing, I already added the "Maximum growing
      size" property anyway).
      (cherry picked from commit 6bec0bc8)
    • Jehan's avatar
      app: add possibility to antialias line art colorization. · c4988efd
      Jehan authored
      (cherry picked from commit d2f9549c)
    • Jehan's avatar
      app: simpler code with gegl_node_blit(). · 53fdd19d
      Jehan authored
      No need to go through an intermediate GeglBuffer when unneeded.
      (cherry picked from commit c32b0ecc)
    • Jehan's avatar
      app: add "line-art-max-grow" property to the bucket fill options. · b00580d4
      Jehan authored
      When flooding the line art, we may overflood it in sample merge (which
      would use color in the line art computation). And if having all colors
      on the same layer, this would go over other colors (making the wrong
      impression that the line art leaked).
      This new option is mostly to keep some control over the mask growth.
      Usually a few pixels is enough for most styles of drawing (though we
      could technically allow for very wide strokes).
      (cherry picked from commit eb042e6c)
    • Jehan's avatar
      app: make sure we reset tool modifier state before saving options. · 706fe079
      Jehan authored
      I had this funny behavior when I was quitting GIMP with the active tool
      using modifiers (for instance bucket fill). Each time I'd quit with
      ctrl-q (and if the image is not dirty), the options would use the value
      from the modifier state and be saved as-is. Hence at next restart, the
      default value was always different!
      (cherry picked from commit dd3d9ab3)
    • Jehan's avatar
      app: properly (bucket) fill created splines and segments in line art. · 19c1f265
      Jehan authored
      For this, I needed distmap of the closed version of the line art (after
      splines and segments are created). This will result in invisible stroke
      borders added when flooding in the end. These invisible borders will
      have a thickness of 0.0, which means that flooding will stop at once
      after these single pixels are filled, which makes it quick, and is
      perfect since created splines and segments are 1-pixel thick anyway.
      Only downside is having to run "gegl:distance-transform" a second time,
      but this still stays fast.
      (cherry picked from commit 5a4754f3)
    • Jehan's avatar
      app: replace gegl:watershed-transform with custom algorithm. · e5bea42b
      Jehan authored
      We don't really need to flow every line art pixel and this new
      implementation is simpler (because we don't actually need over-featured
      watershedding), and a lot lot faster, making the line art bucket fill
      now very reactive.
      For this, I am keeping the computed distance map, as well as local
      thickness map around to be used when flooding the line art pixels
      (basically I try to flood half the stroke thickness).
      Note that there are still some issues with this new implementation as it
      doesn't properly flood yet created (i.e. invisible) splines and
      segments, and in particular the ones between 2 colored sections. I am
      going to fix this next.
      (cherry picked from commit 3467acf0)
    • Jehan's avatar
      app: fix a line art leak in bucket fill tool. · e9b8122e
      Jehan authored
      Introduced in commit b4e12fbb:
      gimp_pickable_contiguous_region_prepare_line_art_async() was running
      gimp_pickable_flush(), which provokes the "rendered" signal on the
      image projection when a change occured. As a result, it was calling
      gimp_bucket_fill_compute_line_art() within itself and since
      tool->priv->async was not set yet, none of the call were canceled. Hence
      the same line art is computed twice, but one is leaked.
      Make sure we block this signal handler as a solution.
      (cherry picked from commit 36c885a6)
    • Jehan's avatar
      app: priority map now unneeded for gegl:watershed-transform. · 29342936
      Jehan authored
      This commit is based on GEGL master as I just made the auxiliary buffer
      of gegl:watershed-transform optional for basic cases.
      It doesn't necessarily makes the whole operation that much faster
      according to my tests, but it makes the code simpler as creating this
      priority map was quite unnecessary.
      (cherry picked from commit 963eef82)
    • Ell's avatar
      app: add gimp_pickable_contiguous_region_prepare_line_art_async() ... · 3c0f1307
      Ell authored
      ... and use in bucket-fill tool
      Add gimp_pickable_contiguous_region_prepare_line_art_async(), which
      computes a line-art asynchronously, and use it in the bucket-fill
      tool, instead of having the tool create the async op.
      This allows the async to keep running even after the pickable dies,
      since we only need the pickable's buffer, and not the pickable
      itself.  Previously, we reffed the pickable for the duration of the
      async, but we could still segfault when unreffing it, if the
      pickable was a drawable, and its parent image had already died.
      Furthermore, let the async work on a copy of the pickable's buffer,
      rather than the pickable's buffer directly.  This avoids some race
      conditions when the pickable is the image (i.e., when "sample
      merged" is active), since then we're using image projection's
      buffer, which is generally unsafe to use in different threads
      Also, s/! has_alpha/has_alpha/ when looking for transparent pixels,
      and quit early, at least during this stage, if the async in
      (cherry picked from commit b4e12fbb)
    • Ell's avatar
      app: in bucket-fill tool, cancel async on tool destruction · 3e5e0e2d
      Ell authored
      When computing line-art, don't ref the bucket-fill tool in the
      async data, and rather cancel any ongoing async upon tool
      destruction, so that the async callback doesn't attept to touch the
      now-dead tool.  This avoids segfaulting in the async callback when
      switching to a different tool, while a line-art async operation is
      Additionally, always cancel any previous async operation in
      gimp_bucket_fill_compute_line_art(), even if not starting a new
      (cherry picked from commit 663a6c70)
    • Ell's avatar
      app: in bucket-fill tool, fix potential leak when computing line-art · 17789431
      Ell authored
      In the line-art async function, pass ownership over the resulting
      buffer to the async object, so that the buffer is properly freed in
      case the async in canceled after line-art computation is complete,
      but before the completion callback is called.
      Also, clear the tool's async pointer in the completion callback, to
      avoid leaking the last issued async.
      (cherry picked from commit 2e45c4c8)
    • Ell's avatar
      app: in the bucket-fill tool, avoid CRITICALs when computing line-art ... · e996020e
      Ell authored
      ... when the current image/drawable are NULL.
      (cherry picked from commit 4575949c)
    • Jehan's avatar
      app: end point detection uses both the end point rate and clamped value. · a83bbf82
      Jehan authored
      (cherry picked from commit 79571231)
    • Jehan's avatar
      app: better handle drawable and image update for line art computation. · 4c3fcdb6
      Jehan authored
      The "update" signal on drawable or projection can actually be emitted
      many times for a single painting event. Just add new signals ("painted"
      on GimpDrawable and "rendered" on GimpProjection) which are emitted once
      for a single update (from user point of view), at the end, after actual
      rendering is done (i.e. after the various "update" signals).
      Also better support the sample merge vs current drawable paths for
      bucket fill.
      (cherry picked from commit 04726533)
    • Jehan's avatar
      app: remove now useless erosion size option. · 61558893
      Jehan authored
      Since commit b00037b8, erosion size is not used anymore, as this step
      has been removed, and the end point detection now uses local thickness
      of strokes instead.
      (cherry picked from commit 3f58a385)
    • Jehan's avatar
      app: improve end point detection for smart colorization. · c1c544f8
      Jehan authored
      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
      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
      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.
      (cherry picked from commit b00037b8)
    • Jehan's avatar
      app: force the image flush after a selection fill. · ee582784
      Jehan authored
      Other bucket fills are now done as filter until committed, but basic
      selection fill is still done automatically. So let's make sure the
      canvas is updated immediately (as it used to be before my changes).
      (cherry picked from commit 287d90ba)
    • Jehan's avatar
      app: fix uninitialized variables. · 8fb83d77
      Jehan authored
      (cherry picked from commit 255f2e1c)
    • Jehan's avatar
      app: recompute line art if needed after a bucket fill commit. · fd0f5c50
      Jehan authored
      (cherry picked from commit 969143c4)
    • Jehan's avatar
      app: update gimp-2-10 for bucket fill changes for smart colorization. · b6227b27
      Jehan authored
      Some differences in PDB between gimp-2-10 and master branches.
    • Jehan's avatar
      app: edit the bucket fill tool options with new line art options. · 6b1d7969
      Jehan authored
      I have not added all the options for this new tool yet, but this sets
      the base. I also added a bit of TODO for several places where we need to
      make it settable, in particular the fuzzy select tool, but also simply
      PDB calls (this will need to be a PDB context settings.
      Maybe also I will want to make some LineArtOptions struct in order not
      to have infinite list of parameters to functions. And at some point, it
      may also be worth splitting a bit process with other type of
      selection/fill (since they barely share any settings anyway).
      Finally I take the opportunity to document a little more the parameters
      to gimp_lineart_close(), which can still be improved later (I should
      have documented these straight away when I re-implemented this all from
      G'Mic code, as I am a bit fuzzy on some details now and will need to
      re-understand code).
      (cherry picked from commit 824af124)
    • Jehan's avatar
      app: bucket fill tool with a "paint-style" interaction. · 808cdc98
      Jehan authored
      Rather than just having a click interaction, let's allow to "paint" with
      the bucket fill. This is very useful for the new "line art" colorization
      since it tends to over-segment the drawing. Therefore being able to
      stroke through the canvas (rather than click, up, move, click, etc.)
      makes the process much simpler. This is also faster since we don't have
      to recompute the line art while a filling is in-progress.
      Note that this new behavior is not only for the line art mode, but also
      any other fill criterion, for which it can also be useful.
      Last change of behavior as a side effect: it is possible to cancel the
      tool changes the usual GIMP way (for instance by right clicking when
      releasing the mouse button).
      (cherry picked from commit e1c40506)
    • Jehan's avatar
      app: make line art pre-computation in threads. · 0e1d8ef6
      Jehan authored
      This makes the speed sensation of the tool much faster as line art can
      be computed in dead time when you start the tool or when you move the
      (cherry picked from commit a3cda4ab)
    • Jehan's avatar
      app: compute line art in advance. · 32f0a49e
      Jehan authored
      Right now, this is mostly meaningless as it is still done sequentially.
      But I am mostly preparing the field to pre-compute the line art as
      background thread.
      (cherry picked from commit f246f404)
    • Jehan's avatar
      devel-docs: small update for GEGL buffer leak debugging. · 6dc959d3
      Jehan authored
      (cherry picked from commit b9de1076)
    • Jehan's avatar
      app: fix line art labellization. · 0cf524e3
      Jehan authored
      The older labelling based off CImg code was broken (probably because of
      me, from my port). Anyway I realized what it was trying to do was too
      generic, which is why we had to fix the result later (labeling all
      non-stroke pixels as 0, etc.). Instead I just implemented a simpler
      labelling and only look for stroke regions. It still over-label a bit
      the painting but a lot less, and is much faster.
      (cherry picked from commit 93a49951)
    • Jehan's avatar
      app: better use GeglBufferIterator! · 4da35468
      Jehan authored
      I don't actually need to loop through borders first. This is what the
      abyss policy is for, and I can simply check the iterator position to
      verify I am within buffer boundaries or not.
      This simplifies the code a lot.
      (cherry picked from commit c4ff8154)
    • Jehan's avatar
      app: directly update the mask buffer with gegl_node_blit_buffer(). · 8557cd8c
      Jehan authored
      No need to create a temporary buffer for this.
      (cherry picked from commit f02993fb)
    • Jehan's avatar
      app: create a simple priority map for line art selection flooding. · 8dc6bcaf
      Jehan authored
      We actually don't need to compute distance map. I just make the simplest
      priority map, with 1 any line art pixel and 0 any other pixel (in mask
      or not), lowest priority being propagated first.
      And let the flooding begin!
      (cherry picked from commit 410c7475)
    • Jehan's avatar
      app: with recent gegl:watershed-transform, no need for intermediate... · 687d0584
      Jehan authored
      ... labels buffer.
      We can watershed directly the mask buffer being correctly flagged.
      This commit relies on merge request gegl!8 being accepted and merged.
      (cherry picked from commit e905ea7b)
    • Jehan's avatar
      app: use char array for temporary data (rather than a GEGL buffer). · 3b0a312e
      Jehan authored
      Also use more GeglBufferIterator on input GEGL buffer.
      Using a char array is much less expensive and accelerated the line
      erosion a lot!
      Moving to GeglBufferIterator is not finished, but I do in steps.
      (cherry picked from commit 0c80f8a7)
    • Jehan's avatar
      app: use simpler allocated variables. · 325398fc
      Jehan authored
      Allocating double-level arrays is just very inefficient.
      (cherry picked from commit f975f15e)
    • Jehan's avatar
      app: make visited into single-level allocated array. · 95bbb262
      Jehan authored
      (cherry picked from commit f19181dc)
    • Jehan's avatar
      app: fix stroke labels in gimp_lineart_estimate_stroke_width(). · 71ec6a98
      Jehan authored
      I must make sure that stroke pixels are labelled 0 and non-stroke other
      than 0.
      (cherry picked from commit 910d7934)
    • Jehan's avatar
      app: use more GeglBufferIterator. · b1a3792a
      Jehan authored
      In this case, it makes the code a bit more messy, but hopefully more
      (cherry picked from commit 1822ea39)
    • Jehan's avatar
      app: use GeglBufferIterator rather than gegl_buffer_sample|set(). · 6dbd12a9
      Jehan authored
      (cherry picked from commit 041a8f1e)
    • Jehan's avatar
      app: babl types returned by choose_format() must be float! · 13466ba7
      Jehan authored
      (cherry picked from commit fcd038eb)
    • Jehan's avatar
      app: implement second step for line art selection/filling. · 239be8ec
      Jehan authored
      When filling colors in line arts, you don't want to leave space between
      the strokes and the color, which usually happen with any of the current
      selection methods.
      A "KISS" trick is usually to grow your selection a few pixels before
      filling (adding an additional step in colorization process), which
      obviously does not handle all cases (depending on drawing style and
      stroke size, you may need to grow more or less) as it doesn't take into
      account actual stroke geometry.
      Instead, I label the selection and the "rest" differently and leave the
      pixel strokes unlabelled. Then I let these unlabelled pixels be flooded
      by the "gegl:watershed-transform" operation.
      Note that this second step is different from the second step from the
      GREYC research paper, as they use their own watershed algorithm taking
      color spots as sources to color the whole image at once. This is a
      different workflow from the one using bucket fill with a single color
      (cherry picked from commit 8502b4e7)