Archive for July, 2010

Week 29: Performance and bugs

Monday, July 26th, 2010

This week I continued to work on fixing Krita. I started with outlines. When you fix something, you break something other sometimes. It happened this time. Duplicate brush known as clone tool in Gimp is somehow special regarding the outlines. It is showing outline even if you are not in outline mode. It is also the only brush engine, that needs to know the position of the input device (mouse/tablet) and my new API with QPainterPath did not included this information. I fixed it, changed the API and also use the position of the device for other brush engines. That resulted in more simple code in the freehand tool. Also I simplified the way you code outlines. Before you had to specify the area of the outline in separate function so that it is erased correctly when redrawing the canvas. Now I use bounding box of the QPainterPath. One bonus of my work is this one: When you wanted to duplicate some area, first you have to setup origin point with CTRL+click. This was outlined by cross. Now it is outlined by the same outline as your brush. You can more clearly see what you are duplicating.

Duplicate outline You can see the duplicate area instead of just cross

Then I had to fix outlines for predefined brushes. That is brush which consists of brush mask saved in format like gimp brush or adobe brush and is stamped as you paint.When you scaled with the brush in the preference, the outline was wrong. Now it is fixed.

Then I spent one day just with profiling the predefined brushes. Various problems occured. The benchmark with QtTestLib did not work correctly. First it ignored the command line argument -iterations n. I needed that one because I wanted to profile just one iteration. QBENCHMARK_ONCE was not solution in that case. The benchmark time was not real. Then Sven Langkamp helped. He was asking if I check if the resource has been loaded (grb/gih brush in my case). I did not, I thought that the resource manager will not continue until the brushes are loaded. And that was the mistake. The code continued. So I added 3 seconds sleep to the benchmark so that I wait for the manager to load the brushes. Then I was able to valgrind my benchmark and provide the log.

We use QImage in Format_Indexed8 as storage for gray-scale masks. We need to scale them and interpolate between them on-the-fly as you paint your stroke. And that was slow.
I started with simple fix of the performance for the case when the size of the brush is constant. Our sensors had curve which was sampled with wrong count of samples, and then some rounding errors have showed. This just avoided a little scaling and improved the performance a little.

Cyrille Berger then pointed out that scaling code is slow due to calls to QImage::width, QImage::height and QImage::scanline. I noticed that too in the log but I did not pay attention as I thought QImage::width would just return m_width or something. I was wrong. It is doing also one check. I cached it and it was success! The performance has improved, the speedup was 1.33 (The benchmark dropped from 16 seconds to 12 seconds).

Regarding scanlines, QImage::scanline calls detach due to implicit sharing of data. We had some code to access single pixel like

quint8 valueAt(){
return m_qimage.scanLine(y)[x];
}

in scaling and interpolation code and that is not very good for performance. First I though that I will cache the pointer to the last accessed line and line below. In scaling code we access 4 pixels, 2 are on line and 2 are on line below. It was not bad, but still the result was not good. Then I spotted QImage::bits(). I decided, let’s try to cache the pointer and access the data in old way dataPtr+(y*width+x). We know the format, so it is safe. It did not work. I was surprised, the image was somehow distorted. Solution was QImage::bytesPerLine(). I did some little test and the conclusion was that QImage::bytesPerLine() != QImage::width() for QImage in Format_Indexed8 (one pixel – one byte). Caching the pointer to the data and accessing the pixel like dataPtr+(y*bytesPerLine+x) is great performance solution. The speedup was ~2.2. Test dropped from 16 seconds to 7.3 seconds.

I managed to fill the target of the week, speed up predefined brushes. I decided that one day I will spent on drawing lines routines. I thought that I will focus on optimizing the way how we draw the QPainterPath, but then I got source code of scanline algorithm for drawing lines with nice endpoints  from my cousin, who is great graphics coder. The code was written in Delphi long time ago. I ported it to Krita API with his permission. The direct port was little slow, it drown 100 lines on 4096×4096 in 4.5 seconds. I optimized it and my time was 2.5 seconds for 100 lines. It is still slower then KisPainter::drawThickLine() which has ugly endpoints, this routine is good for drawing 1-2 pixel wide lines as it is fast. If you need some wider lines, you can use the new routine. Drawing lines with QPainterPath is still the slowest way. I plan to try to optimize it when some time will left. Lines can be used e.g. in hatching or sketch brush. Thanks to my cousin for his code ;)

I noticed that our Autobrush is somehow slower compared to what we had when it was freshly optimized. I profiled and spotted that feature called randomize is not used but computed. Random generator is used and it was expensive. I added one special case condition and now the brush is 1.5x faster. It is used in various brush engines, so everybody can benefit.

In my hobby time with Krita I worked on sketch brush engine. I still has to prepare some presets, add few options and then I will blog about it more. It is new exciting brush engine and was already used for creating art!

Animtim: Tribal WarriorAnimtim: Tribal Warrior Illustration

This week I will spent time on making the process of creating bitmap brushes easier. I will probably fix the custom brush and make it better or copy the workflow from GIMP.

Week 28: Bug fixing

Monday, July 19th, 2010

This week was devoted to bug fixing. I started with line algorithms. We had some various todo’s in our KisPainter’s line rasterization routines. KisPainter is something like QPainter in Qt, but works with our KisPaintDevice. KisPaintDevice is pixel buffer with no boundaries (infinite buffer if you have infinite memory! But Chuck Norris was able to fill it anyway it is bounded from qint32_MIN to qint32_MAX pixels) and can store pixels with various bit depth (8-bit,16-bit,…, various colorspaces supported in Krita).

We had API bug in the rasterization. KisPainter has KisPaintDevice and can operate on it. The rasterization line algorithms used iterators and overwrite pixels with memcpy, which was wrong. Correct way is to respect the actual composite operation. Also opacity of the painter (alpha) was not respected. Guess who fixed it? Yes, me. Now the routines are little slower due to using compositing, but that is unavoidable. What is this good for? E.g. now if you render some brush mask or anything, the lines in the mask can be composited with different composite modes (Alpha blending, Multiply, Add, Divide, etc. ) and every line could have different opacity. That would be usable for hatching brush or new cooked sketch brush I’m working on in my spare time (which actually does not exist much these days) or even in hairy brush somehow. Anyway it is usable and it was a todo to fix :)

I fixed also DDA algorithm, which we use for rasterization of the aliased lines. Rounding the coordinates is not always correct, this time ceil was needed. This fixed bug in Pen brush when 1 pixel line is used. We could use Bresenham’s code which is known to be faster, but when I did benchmarking last time, the result was that DDA was slightly faster on my laptop. Maybe my implementation was bad. It was long time ago :) .

For anti-aliased lines with 1 pixel width we use Wu lines. For anti-aliased with variable width we use drawThickLine method, which has ugly endpoints. I decided that we will reuse rasterization routines from QPainter. It has nice lines with nice styles and cool endpoints. But because Krita is using higher bit depth, it has to be done little different. First we render the curve saved in QPainterPath with some style saved in QPen into QImage as black&white mask. We fill our pixel buffer with e.g. 16-bit colorspace pixels and we use QImage mask on it. It is slower 10-times as our routine for painting anti-aliased lines. But it works. It’s on my TODO to fix the performance of it.

Deevad reported the bug about general usability of Krita, brush outlines. I worked on outlines before, so I was assigned to this bug. It was quite complicated before to construct outline as many transformations was needed. In Krita we have pixel coordinates, document coordinates (dpi) and view coordinates (respect zoom, etc.). You had to care about this in your brush engine. I hated that as I wanted to care only about pixels in brush engine. First Sven moved the view coordinates out of the paintop (brush engine) to tool. Now I even moved the document coordinates to tool. Before we painted the brush outline in paintop using QPainter, now the paintop passes QPainterPath in pixels to tool and tool is responsible for painting the outline correctly (conversions, painting, …).

According the bug report, the outlines were invisible as they were black always. Now we use XOR for them, so they are always visible. For Arthur canvas  (QPainter based one) we use QPainter::RasterOp_SourceXorDestination which does not work for OpenGL-based canvas in Krita (QGLWidget).You see just Unsupported Composite Mode or something like that. So we use native OpenGL code for rendering the outline with XOR. The rendering of the outline is quite fast. But some brush masks are slow to render. That’s what I will be working on this week. Make bigger brushes painting faster. Sven Langkamp added option for Deevad so that the outline is visible as you paint.

I will try to finish sketch brush engine and blog about it, coz it is quite fun brush engine. See you next week.

Week 26,27: Photoshop brushes, Krita on Windows

Wednesday, July 14th, 2010

I continued to work on Photoshop ( PS in text further) brush presets support in Krita. I was exploring what features we miss and I started to implement some of them. I implemented parsing of the brush and tip dynamics and brush properties serializing so far. Parsing of the presets is complicated as it can be somehow context related. Some attribute is present only when some other attribute is present. I realized that a lot of work will be needed to support the brush presets. I think that support of the PS brush presets is big project and with Action Plan II we don’t want only new features, but we aim for user-readiness and that means fixing serious bugs. I and Boudewijn, we decided to post-pone the brush preset support. My task was to write a list of missing features. I did that in our wiki, which was moved recently to http://community.kde.org/. As you can see there, it is quite a lot of work.

I managed to implement mirroring feature shortly before we decided to post-pone this task. So you can turn on mirroring of the brush mask in new dialog added to Pixel Brush. It is equivalent to Flip X, Flip Y from the PS. So far it is dynamic attribute controlled by sensor like pressure. If you use mouse+pressure and turn on mirroring and e.g. mirroring horizontally, the mask will be mirrored. Basically the mask is mirrored when pressure is more or equal of 0.5 (50%) which is what the sensor gives by default. If you use tablet, the pressure is changing. If you want to mirror the mask always, select pressure sensor and tweak the curve, so that it always gives 1.0 result (move the first point of the curve from bottom left to top left).

Brush mask mirroring

Brush masks can be mirrored vertically, horizontally or in both ways. Equivalent feature to Flip X, Flip Y in PS.

One interesting problem is where to put the feature in UI. Some features are shared among the brush masks (autobrush, predefined brush, text brush) like spacing and it is duplicated on every tab. It might be interesting to clean it in some way so that it is shared instead of duplicated. I did not know where to put the mirroring. If only to autobrush and make it part of the brush mask generation or if I should duplicate it on tabs like the spacing is. It is dynamic parameter in PS so I decided to implement it as sensor. It is not part of the brush mask generation, it is operation that post-process the brush mask. It can be dynamically controlled by pressure, tilt, randomness (fuzzy sensor) etc.

Then I was at Akademy for the first time. Thanks to the KDE e.V. I was able to go there. The travel expenses to Finland are quite big for Central East European. Thanks again.I had presentation about Krita, little bit similar to the one for Libre Graphics Meeting. I was demonstrating the brush engines a bit and talk about what I’m working on in Krita. You can see it on-line here. I was giving the presentation on Sunday. I attended also a lot of the program, it was interesting. E.g. Lubos Lunak’s KDE Performance or Tips&Tricks for KDE development was nice and some more I liked that I can’t remember now :)

We decided that I will spent the week devoted to Krita on Windows here. I joint the KDE Windows team at Akademy and I managed to build Krita on Windows with huge support from Patrick and others from KDE Windows team! It took few days. I complied Qt, kdelibs and some other dependencies. It took quite a lot of time. Especially Qt’s WebKit took forever to compile and kdelibs are also longer to compile. I compiled Krita on Windows 7 with MS Visual Studio Express (free edition) using emerge, tool written by the KDE Windows team. Nice tutorial I followed is on TechBase. After that it quite easy. Patrick prepared target krita for me. It builds only Krita and not other KOffice applications like KWord etc.All I needed to do is “emerge krita” and follow console if the compilation fails or not. We used kdelibs-4.4, it’s more stable, last time I tried, kdelibs were used from trunk and that had some compilation problem I was not able to solve. I split the compilation into steps as I knew that it took quite long to compile some dependencies. So I did emerge kdelibs-4.4 etc. Hopefully I managed to compile Krita, and then run it.I have development environment, I could develop Krita now on Windows. What is better is probably debugger. What is worse – valgrind does not work on Windows. I will stay in my beloved Linux environment.

Krita trunk on WindowsKrita on Windows: Native look&feel
Higher resolution

I was scared from the native look&feel from Windows, but that’s maybe because I don’t spend much time on Windows. Good thing is that you can tweak kdeglobals and use Oxygen for Widgets.

Krita on Windows: Oxygen

With Oxygen it looks more native to Linux user :)
Higher resolution

I tested Wacom Intuos 3 tablet on Windows. It worked, I prepared the screenshot with the tablet ^. You can see that pressure works! I had small problem, the tablet was not calibrated. I had small screen dependent offset between the stroke and cursor in both apps -  GIMP and in Krita. The performance was quite good and I think usable. I even used debug build which is usually slower then release build. Last problem that we need to solve is to have simple installer for users. I discussed with Patrick about it, but it is not very easy issue, so I don’t know what will happen. We will see.

That’s it. I will work on bug fixing these days. Also it seems like a lot of features will be scratched off from Action Plan and we will focus more on stability. I will report again next week ;)