This MR is an implementation of the "enclose and fill" tool proposed in https://phabricator.kde.org/T14993. It is inspired by the tool of the same name present in Clip Studio Paint.
The goal of the tool is to fill shapes of the image that lie inside a user-defined region. The user just provides a rough region around those shapes and the tool tries to find the shapes themselves and fill them:
Here is a video showcasing the tool:
Since the MR is a bit long, I'll write about the relevant changes to external code first and about the code of the tool later:
Relevant changes to previously present code
I tried to keep already existent code untouched as much as possible, but some changes where needed:
- Changed some getters to const
- Made the
genericFillStart/genericFillEndmethods protected so they can be used in the derived
setCurrentFillSelection/currentFillSelectionto access the
m_fillSelectionvariable used by
- Added some methods and policies to be able to fill everything, no matter the color, from the given seed point to a given "stop" color.
- Before there was only a "setOutlineEnabled" method that allowed to show/hide the tool outline. This was used for example to hide the outline if the alternate action (color sampling) was activated and show it when the action was deactivated. The problem with this is that if a KisToolPaint derived class wants to always hide the outline via "setOutlineEnabled(false)" it will be re-shown when the alternate action is deactivated (basicaly every time shift is released). So I introduced a new "setOutlineVisible" that can be used to temporarilly hide the outline. "setOutlineEnabled" is still present and permanently hides the outline, regardless of what is set via "setOutlineVisible".This is needed in this new tool because I want some "subtools" (like rectangle, ellipse or lasso) to be able to pick a color while always having the outline hidden.
The new tool
In summary, the new tool works like this:
The users make some shape with one of the methods available. This effectively creates an enclosing mask. All the other steps operate inside this mask.
Then some regions are selected in the area inside the enclosing mask. The "region selection" options control what regions are selected.
The selected regions mask is used as a to fill just like the fill tool: the can be modified (expanded, feathered, etc.) and the result is used to fill with the selected fill. The layer reference method also works like in the fill tool.
Since most of the options work like in the fill tool, I'll explain the first two points:
The enclosing method
I wanted the users to be able to create the enclosing region using different methods (rectangle, ellipse, polygon, lasso, brush). I also wanted to reuse the already present tool classes for that. The main problem here is that the "subtool" needs to be changed dynamically, so I had to implement a "dynamic delegated tool" wrapper. It differs from the already present "delegated tool" in that here the delegate tool can be changed at execution time. This delegated tool derives from
KisToolPaintas well as the delegate tools, and just fordwards (almost) all the calls to the delegate tool. This was kind of intricate, and I don't know if I missed anything, but it seems to work fine. Some methods , line
setCursor, besides being forwarded are also applied in the delegate tool since they result in a ui change.
For the brush enclosing method I implemented a new class with functionality similar to that of the smart patch tool, and more. I didn't want to use the freehand tool itself, since it is more complicated. I just wanted to have a simple circle based brush to define regions quickly. It uses a
QPainterPathinstead of a paint device. It can show the outline and cursor according to the settings, and can change the brush size with shift. It could be reused in the smart patch tool to make the experience more pleasant and reduce its code.
The region selection methods
The users can choose which regions inside the enclosing mask should be selected. Basically they can choose to select:
Regions of any color: a series of flood fills are performed from the contour of the enclosing mask towards the inside until some different regions are reached. Then this selection is inverted to keep those inside regions.
Regions of a specific color: The regions of a specific color are selected. The users can choose to include or exclude the independent regions that touch the border of the enclosing region.
Regions that are not of a specific color: The regions that are not of a specific color are selected. The user can choose to include or exclude the independent regions that touch the border of the enclosing region.
Regions surrounded by a specific color: The new flood fill methods are used to select the regions from the contour of the enclosing region until the selected color. Then those are inverted, so that the result are the areas inside that are surrounded by the chosen color. The users can choose if the surrounding regions should be removed.
- The users can also choose if the resulting region should be inverted at the end.
I can explain further each method if needed.
There is an issue when I use the
KisPixelSelection::invert (use the invert option or one of the surrounding color region selection methods). Basically when I invert the mask I have to also intersect it with the enclosing mask to avoid having selected regions outside of it. But it seems that some regions outside the image are not removed. In the following image I first filled with the brown color and then with the green and there can be seen the border outside in the thumbnail. The method used is "regions surrounded with specific color", which has to invert and intersect the mask, but the same happens for example with "select regions of a specific color" method if we check the invert box:
Edit: It seems that this unwanted border is thin but the effect gets exagerated if a large grow value is set in the fill options.
(Tell us how to test the changes you made.)
I confirmed this builds.
I confirmed Krita ran and the relevant functions work.
I tested the relevant unit tests and can confirm they are not broken. (If not possible, don't hesitate to ask for help!)
I made sure my commits build individually and have good descriptions as per KDE guidelines.
I made sure my code conforms to the standards set in the HACKING file.
I can confirm the code is licensed and attributed appropriately, and that unattributed code is mine, as per KDE Licensing Policy.