You can open Mesh Gradients in Krita now!

TL;DR: meshgradient get rendered in Krita just like you'd expect them to render in other apps

Well, because I couldn't get Bicubic interpolation fully working by the time of writing and publishing this blog. This part is still in pending state :(

Firstly, here are is a screenshot of a complex meshgradient which I found from Inkscape (I modified it to be Bilinear to get this):

pepper.svg



As, there isn't much else to say besides giving out the details, I'll jump straight to the technicalities :-)

Technicalities

Rendering (Bilinear)

I started with reading the algorithm mentioned in the specs sheet i.e Divide And Conquer. I had some confusions about it, but thanks to my mentors. I got the idea. Then, to implement this algorithm, the idea was to subdivide the patches, until the difference between the corners is small enough.

Now the question was, how am I going to divide the surface. Luckily, there was a paper, one search away, which I picked up. I read it quickly and started writing the algorithm on paper. Somethings didn't quite make sense, but as I later found out that they were probably misprints.

So, as I implemented the algorithm, the first thing that I tried to open was this, to check if the subdivision algorithm was right. Comparing this to Inkscape, it was pretty accurate (not the color, but the surface itself).



Next, get colors for the new corners was simple too. You could get it by dividing it in half (we get this from the fact that the surface is Bilinear). Then just mix them when difference is less than the tolerance and you get this:


Caching

Because rendering meshgradients is expensive and anything e.g a simple translation would call for a complete redraw. I had to write a way for poor man's caching. Instead of painting directly on a the canvas. I would first paint on QImage and then paint it on QPainter.

Simple enough! However, there was a problem, rendering on QImage and then on scaled up QPainter made things look very pixelated because of scaling. So, to counteract this, my solution was to paint on scaled up QImage i.e scale it up by the same factor as the QPainter. This made everything a whole lot better.

Rendering (Bicubic Interpolation)

This has been the hardest part about this GSoC (till now). The way I usually write code is, I write everything in a bit messy way till I get something running. Then I come back and check if something could be written in a better way (read efficiency and readability). This approach went horribly wrong as I probably had made mistakes and my render was pure chaos and nowhere close to what you call interpolation. Then I git stashed everything and started over in a more manageable way, now the interpolation was working. But turns out, it was an exact replica of the Bilinear interpolation, but slower (because it did actually calculate derivatives and what not).

So, I asked for help. But then I immediately realized that my assumptions were wrong. In short, I was calculating derivatives incorrectly. I quickly modified the code in the dirty way and finally I could see a smooth render with no Mach Banding. Unfortunately, this too is based on an assumption, which is not correct for the all cases. So, Bicubic interpolation works neatly for Mesh Patches which are relatively linear. But falls out of place for a more messy (generic case) ones.
But why? The problem here is with the way I do subdivision, it is only valid for linear Coons patches. I haven't written the code to subdivide a Bicubic surface. That isn't the only problem, subdividing Bicubic surface is an expensive operation as well. So, I'll have to find a middle ground.


Saving

Since I spent a lot of time on getting Bicubic Interpolation to work, for a context switch I moved to the next milestone.

So, I tried to implement the saving operation. Thanks to all the abstractions! This was pretty straightforward to implement.

I will now write tests for it and the final rendering :)

That's all I have for now, hopefully I'll post an update when I get the Bicubic shading working :)

Bugs

...while rendering

Hello once again!

First of all, sorry for not making a blog post early on during the community bonding period. I couldn't because I was mostly busy with Krita's Android release.

Secondly, some of you might remember me from the previous year. I was GSoC student for Krita. Now it is my second time! :-)

Finally, to tell a bit about my project. My project is to add support for SVG mesh gradients in Krita and the relevant task is: https://phabricator.kde.org/T13101 and branch is: https://invent.kde.org/graphics/krita/-/merge_requests/378

Now over on to the interesting stuff!

So, let's see what I did the past couple of weeks.

1. Parsing

This is probably going to be easiest and most of us know hows and whats about his. So, we throw any SVG with meshgradient element, in it and Krita understands it now.

2. Making a shape out of the path.

Now, this turned out to be a bit complicated than I had anticipated, not because this was hard. But because there were a few edge cases which I overlooked. I would fix one edge case, but it would break the other one, classic whack-a-mole. So, I had to do a few rewrites of this tiny 'SvgMeshPatch and SvgMeshArray' component. Finally, I just put it all on paper and got it working for all cases, in a proper manner *.

The way I started was to manually compare some 'edge case' values to see if they're correct and fit the logic. But, there's so much that an eye can overlook. What I think I should've done is to write unit tests and then handle each edge case respectively. However the good thing is after writing unit tests, I discovered two cases where my logic wasn't right.

3. Deeper dive into the technicalities

I will try to explain how I did in a bit more detail. If you have any suggestions/critique, you're welcome.

In the big picture sense there are two things to consider, when talking about meshgradients, meshpatch and meshrow (which is a linear array of meshpatches). As per the specifications, meshpatch is a Coons Patch, which is just the shading/fill defined by the interpolation of colors placed on the corners (i.e. edges of the curve).

Because they can be seen as a two dimensional arrays, creating an array of meshpatches, seems the most logical approach and that's what I did. However there is a slight catch, each patch shares side with other patches. Eg. in case of a gradient with a single row, up to two sides are shared. So, while parsing this had to be taken care of.

So, now we have SvgMeshArray which is an array of of SvgMeshPatch and each SvgMeshPatch owns a KoPathShape, which is how the fill boundaries are going to be defined. And that's basically all there's to it for now...

On a side note, one thing which is in my mind is that because each meshpatch is treated as an individual Shape, there's some duplication with the shared sides. But I think this is an optimization problem, which has to be taken care of, but after rendering :-)

Now a couple of obligatory screenshots, just to double check :)





PS:

* I in no way consider myself an expert, yet. Feel free to look at the code and comment on it.

You can open Mesh Gradients in Krita now!

TL;DR : meshgradient get rendered in Krita just like you'd expect them to render in other apps Well, because I couldn't get Bicubi...