That dashed TODO list keeps growing

I’ve spent the better part of this week rather feverishly (literally, I’m afraid) hacking on Krita, trying to bring it up to KPaint-level feature-wise, at least, and all that seems to happen is that my TODO list is growing.

Yesterday I fixed that silly slider that regulates the spacing with which brushes are painted on the canvas, did some code clean-ups and made sure the correct cursors are used. And I updated that TODO list:

* The tabs for images inside a document seem to be broken :-(
* Brush cursor shapes for tools (updating cursors from the tools doesn't work yet).
* Give each layer its own imagetype; allow layers of diverse types in one image.
* Option dialog for tools
* Make tools kparts
* Fix scrollbars
* make brush tool antialiased; make un-antialiased pen tool
* If you have a tablet, you can have more than one active tool at a time: 
	stylus, eraser, pointer and your ordinary mouse, not to mention
	the possibility of having several styluses.
* Implement saving/loading of tool properties.
* redo configuration dialog
* Find a way to set the hourglass cursor when doing complex things from
	e.g. KisPainter.
* Make pipe brushes pay attention to the instructions in the 'parasite'
* Use patterns and gradients while painting


And that’s just the bit that got added this week. And I would really like to bring some more structure in the /core source directory, and make a  separate library of the KisPaintDevice/KisPainter code. But that’s a refactoring that can be done at a later stage, when I’m more confident of my C++ abilities. I am growing more confident — I think I could put C++ on my curriculum vitae by now.

The thing that’s most needed at the moment is more tools; that will flesh out the KisPainter interface and give me a reason to improve the user interface. Having more than one layer type in one image would be a good thing, too. That would make it easy to have a CMYK or B/W layer and a layer with spot colour on top.

Gimp’s pipe brushes

The Gimp nowadays comes with a small set of rather nifty brushes — the so-called pipe brushes, recognizable from the file-extension .gih. The fileformat for these brushes is actually really horrible, a mix between text and binary. The first line contains the brush name, the second the number of brushes the brush contains, a space, and a bit of text detailing the way the brush ought to be used.

Today (and yesterday) I spent time creating a class that could load those brushes, and actually use them. Well, that’s done. I still don’t parse the bit of text — a ‘parasite’ in Gimp parlance — so you just get all the images in the brush in order, and I don’t make a difference between brushes that are really gray-scale masks to be used with the currently selected colour, and the brushes that are really collections of images that can be pasted into the drawing directly.

But on the other hand, I support pressure-sensitivity for every pipe brush:

Pity that pre-computing all those masks takes such a lot of memory. Krita now wants about 256 MB of memory at startup…

Gosh, Python is easy

I’d almost forgotten how easy Python is, how comfortable it is not to have to recompile, how nice it is not to have to type all kinds of superfluous punctuation… Java is better than C++ in this regard, but still… Compared to Python.

Interest in Kura is picking up a bit, and I am getting bug reports and feature requests. And that forces me to go back to this source. Source that I started writing in august 1999, that has been through a rewrite (from PyKDE-based to pure PyQt), that I haven’t looked at for months. After all, it did what I wanted it to do, it was sufficient for my current needs, which is keeping track of language data for the novel I should be working on.

And I can still find my way around the code, make a change, no need for five minutes of compiling, restart the application (it loads on Debian/PPC in under a second, OS X is far slower), and check whether the fix worked… (Dash it, there is something wrong with my webcvs.cgi script…

Pity you cannot do an optimized natural media paint application in Python. In fact, I sometimes find C++ enough of a paint that I wish I could do Krita in Java, using gcj…

Pressure-sensitivity support in Krita

I just completed the first stab at pressure sensitivity support… It’s still slow, and not completely correct in all places, but you can make beautiful galaxies with it and gorgeous blobs that look like you’ve been painting with a brush and thick india-ink.

I’m rather looking forward, too, to re-activating a few of the other tools. I’ve got some ideas about a nice airbrush tool, one that really fakes an airbrush instead of spattering random dots (although there’s a tool like that in real life too — anyone remember the toothbrush and spatterboard from kindergarten). And a line tool is going to be easy now…

Finally a decent brush!

I’ve finally discovered how to do a decent brush in Krita. Turns out that the most time-intensive bit was the redrawing of the picture. This happened a lot in the in-betweening code, that painted a line between the previous and the current mouse position if Krita couldn’t keep up with the mouse.

So, now I update only once per mouse-move event, and all is pretty:

It’s not as good as the Gimp’s brush, and circles tend to come out a bit angular, but I am pretty chuffed with this result anyway.

Learning C++ and achieving a decent brush

I am finally being developing the feeling that I am starting to begin achieving the first step towards a modicum of confidence in my ability to achieve a moderate competence in C++. That’s to say, last night I spent a few hours hacking the KisToolBrush class for Krita. I want to achieve what nearly all paint applications manage to achieve: to draw a beautiful, antialiased line that accurately follows the mouse or stylus and is painted in the right colour, gradient or pattern using the correct brush. And I don’t seem able to figure out how to do that.

However, in the process of trying various approaches, I noticed that I was producing new code, instead of cut & paste jobs, and code that was working after only two compiles (the first to fix the typoes and add any semicolons that had shimmied off, the second to actually compile all the code). And when I say working, I mean that the code did what I wanted it to do. Always excepting my naive implementation of Bresenham’s line drawing algorithm — I hadn’t understood that I needed to implement it four times, one for ever major direction. Anyway, keying in code and seeing it compile and work feels good, even though I still have trouble with the *&->.! pointers and references, and especially with const.


A propos of brushes, I have tried the following approaches:

Create a small layer that contains a colored version of the brush shape, and bitBlt it to the target layer on every mouse move event. This left huge gaps where Krita was too busy to allow Qt to push fresh mouse events. It seems that this is a standard feature of GUI toolkits.

Ditto, but copy the old in-betweening code from the early days of Krita. This uses a rather complex KisVector vector class and does a lot of computing to calculate where to place the points in between the positions we get from mouse move events. This was really, really slow; slow enough that when you tried to paint a circle with any celerity, you’d get a straight line from the point where you pressed the mouse button to the point where you released the mouse button.

See the first attempt, but now don’t paint until the mouse button is released. Show the path using a simple QPainter based line, and collect all mouse positions along the way. On mouse-up, fill the path with the brush. This is what I was doing yesterday evening; I still need to do the brush drawing on mouse-up. I suspect that this will also be too slow to be useful.

Mooching code

Of course, when I couldn’t find a way myself, I took a look really hard at other OSS paint applications to see how they manage… I have found the following application that could, or could not teach me the tricks of the trade:

    • Perico, by Peter Jodda. This application has the advantage that it is written using Qt, just like Krita, and that it is well-commented. However, his backend is fairly simple, doesn’t use tiles, and his paint tool doesn’t use brush masks. He paints a simple ellipse on every mouse move event, and inbetweens using an algorithm that I am going to copy tonight.
    • The Gimp. Now nearing version 2.0 (development started in 1994 or 1995, first using Motif, then using a purpose-built toolkit, GTK), the Gimp is everything that Krita still isn’t. And Krita is now in its fifth year of development. I rather suspect that GDK, the underlying layer between the GTK and X11, does a lot of useful work for paint applications. The Gimp takes the whole event loop, picks the most useful and likely events, returns the other events back to the GDK event loop and starts processing the events, in between updating cursor shapes and things. This is complex code, full of special cases and optimizations that I don’t understand, but the most important clues appear to be in the display/gimpdisplayshell-callbacks.c file. To be investigated more fully.
    • GSumi. This application is so weird I just don’t get anything about it…
    • KolourPaint. This uses QPainter/QPaintDevice for its core, and as such is fast, but not really suitable as an example. As you can see from the image above, I already can use QPainter, and it’s not enough.
    • Wet Dreams. This is an example I am going to copy in any case, but interestingly, there doesn’t appear to be any special connect-the-dots code, as you can see from the screenshot:

Showing the code

Oh, well, I knew I wasn’t competent when I started doing this: it’s all a learning exercise… Here’s the source, with all appropriate ifdefs:

 *  Copyright (c) 2003 Boudewijn Rempt <>
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


#include "kis_vec.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_doc.h"
#include "kis_view.h"
#include "kis_tool_brush.h"
#include "kis_tool_paint.h"
#include "kis_layer.h"
#include "kis_alpha_mask.h"
#include "kis_cursor.h"

        : super(),
          m_mode( HOVER ),
	  m_hotSpotX ( 0 ),
	  m_hotSpotY ( 0 ),
	  m_brushWidth ( 0 ),
	  m_brushHeight ( 0 ),
	  m_spacing ( 1 ),
	  m_dragDist ( 0 ),
	  m_usePattern ( false ),
	  m_useGradient ( false )

        m_painter = 0;
	m_dab = 0;
	m_points = 0;

	if (m_points) delete m_points;

void KisToolBrush::update(KisCanvasSubject *subject)
	m_subject = subject;

void KisToolBrush::paint(QPainter& gc)
 	if (m_mode == PAINT)
 		paintLine(gc, QRect());

void KisToolBrush::paint(QPainter& gc, const QRect& rc)
 	if (m_mode == PAINT)
 		paintLine(gc, rc);

void KisToolBrush::mousePress(QMouseEvent *e)
        if (!m_subject) return;

        if (!m_subject->currentBrush()) return;

        if (e->button() == QMouseEvent::LeftButton) {
		kdDebug() << "mouse press button:" << e->button() << "\n"; m_mode = PAINT; initPaint(); // Remember the startposition of the stroke #if 0 m_dragStart = e -> pos();
		m_dragDist = 0;
		if (m_points) delete m_points;

		m_points = new QPointArray(1);
		m_points -> setPoint(0, translateImageXYtoViewPort( e->pos()));
                paint(e->pos(), 128, 0, 0);

void KisToolBrush::mouseRelease(QMouseEvent* e)
	if (e->button() == QMouseEvent::LeftButton && m_mode == PAINT) {

#if 1
void KisToolBrush::mouseMove(QMouseEvent *e) {
	if (m_mode == PAINT) {
		Q_INT32 s = m_points -> size();
		m_points -> resize(s + 1);
		m_points -> setPoint( s, translateImageXYtoViewPort( e->pos()));
		paintLine( s - 1 );

#if 0
void KisToolBrush::mouseMove(QMouseEvent *e)
	// XXX: Funny, this: the mouse button of a mouse-move event is always 0; this problably means
	// I should be checking the status of every button here.
	// XXX: Even if I accept all events, playing around with the stylus gives two or three spurious
	// mouse-move events if I lift the stylus from the pad.
	if (m_mode == PAINT) {
  		QPoint pos = e -> pos();
#if 0
		paint(pos, 128, 0, 0);
		KisVector end(pos.x(), pos.y());
		KisVector start(m_dragStart.x(), m_dragStart.y());
		KisVector dragVec = end - start;
		float savedDist = m_dragDist;
		float newDist = dragVec.length();
		float dist = savedDist + newDist;

		if (static_cast(dist) < m_spacing) { m_dragDist += newDist; m_dragStart = pos; return; } m_dragDist = 0; #if 0 dragVec.normalize(); // XX: enabling this gives a link error, so copied the relevant code below. #endif double length, ilength; double x, y, z; x = dragVec.x(); y = dragVec.y(); z = dragVec.z(); length = x * x + y * y + z * z; length = sqrt (length); if (length) { ilength = 1/length; x *= ilength; y *= ilength; z *= ilength; } dragVec.setX(x); dragVec.setY(y); dragVec.setZ(z); KisVector step = start; while (dist >= m_spacing) {
			if (savedDist > 0) {
				step += dragVec * (m_spacing - savedDist);
				savedDist -= m_spacing;
			else {
				step += dragVec * m_spacing;
			QPoint p(qRound(step.x()), qRound(step.y()));
			paint(p, 128, 0, 0);
			kdDebug() << "paint: (" << p.x() << "," << p.y() << ")\n"; dist -= m_spacing; } if (dist > 0)
			m_dragDist = dist;

		m_dragStart = pos;

void KisToolBrush::tabletEvent(QTabletEvent *e)
         if (e->device() == QTabletEvent::Stylus) {
		 if (!m_subject) {

		 if (!m_subject->currentBrush()) {

		 Q_INT32 pressure = e -> pressure();

		 if (pressure < 5 && m_mode == PAINT_STYLUS) { endPaint(); } else if (pressure >= 5 && m_mode == HOVER) {
			 m_mode = PAINT_STYLUS;
			 paint(e->pos(), e->pressure(), e->xTilt(), e->yTilt());
		 else if (pressure >= 5 && m_mode == PAINT_STYLUS) {
			 kdDebug() << "Tablet: painting " << e->pos().x() << ", " << e -> pos().y() << "\n"; paint(e->pos(), e->pressure(), e->xTilt(), e->yTilt());

void KisToolBrush::initPaint()
	// Create painter
	KisImageSP currentImage = m_subject -> currentImg();
	KisPaintDeviceSP device;
	if (currentImage && (device = currentImage -> activeDevice())) {
		if (m_painter)
			delete m_painter;
		m_painter = new KisPainter( device );

	// Retrieve and cache brush data. XXX: this is not ideal, since it is
	// done for every stroke, even if the brush and colour have not changed.
	// So, more work is done than is necessary.
	KisBrush *brush = m_subject -> currentBrush();
	KisAlphaMask *mask = brush -> mask();
	m_brushWidth = mask -> width();
	m_brushHeight = mask -> height();

	m_hotSpot = brush -> hotSpot();
	m_hotSpotX = m_hotSpot.x();
	m_hotSpotY = m_hotSpot.y();

	m_spacing = brush -> spacing();
	if (m_spacing <= 0) { m_spacing = brush -> width();
	// Set the cursor -- ideally. this should be a pixmap created from the brush,
	// now that X11 can handle colored cursors.

#if 0
	// Setting cursors has no effect until the tool is selected again; this
	// should be fixed.

	// Create dab
	m_dab = new KisLayer(mask -> width(),
			     mask -> height(),
			     currentImage -> imgType(),
        m_dab -> opacity(OPACITY_TRANSPARENT);
	for (int y = 0; y < mask -> height(); y++) {
		for (int x = 0; x < mask -> width(); x++) {
                        m_dab -> setPixel(x, y, m_subject -> fgColor(), mask -> alphaAt(x, y));

void KisToolBrush::endPaint() 
	m_mode = HOVER;
	KisImageSP currentImage = m_subject -> currentImg();
	KisPaintDeviceSP device;
	if (currentImage && (device = currentImage -> activeDevice())) {
		KisUndoAdapter *adapter = currentImage -> undoAdapter();
		if (adapter && m_painter) {
			// If painting in mouse release, make sure painter
			// is destructed or end()ed
			adapter -> addCommand(m_painter->endTransaction());
		delete m_painter;
		m_painter = 0;
		m_dab = 0; // XXX: No need to delete m_dab because shared pointer?

void KisToolBrush::paint(const QPoint & pos,
                         const Q_INT32 /*pressure*/,
                         const Q_INT32 /*xTilt*/,
                         const Q_INT32 /*yTilt*/)
#if 0
        kdDebug() << "paint: " << pos.x() << ", " << pos.y() << endl; #endif Q_INT32 x = pos.x() - m_hotSpotX; Q_INT32 y = pos.y() - m_hotSpotY; KisImageSP currentImage = m_subject -> currentImg();

        if (!currentImage) return;

        // Blit the temporary KisPaintDevice onto the current layer
        KisPaintDeviceSP device = currentImage -> activeDevice();
        if (device) {
                m_painter->bitBlt( x,  y,  COMPOSITE_NORMAL, );
        currentImage -> notify(x,
			       m_dab -> width(),
			       m_dab -> height());

void KisToolBrush::setup(KActionCollection *collection)
        KToggleAction *toggle;
        toggle = new KToggleAction(i18n("&Brush"),
				"handdrawn", 0, this,
                                   SLOT(activate()), collection,
        toggle -> setExclusiveGroup("tools");

void KisToolBrush::paintLine(Q_INT32 s)
	if (m_subject) {
		KisCanvasControllerInterface *controller = m_subject -> canvasController();
		QWidget *canvas = controller -> canvas();
		QPainter gc(canvas);
		QRect rc;

		paintLine(gc, rc, s);

void KisToolBrush::paintLine(QPainter& gc, const QRect&, Q_INT32 s)
	if (m_subject) {

		RasterOp op = gc.rasterOp();
		QPen old = gc.pen();
		QPen pen( Qt::SolidLine );


		gc.drawPolyline(*m_points, s);


QPoint KisToolBrush::translateImageXYtoViewPort(const QPoint& p) {
		KisCanvasControllerInterface *controller = m_subject -> canvasController();
		QPoint p2;
		p2 = controller -> viewToWindow(p);

		p2.setX(p2.x() - controller -> horzValue());
		p2.setY(p2.y() - controller -> vertValue());

		p2 *= m_subject -> zoomFactor();

		return p2;