This document describes the brush engine design for Krita 2.0. For Krita 2.1, certain changes are needed in certain areas, which will also be noted here.
Krita is designed to accommodate pluggable brush engines. The core principle of the brush engine framework is to enforce as little policy as possible: a brush engine does not need to fit into a framework of cooperating brush engines (as proposed by Matthew Woehlke), rather, a set of brush engines can implemented to cooperate with each other. A brush engine can also be completely independent. Brush engines can be as simple as the eraser engine and as complex as the dynamic paintop.
Brush engines modify paint device pixel data destructively, although it is possible to record brush strokes for replay, which means that brush engines need to save pertinent data.
Brush engines can make use of KisBrush-derived brush or not, or not.
The brush engine framework is found in krita/image/brushengine and contains the following classes:
- KisPaintopFactory: the paintop factory creates instances of paintops. You don't call this directly, but rather through the registry..
- KisPaintInformation: contains information about the stroke. You don't reimplement this, but rather get paintinformation objects passed to the paintline method in your paintop.
- KisPaintopPreset : a KoResource that contains a KisPaintOpSettings instance that describes for a particular paintop a predefined set of settings. Note: in 2.1, we'll be able to save presets and organize them. It's important to realize that the gui now works with presets, not settings or paintops anymore.
- KisPaintopSettingsWidget: base class for widgets that users use to modify existing presets.
- KisPaintop: the base class for brush engines. There are three methods that are important: paintAt, paintLine and paintBezierCurve. These implement the actual painting.
- KisPaintopRegistry: a central singleton where every paintop factory needs to be registered. The factory can create default paintop presets.
- KisPaintopSettings: responsible for serializing/deserializing paintop settings and storing them.
Note about 'incremental' : there are two kinds of "incremental": one must be renamed to "rate" and is a property of paintops that determine whether the paintop action should continue even when the pointer doesn't move. This should be (2.1) replaced with 'rate' and a customizable interval for the timer that calls KisPaintOp::paintAt(). The other type is about the paint deposition method: either 'direct', which means we work directly on the node's paint device, or 'indirect', which means we paint on a temporary paint device with the ALPHA_DARKEN composite op and on stroke end composite the temporary device with the node's paint device. Also see [An architecure for "natural" brush types in Gimp 1+] by Raph Levien: the Gimp makes exactly the same distinction.
In 2.1 we must make this more flexible, it would be nice, for instance, to allow a filter to modify the stroke so we get gradients, or wet fringes etc. This is mainly the responsibility of the KisPaintActionTypeOption in the libpaintop library.
Many brush engines are similar in one or more respects: a lot need access to KisBrush based brushes, a lot need custom pressure-response curves, and so on. libpaintop is a convenience shared library brush engines can use to construct consistent-looking brush engines.
(Note: in 2.0, the widget to modify a setting, the data representing that setting and often the algorithm to apply the setting are coalesced into one class, look for instance at KisPressureDarkenOption. This makes it impossible to untangle the settings and the settings widget, which leads to all kinds of problems. This must be redesigned in a responsible mvc way for 2.1. The current situation is caused by historical reasons.)
- If you want to make use of the list-of-option-plus-widget-stack design for paintop settings widgets, derive your settings widget from KisPaintOpOptionsWidget
- You can mix and match any of the KisPaintOpOption derived options or derive an option yourself. If it is an option that's likely useful for many brush engines, consider adding it to libpaintop. You are responsible for calling the apply() methods of the options at the appropriate places in your brush engine's algorithm
- If your paintop needs brushes, you want to consider deriving it from KisBrushBasedPaintOp, which handles the spacing of KisBrush-based brushes for you.
A brush engine plugin consists of the following parts:
* factory * settings widget * settings object * brush engine
The factory is responsible for creating a settings widget. The single settings widget is shared by all views and documents in a single Krita process (Note: in 2.1, refactor this to have one widget per view). The factory also creates the paintop itself from a settings instance.
Your settings widget can either be derived from KisPaintOpOptionsWidget in libpaintop or from KisPaintOpSettingsWidget. Take care about initializing the widget from the data in the paintop settings instance, and saving changes in the widget to the settings.
Note: actually, the widget implements KisConfigWidget, which means that changes in the widget should lead to a sigConfigChanged signal, which in turn should be implemented in KisPaintOpSettingsWidget to write the widget state to the settings object, using the sigPleaseUpdatePreview signal that is emitted.
Note: and then, this should lead to an update of the preset preview (which is already almost implemented).
The gui design right now is quite simple: we have a combobox to select a certain brush engine. Next to that is a combobox that can be used to select a preset. The list with presets is empty right now, except for the default preset. Next to that is a widget that (should) show a preview of the a stroke made with the preset, and that, when clicked, expands to preset settings popup.
The preset settings popup (should have) has two tabs: one with the current settings, one with an overview of all the presets for this paintop. This overview is disabled for now, but will be similar to the brushes overview. However, this overview widget should be expanded (2.1) to allow a listview, an iconview etc. of the preset previews.
We have the following data model for presets:
* KoInputDevice 1 * paintopid1, current settings * paintopid2, current settings * paintopid3, current settings * KoInputDevice 2 (like stylus with id 1) * paintopid1, current settings * paintopid2, current settings * paintopid3, current settings * KoInputDevice 3 (like stylus with id 2) * paintopid1, current settings * paintopid2, current settings * paintopid3, current settings
So, whenever the current input device or current paintop changes, we need to make sure the current settings are saved with the right input device and paintop, and make a different settings object active.
* krita/ui/kis_paintop_box.h * krita/ui/widgets/kis_paintop_presets_popup.h