Week 29: Performance and bugs

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

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

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 ;)

Week 25: Photoshop brush support II

June 28th, 2010

Last week I worked on spray brush to make some room for Photoshop presets. This week I focused on abr format again.

I started with inspecting the Krita preset format. We save our presets as embedded text in PNG file. We use Qt for it, class QImageWriter is responsible for it. The preset is saved as XML. It used to be a text file, but Cyrille made it png file for few reasons. First was that the preset contains binary data – image of the preview stroke used in UI. Second reason is the performance. PNG compress the file nicely, huge amount of presets are loaded much faster at the Krita start-up compared to presets saved as XML files with binary data. Bonus is that you can see preview of the brush preset in Dolphin as the preset is image. Editing or viewing the XML became a problem. You can’t open the PNG in editor and change the XML of the preset easily. I needed to see the structure of the XML so I wrote easy viewer in few lines thanks to Qt. Reading the structure from Krita source code would be kill :)

Then I looked back at the ABR parser, python script written by Valek Filippov a.k.a. frob. Frob and Alexander Prokoudine did some work on Photoshop resources. I’m interested only in ABR brushes as it is part of our plan to support those. I ported Frob’s abr parser before, but there were some changes in the script lately. I tried to port them, but I don’t know if they were useful. The abr parser is becoming more general and contain some code that is useful for parsing other resources like Photoshop gradients. The structures used for saving binary data for Adobe resources share some ideas. The commit message to git repositary with changes was not very helpful – I found out this in chat with Frob.The parser is quite a hacky tool to discover what is in abr file. There isn’t specification available which would allow you to implement the abr brush. The whole parser is output of reverse engineering.

I decided to move to work on translator of the ABR brushes. Thanks to Alexander Prokoudine’s abr description I was able to start. I worked between the parser and translator for the rest of the week. I created new class that takes into account attribute, type and value parsed from abr as QString and then it put those values into classes. Every class has toXML method. That method specify how is the abr translated to our preset. It can create preset for Spray brush or Paint brush.

Let me show you how the abr looks like:

"Objc" "brushPreset 20"
"Nm  " "TEXT" "Hard Round 1 1 - shape dyn - rotation - all off/zero"
"Brsh" "Objc" "computedBrush 8"
"Dmtr" "UntF" "#Pxl 1"
"Hrdn" "UntF" "#Prc 100"
"Angl" "UntF" "#Ang 0"
"Rndn" "UntF" "#Prc 100"
"Spcn" "UntF" "#Prc 25"
"Intr" "bool" "1"
"flipX" "bool" "0"
"flipY" "bool" "0"
"useTipDynamics" "bool" "1"
"flipX" "bool" "0"
"flipY" "bool" "0"
"minimumDiameter" "UntF" "#Prc 0"
"minimumRoundness" "UntF" "#Prc 25"
"tiltScale" "UntF" "#Prc 200"
"szVr" "Objc" "brVr 3"
"bVTy" "long" "8"
"fStp" "long" "25"
"jitter" "UntF" "#Prc 0"
"angleDynamics" "Objc" "brVr 3"
"bVTy" "long" "0"
"fStp" "long" "25"
"jitter" "UntF" "#Prc 0"
"roundnessDynamics" "Objc" "brVr 3"

It was quite a lot of work to find out what does this mean and put all the structures together according what they mean. So far I managed to create preset only for computed brushes in Photoshop (ellipse-based), so the result produce something like this so far:

<Preset paintopid="paintbrush">
<param name="brush_definition">
<![CDATA[<Brush type="auto_brush" randomness="0" spacing="0.25" angle="0">
<MaskGenerator radius="0.5" ratio="1" type="circle" vfade="1" spikes="2" hfade="1"/>
</Brush>
]]></param>
</Preset>

It is not integrated into Krita yet. I have just class that translate the first brush parameters, I will add option to convert abr into set of kpp (Krita paintop presets). I would like to work further on translating Photoshop attributes into ours. It will require still a lot of work. Some features are missing and they need to be implemented, some parameters will be harder to map. E.g. abr structure contains image data and they are not parsed by Frob’s script so I will need to find a way how to connect the image data with presets. I ported some other abr parser which parses only brush masks – images. That’s already integrated in Krita. These two parsers will have to be merged somehow in the future.

Week 24: Photoshop brush support in Krita

June 21st, 2010

Thanks to the pledge we did long time ago and thanks to the community member, Silvio Grosso, who made major donation, I’m working on Krita for next ~12 weeks full-time.  The Krita community and me prepared Action Plan II. It’s something like Google Summer Of Code proposal but with much more targets. I have tasks per week, not one big target like Summer Of Code projects.

First two weeks of the plan are devoted to the support of the Photoshop brushes. I worked on it already in Action Plan I, I added support of the brush masks (bitmaps) from the file format, but there are still presets saved in the ABR format and we want to support them. The presets are for defining the brush rotation, roundness,  it’s dynamics like spreading in some area or some color stuff like HSV changes. I’m working on it right now.

First I thought that let’s create new brush engine that mimics the Photoshop one and then load the presets into this one. But that would be waste of time. We already have brush engines that have features like the Photoshop one, but they are scattered in our brushes engines – especially in Pixel Brush and in Spray brush. Cyrille pointed me to this fact.And other problem is that we need to unify the brush engines. Those I created are little divergent to the other brush engines, so I’m unifying.

First thing I had to do was to add support of our standard brush dialog to the spray. That way you can spray abr bitmaps because of preddefined brush dialog shows them and you can spray Auto Brush masks and even Text. Great, isn’t it? I spent few days with that. I fixed some bugs and now the standard dialog brushes are sprayed in the same way as the old limited shapes which were there before (pixel, anti-aliased pixel, ellipse, rectangle, brush bitmap – texture). This step is needed to support the abr presets.

New clouds with Krita are even better then the real things (U2)!

Made with Krita now: More realistic clouds

We also started to work on backward compatibility with our presets so I just could  not dropped old shapes and pixels because your presets would be broken.Working on backward compatibility is not very funny – but it’s important. It builds character as Boudewijn said.The backward compatibility works for spray presets, if you find bugs, contact me on IRC or fill a bug report.

Old clouds with sprayMade with Krita before: Comic clouds before

The old shapes are useful so you can still spray old not-softness-aware circles, ellipses etc. I wanted to merge those stuff into our standard brush dialog, but it turned out to be complicated. We have KisBrush class that units all the brush masks under one class. I was trying to turn my shapes into that one, but I would need more time to design it and implement it wisely. The outcome of this is some interesting discussion in the Krita mailing list about my idea of random shapes based on QPainterPath.

At the end of the week I spent some hours on something different than ABR brush for a while to rest from ABR. It’s basically UI thing for airbrushing.
We had Airbrush brush engine in Krita, but it was basically only copy of Pixel Brush with hardcoded option for incremental deliver. We dropped it due to spray brush which simulates the Airbrush much more better and then we want to have less brush engines with overlapping features, so it was wise decision to drop the Airbrush brush engine.

Airbrush as optionTurn your brush engine into airbrush mode

Every brush engine can act like “airbrush”, by delivering the brush mask when you do not move with your input device in the actual position. It’s done on the tool level – the free hand tool incrementally call paintAt at the same position in time interval specified by rate. So I moved the rate option from the tool to the brush engines GUI and now it is part of the UI and you can turn on/off airbrush option in the UI for brush engines which want to support “airbrush” mode. And even it can be part of the brush engine settings you can save, that was not possible before.

On the different topic, I spent nice weekend shortly before my final state exam in Brussels at the Libre Graphics Meeting. Thanks to the KDE e.V. for the sponsorship! You can watch my talk in some funny language slovakish-english about some brush engines I wrote for Krita . It was great event.  I met some cool guys like GIMP developers for the first time. I also teamed up with Martin maxy Reynolds, mypaint maintainer and core developer. Great event, nice place to talk about Krita and meet people in real-life.

What I realized at LGM:
1. Krita does not have many users. Most probably due to not releasing stable release for a long time. Distributions ships the stable ugly Krita 1.6.x. Let’s hope in change with Krita 2.3 which should be the first user-ready,  stable, awesome, great, unbelieveable release.
2. I don’t like the vegetarian food at all. Brussels chips fries FTW!

In my real life I graduated two weeks ago! I’m Ing. Lukáš Tvrdý now. I finished 5 years of university, it can be translated to Master Of Science. Weeeee. I’m now going to job interviews and writing cover letters. So far it’s fun.

I'm going to Akademy

I’m giving a talk at Akademy 2010 about Krita: what it was like to be GSoC student, community sponsored developer, talk about how we are doing in Krita community and I will show some results of my work in Krita. Be there ;)

Week 10: Default button

April 7th, 2010

I hopped to the second part of the action plan already in week 9 and I’m working on features now. I also fix bugs these days. Performance part is pretty much done. But when we discover some regression or some other performance issue, I solve it. We also discovered that now the parts of the Action Plan are actually obsoleted. I also need time to focus on my thesis text and prepare for the state exam to become Master Of Science so we moved my last 3 weeks of work to June. I will continue to work on June til 1st week in September. It was already announced in Last Week In Krita. Thanks to the sponsors who allowed me to work more on Krita full-time.

The 10th week was devoted to concept of default button for presets. When you paint, you change the curve of the brush engine, then you tweek this value and maybe now you want to have default settings of the brush engine back. I added button for it to the brush engine configuration dialog so that you can access it fast.

How it works? That was quite complicated path, I did not know exactly what it should do in the beginning and unclear or weak requirements are bed thing. So I discussed with Boud about it and also with Sven Langkamp to come up with solution or better said how we want to implement this feature.

We have default settings for brush engines which are saved in the UI. When you code the brush engine, you setup default values in the GUI. Then when Krita starts, it loads those defaults. We decided that we don’t want to load the default preset out of the GUI but we will use saved preset. You can setup the GUI of the brush engine – that is the brush preset – and save it for later reuse.

Default button

Now we install default preset per brush engine into …/share/apps/krita/defaultpresets. We still load the defaults out of the GUI but when you click on default button, the saved default preset is loaded. The benefit is that if you don’t like our default preset, you can put your default preset to ~/.kde/share/apps/krita/defaultpresets and when you click the default button, the preset is loaded. You just have to name it according /install-path/share/apps/krita/defaultpresets. We still load the default preset from the GUI. This way we take the burden of maintaining the default presets according the state of the GUI of the brush engine. When I add new feature to the brush engine, I have to update the default preset.

Besides this I worked on performance regression, I checked the flood fill. It’s cool to have benchmarks around. No regression found! I also put some time to be in connection with potential GSoC students. So far it looks like Krita will have really great student proposals.

It was nice week, let’s hope the default button will be useful for you. I hope you had nice Easter!

Hairy brush improvements

March 29th, 2010

I renamed my very first brush engine, sumi-e, I wrote a long time ago. It was Google Summer Of Code 2008, the year I joined the Krita team. This brush engine started my joy over  the brush engines. I have improved it lately.

In hairy brush you don’t define the brush mask, but you define the bristles. Every bristle has a position and lenght. For setting up the bristles I used to use Guassian. I like this function for it’s naturalness, that’s why I used it. The value of the function defined the lenght of the bristle. But in turned out that it is not very effective. It produces masks you never had enough control over its dimension. You had to understand the sigma in Gaussian and not every artists care about math.

So I decided to reuse the brush dialog that is shared among the other paintops and use pixel masks to define the bristles. Every pixel define the bristle. The intensity and alpha of the pixel define the lenght of the bristle. The position is computed according the center of the brush. What it gives? Many new possibilities for shapes of the hairy brush. You could have only round shapes before, now you can have any shape. If the pixel is transparent, the bristle is not in the brush – that is the trick to have any shape. Other bonus is that you can have colourful shape also, that was not possible before.

Confeti brush defines the shapeConfeti brush can define position and the length of the bristle

You can control the hairs spacing in the brush shape by parameter scale. This parameter scales the position of the bristle from the center of the brush. Next parameter is random offset. The path of the bristle is randomly deformed according this parameter. You can setup the limit of the offset of the bristle from the initial position. So far the scale has been always controlled by the pressure. I made this optionally now by adding the size sensor and implement that support in hairy brush.  So you can turn off the change of the size according pressure or you can control the size by different sensor like time or distance or tablet’s tilt. And now you can paint also grass more easily as enkithan reported to me.

GrassLukáš Tvrdý – Developer tries to paint grass

There were some performance problems also with sumi-e, now hairy brush. I benchmarked and fixed the related bug and I introduced also some feature that helps with performance. Usually the more bristles, the more slowdown. I added possibility to control the amount of the bristles. You can specify the percentage of the density of the brush. Quite useful when you need big brush.

Related feature is also threshold according the pressure. Now when you paint, only the bristles which are able to touch the canvas, will be painted. Before I used opacity to control the used/unused bristles. Now it is more fun. You can turn off the size sensor and use threshold to control the size of the brush stroke. Or you can combine as these features complement.

Threshold

Next feature is called soak ink in Ink options. Bristles can be colorified according the first contact with the canvas. So you can use photo as the source of the color. Or you can process some photo like I did to demonstrate the feature

Hairy brush used for post-processingHairy brush used for post-processing image to give hairy look

I have some other ideas how to improve Hairy brush. Rotation has to be also controllable according the sensors.Ink depletion has to be reconsidered, more color effects has to be added. The bristles should be more anti-aliased. We will see.  Feel free to contact me if you have anything related to hairy brush features.

Here is the crop of the sketch that Boud, the Krita maintainer, did with hairy brush lately.I like especially the hairs and the eyes.

Wounded girl

Boudewijn Rempt – Wounded Girl

Week 9: Mirroring feature

March 29th, 2010

Last week we finished the part of the action plan regarding the performance. I started to work on usability improvements. The first usability improvement is canvas mirroring.

Canvas mirroring is useful in your painting work flow and Boudewijn explained that it is useful for artists to check errors in their paintings. In the classical painting you need some hardware for that — you need to find a real mirror and see if your painting looks creepy or not. In Krita you don’t have to buy a new mirror, you can use the canvas mirroring feature.

It is also useful when you paint something that supposed to be “almost” symmetrical. You paint ears of your hero or you paint head outlines. You can paint the first half, mirror the canvas and paint the second half and check back quickly. Other possibility is to use symmetrical feature I wrote for Softbrush lately.

In Krita you could already do this by transforming the image and it’s data but that can be slow if you are painting on big canvases. Krita aims to be professional tool, we want to support big canvases. So we need this also as projection feature. We will not transform the image data, we transform the projection of the image data.

I started to work on this and I had a feeling that this is not going to be about a lot of code but this task is going to be about orientate yourself in some complex part of the Krita called projection and about orientate in coordinate systems we have. You also have to find out and understand how the scrolling and zooming works.

I contacted mypaint maintainer maxy with some short mail asking how mirroring is done within mypaint. The answer was what I basically expected and it proved me that I might go in right direction.So first I started with mirroring the coordinates of the tools. We have few coordinate systems in canvas. First coordinate you get is the widget coordinate. That one is transformed according the scrolling offset. Then document origin, the top left coordinate of the image in canvas, also has to be taken into account. Next the zooming transformation follows.After this you are on document level coordinate system. On this level I mirror the coordinate. That means that the document is changed in mirrored manner. This works so far very nicely. For vertical mirroring you need to do transformation like scale(-1,1) and then translate(width,0) where width is the width of the document in my case. We mirror just vertically.

The next step is to mirror the projection of the image. I started to work in QPainter canvas as that one is the more complicated one. First I mirrored extra buffer QImage we have in canvas to be able to use RasterOp operations with Arthur. QImage::mirror()  was just quick&dirty hack to see what will be broken. So tools outlines were broken as they don’t know about the mirroring and some features worked in mirrored way. I removed the dirty hack and I used QTransform for transforming the QPainter. I left the QPainter transformed this time also for tools so the outlines were fixed.

The OpenGL canvas was similar to QPainter canvas, I needed to do just the transformation and again setup the QPainter which is used even in OpenGL based canvas for painting the tool outlines.

Then I started to work on fixing some related, quite complicated, bugs. I spent a lot of time with them without solution. The issue does not seem to be complicated. You just need to mirror some coordinates. But it is hard to know which ones, if you did not write that projection code. The issues are that when you scroll and zoom-in  in the part of the image, it became transparent. I suppose that projection thinks that part of the image is not visible so it does not update it or something like that. But I don’t know really if that is true and I can’t fix it either. Similar problem occurs in OpenGL canvas. I was not able to fix it either. I was trying but without lack. Try&error method did not bring any good results.

Then I spent time integrate the feature into Krita. You can mirror the canvas by View->Mirror Image (CTRL+I).To sum it up, the feature is in koffice-ko feature branch as trunk is frozen. It works with bugs we know about. We just need to fix them. The week I spent on it was nice start for this feature. Let’s hope the feature will be bug-free for Krita 2.3.

New brush in Krita: Softbrush

March 22nd, 2010

I’m quite busy right these days. I don’t have much time to blog about some nice stuff I have done in Krita in my “spare-thesis time”. I’m writing the thesis about brush engines I implemented for Krita and I wanted to have a brush that is very common among digital painters. I noticed that they heavily uses the basic Pixel brush. I decided I want to have some special pixel brush.

I stared to change the function that produce the brush mask and affects it’s softness. I selected Gaussian as it is nice function and I experiment with this function, but I found it complicated to control it (you setup sigma, uh what is sigma, you artist ask?). So let’s add some different function to the brush mask code. Oh, let’s put this decision to the artists hands, let’s give him some curve he can model as he want. We already has nice widget for that in Krita, so use it. So you can setup the brush mask by curve!

Default CurveSetup brush softness with the curve

On the picture you can see Curve and Gaussian. The top red point represents the value of the brush mask in the center of the brush mask and the red point down in the right bottom corner represents the value of the edge of the brush mask. So far the brush mask supports only elliptical shape but I have also plans for rectangular shape of the brush mask but I need to find a way how to extend the 24-hours limit of the day so that I can code it :) You can add as many control points as you wish and tweak the softness between the center and edges.

You noticed the Gaussian also there, the mode and it’s code works but it will need some more love in the future. It gives some nice results also sometimes. I will see how it will end up.

So let’s make crazy setting of the curve and you can end up with the brush mask like this one with interesting edges:

Funny maskStroke with custom curve to change brush mask

As you can see, the brush supports pressure with no problems. Thanks to the Cyrille’s sensors you can change size by various attributes of your tablet or you can use some silk ones like a time or a distance. The brush supports also a rotation so you can setup some ellipse shape of the brush with a diameter and an aspect ratio and do some funny stuff like this one:

Rotation Dynamics of the soft brush in action

You setup an angle for your shape and you can allow Krita to manage the rotation for you. As you noticed, the opacity has been changing also, yes, you can control opacity with pressure. Those attributes are present in Pixel brush in Krita also. Let’s go for some new one.

I added parameter density to brush so that you can simulate in some non-photorealistic way brushes like charcoal, chalk and crayon. Basically you setup how many pixels from the brush mask will be used in a single dab.  Very nice attribute, I managed to simulate a little charcoal drawing, here is my “developer” painting:

Softbrush, charcoal attemptSoftbrush density demo: Lukáš Tvrdý – House with charcoal

When you paint, your hand can shake. Maybe your hand is very stiff, and you don’t shake with your hand when you paint :D . So I added a parameter for you. It is called Jitter movement. You can setup how much the brush will be jittered when you paint. Maybe want to draw straight lines with shaky feeling. This is example of some mild jittering:

JitteringSoftbrush mild jittering

If you exaggerate the parameter, you can end up with the result that spray brush, my other brush,  do for you

Spraying with jitterSoftbrush crazy jittering

Last feature, that is a little experimental, is called a HSV dynamics. I suppose you know what is HSV, it is physical color model, which model the color in terms of tint – hue, saturation and intensity – value. It was invented for artists so that they can more powerfully control the color they want to choose. I decided to change the parameters of the color dynamically in a stroke. This is still WIP (work-in-process) but so far you are able to grow the parameter, shrink and control it by pressure. Here is some demo, the feature with pressure can produce images with more gradients easily and you can avoid pick-that-color-and-put-it-here problem a little bit:

HSV DemoSoftbrush: Hue growing, saturation shrinking and value growing

You can control the progression by a curve again and more control over the HSV can be done by using pressure option and using the tablet. These features are for 2.2. For 2.3 I already have some new feature and it is called mirroring and it was inspired by David Revoy video tutorial. He is using something very similar in video (link to 4:40).

You define the vertical axis with the softbrush with CTRL+LEFT click and then you paint in mirror mode. If you setup the axis outside the canvas, it is turned off.

As you paint, your brush mask is mirrored and then painted in the mirror mode. It is WIP also, we plan to make it more general so that every brush engine can use it. I get the idea as I was working on canvas mirroring.

I produced video, watch it here in action.

I plan to blog about other new achievements but again time is issue. Beside I work on Krita full-time, I write thesis so I don’t have time these days. If you want to help me, you can produce some art with my brush engines and then I can include your work in my thesis! I’m interested in pictures created with spray, softbrush, sumi-e (will be renamed to hairy brush), deform brush and particle brush and its combination. Some of the pictures you see here are already part of my thesis. You need to compile trunk for it but it is easy!

So far enkithan and n-pigeon provided a lot of nice paintings for me. I pick up the picture that n-pigeon did lately with just soft brush and spray brush.

Softbrush and spray brush: n-pigeon - Watercolor Softbrush & spraybrush: n-pigeon – Watercolor

Week 8: Vectorization cancelled

March 22nd, 2010

In week 8 I started working on a part of the Krita I had never touched before. It is actually a library inside KOffice called Pigment. Pigment is responsible for the color management. It contains some colorspaces we actually don’t use in Krita because they are too simple for Krita’s needs. They are intended for use by other applications in KOffice. So far the applicationss do not have color management, but they can if the developers want it. Pigment contains many useful classes which operates on the color in some colorspace in many ways like doing the math, compute the histogram. It also contains the implementation of the composite operations. And here was my interest as we wanted to try to vectorize the composite operations by using SSE instructions and the vectorization feature in GCC4.x.

So first I started to write benchmarks for the various composite operations. And then I started to work with GCC feature. Vectorization of the composite operation is already implemented in GEGL by Øyvind Kolås. Also GIMP 2.6.x is using MMX, SSE, SSE2 so I had inspiration and I was trying to map it to our implementation of composite operations. GEGL is using nice code, but GIMP is using assembler directly. I don’t have much experience with assembler. I wish I had write assembler lessons previous year at the university. It is not hard
to code something, but it is hard to do it correctly. I read somewhere in Inkscape mailing list that they had some assembler code to speedup some work but it ended up to be slower then code optimized by compiler.

I had quite a hard time and I did not manage to implement the vectorization even with help I get regularly from Cyrille and boud and other Krita hackers and
GEGL hacker. We stopped it on Wednesday because we discovered that the issue is more complicated then we thought and it would require much more than two days to finish. Maybe another week or even weeks. And the result could be not faster. One of the problems we discovered was that the RGBA 8-bit colorspace uses the unsigned char datatype (quint8 in Qt) for the memory storage but when you do a composite operation, you have to retype it to the bigger data type like a int32. Why? If you have a pixel in quint8 with value 255 and other pixel with value 200 and you add or multiply them, you overflow the data type. And the result is bad. If you retype, you have solved that issue. I studied the GIMP code and how it is
implemented there. It is solved using MMX instuctions for this case. The MMX technology supports both saturating and wraparound modes. You can read about that more in details here. Another issue was that GIMP does not compute every composite operation with vectorization but only some composite operations are implemented this way.

So we decided to start to work on the other item in the action plan which is mirroring of the canvas and possibly rotation. On Thursday and Friday I was back in the canvas code. So far I have working code of the mirroring of the events from input devices. Now the hardest part will be to implement  mirroring in the projection. Projection is the code responsible for correct displaying of the zoomed image and it computes the image you see in the canvas when you scroll or move or some tool paint it’s outline or some part of the
image is changed by some tool. The task will continue also for OpenGL canvas as we have two canvases in Krita.

You see, the vectorization week does not bring any speedup, but I don’t want you to be sad so I decided to write a blog post about other Krita work I do in my spare time. Read it here.