This is a continuation of #5888 as I realized that most layer modes were
fixed with my commit b3fc24268a (and follow-up f40dc40cbc) but at least
2 were still crashing GIMP: "Luma Lighten/Darken only" modes.
There were 2 bugs here:
* The first bug was that when gimp_operation_layer_mode_real_process()
ran, gimp_operation_layer_mode_prepare() had not been run yet.
prepare() is called before the process() of GeglOperation, but it
would seem the process() of GimpOperationLayerMode on the other end
happens before GeglOperation's prepare() is run. I am absolutely
unsure if this is expected or not and have a hard time figuring out
all the details of the C/C++ cohabitation.
As a solution, I am moving out the fish caching (the needed part
inside gimp_operation_layer_mode_real_process()) in its own function
so that I can easily call it separately before inspecting the fishes.
* The second issue was that some blend functions needed more than a
GeglOperation alone. E.g. blend_function() for luma lighten
gimp_operation_layer_mode_blend_luma_lighten_only() would call
gegl_operation_get_source_space() which requires the node to exist.
Similarly for the Luma darken only mode. So I keep both the node and
operation around, and when finalizing, I free the node (which in turn
frees the operation).
Ell > if you are reading our commits, I would really appreciate your
review (or fixes) of my code here! :)
My previous commit broke the autotools build. Apparently when using
g_object_unref(), some C++ symbol leaked into libapppaint.a archive
library, hence the main binaries (e.g. gimp-2.99) could not be linked
without adding -lstdc++ flag:
> /usr/bin/ld: paint/libapppaint.a(gimppaintcore-loops.o): undefined reference to symbol '__gxx_personality_v0@@CXXABI_1.3'
> /usr/bin/ld: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: error adding symbols: DSO missing from command line
Not exactly sure why using this GLib function in particular caused this,
but let's just try another approach in order not to link the main binary
with C++ standard lib.
Instead let's manage all GeglOperation allocation in gimp-layer-modes.c
by adding a gimp_layer_modes_exit() function and some static array for
storing operation object of each layer mode.
The GimpOperationLayerMode variable member in DoLayerBlend was not
properly constructed. C++ class constructor can be called by creating
object variables, but with GObject, we have to do it with pointers.
Otherwise here we were only allocating the memory for the struct, but
not actually calling any initialization functions.
Also it would seem that the struct was not initialized at zero, as the
space_fish variable was not NULL when it should (i.e. even with same
composite and blend space), hence composite_to_blend_fish was not NULL
and since the operation was not a valid GeglOperation when entering
gimp_operation_layer_mode_real_process(), we crashed.
Not sure how it went unseen for so long!
So instead let's make the layer_mode class member into a pointer. As
such, I have to properly allocate and free it. This is also why I am
adding a copy constructor which will ref the pointer (otherwise we unref
more than we ref as the default copy constructor would just copy the
pointer).
In GimpOperationLayerMode, when the op has a mask connected, and
we're processing an area outside the mask bounds, set the op's
opacity to 0, so that the backdrop shows through. The actual
process() function gets a NULL mask pointer in this case, and so
would composite the layer as if it had no mask, exposing areas that
should be masked out.
Add a GimpOperationLayerMode::parent_process() function, which
subclasses can override instead of GeglOperation::process(), and
make sure to update the GimpOperationLayerMode::opacity field
before calling this function (and, subsequently, before calling
GimpOperationLayerMode::process()).
Clean up the rest of the fields, and adjust the rest of the code.
... brush does not have transparency
In the PAINT_MASK_TO_COMP_MASK paintcore-loops algorithm, used when
painting incrementally, multiply the paint mask values by the paint
opacity. Previously, the paint opacity was ignored, breaking
various dynamics affecting the opacity.
In gimp_layer_mode_get_format(), disregard the requested composite
space when selecting the format, if the input layer mode is alpha-
only, and the requested composite mode is not UNION, since, in this
case, the layer mode doesn't combine the layer/backdrop colors, and
rather only modifies the alpha of one of them. This allows us to
use the preferred format, avoiding gamma conversion.
This particularly improves the performance of the Eraser tool in
perceptual images.
We now have enough machinery in gimppaintcore-loops to avoid
modifying the paint buffer in gimp_paint_core_paste() in the no-
applicator case, by using the same set of algorithms as
gimp_paint_core_replace(). Other than reducing the number of
different code paths we have, this is both more efficient, and
allows us to reuse the paint buffer across dabs, as done in the
following commits.
Implement gimp_paint_core_replace() in terms of
gimp_paint_core_paste(). We keep the two functions separate, since
their implementation is still differnet when using an applicator.
Suppress the paint-buffer-modifying algorithms in
gimppaintcore-loops, but keep them around; using the same logic for
normal painting as we use for REPLACE painting is possible due to
the fact that all our current non-REPLACE modes treat alpha values
and mask values interchangeably. In the future we might have modes
that distinguish between alpha and mask values, requiring the old
algorithms.
In gimppaintcore-loops, fix the CanvasBufferIterator algorithm
helper-class so that it may appear more than twice in the
hierarchy, and integrate it into the normal dispatch-dependency
system, instead of having dependent algorithms inherit it directly.
In gimppaintcore-loops, unsuppress the
COMBINE_PAINT_MASK_TO_CANVAS_BUFFER algorithm (partially
reverts commit b717ead1abd487f663668ac131883dff0ffe4557.)
In gimp_paint_core_paste() it's always used together with
CANVAS_BUFFER_TO_PAINT_BUF_ALPHA, which matches a combined
algorithm, preventing it from being called, however, it can still
be called through gimp_paint_core_replace(), which uses it
together with CANVAS_BUFFER_TO_COMP_MASK, which doesn't have a
combined algorithm. We can, however, filter out
CANVAS_BUFFER_TO_PAINT_BUF_ALPHA whenver
COMBINE_PAINT_MASK_TO_CANVAS_BUFFER is matched (since the combined
algorithm will be matched beforehand when both algorithms are
included).
Remove the mask_components_onto() gimppaintcore-loops function, and
the GimpPaintCore::comp_buffer member. Instead, in
gimp_paint_core_paste() and gimp_paint_core_replace(), use the
MASK_COMPONENTS algorithm, added in the previous commit.
In gimppaintcore-loops, add a new MASK_COMPONENTS algorithm, which
masks the output of compositing into the destination buffer,
according to a component mask. The algorithm uses the same code as
gimp:mask-comopnents, and can be used as part of a
gimp_paint_core_loops_process() pipeline, instead of using a
separate function.
In gimppaintcore-loops, add a CompBuffer algorithm helper-class,
which provides access to the output buffer used for compositing,
to be used by the DO_LAYER_BLEND algorithm instead of the
destination buffer.
CompVuffer itself doesn't provide the storage for the buffer; this
is rather the responsibility of the algorithms that use it. The
TempCompBuffer algorithm helper-class provides temporary storage
for the compositing buffer, and can be used by algorithms that need
a temporary buffer.
In gimppaintcore-loops, use {Mandatory,Supressed}AlgorithmDispatch,
added in the previous commit, to mark certain algorithms as always
occuring, or never occuring, in all hierarchies.
In gimppaintcore-loops, add MandatoryAlgorithmDispatch and
SuppressedAlgorithmDispatch class templates, which implement
dispatch functions suitable for algorithms which are always part of
the hierarchy, or never part of the hierarchy, respectively. Using
one of these classes as the dispatch function for a given algorithm
verifies that the algorithm is/isn't included in the requested-
algorithm set, but doesn't otherwise increase the number of
instanciated hierarchies, since only one of these cases has to be
handled.
In gimppaintcore-loops, remove the individual-algorithm convenience
functions, which are merely wrappers around
gimp_paint_core_loops_process(), and aren't used anywhere anymore.
This allows us to avoid instanciating certain algorithm-hierarchies
which aren't used in practice, as will be done by the following
commits.
In gimppaintcore-loops, add CANVAS_BUFFER_TO_COMP_MASK and
PAINT_MASK_TO_COMP_MASK paint algorithms, which copy the canvas
buffer and the paint mask, respectively, to the compositing mask.
When there is an image mask buffer, the algorithms additionally
combine the copied mask with the mask buffer. When possible, the
algorithms use the canvas buffer/paint mask data directly as the
compositing mask data, instead of copying.
These algorithms are necessary in order to implement
gimp_paint_core_replace() in terms of
gimp_paint_core_loops_process(), which is done by the next commit.
In gimppaintcore-loops, in the DO_LAYER_BLEND paint algorithm, and
in the CanvasBufferIterator algorithm helper-class, initialize the
iterator *before* initializing the base class, to make sure that
dest_buffer, or canvas_buffer, respectively and in that order, is
the primary buffer of the iterator. In particular, this avoids the
mask buffer being the primary buffer. This is desirable, since
most of the buffers we iterate over are tile-aligned to the dest/
canvas buffers.
In gimppaintcore-loops, add a MaskBufferIterator algorithm helper-
class, which provides read-only iterator access to the mask buffer,
if one is used.
Use the new class in DoLayerBlend, instead of manually managing the
mask-buffer iterator.
In gimppaintcore-loops, add a CompMask algorithm helper-class,
which provides access to the mask buffer used for compositing, to
be used by the DO_LAYER_BLEND algorithm instead of the image mask
buffer.
CompMask itself doesn't provide the storage for the mask; this is
rather the responsibility of the algorithms that use it. The
TempCompMask algorithm helper-class provides temporary storage for
the compositing mask, and can be used by algorithms that need a
temporary mask.
In gimppaintcore-loops, add finalize() and finalize_step()
algorithm functions, which get called at the end of processing the
entire area, and at the end of processing each chunk, respectively.
Algorithms can use these functions to clean up allocated resources.
In the various gimppaintcore-loops algorithms, assign each
algorithm's iterator indices dynamically, rather than statically,
so that algorithms have more freedom in the way they initialize the
iterator.
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.
In gimp_paint_core_loops_process(), initialize the iterator with
sufficient room for the number of iterators used by the algorithm
hierarchy, instead of a fixed number.
Add an additional 'rect' parameter to the init_step() and
process_rows() algorithm member functions, which receives the area
of the currently-processed chunk, to be used instead of the
iterator's ROI member. This allows us to pass a NULL iterator to
hierarchies that don't use an iterator, and avoid the stack-
allocated iterator hack we used in this case (and which became even
more problematic with the new iterator API).
All babl formats now have a space equivalent to a color profile,
determining the format's primaries and TRCs. This commit makes GIMP
aware of this.
libgimp:
- enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA
as deprecated aliases, add PERCEPTUAL values so we now have LINEAR,
NON_LINEAR and PERCPTUAL for each encoding, matching the babl
encoding variants RGB, R'G'B' and R~G~B~.
- gimp_color_transform_can_gegl_copy() now returns TRUE if both
profiles can return a babl space, increasing the amount of fast babl
color conversions significantly.
- TODO: no solution yet for getting libgimp drawable proxy buffers in
the right format with space.
plug-ins:
- follow the GimpPrecision change.
- TODO: everything else unchanged and partly broken or sub-optimal,
like setting a new image's color profile too late.
app:
- add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as
replacement for all "linear" booleans.
- change gimp-babl functions to take babl spaces and GimpTRCType
parameters and support all sorts of new perceptual ~ formats.
- a lot of places changed in the early days of goat invasion didn't
take advantage of gimp-babl utility functions and constructed
formats manually. They all needed revisiting and many now use much
simpler code calling gimp-babl API.
- change gimp_babl_format_get_color_profile() to really extract a
newly allocated color profile from the format, and add
gimp_babl_get_builtin_color_profile() which does the same as
gimp_babl_format_get_color_profile() did before. Visited all callers
to decide whether they are looking for the format's actual profile,
or for one of the builtin profiles, simplifying code that only needs
builtin profiles.
- drawables have a new get_space_api(), get_linear() is now get_trc().
- images now have a "layer space" and an API to get it,
gimp_image_get_layer_format() returns formats in that space.
- an image's layer space is created from the image's color profile,
change gimpimage-color-profile to deal with that correctly
- change many babl_format() calls to babl_format_with_space() and take
the space from passed formats or drawables
- add function gimp_layer_fix_format_space() which replaces the
layer's buffer with one that has the image's layer format, but
doesn't change pixel values
- use gimp_layer_fix_format_space() to make sure layers loaded from
XCF and created by plug-ins have the right space when added to the
image, because it's impossible to always assign the right space upon
layer creation
- "assign color profile" and "discard color profile" now require use
of gimp_layer_fix_format_space() too because the profile is now
embedded in all formats via the space. Add
gimp_image_assign_color_profile() which does all that and call it
instead of a simple gimp_image_set_color_profile(), also from the
PDB set-color-profile functions, which are essentially "assign" and
"discard" calls.
- generally, make sure a new image's color profile is set before
adding layers to it, gimp_image_set_color_profile() is more than
before considered know-what-you-are-doing API.
- take special precaution in all places that call
gimp_drawable_convert_type(), we now must pass a new_profile from
all callers that convert layers within the same image (such as
image_convert_type, image_convert_precision), because the layer's
new space can't be determined from the image's layer format during
the call.
- change all "linear" properties to "trc", in all config objects like
for levels and curves, in the histogram, in the widgets. This results
in some GUI that now has three choices instead of two.
TODO: we might want to reduce that back to two later.
- keep "linear" boolean properties around as compat if needed for file
pasring, but always convert the parsed parsed boolean to
GimpTRCType.
- TODO: the image's "enable color management" switch is currently
broken, will fix that in another commit.
... and gimppaintcore-loops
When a rectangle argument is NULL, use the extents of the
corresponding buffer, instead of raising a CRITICAL, to match the
old behavior.
The gimppaintcore-loops functions perform very little actual
computational work (in case of do_layer_blend(), at least for
simple blend modes), which makes the cost of buffer iteration, and
memory bandwidth, nonnegligible factors. Since these functions are
usually called in succession, acessing the same region of the same
buffers, using the same foramts, coalescing them into a single
function, which performs all the necessary processing in a single
step, can improve performance when these functions are the
bottleneck.
Add a gimp_paint_core_loops_process() function, which does just
that: it takes a set of algorithms to run, and a set of parameters,
and performs all of them in one go. The individual functions are
kept for convenience, but are merely wrappers around
gimp_paint_core_loops_process().
Be warned: the implementation uses unholy C++ from outer space, in
order to make this (sort of) managable. See the comments for more
details.