Yet Another Brush Proposal

Matthew Woehlke mw_triad at
Tue Mar 25 23:40:43 CET 2008

Here's my attempt at an update based on IRC discussion. I'm sure I've 
screwed *something* up, so please tell me what :-).

(Note: I still don't like KisPaintTool, it has nothing to do with the 
paint! :-). So I left that KisStrokeTool, but I renamed (or tried!) 
everything else to follow the current stuff.)

= Introduction =

== Glossary ==

:Generates/updates one or more KisStroke(s). Currently named KisPaintTool.

:Holds all information generated by a KisStrokeTool, used by KisStamp to 
apply paint to the canvas.

:Holds information about the current paint, such as the color and opacity.

:Determines the mask of affected pixels from a KisStroke.

:Determines how the canvas should be updated using the current content 
of the canvas, the current KisPaint (likely a subclass specific to the 
subclass of this KisPaintOp), and the mask from KisStamp.

:The word "paint" will be used throughout to refer to the material 
currently on the canvas, which may be paint in the usual sense, but also 
ink, chalk, charcoal, water, etc.

== Justification ==

To implement "natural" painting, it is desired to simulate a number of 
real-world tools. Currently, we have a Chinese ink tool, and a sumi-e 
tool is planned. Eventually, we would like to support a number of tools, 
such as:

* Bristle brushes of varying types (e.g. flat, tapered), with varying 
paints, such as ink, tempera, oil, powders, etc.
* Airbrushes with inks
* Various types of pens (roller, felt, quill)
* Pencils (charcoal, chalk, others?)
* Brushes with no paint applied to the canvas (i.e. affecting the 
"paint" already on the canvas without introducing new "paint")

This proposal is instead to describe a clear separation of the tool from 
the physical interaction of the paint. This will enable much greater 
code re-use, and also allow artists to make their own decisions about 
what combinations of tool and paint are sensible, rather than making the 
decision at an architectural level.

The UI will need to be carefully thought out (or else, we don't expose 
the guts until we feel we have a usable interface), but I believe that 
good presets can make this system work effectively while allowing 
tremendous flexibility, and at the same time significantly reducing code 
duplication. Combining good presets with a rich, physics-based pigment 
algorithm should reduce the number of KisPaintOp types to a very small 
number, possibly even one single KisPaintOp that can charcoal, ink, oil 
paint, and most other real-world media. At the same time, most tools can 
be folded into a full-featured bristle brush KisStamp, a rigid-body 
KisStamp to handle quills, felt tips, chalks, and so forth, an airbrush 
KisStamp, and a few others. This results in about a half dozen or so 
classes that can implement dozens or even hundreds of tool/paint 

== Basic Operation ==

KisStrokeTool takes raw input and translates it into a KisStroke. The 
KisStroke is a parametric function of t, that can be evaluated for such 
things as x, y, pen tilt, pen angle, pen pressure, dx/dt, dy/dt, stroke 
angle, etc. Various stroke tools would cover freehand, lines, paths, 
Valerie's "double-ended strokes", tablet simulation, etc. KisStamp does 
not need to know any of these details.

KisStamp takes the stroke and translates it into a series of pixel masks 
where the KisPaint will be applied by the KisPaintOp. This operation 
should be designed to work on partial strokes, to allow real-time 
updating. Tools include bristle brushes, sponges, buckets, etc.

Finally, KisPaintOp takes the mask from KisStamp and uses it to update 
the canvas, and optionally the current KisPaint (for bidi painting, not 
necessarily supported by all KisPaintOps).

= Classes =

== KisStroke ==

KisStroke contains information about the current stroke. It represents a 
multidimensional, single-parameter parametric function that can be 
evaluated by time to determine various information about the requested 
point, including x, y, distance along the stroke, pen 
pressure/tilt/angle, stroke angle, and any rates-of-change useful to 
KisStamp. It is a concrete class and has no subclasses.

Time is used as the parameter as some KisPaintOps may operate on this 
bases (e.g. airbrushes).

== KisStrokeTool ==

KisStrokeTool maps user input to generate KisStrokes, creating "fake" 
stylus data as needed. KisStrokeTool is responsible for smoothing, etc. 
The class is abstract with various implementations including:
* paths (paths are persistent), bezier tool (non-persistent paths)
* freehand tool, line tool
* various shape tools

== KisStamp ==

KisStamp takes a KisStroke (and optionally various data from the canvas) 
and generates a series of pixel masks that identify where the paint is 
being applied to the canvas. Each mask is linearly distributed along the 
time axis. In addition to alpha data, each mask contains an overall 
distance-delta (the distance travelled along the stroke since the 
previous mask), distance along the path, stroke angle, and some 
additional information (per-pixel) conveying what part of the tip 
touched the canvas. It may also contain any additional data we decide is 
needed by KisPaint or KisPaintOp. The KisStamp specifies if bidi 
painting is allowed (since bidi doesn't make sense for some tools).

The class is abstract with various implementations including:
* bristle brush (including bristle stiffness, density, size, brush shape 
and size, etc)
* airbrush
* sponge brush; parameters including stiffness, hole size/density, and 
shape allow simulating...
** felt tips
** quill tips
** pencils, chalks
** ballpoints
** "digital brushes"
** stamps
* bucket tool; boundary options include...
** none
** impasto (height)
** color similarity (can be broken down by channel or hue/luma/sat/etc)

== KisPaint ==

The base KisPaint produces a color that is constant or based on the 
stroke distance, angle, and KisStamp 'part of tip' parameters, or any 
combination thereof. Subclasses may ignore these parameters or use 
others, and may do other things like bidi transfer. The 'part of tip' 
parameters may be combined with the stroke distance, e.g. to simulate a 
pencil/chalk with various colors of pigment, stacked.

Currently I only know of one reasonable subclass; 'natural paint'. With 
parameters for wetness, viscosity, etc, this simulates:
* paints (oil, tempera, etc)
* inks, watercolors, washes
* pastels, chalks, charcoals
* no paint (pushes paint-on-canvas around, also bidi transfer can happen)

In general, a KisPaint subclass will correspond to a particular 
KisPaintOp subclass. KisPaint holds the color information, while 
KisPaintOp manipulates the canvas and optionally (bidi painting) the 
KisPaint. A KisPaint is always preserved and may optionally be restored 
for each new stroke. KisPaint can be thought of as the data portion of a 
painting operation, while KisPaintOp is the algorithm portion and may 
contain no data of its own. It would thus be reasonable to merge the two 

== KisPaintOp ==

KisPaintOp takes the masks from KisStamp and, combined with the KisPaint 
and the existing canvas contents, determines how to update the canvas.

The class is a reference implementation that applies the base KisPaint 
directly (simple "digital painting"). Subclasses (particularly the 
'natural paint' variant) may alter their reaction to the masks, e.g. to 
simulate "bleeding" of the paint, and may also update their KisPaint. 
Some subclasses (e.g. filter, clone) may not use a KisPaint.

Subclasses include:
* natural paint (see 'natural paint' under KisPaint)
* filters
* clone operation

This results in the canvas content being generically represented as one 
or more "natural paints".

ELANG: input is in wrong language (please use English on English lists)

More information about the kimageshop mailing list