Yet Another Brush Proposal
mw_triad at users.sourceforge.net
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,
* 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
== 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
The class is abstract with various implementations including:
* bristle brush (including bristle stiffness, density, size, brush shape
and size, etc)
* sponge brush; parameters including stiffness, hole size/density, and
shape allow simulating...
** felt tips
** quill tips
** pencils, chalks
** "digital brushes"
* bucket tool; boundary options include...
** 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.
* natural paint (see 'natural paint' under KisPaint)
* 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