Clone Brush Preview / improved preview system
Open, NormalPublic

Description

Introduction
This is a proposal to improve the Krita brush stroke preview system. It primarily affects the ‘Clone Brush’-workflow, a combination of using the brush tools and the Duplicate PaintOp (KisDuplicateOp). Additionally, other combinations of tools and paintops could possibly benefit from an improved preview system.

Current workflow
Currently the Krita brush preview system displays a polygonal QPainterPath following the outline set by the currently selected PaintOp. Although this works fine for most brush tool / paintop combinations, while using KisDuplicateOp in combination with KisToolBrush, the placement of the brush is rather inaccurate, as is illustrated here:

Although the outline helps somewhat with approximating the placement of the brush before the stroke is being applied in Before 2, the placement of the resulting stroke is obviously not very accurate, resulting in unwanted artifacts as is shown here:

Improved workflow
To improve the workflow, instead of drawing an outline for the active paintop, Krita could show a bitmap representation of the stroke as the paintop will paint if the stroke would be applied, as is shown here:
With this preview system, the stroke can be interactively adjusted to align, even before the application of the stroke has started. This will result in a much better alignment of the stroke and therefore in a much more versatile clone tool as is shown here:

Proof of concept
A proof-of-concept was coded to proof the feasibility of preview functionality. This turned out to work well in terms of usability as well as performance. A video of the new functionality was uploaded to Youtube: link

Scope
The suggestion to improve the outline preview system was initially inspired by the shortcomings of the current ‘Clone Brush’-workflow. However, an improved preview system could potentially improve other Tool / PaintOp workflows as well.

For example, brush and shape tools could show the stroke (or dab) to be applied in advance, providing both better alignment as well as a more accurate representation of the end result, including the colors to be applied by the currently selected PaintOp.

Lastly, a more refined, for example anti-aliased, outline could be combined with the new preview system, improving over the current -sometimes harsh- xor-ed outline and resulting in a more pleasant painting experience while working with Krita.

Implementation
At first I have tried to put all extra functionality in a separate plugin, so the code would be independent of Krita. Since I could not link to KisToolBrush in kritadefaulttools (since that is a plugin as well), I had to copy all of KisToolBrush to a new copied class, which is no good imo. Therefore, the preview cannot be implemented without touching the rest of Krita.

Separate Tool?
For the proof-of-concept, I have added a 'Clone Stamp Tool' to differentiate from the original Freehand Brush Tool, and keep the latter available for comparison purposes. A separate tool could be implemented to isolate the 'Clone Brush Workflow'. That would however contradict the design of the Krita tool system in which a combination of a Tool and a PaintOp defines the workflow.

Some combinations of Tools and PaintOps do not make sense, and no mechanism to exclude such combinations exists in Krita afaik.

Krita has a docker for 'Tool Options' but not for 'Brush Options', so a separate Tool would have the advantage of showing specific options in its 'Tool Options'.

Preview code
To enable the proof-of-concept preview code, KisOpenGLCanvas2 was modified to provide a paintToolPreview function in addition to paintToolOutline.

Custom vertex- and fragment shaders are loaded to draw the preview before the stroke starts. The preview is hidden while drawing or when setting a new source point. The size of the preview is hard coded for now, but can be variable. The shaders are used to fade out the opacity from the center.

Other Tools
The new preview functionality could be implemented exclusively for the Clone Brush PaintOp, but it could also be implemented universally, so other Tools can benefit from it as well. For example, It could show an actual representation of a Freehand Brush before starting the stroke.

I think it would be awesome if PaintOps could override the default preview code (currently in KisOpenGLCanvas2::paintToolOutline. I am aware that this could possibly conflict because it would put UI-code into a PaintOp, however the Clone Brush-workflow dictates the need for such functionality imo.

To Do
Further development of the Clone Brush Preview would require taking the following steps first:

  1. Decide if a Clone Stamp Tool should be a separate tool.
  2. Decide if the preview functionality will be specific to Clone Brush PaintOp or a universal system overriding preview code in a PaintOp.
fonkle created this task.Jan 9 2020, 2:16 PM
fonkle triaged this task as Normal priority.
fonkle claimed this task.Jan 12 2020, 11:06 AM

Separate Tool

The separate Clone Stamp Tool is now in a separate branch: fonkle/clonetool. Deriving a class from, and linking to, KisToolBrush was only possible from within krita/plugins/tools/ so to prevent cloning KisToolBrush I had to move the Clone Stamp Tool there.

A separate Clone Tool would only make sense in combination with the intended DuplicateOp. Since this would require duplicating (or moving) functionality from DuplicateOp into the new tool, the current combination of the Freehand Tool with a Duplicate PaintOp seemed preferable.

Integrated into Freehand Brush Tool

So I made another branch: /fonkle/clonebrush and integrated the proof-of-concept functionality into the existing Freehand Brush Tool, which works -despite its sloppy experimental code-.

I am currently investigating how to mix the preview UI code with the PaintOp code. Currently, Krita only stores a QPath containing the brush outline while moving the mouse, which is then drawn later in a (different?) OpenGL canvas drawing thread. This works with all combinations of Tools and PaintOps. Differentiation of preview code in different PaintOps can be implemented in 2 ways:

  1. Move preview code inside PaintOps. This way, every PaintOp can override to provide its custom preview drawing code.
  2. Universal preview drawing code. PaintOp specifics are defined in (overridden) member data or functions which are queried by the preview drawing code.

So far it seems that the preview code resides under KisOpenGLCanvas2 and does not mix very well with KisPaintOpSettings...?

Any feedback on this would be greatly appreciated!

Update: Clone Stamp Tool

Branch: fonkle/clonetool on invent.kde.org/fonkle/krita.

  • The Clone Stamp Tool now saves a separate current and previous preset. Switching between current and previous presets therefore occurs separate from the other tools.
  • Both the last preset for the Clone Stamp Tool (LastDuplicateOpPreset) and the rest (LastPaintOpPreset) are saved to kritarc and restored when appropriate.
  • When switching between the Clone Stamp Tool and other tools, the appropriate preset is selected.
  • Selection in both 'Edit Brush Settings' and 'Choose Brush Preset´ Popups and buttons is updated accordingly.
  • When the Clone Stamp Tool is the active tool, the selection of available presets in both 'Edit Brush Settings' and 'Choose Brush Preset' Popups is limited to 'Clone Engine' presets. The 'Engine' dropdown in the former is also set to 'Clone Engine'. This includes saved custom 'Clone Engine' presets.

Note: Currently the master-branch is broken, causing Krita to crash (in KisFloatingMessage::determineMetrics()) when doing anything that invokes a Floating Message, e.g: switching to the previous preset. I assume this is only temporary and will get fixed soon.

Update: Clone Stamp Tool

Branch: fonkle/clonetool_4.2.9 on invent.kde.org/fonkle/krita (link).

The problem with KisFloatingMessage::determineMetrics() is fixed in master. However, I still can not rebase the code to master since KisPresetProxyAdapter does no longer exist in master. So, I have rebased the code to the stable v4.2.9 tag. I have tried to submit a merge request to have someone check out the code, however that generates an error since it cannot be applied to the master branch.

The workflow using a separate Clone Stamp Tool and separate current and previous brush presets works great. I have adapted the methods to set and get the current and previous brush presets to operate on multiple values selected by a constant (enum) separating the Clone Stamp Tool presets from the rest. This could be useful for other tools as well.

I believe the initial implementation of this tool is good enough to start implementing it into Krita, yet I seem to be stuck on motivating any Krita developers to (help me) implement the tool.

Any feedback will be greatly appreciated!