Archive for the ‘Krita’ Category

Week 32,33,34: Sheep, fixing and cleaning Krita

Monday, August 30th, 2010

Let’s review what I was working on in Krita lately. On Monday, week 32 I pinged  David Revoy on #krita IRC channel, digital painter,with question what bugs him in Krita the most lately. He wrote nice report for us, but he started with 3 things.

First, he was missing global curve for the pressure mapping. Every dynamic parameter can be controlled by pressure from tablet and with custom curve for it. But the global curve is also useful and we did not have that one. I implemented it, you can tweak it in Settings->Configure Krita->Tablet settings, which shows just one big curve widget for you. On the related note, Adam Celarek is working on new curve widget as the current one is optimal for image processing but not much e.g. for painting.

Next David’s request was option for setting the color of the canvas border. He is using GNOME and some dark theme. Our gray color was too much for him. First enkithan suggested some GTK theme for Qt to him. But the canvas border is not theme-able. I’m not sure if it should be, probably not.So I added new option in Settings->Configure Krita->Display -> Canvas Border.

When you do speed painting as David, you need to be fast :) . One of the tasks that is slow is to pick color. And to make it fast, we made the painting tool to be able to pick colors.You just press CTRL and you pick color. Precision was missing, you were not able to see what color you are picking when the tools are in brush outline mode. I fixed it by showing the Color picker tool icon. Next problem was our strategy to pick color. Before the color was picked when you pressed the mouse button. If you moved, the color was not changed. I did not like that behavior, so I changed it. Now when you pick color, you click and you can move and you see in color selectors the color you are selecting. When I was on it, I checked also our color picker tool and fixed the behavior there too. Color picker is little more stronger then the freehand tool,e.g. it can do averaging of the area you are picking.

Holiday week 33 followed, I had holiday week,I was taking care of some sheep in Greece and enjoying the sea.

No code, just sheep

Next week was devoted to cleaning. We have same brush engines that overlap with features. So I started to merge them. I started with Soft brush. It is brush engine I wrote for my thesis. It’s main feature is that you can setup softness/hardness by curve, so it is more powerful then just one linear value. Also soft brush has density feature which is usable e.g. for chalk simulation and you can control softness with pressure. I merged it with Pixel Brush.

I ported the code as circle mask generator based on curve and wrote a new rectangular one. The rectangular mask generator was not present in the old one, so it is new feature ;) I also added support for the spikes. Then I fixed the way we compute the preview — this is also nice, now you can have live preview of the brush mask inherited from Pixel Brush gui. It was not there before! It’s cool as you can see how the change of the curve reacts to the resulting mask without the need of some stroke testing.

Then I ported the density feature. Now you can have density also with old (default) Pixel Brush masks.I plan to add the density feature also to brush masks.

Soft brush had also “jitter movement” feature. As you paint, the position of the dab is jittered as you would have shaking hands. I implemented it as sensor and I renamed it to “Scattering” and added possibility to jitter in two axes (X, Y), which are rotated according the drawing angle. It will be useful when we will map the Photoshop brushes to Krita – Photoshop has similar feature with less options. It is merged, so now you can spray in Pixel brush. But only one dab per scatter event. Spray brush has a new competitor.

Other feature was the softness controlled by pressure. Now it is dynamic also for Pixel Brush. Both fading/curve softness can be controlled by sensor. It was little tricky for soft brush, I wanted to improve it so I changed the way I compute it now: You setup the curve and the softness is changed by moving the points of the curve on it’s y-coordinate according the pressure. All points are transformed expect the first and the last one. If there are only two points, one artificial point is added to the center of the curve and translated up&down according the pressure (or sensor value).

The merge has many benefits for the users. Now you can spray the soft brush masks with spray! Or you can use soft brush mask for hairy brush bristles! Cool!

Then I co-operated with Sven Langkamp regarding the fix of the deserialization of the gimp brush presets. The brush was not selected in the UI when you loaded the preset, but it painted. It had bugs like wrong outline etc. Reported by David Revoy, fixed by Krita team!

Other brush engine, called Pixel Pencil, was not written by me. But the code was so similar to Pixel brush, that I decided to merge it. I created one new sensor called Sharpness and it is responsible to give Pixel Pencil behavior. All it does is converting the sub-pixel positions into pixel positions – the masks are more sharp. Then it does thresholding of the alpha values of the mask. If the brush mask pixel is lower then threshold, it is replaced by transparent pixel, otherwise it is replaced by opaque pixel. No anti-aliasing — Pencil effect. Merged!

My first brush engine, the derivation of the Hairy Brush (actually Hairy Brush is derivation of the Chalk but that is different story) called Chalk is merged too as it does everything as Soft brush, which is now merged in Pixel Brush. Only the Fade effect was missing. I implemented it as a new sensor. It will be also useful when the Photoshop brushes will be mapped to Krita more. It was missing for that work too. Extra points also for work on abr brushes ;) I will prepare the factory preset so that the chalk is easily found.

I spotted some bugs with deserialization of the brush masks. E.g. deserialization of the rectangular mask type did not worked – nobody tested the preset with that type of the mask probably. It might be less used. I did mistake too when implementing the merge and I fixed the deserialization of the mask types (so far default mask type and soft brush mask type – Gimp brush mask type might come handy in the feature).

Next week I will continue to work on Krita. I suppose to prepare Action Plan v3 thanks to the donation from Silvio, big Krita supporter. Basically I will fix bugs from bugzilla and from reports from the Krita artists (enkithan, Animtim, deevad, …). The aim of the last part of my sponsored work is stability effort. It will take one month and maybe some more. Cheers!

Week 31: Fixing custom and predefined brushes

Monday, August 9th, 2010

Custom brush was disabled feature for quite a long time in Krita and I resurrected the feature from the dust in week 30. Further testing uncovered a lot of bugs.

First bug occurred when you used color mask as grayscale mask. The mip-mapping levels were still grayscale when you returned from grayscale mode to color mode, so the brush looked grayscale even when you wanted to paint in color. Fixed.

When you paint in mode “Use color as mask” with some predefined brush, the colorful image is converted into gray scale image first. But for the mip-mapping it is converted to masks, where white means transparent and black means opaque. We had bug in computing grayscale-convert-to-mask. Fixed. Another use-case when we convert color image is when we create custom brushes. You can save colorful brush (with RGBA) and then paint with it as mask if you want with Use color as mask mode or you can select Use color as mask in the custom dialog and the brush will be saved as grayscale. In the later option your brush file (gbr) is smaller. The workflow was broken. Fixed. Professional digital painters usually use grayscale masks. Colorful masks are on the other hand used by me when I’m trying to do crazy colorful stuff with spray brush (e.g. HSV transformation is fun with colorful masks).

Then I fixed the saving of the gbr with Krita. Gbr stands for Gimp brush. We were saving gbr brushes in wrong format, usually just the name, which has to be in UTF8, was saved wrong and was corrupted. I had nice opportunity to look at GIMP code. ANSI C! Fixed.

GIH Brush scaled!GIH brush scaling works now

When we saved the GBR brush, we used just random name for it. It was code to avoid name conflicts. We use different strategy with presets. If the name conflicts, we append number til we don’t conflict. First I fixed our strategy for brush engine presets, first let’s try saving the file with name specified, then add numbers only if in conflict and then I added this behavior to custom brush dialog.

Creating brush engine presets for the dialog Custom brush is not usable. You usually use custom brush dialog as editor for creating the bitmap brushes, which are, if usable for you, added to predefined brush dialog. Then you can select the new created brush and make some useful preset for it. I fixed the UI – I disabled the saving button for saving preset when you are in Custom brush dialog. We have some UI defects in Krita, we have to fix them so that user is not confused.

I was admiring David’s latest work, Alice. I kinda missed that Krita was not used so when David was around, I pinged him on IRC at #krita, what bugs him in Krita these days (he use trunk). He replied:
o scaling the predefined brushes does not work for grb/gih brushes on canvas
o option for canvas border color
o global curve for pressure mapping

I looked at the Shift-drag feature (my internal name for changing the size of the brush on canvas – you press shift and you start to drag the mouse to change the diameter). I fixed it for
predefined brushes. But scaling for GIH brushes was not implemented. GIH stands for Gimp Image Hose. So I added the code and now also GIH brushes are scaled! Slangkamp did some testing and he found some bug when you reset the boundery (e.g. it is triggered by Use color as mask checking) and the scale != 1.0, the brush outline was wrong. Fixed. And that was the end of the week.

I will continue with global curve and if nobody takes over, I will implement also the option for color of the canvas border. I go to holiday this week (no computer, just N900) , but good news! I will work on Krita even in September! It will be focused on Krita stability. A lot of excitement in Krita development! Krita rocks!

Week 30: Custom brush works

Monday, August 2nd, 2010

Creating custom brushes is important for digital painters. A brush mask you can create using procedural dialogs we have (like Auto brush, Softbrush) has advantages of being somehow like a vector – you can easily scale them and rotate them without artefacts. But they are restricted to the shape – we support rectangular or circular shapes only.Recently I had idea of creating vector brushes with QPainterPath. I think we need to create some nice ui for that and it could work. There are problems, like supporting features like hardness/softness, but I did not dig deeper. It is nice area where somebody can play in Krita.

Bitmap brush has advantage of being any shape you want, but when you scale it too much, you see artefacts. Usually you open some image or photo reference or you use your current state of the canvas, make some selection with selection tools and save the selected pixels to your palette of your brush masks. This did not worked in Krita for a long time. We have custom brush dialog in Brush Tip for this in Krita. The custom brush dialog was disabled probably since 2.0.x series, because of it’s buggy behaviour. We decided in the planning phase that we add this on the Action plan and I will work on the fixes.

I started with the exploration of the code. First I fixed cloning of the KisBrush, somebody forgot to copy some important d-pointer members when we create a new clone from KisBrush.
Next problem was that the selection made on canvas was not taken into account. The brush was always created from the whole image you see on canvas. It was fixed quite easy. I just did copy of the projection of the canvas (the composition of all layers) and I applied global selection mask on it. Then I computed bounding box and tada – we have correct brush data.

Custom brush in KritaCloudy brush in custom brush

Then I was working on improving the workflow. E.g. you setup spacing, we recreated the brush again, so if you tried to paint with the brush, tweak the spacing, you lost the brush. You lost the brush also everytime the dialog of custom brush was shown. Fixed. Use button Use as Brush if you need to recreate the brush. Also you could not give a name to the brush which is shown in the UI. I added this little feature there too. If you use empty name, the brush will be named with the current date&time. The file name of the brush is always random, that might change soon. I would like to thank enkithan for input on the UI of the dialog.

We separated the GUI from the painting of the widgets a long time ago when Sven worked on presets. Custom brush did not painted. The fix for painting involved putting temporary brush into our brush resource server.  I had some problems with crashes. KisBrush is KisSharedPointer. You probably know this concept. Short version: you don’t delete the allocated memory manually, the data are deleted automatically thanks to the reference counting in shared pointer. We add the brushes to our resource server based on KoResourceServer and that one is not aware of the shared pointers. I added some new behaviour to survive with shared pointers. The solution is not the best one, it smells like a workaround, but I discussed this with team and we decided that it is good for now. The proper solution would be resource server with support for adding objects that are shared pointers, not just pointer that will be owned by resource server.

I started to fix painting with color masks. You can check option Use color as mask. It means that the mask will be converted to grayscale and will serve as alpha mask. You can select any color for the brush mask. Currently it is broken, I will try to fix it. I worked on it already, but the design is little complicated to read and I’m not finished yet. As I worked on it, I noticed some place for optimization and I made the painting with predefined brushes little faster (speedup is 1.1).

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

Week 25: Photoshop brush support II

Monday, 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

Monday, 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

Wednesday, 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

Monday, 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