Week 38: On the path to fix smudge bugs

The week started with some fixes in Dyna tool I already wrote about in the previous blog entry. Then I started to work on bug with smudge. And I still did not fix the issue completely. I started to investigate the smudge. I did not write that brush engine, so it was new code for me. I already did some bug fixing there previously. But the code has been changed in the meantime by Pentalis, our Google Summer Of Code 2010 student who by the way did great testing for smudge.  I had to start to understand the code again. I did some small cosmetic changes in the code first.

Then I started to track down the bug. Smudge currently gives some blocky effect we need to fix. I started to code small unit tests to test various code places. I tested KisPainter and that one seems to be ok as other composite operations work correctly. Then I started to test the composite operation, which Cyrille wrote recently for the smudge and I discovered the problem in it. It does not work as it supposed to and instead of copying according the mask, it is doing strange things by copying rectangular area of pixels. You can see it on images created by tests TestKoCompositeOps and KisAutobrushTest.

When preparing the unit test, I discovered segmentation fault when converting part of the KisFixedPaintDevice to QImage. Fixed.Finally I commited the test I prepared. Now I’m going to try to fix the composite copy operation. Patience is the key to fix this bug. That’s all for the week 38.

Krita is becoming more robust and stable. Krita community is working in full swing fixing various bugs. I suggest you to go and compile trunk and play with it. If you find some problem, bug, crash or slow performance, please report it to us.

Posted in Krita | 2 Comments

Week 37: Brush rendering, UI and some performance fixes

This week of sponsored Krita development started with fix for bug 250621. I merged some of the brush engines as I promised at Akademy 2010. One of them is softbrush, which is now part of the default Pixel Brush. The merge also requested some new requirements. One of them was adding support for rectangular masks. They are probably not very used by artists when they paint, becausethey were quite buggy and we did not get any bug report about it. But hopefully we have developers, who test Krita and we found crash when the mask was rotated. I fixed it. I forgot to define the value of the rotated mask, so it crashed.

Next fix was for bug 242547,which was about corrupted masks in auto brush. When you rotated some mask, the result was corrupted. We added rotation first to Autobrush, then to Predefined brushes. It was implemented probably fast and old rotted code was still around causing some inconsistencies. Also the bug was caused by another code I was fixing. Preview computation was done by own way of rendering of the mask. I fixed it so that it reuse the code that brush is using when painting on canvas. Pixel brushes are using preview image in the architecture of brushes as base for the painting. So the base class set the width and height according the preview image, but mask brush called autobrush has different dimension, mask dimension. Problem is solved, fixed autobrush corruption.

Bug 245130 and bug 217124 are quite complicated as they require new composite operation. I enabled new version of the composite operation called COPY composite and it is used when you want to copy pixels as they are, from source to destination. It is needed for paintops that move pixels, but also in various parts of Krita. Cyrille added missing features like respecting the selection and respecting opacity needed for the fix. This composite operation was disabled by Sven from being user-visible, because it caused result, that was not usable much. It is not visible for layers and tools now. But we found new solution and some composite operations will be visible only for brushes that can make a use of them. In case of COPY – duplicate, deform and smudge can use them maybe as default operation but we did not find nice solution for it yet. In GIMP smudge does not allow you to use different composite operation.

I was working in my spare time on spray brush a little. I wanted to disable QImage brushes, but apparently Boud liked the workflow and I ended up with fixing the predefined brushes, because they were not respecting the HSV transformation done in color dialog. The workflow for professionals usually is different – they have a lot of brushes around. For casual user who wants to test spray brush and spray some image, the current workflow with QImage is much more faster. So it stayed for 2.3, but it will be changed for 2.4. Instead of one hour of programming I spent time on discussing the problem.But nice idea we produced by us with Boud – putting the QImage file dialog to custom brush.

I also fixed the color dialog in spray. Signed char was quite small for int. Also I used the new Krita’s slider there. I also commited some presets for Harmony project adoption in Krita I plan to blog about.

Back in work I fixed painting with dyna brush in our scratch pad. It was bug 231468 which was about non-working paintops in scratch pad. Scratch pad is small canvas without flake and other heavy-weight stuff. It is also without KisImage class and that is hard dependency for some brushes like duplicate. The bug was closed with ability for paintops to disable scratchpad.

Bug 244910 was about slow brushes in CMYK and when something is slow in Krita, I make it faster. CMYK is not widely tested as artists we have use RGB colorspace. Brushes were slow due to recreating color transformation in CMYK called HSV adjustment. HSV adjustment is RGB specific feature, so the pixels are converted anyway. The creation is now done once and brushes are usable again. Bug is fixed and closed. Though I noticed that HSV transformation is working on CMYK with filter but not with brushes. Sven commited working solution. Other paintops that use it have to be ported. When I cached the transformation, I forgot to delete them and I noticed also memory leak in Hairy brush for bristles. It is fixed.

Dynamic strokes with dyna toolCalligraphy strokes with dyna brush in Krita
Made with two-buttons and wheel button mouse

Bug 248695 was about outline of the dyna tool now showing. I made it different so that it can reuse freehand tool code and have all the features from the freehand tool and also the brush outline. I implemented the dyna tool so that user without tablet can draw with pressure or rotation. Dyna tool can provide this information from mouse movements. Now we have sensors and you can paint with different pressure e.g. from mouse speed and also rotation can be set to some drawing angle or fuzzy. But still Dyna  tool has it’s use case not present in the sensors and that is calligraphy strokes. The simulation has physical background, is very simple and works nice but you have to master it a bit. It was proof of concept long time ago and I still find this tool a nice toy for painting.

I also worked on the bug in the move tool but Sven has taken over and fixed it. I did small research that might hopefully make it easier for Sven.

That’s it for week 37. The action plan III has many bugs fixed, but not just by me. Bugs are also fixed by regular contributors like Sven, Adam and Marc. Great job, Krita team! We are quite fast approaching the user-ready Krita!

My full-time sponsored contribution is heading slowly to it’s end. I have two weeks of bug fixing in front of me. On a personal note, I start my job career in company Ixonos Slovakia, but starting it in Finland for some period. I will most probably work on Linux/Qt/MeeGo stuff and even maybe some OpenGL based projects. I look forward!

Posted in Krita | 3 Comments

Week 35,36: I fixed, I fixed, I implemented, I fixed…

The week 35 started with fixing issues in paintops, which occurred when I merged some brush engines. I fixed deserialization in the presets, I cleaned some code in the mask generation code and I commited the presets which mimics the merged brush engines. They are now installed with Krita. The merged brush engines are now removed, you need to clean your installation directory to see the effect.

In the last minute (feature freeze in KOffice trunk currently) I also worked on small feature, which improves usability and I called it dynamic outline. We used to have static outline of the brush, but when you rotated the brush or scaled it, the outline was still the old one. I made it dynamic. If any sensor or tablet changes dynamic attribute of the brush through the stroke, it will be shown in the outline (scaled, rotated).

Then I started the work on bug 217124. It’s quite complex bug, so I’m still working on it. But I have first results. I fixed deform brush, so now deform moves the pixels correctly when you are in COPY composite mode. The price is that deform is now little slower. I added benchmark to see what is happening. I will optimize it a little, but later. When I was on deform brush, I fixed some UI stuff like using the new slider we have in Krita.

I spotted some problem with Line tool not setting the background color for the paintop, I fixed it.

Some testing showed some problems with outlines (deevad reported it, thanks). I was fixing some offset issues. When I was on it, I improved the performance a bit. We converted the polygons to the QPainterPath every time we draw the outline, so I cached it now and it is converted only once.

Then I fixed 247835. Hairy brush now paints correctly with predefined brushes. It also brought some speedup, and cleaner&clearer code. I think the performance was improved by not copying some big arrays there. It was my very first code I wrote for Krita.

Then I continued on the bug 217124 . I implemented Multiply composite op for alpha colorspace, which is needed for merging the selections. Merging selection was also fixed in KisPainter, first by our GSoC 2010 student, Pentalis. I benefited from his code and I based my fix for different method on his approach but because I had multiply composite op, I changed the code, so that it includes less copying and improved the performance.

Next task was adding the selection awareness to composite called Copy. This composite operation mix the pixels in a way where the destination pixel is overwritten with source pixel. But the selections were ignored. I implemented basic version, which I need to be able to fix the bug 217124 with basic formula and Cyrille Berger implemented the formula he suggested and which is somehow more complicated and require some template foo. When I was at it, I noticed infinite loop in Composite Copy. Fixed. I prepared also unit test which tests if the infinite loop will occur and also if the mask is respected in the simple formula.

Krita has now 2 community sponsored developers. Me and Dmitry Kazakov. Check out our III. generation plan. Back to making Krita best application of the year 2011 2010!

Posted in Krita | 4 Comments

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

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!

Posted in Krita | 7 Comments

Week 31: Fixing custom and predefined brushes

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!

Posted in Krita | 3 Comments

Week 30: Custom brush works

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

Posted in Krita | 3 Comments

Week 29: Performance and bugs

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.

Posted in Krita | 2 Comments

Week 28: Bug fixing

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.

Posted in Krita | 3 Comments

Week 26,27: Photoshop brushes, Krita on Windows

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 😉

Posted in Krita | 15 Comments

Week 25: Photoshop brush support II

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"/>

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.

Posted in Krita | Leave a comment