This forum has been archived. All content is frozen. Please use KDE Discuss instead.

Blending mode that emulates single-stroke compositing

Tags: None
(comma "," separated)
NichG
Registered Member
Posts
5
Karma
0
One thing I've noticed when shading is, if I do all of the shading with a single brush stroke and a hard-edged brush, it tends to look very natural and have a smooth progression of color/alpha/value/etc while retaining the appearance of the strokes used to shade it. However, if I shade with multiple strokes, I'll often get bands of darker or lighter canvas where either I've overlapped with the previous stroke or left a gap.

Image

I think this is because Krita builds the stroke as you draw, and when you release the stroke it composites that entire thing down to the canvas. So if I e.g. reverse stroke back over an area that I've already covered in that stroke, it only updates the values there if the pressure I'm using is higher (e.g. if the stroke is more opaque). I was trying to figure out if there was some way to produce that behavior in general using the various blending modes, but I haven't found anything that quite behaves in that fashion. I think its pretty easy to specify the pseudocode though; something like:

if (stroke_alpha > canvas_alpha) { dest_color = stroke_color; dest_alpha = stroke_alpha; }
else { dest_color = canvas_color; dest_alpha = canvas_alpha; }


This might give some harsh lines if you change colors between uses, so maybe some sort of sigmoidal suppression:

weight = 1.0/(1.0 + exp(-constant*(stroke_alpha-canvas_alpha)))

dest_color = weight*stroke_color + (1-weight)*canvas_color
canvas_alpha = weight*stroke_alpha + (1-weight)*canvas_alpha


The larger 'constant' is, the sharper the transition line between the new stroke having to have a higher alpha than what's already on the layer to come through.

This seems like a simple enough thing to try, but I can't find much documentation about implementing new blending modes or writing Krita plugins in general.
User avatar
halla
KDE Developer
Posts
5092
Karma
20
OS
Hi,

The code for blending modes is in libs/pigment/compositeops. Blending modes are fairly easy to write, so it should be quite doable to experiment there. You might also want to experiment with the difference between the build-up and the wash mode.
NichG
Registered Member
Posts
5
Karma
0
Thanks for that, I've now been able to create a new blend mode that works for greyscale. Something weird happens with color though - it appears to convert whatever was underneath it into greyscale.

I used the Behind blend mode as a template, and replaced the core of the loop with:

Code: Select all
       ...
       float w=1.0/(1.0+exp(-1.0*(dstAlpha-srcAlpha)));

       newDstAlpha=w*dstAlpha+(1-w)*srcAlpha;

        if (dstAlpha != zeroValue<channels_type>()) {
            for (qint8 channel = 0; channel < channels_nb; ++channel)
                if(channel != alpha_pos && (allChannelFlags || channelFlags.testBit(channel)))
                    dst[channel] =    /*each color blended in proportion to their calculated opacity*/
                    ( (dst[channel] * w)     +    (src[channel] * (1-w)))
                    /*------------------------------------------------------------------------------------------------*/
                                             /  newDstAlpha; /*...divided by total new opacity*/
        }
        ...
User avatar
halla
KDE Developer
Posts
5092
Karma
20
OS
Well... If the destination paint device is grayscale, then src will be converted -- or isn't that what you mean? In any case, if you get this working, don't hesitate to submit a patch :-)
NichG
Registered Member
Posts
5
Karma
0
Alright, I managed to fix the greyscale thing - it had something to do with dividing by the new opacity. I'm not actually sure about what the channel_type variable ranges are (0..1 or 0..255) so I tried to normalize by unitValue. There's still something weird where the new hue immediately shifts to whatever you're drawing with instead of the old color, so my guess is that my code for updating each channel is still a bit messed up, but the code for determining the overall new alpha value is fine (that would give the observed behavior).

Current status:

Image

I made a patch file for this version since it does work perfectly fine for drawing with the same color at least; I should say though, this'll be the first time using Git for me, so I apologize for anything I've messed up in constructing this:

http://www.urbanhermitgames.com/greaterblend.patch
NichG
Registered Member
Posts
5
Karma
0
Okay, I think I've managed to fix it and improve the blending a bit. Now it works properly for different colors and doesn't have the weird banding you could sometimes get.

http://www.urbanhermitgames.com/greaterblend2.patch
User avatar
halla
KDE Developer
Posts
5092
Karma
20
OS
Looks good! I'll test it today and if all is well, push it to master!
User avatar
halla
KDE Developer
Posts
5092
Karma
20
OS
Hm... I get two weird results:

* the blending mode doesn't work on the non-transparent default bottom layer
* when using a mouse instead of a tablet, the edges of overlapping strokes are white

(Both are logical, I guess, but surprising when using the blending mode)
NichG
Registered Member
Posts
5
Karma
0
The bottom layer thing is because the alpha of that layer is already at maximum, which as you say is 'logical but surprising'. Can you think of a way to make it seem more natural without actually changing the intended function of the mode to be a 'greater than' operation on the alpha? Maybe re-classifying the blend mode into a different category would be appropriate (there's always 'Misc' of course, but that's not very helpful).

The white edges are kind of surprising to me, and I'm not sure why that's happening. I think I may not be understanding how Krita actually stores colors internally, and that may be part of it. When I wrote the blend mode, this was my thought:

The image contains three channels storing colors (r,g,b or c,m,y or whatever) and one channel storing alpha (a). When you paint with a brush of color (r0,g0,b0) and opacity 'o' onto a blank section of transparent layer, the new value there will be (r0,g0,b0, o*pressure_alpha*brush ).

Instead though, it seems as though the color channels are also not constant, so you get something like (r0*pressure_alpha*brush,g0*pressure_alpha*brush,b0*pressure_alpha*brush,o*pressure_alpha*brush), even when you set dst[channel] = src[channel]. Is that true?

If so, then it seems like to remove the white edges I'd need to actually go one step up in the compisiting code to change where it's post-multiplying the destination color values by the total alpha. Or I need to divide out the alpha somehow while still making sure that values stay within the range allowed by channels_type (I suspect this won't work for anything but 32bit float layers)

Edit:

Here's a patch which appears (for me) to fix the white edges on the mouse. There's a small problem now with drawing strokes of very different hues but at nearly the same pressure, where those now have white edges (due to red+cyan = grey type math). However I think this is probably less disruptive since its not such a 'sharp' feature.

http://www.urbanhermitgames.com/greaterblend3.patch


Bookmarks



Who is online

Registered users: bancha, Bing [Bot], Evergrowing, Google [Bot], Sogou [Bot]