Yet Another Brush Proposal

Matthew Woehlke mw_triad at
Tue Mar 25 20:38:42 CET 2008

This is actually the second time I've posted this concept (quite a few 
more lines of text this time, though)... don't think I got much reaction 
if any last time. Anyway, this seems to match what Emanuele is thinking, 
but not what Cyrille is thinking. Anyway, I wrote this as a wiki article 
but Cyrille asked me not to post it there yet, so please excuse the markup.

Cyrille: I don't think your idea works, as I don't see how it can 
account for the paint to bleed (i.e. very wet paints totally don't fit 
in your model).

This is based on trying to conceive a system that takes a physics-based 
approach, which is, you make a stroke, using a tool, that has paint on 
it. Therefore the idea is to model the stroke, use that to model how the 
tool touches the canvas, and use that to model what happens with the 
paint. I've tried to include in the model paint transfer in both 
directions, paint interacting with itself, as well as parameters that 
users will want to fiddle with. I don't think doing things in the 
opposite order makes sense, or will work as well :-).

Some of this almost certainly needs refining. I rather just stopped 
writing this, so some bits may be incomplete or outright missing.

= Introduction =

== Glossary ==

:generates/updates one or more KisStroke(s)

:holds all information generated by a KisStrokeTool, used by 
KisPaintTool 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 KisPaintTool

: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")

My understanding is that the current architecture favors combining the 
tool and the paint. While this makes a certain amount of sense, it 
imposes limitations that I feel are unnecessary and perhaps not in the 
spirit of open-source.

My proposal is instead to separate the tool from 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 KisPaintTool, a rigid-body 
KisPaintTool to handle quills, felt tips, chalks, and so forth, an 
airbrush KisPaintTool, and a few others. This results in about a half 
dozen or so classes that can implement dozens or even hundreds of 
tool/paint combinations.

== 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. KisPaintTool 
does not need to know any of these details.

KisPaintTool 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. KisPaintTool may also generate multiple masks per 
time snapshot (representing the same time). Tools include bristle 
brushes, sponges, buckets, etc.

Finally, KisPaintOp takes the mask from KisPaintTool 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 
KisPaintTool. 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

== KisPaintTool ==

KisPaintTool 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 two 
arbitrary parameters from 0.0 to 1.0, the second of which is per-pixel. 
It may also contain any additional data we decide is needed by KisPaint 
or KisPaintOp. The KisPaintTool specifies if bidi painting is allowed 
(since bidi doesn't make sense for some tools), and also how many unique 
first-parameter values will exist (the expectation being that each 
unique parameter value corresponds to a unique KisPaint instance).

The first parameter is used e.g. by the bristle brush to represent the 
individual bristle that produced the mask (each bristle produces a 
separate mask), and also relates to bidi transfer between bristles. It 
could be used for other things with other tools; for example, the 
airbrush might use it to simulate multiple simultaneous paint chambers. 
The second parameter is used to represent "tool height", i.e. it 
corresponds the the position along a bristle, height along the tip of a 
sponge, etc.

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, KisPaintTool second parameter, or any 
combination thereof. Subclasses may ignore these parameters or use 
others, and may do other things like bidi transfer. The color is not 
dependent on the KisPaintTool first parameter, but a unique KisPaint may 
be assigned based on the first parameter. The second parameter would 
typically be used alone or in combination with the stroke distance, e.g. 
to simulate a pencil 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 KisPaintTool and, combined with the 
KisPaint and the existing canvas contents, determines how to update the 

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