In gimp_drawable_fill_buffer(), when the fill-source is a pattern,
avoid going through an intermediate buffer when there's no profile
transform, and use the destination-buffer format for the
intermediate buffer, instead of the pattern format, when there is a
profile transform.
Add gimp_fill_options_get_format(), which returns the format to be
used for the fill buffer; this is the same format used during
compositing. Use this format in gimp_fill_options_create_buffer(),
instead of the drawable format.
This fixes the result of fill operations when the fill color/
pattern is not representable in the drawable format, and speeds up
color fills by avoiding color-conversion for the fill buffer during
processing.
In gimp_drawable_real_apply_buffer(), use GimpChunkIterator to blit
the applicator's output to the drawable's buffer in chunks, to
minimize the space used for intermediate results.
In GimpChunkIterator, redajust the target area at each step,
instead of at each iteration, to adapt more quickly to the current
processing speed. To avoid creating uneven chunks as a result,
only change the chunk height at the beginning of rows, unless the
resulting area would be more than twice as big as the target area.
In GimpProjection, store the priority rect in image coordinates,
and only convert it to projectable coordinates when initializing
the chunk-iterator's priority rect. This allows us to preserve the
priority rect across projectable structure/bounds changes.
In gimp_drawable_merge_filter(), don't disable the filter
applicator's output-format conversion node if the output format is
different than the drawable's format, since it may change the
result.
In gimp_drawable_merge_filter(), disable the filter applicator's
cache and output-format conversion nodes before processing the
uncached region of the filter, so that the result is written
directly to the drawable's buffer.
... immediately after an image precision change
When flushing a projection, make sure it has a buffer, instead of
bailing if it doesn't. We rely on the image projection's "update"
signal to update the display after certain operations that free the
buffer, which would previously fail to happen, and cause subsequent
flushes to be ignored until the buffer is explicitly accessed.
This fixes commit b07f810273.
In gimp_group_layer_get_size(), make sure to always set *width and
*height, even when the group is empty, so that when the function is
called through gimp_projectable_get_size() by the group's
projection, the correct size is reported. This makes sure we
update the correct area when the group becomes empty.
In gimpchannel-select, move some of the common functionality of the
various gimp_channel_select_foo() functions to gimpchannel-combine.
Furthermore, don't special-case CHANNEL_OP_INTERSECT, but rather
pass it over to gimpchannel-combine, which is now prepared to
handle it in all functions, as per the previous commits.
In gimpchannel-combine, factor out the common functionality of the
various gimp_channel_combine_foo() functions into a pair of
gimp_channel_combine_{start,end}() functions, which are called
before/after the actual gimp_gegl_mask_combine_foo() function,
respectively. In particular, these functions deal with calculating
the new channel bounds. Previously, the various
gimp_gegl_mask_combine_foo() functions would implicitly invalidate
the channel bounds (since commit
d0ae244fe8), rendering the bounds-
recalculation code ineffective. This avoids manually recalculating
the bounds in many cases, speeding up selection operations.
Improve gimp_gegl_mask_combine_ellipse_rect() -- the funciton
responsible for rendering ellipse/rounded-rectangle selections.
Most notably, this commit significantly improves the function's
performance, by identifying whole tiles, whole rows, or parts of a
row, that are fully inside, or fully outside, the ellipse, and
filling them in bulk, instead of calculating the anti-aliasing
value at each pixel, which is now only done along the
circumference.
This commit also improves anti-aliasing, by more accurately
approximating the distance from a pixel to the ellipse, and by
normalizing the distance according to the pixel's cross-section
length in the direction of the said point. In particular, we
guarantee that pixels that are fully inside/outside the ellipse
have a value of 1/0, respectively, facilitating the aforementioned
optimization.
Additionally, this commit fixes various edge cases where several
primitives coincide at a single pixel (in the rounded-rectangle
case), adds support for CHANNEL_OP_INTERSECT, and parallelizes
processing.
Do not take "Sample merge" into account when picking colors in a
single-layer image. The reason is to be able to get the index
information on indexed image. This information is lost otherwise when
using the whole image as a pickable.
Of course, other exceptions are possible, when you'd pick exactly a
colormap color, but I don't think it's worth making the code
extra-complicated for these. My previous commit will anyway already
select the right color in the colormap on common cases. Though it will
still fail to select the right index when several indexes store the same
color, on a multi-layer image, if you check "Sample merged" while the
right index was not the first one amongst the duplicates.
The colormap saves colors as unsigned char, which can be very inaccurate
compared to high precision colors. When adding colors from GimpRGB into
the colormap, use the original value to fill the colormap palette
instead of making a round trip conversion from double to uchar, then
back to double.
This also fixes a direct bug I encountered when adding the current
foreground color in the image colormap. Yet the GimpFgBgEditor or the
GimpColorHistory would still show the color out-of-gamut in cases when
the returned RGB after the roundtrip was not close enough to the
original RGB (even despite using an epsilon in GimpPalette code).
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.
... which is equivalent to gimp_parallel_run_async_independent(),
except that it takes an additional "priority" parameter, which
specifies the task's priority, with 0 being the default priority,
and lower values indicating higher priority. Unlike
gimp_parallel_run_async_full(), the priority parameter doesn't
directly control the task's priority in a queue, but rather, we use
it to control the priority of the task's dedicated thread, on
supported platforms (previously, all independent async tasks would
run with low priority.)
Use low priority when loading fonts, which can take a long time, to
keep the existing behavior.
Remove gimp_item_tree_clear(), added in last commit, and move its
code to gimp_item_tree_dispose(). Likewise, in
gimp_image_dispose(), use g_object_run_dispose() on the image item-
trees, instead of gimp_item_tree_clear().
Add gimp_item_tree_clear(), which removes all the items of a
GimpItemTree, and clear the layers/channels/vectors item trees in
gimp_image_dispose(), *before* finalizing the image, so that the
corresponding items' desctructors are called while the image is
still alive. In particular, this allows the destructors to safely
call gimp_item_is_attached(), which happens when the image has a
floating selection, since commit
8d4e5e0ff7.
... in GIMP 2.10.9 from git
In gimppickable-contiguous-region's pixel_difference() function,
which is used, among other things, by the select-by-color and
fuzzy-select tools, when selecting by LCh/HSV hue, treat a pair of
colors as inifinitely far apart if one of them has positive chroma/
saturation, and the other has chroma/saturation that's very close
to 0; conversely, treat a pair of colors as equal if both of them
have chroma/sautation that's close to 0.
As a result, when the seed color is saturated, gray pixels are
never selected, while when the seed color is desaturated, all, and
only, gray pixels are selected.
When clicking on a line art pixel, only this pixel gets colored, which
is fine for actual (original) line art pixels. But on generated ones
(closure pixels, which are internal only), you end up with a single
pixel colored while the whole surrounding area is empty. This feels like
a bug (even though it was not one technically) as you have no way to
guess you are clicking on a closure pixel.
Instead, when this happens, simulate clicks on all neighbour pixels,
hence potentially coloring all surrounding regions, which is most likely
what you wanted.
On various property changes, only recompute the line art when the
property actually changed. Also add a gimp_line_art_bind_gap_length() to
avoid computing twice the line art when changing both type of closure
(splines and segments) together, as is currently the case.
… Bucket Fill tool options.
This will provide feedback when the line art closure is being computed,
which may be useful on big images where it may take some time. Otherwise
painter may be left hanging without knowing what takes time.
In gimp_pickable_contiguous_region_by_color(), add a small epsilon
to the threshold value, to allow for small errors due to the input
color and pickable pixel-colors being converted to the common
format through different paths.
While we *could* special-case threshold == 0 when the input color
comes from the same pickable, as is the case for the select-by-
color tool, and perform an exact comparison in the original format,
in the more general case the input color can come from an arbitrary
source, such as a plug-in.
In gimp_image_merge_layers() -- the internal function used by the
various layer-merging/flattenning functions -- process the merged-
layer graph in chunks, using gimp_gegl_apply_operation(), instead
of in one go, using gegl_node_blit_buffer(). Processing in chunks
better utilizes the cache, since it reduces the size of
intermediate buffers, reducing the chances of hitting the swap when
merging large images (see, for example, issue #3012.)
Additionally, this allows us to show progress indication. Have the
relevant gimpimage-merge functions take a GimpProgress, and pass it
down to gimp_image_merge_layers(). Adapt all callers.
Just the GimpData::save() and ::copy() part that is needed to
duplicate and rename them, the image-to-pipe logic from the export
plug-in remains to be ported.
- don't clear the names of the individual brushes, we need them for
a load -> save roundtrip
- for the same reason, and for convenience, store the parameter string
in the object
- clean up gimp_brush_pipe_finalize()
In gimp_drawable_filter_sync_affect(), don't mask-out the filter's
alpha component when the drawable doesn't have an alpha channel,
since this is no longer necessary -- we now explicitly convert the
output to the drawable format as part of the graph -- and it
prevents the gimp:mask-components node from becoming a NOP.
In gimp_drawable_get_active_mask(), when the drawable doesn't have
an alpha channel, set or clear the mask's alpha bit, according to
the state of the other bits, so that it never gets in the way of a
fully set/clear mask. The value of the alpha bit doesn't matter
when there's no alpha channel, however, having a uniform mask
allows us to skip component masking altogether.
Additionally, provide a default implementation for
GimpDrawable::get_active_mask() which returns a full mask, and
remove the equivalent implementation for GimpChannel.
Mostly I am adding a counter to the insignifiant zone fill, to be double
sure we are not going to fill huge areas (this should not happen already
anyway) and also it is no use to sample the line art buffer in such
case.
Remove gimp_drawable_replace_buffer(), which is no longer used
anywhere since commits ddb69b77a7 and
3451ffb62c. This eliminates
redundancy, since all compositing is now done through the layer-
mode code.
Furthermore, gimp_drawable_replace_buffer() used the drawable's
active-component array, whose layout depends on the image mode, as
an argument to gimp_gegl_replace(), which always expects an RGBA
component array, resulting in broken component masking in non-RGB
images.
The algorithm to compute a zone area by following its border only works
well for fully closed zones. It may return negative values otherwise.
Let's just assume the created zone is big in this case (which may or may
not be the case, but this is the safe case as it does not prevent
closure creation).
Add GimpData::save() implementation to GimpPattern, and change some
glue code to make patterns editable.
Also implement GimpData::duplicate() in GimpPatternClipboard, which
makes it possible to simply copy an area and duplicate the clipboard
pattern to create a new persistent pattern.
Add some comments and string docs as it is not that obvious to
understand the whole logics, invert the return value (returning TRUE
when the closure line is accepted, instead of the opposite) and rename
it to more appropriate gimp_line_art_allow_closure().
We currently have brush and pattern I/O code in both the core and
plug-ins. This commit starts removing plug-in code in favor of having
one copy of the code in the core, much like XCF loading and saving is
implemented.
Add app/file-data/ module with file procedure registering code, for
now just with an implementation of file-gbr-load.
Remove the file-gbr-load code from the file-gbr plug-in.
The line art imaginary segments/splines are not added when they create
too small zones, unless when these are just too small ("unsignificant").
Why the original algorithm keeps such micro-zones is because there may
be such zones created when several splines or segments are leaving from
a same key point (and we don't necessarily won't to forbid this). Also
we had cases when using very spiky brushes (for the line art) would
create many zones, and such micro-zones would appear just too often
(whereas with very smooth lines, they are much rarer, if not totally
absent most of the time).
Also it is to be noted that the original paper would call these
"unsignificant" indeed, but these are definitely significant for the
artists. Therefore having to "fix" the filling afterwards (with a brush
for instance) kind of defeat the whole purpose of this tool.
I already had code which would special-case (fill) 1-pixel zones in the
end, but bigger micro zones could appear (up to 4 pixels in the current
code, but this could change). Also I don't want to use the "Remove
Holes" (gimp:flood) operation as I want to make sure I remove only
micro-holes created by the line art closure code (not micro-holes from
original line arts in particular).
This code takes care of this issue by filling the micro-holes with
imaginary line art pixels, which may later be potentially bucket filled
when water-filling the line art.
Avoid unnecessary calls to gimp_temp_buf_data_clear() in various
places, where either the entire buffer is being written to, or most
of it is, only requiring clearing the edges.
Promote the precision of generated brushes to 32-bit float, and
modify brush preview generation, and gimpbrushcore-loops, to handle
float brushes. This avoids posterization in large brushes.
Note that non-generated brushes are still uint8.
In GimpTempBuf, add gimp_temp_buf_lock() and gimp_temp_buf_unlock()
functions, which lock/unlock the buffer for data access. Unlike
gimp_temp_buf_get_data(), which returns a direct pointer to the
buffer's data, the new functions take a format parameter and may
return a temporary buffer, allowing the buffer to be accessed using
an arbitrary format.
In gimp_transform_matrix_generic(), apply the resulting matrix even
if the transformation is invalid, since GimpGenericTransformTool
relies on the matrix to properly update the transform-grid widget.
When clearing a channel, do nothing if the channel is already
empty; otherwise, align the cleared rectangle to the channel
buffer's tile grid, so that all affected tiles are dropped, rather
than zeroed. Furthermore, only update the affected region of the
channel.
After discussion with Sébastien Fourey and David Tschumperlé, it was
decided that a better fix for the edge case raised in #2785 was to add a
keypoint anyway, even if the point and none of its neigbours have a
positive smoothed curvature, yet they have a positive raw curvature. In
such case, we use the local maximum raw curvature instead of the local
maximum smoothed curvature.
Additionally to sample merge and active layer, now we can only use the
layer above or below the active layer as line art source.
The line art fill is meant to work on drawing lines. Though sample merge
still is ok in many cases, the more you fill with colors, the more the
line art computation becomes unecessarily complex. Also when you use a
lot of layers with some of them already filled with colors, it makes it
impossible to colorize some line art zones with the tool. Moreover you
just don't want to have to hide every layers out there to colorize one
layer (especially background layers and such as you may want to see the
result with your background).
Thus we want to be able to set the source as a unique layer, while it
not being necessarily the active one (because you want lines and colors
on different layers). In this case, I am assuming that the color and the
line layers are next to each other (most common organization).
In gimp_projection_finish_draw(), clear the chunk iterator's
priority rect before finishing rendering, since it's not needed at
this point, and this is slightly more efficient.
In gimp_projection_chunk_render_start(), when the current
projection rendering is complete, but not finalized yet, and no new
rendering is started (since the current update region is empty),
make sure to invalidate the projectable's preview, since it
normally happens when rendering is finalized, which doesn't happen
in this case.
Factor out the region-chunking logic of GimpProjection into a new
GimpChunkIterator type, providing a generic mechanism for iterating
over a cairo region in discrete chunks. The iterator doesn't
perform any processing itself, but rather dispenses rectangular
chunks, which the user then processes.
Iteration is broken into intervals, the duration of which is
configurable. Each iteration begins with a call to
gimp_chunk_iterator_next(), after which
gimp_chunk_iterator_get_rect() should be called in succession to
fetch a rectangle to process, until it returns FALSE, which marks
the end of the iteration. Updates to the UI should take place in
the interval between iterations, but not during an iteration. The
iterator dynamically adjusts the chunk size according to processing
speed, in order to match the target iteration interval.
The iterator can be given a priority rectangle, which is processed
before the rest of the region. It can also be given a
representative tile rectangle, defining a regular tile grid;
dispensed chunks are aligned to the tile grid as much as possible.
This is sometimes asked, and myself also need to find it from time to
time. I may as well put the link inside the code comments, where it is
just easy to find!
If you click on a zone filled in several visible layers, you don't
necessarily want the top layer. You may want one below. With this
change, as long as you hold alt, you will loop through all candidate
layers from top to bottom (then looping back top when reaching the
bottom).
In a first alt-click, you will always end up to the top candidate.
The scratch allocator has been moved to GEGL (commit
gegl@b99032d799dda3436ffa8c1cc28f8b0d34fb965d). Remove gimp-
scratch, and replace all its uses with gegl-scratch.
...palette views despite selected color being in the currently
selected pallette
As suggested by Massimo, changing the color comparison EPSILON in
gimppalette.c from 1e-10 to 1e-6 fixes this, and is really small
enough.
Also, generally clean up color comparison epsilons:
- use a #define, not hardcoded values for all uses of
gimp_rgb[a]_distance()
- call the #defines RGB_EPSILON and RGBA_EPSILON
- make them all 1e-6 or larger
...text layer, not an image of the text
In gimp_edit_paste_get_layer(), when pasting as floating selection,
collapse the pasted layer into an ordinary layer only if it's a group
layer. There is nothing that speaks against having a floating text
layer, it works just fine.
Add gimp_image_colormap_update_formats() which does:
- always update the cached palette formats based on the current space
of the image
- always set the palette entries on the palette formats
Make sure the function is called whenever the space or the colormap
change.
Fix gimp_image_convert_profile_colormap() to convert the colormap
entries between the right spaces.
Don't just pass the input format's encoding because that loses the
palette. Instead, pass the input format directly so
babl_format_with_space() can do its job of preserving the palette.
In gimp_drawable_edit_fill(), if the fill only affects the alpha
channel, and if the drawable has no alpha channel, or the alpha
channel is masked out, do nothing, instead of unnecessarily
performing the fill, which has no effect.
In GimpDrawableFilter, when updating the entire filter area, only
update the crop/preview area (as set by
gimp_drawable_filter_set_{crop,preview}()), instead of the entire
drawable.
Add gimp_drawable_filter_set_crop(), which allows setting an output
crop rectangle for the filter; anything outside the rectangle
doesn't get filtered. The crop area is combined with the preview
area to determine the filtered area during preview, however, unlike
the preview area, the crop area remains in effect while committing
the filter.
Consequently, when merging a drawable filter, if the filter has a
crop, only process the cropped area.
We're going to use GimpApplicator's output crop for more than just
split previews. Rename gimp_applicator_set_preview() to
gimp_applicator_set_crop(), and add gimp_applicator_get_crop(),
which returns the output crop rectangle, or NULL if cropping is
disabled.
In gimp_drawable_apply_operation(), use a temporary
GimpDrawableFilter to apply the operation, instead of using a
shadow buffer. This renders and composits the op directly into the
drawable buffer, avoiding an intermediate buffer, requiring less
space and speeding up processing.
Remove the use_split_preview and use_result_cache parameters of
gimp_applicator_new(), and allow enabling/disabling the cache
(through gimp_applicator_set_cache()) and the preview crop (through
gimp_applicator_set_preview()) after construction.
Move the preview crop node after the result cache, and remove the
separate preview cache node. This eliminates an extra cache
buffer, reducing the space consumed by filters, and speeds up split
preview, since the cached result now includes the output
compositing.
We now perform the conversion of filter output to the drawable
format as part of the individual filter nodes (see the last few
commits), so there's no need for another conversion after the
filter stack.
This reverts commit d6e0ca5054.
Set the output format of floating-selection applicators to the
target drawable format. We're going to remove the global
GipDrawable convert-format node, which we use to get correct
previews for indexed drawables, so that each filter now has to do
its own format conversion.
In GimpDrawableFilter, set the applicator's output format to the
drawable format, so that the cache uses the drawable format, and so
copying the cached result to the drawble's buffer when comitting
the filter becomes much cheaper, and, in particular, doesn't
require reading tiles out of the swap. This notably improves
commit speed in large images, at the expense of requiring a few
extra conversions during preview.
In GimpLineArt, use the "invalidate-preview" signal of the input
viewable, instead of its "painted" or "rendered" signals, for
asynchronously computing the line art. Subsequently, remove the
aforementioned signals from GimpDrawable and GimpProjection,
respectively. This simplifies the code, and reduces the number of
signals.
This commit completely removes the "Edit -> Fade..." feature,
because...
- The main reason is that "fade" requires us to keep two buffers,
instead of one, for each fadeable undo step, doubling (or worse,
since the extra buffer might have higher precision than the
drawable) the space consumed by these steps. This has notable
impact when editing large images. This overhead is incurred even
when not actually using "fade", and since it seems to be very
rarely used, this is too wasteful.
- "Fade" is broken in 2.10: when comitting a filter, we copy the
cached parts of the result into the apply buffer. However, the
result cache sits after the mode node, while the apply buffer
should contain the result of the filter *before* the mode node,
which can lead to wrong results in the general case.
- The same behavior can be trivially achieved "manually", by
duplicating the layer, editing the duplicate, and changing its
opacity/mode.
- If we really want this feature, now that most filters are GEGL
ops, it makes more sense to just add opacity/mode options to the
filter tool, instead of having this be a separate step.
... in GimpBucketFillOptions for the line art algorithm.
Inside GimpLineArt, there are still 2 properties, but we don't show them
anymore in the Bucket Fill tool options. One of the main reason is
probably that it's hard to differentiate their usage. One is to close
with curved lines, the other with straight segments. Yet we don't
actually have any control on one or the other. All one knows is that you
can have "holes" in your drawing of a given size and you want them
close-like for filling. Only reason I can see to have 2 types of closure
is whether you'd want to totally disable one type of closure (then you
set it to 0). But this is a very limited reason for making the options
less understandable overall, IMO.
So for the time being, let's show up only a single option which sets
both properties in GimpLineArt. As patdavid says "it makes sense as a
first pass".
Also rename the option to shorter/simpler "Maximum gap length". Thanks
to patdavid and pippin for helping on figuring out this better label!
Finally I am bumping the default for the gaps to 100px. The original
values were ok for the basic small images used in demos, but not for
real life image where it was always too short (even 100px may still be
too short actually, but much better than the 20 and 60px from before!).
Improve the speed of gimp_gradient_get_color_at(), which is used by
gimp:gradient during processing when the gradient cache is too big,
by disabling type checking, and inlining and avoiding some function
calls.
Practically it means that the algorithm won't close line art anymore
with both settings at 0. This can nevertheless still be a very useful
tool when you have a drawing style with well-closed lines. In such a
case, you will still profit from the color flooding under the line art
part of the algorithm.
Moreover with such well-closed zones from start, you don't get the
over-segmentation anymore and the threaded processing will be faster
obviously.
This was my initial choice, but the more I think about it, the less I am
sure this was the right choice. There was some common code (as I was
making a common composite bucket fill once the line art was generated),
but there is also a lot of different code and the functions were filled
of exception when we were doing a line art fill. Also though there is a
bit of color works (the way we decide whether a pixel is part of a
stroke or not, though currently this is basic grayscale threshold), this
is really not the same as other criterions. In particular this was made
obvious on the Select by Color tool where the line art criterion was
completely meaningless and would have had to be opted-out!
This commit split a bit the code. Instead of finding the line art in the
criterion list, I add a third choice to the "Fill whole selection"/"Fill
similar colors" radio. In turn I create a new GimpBucketFillArea type
with the 3 choices, and remove line art value from GimpSelectCriterion.
I am not fully happy yet of this code, as it creates a bit of duplicate
code, and I would appreciate to move some code away from gimpdrawable-*
and gimppickable-* files. This may happen later. I break the work in
pieces to not get too messy.
Also this removes access to the smart colorization from the API, but
that's probably ok as I prefer to not freeze options too early in the
process since API needs to be stable. Probably we should get a concept
of experimental API.
The code was too much spread out, in core and tool code, and also it was
made too specific to fill. I'll want to reuse this code at least in the
fuzzy select tool. This will avoid code duplication, and also make this
new process more self-contained and simpler to review later (the
algorithm also has a lot of settings and it is much cleaner to have them
as properties rather than passing these as parameters through many
functions).
The refactoring may not be finished; that's at least a first step.
In GimpProjection's chunk renderer, when the chunk height changes
in the middle of a row, we need to merge the remainder of the
current render area back into the renderer's update region, and
refetch the remainder of the row as the new render area, so that we
don't miss any unrendered area, or re-render already-rendered area,
due to the change in chunk height. However, we should previously
fail to verify that the fetched area is, in fact, the remainder of
the current row, which could cause us to render the wrong area,
missing parts of the update region.
Fix this, by breaking up some of the chunk-renderer fucntions into
smaller sub-functions, and using those in order to explicitly set
the new render area to the remainder of the current row when the
chunk height changes. This also avoids erroneously merging the
unflushed update region of the projection into the renderer's
update region.
Actually, image grids are saved as parasites, so even though older
GIMP versions round their coordinates upon loading, they maintain
the fractional coordinates when re-saving the image, hence bumping
the XCF version is not really necessary.
This reverts commit 13119efda33a7aba323dc13e6a56207a15a9f000.
Fractional-coordinate support for image grids was added in commit
1572bccc9f, right before the
introduction of XCF version 10. While images with fractional grid
coordinates can be loaded with earilier versions of GIMP, the grid
coordinates are rounded to the nearest integer.
Bump the minimal XCF version when saving images with fractional
grid coordinates to 10, which should have been the case all along.
Add a boolean "direct" parameter to gimp_projection_flush_now(),
which specifies if the projection buffer should only be invalidated
(FALSE), or rendered directly (TRUE).
Pass TRUE when flushing the projection during painting, so that the
affected regions are rendered in a single step, instead of tile-by-
tile. We previously only invalidated the projection buffer, but
since we synchronously flush the display right after that, the
invalidated regions would still get rendered, albeit less
efficiently.
Likewise, pass TRUE when benchmarking the projection through the
debug action, and avoid flushing the display, to more accurately
measure the render time.
In gimp_drawable_edit_fill(), when filling/clearing the whole
drawable, without any special compositing (i.e., when there's no
selection, the opacity is 100%, and the layer mode is trivial),
fill/clear the drawable's buffer directly, without using an
applicator. This makes such operations much faster, especially in
big images.
... which is similar to gimp_fill_options_create_buffer(), however,
it fills an existing buffer, instead of creating a new buffer.
Implement gimp_fill_options_create_buffer() in terms of the new
function.
When creating a drawable undo from the drawable's buffer, align the
copied rectangle to the buffer's tile grid, so that all the copied
tiles are COWed, saving memory and gaining speed.
Add applied_x and applied_y fields to GimpDrawableUndo, specifying
the position at which to apply the applied_buffer, so that we apply
it in the right place, even if the undo rect has changed due to
alignment.
gimp-scratch is a fast memory allocator (on the order of magnitude
of alloca()), suitable for small (up to a few megabytes), short-
lived (usually, bound to the current stack-frame) allocations.
Unlike alloca(), gimp-scratch doesn't use the stack, and is
therefore safer, and will also serve bigger requests, by falling-
back to malloc().
The allocator itself is very simple: We keep a per-thread stack of
cached memory blocks (allocated using the normal allocator). When
serving an allocation request, we simply pop the top block off the
stack, and return it. If the block is too small, we replace it with
a big-enough block. When the block is freed, we push it back to
the top of the stack (note that even though each thread uses a
separate stack, blocks can be migrated between threads, i.e.,
allocated on one thread, and freed on another thread, although this
is not really an intended usage pattern.) The idea is that the
stacks will ultimately stabalize to contain blocks that can serve
all the encountered allocation patterns, without needing to reisze
any of the blocks; as a consequence, the amount of scratch memory
allocated at any given time should really be kept to a minimum.
... which is similar to gimp_async_add_callback(), taking an
additional GObject argument. The object is kept alive for the
duration of the callback, and the callback is automatically removed
when the object is destroyed (if it hasn't been already called).
This is analogous to g_signal_connect_object(), compared to
g_signal_connect().
In gimp_async_remove_callback(), if removing the last callback
while the callback idle-source is already pending, cancel the idle
source and unref the async object (the async is reffed when adding
the idle source.)
Use gimp_tile_handler_validate_validate(), added in the last
commit, in GimpProjection, in order to render the projection,
instead of separately invalidating the buffer, undoing the
invalidation, and then rendering the graph. This is more
efficient, and more idiomatic.
Add begin_validate() and end_validate() virtual functions, and
corresponding free functions, to GimpTileHandlerValidate. These
functions are called before/after validation happens, and should
perform any necessary steps to prepare for validation. The default
implementation suspends validation on tile access, so that the
assigned buffer may be accessed without causing validation.
Implement the new functions in GimpTileHandlerProjectable, by
calling gimp_projectable_begin_render() and
gimp_projectable_end_render(), respectively, instead of calling
these functions in the ::validate() implementation (which, in turn,
allows us to use the default ::validate() implementation.)
In GimpProjection, use the new functions in place of
gimp_projectable_{begin,end}_render().
In gimp_projection_finish_draw(), make sure we don't accidentally
re-start the chunk renderer idle source while running the remaining
iterations, in case the chunk height changes, and we need to reinit
the renderer state.
Don't needlessly flush projections whose buffer hasn't been
allocated yet. This can happen when opening an image, in which
case the image is flushed before its projection has a buffer.
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.
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).
When an error occurs, we want to prevent overwriting any previous
version of the file by incomplete contents. So run
g_output_stream_close() with a cancelled GCancellable to do so.
See also discussion in #2565.
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).
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.
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.
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.
... 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
concurrently.
Also, s/! has_alpha/has_alpha/ when looking for transparent pixels,
and quit early, at least during this stage, if the async in
canceled.
When an async that was created through
gimp_parallel_run_async[_full](), and whose execution is still
pending, is being waited-upon, maximize its priority so that it
gets executed before all other pending asyncs.
Note that we deliberately don't simply execute the async in the
calling thread in this case, to allow timed-waits to fail (which is
especially important for gimp_wait()).
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.
Since commit b00037b850, erosion size is not used anymore, as this step
has been removed, and the end point detection now uses local thickness
of strokes instead.
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.
The parallel_distribute() family of functions has been migrated to
GEGL. Remove the gimp_parallel_distribute() functions from
gimp-parallel, and replace all uses of these functions with the
corresponding gegl_parallel_distrubte() functions.
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).
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).
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.