Gaussian Blur / Blur Engine

As @TheTwo mentioned, there is already an MR here to achieve this function. In fact, besides Clip Studio Paint, the smudging brushes in Easy Paint Tool SAI are also similar to the blur brush engine. The existing blur and smudging brushes in Krita use shaking to achieve a similar effect, which makes the smudging strokes look not smooth. Although it is subtle, it can be felt. Of course, this feeling of roughness can be alleviated by modifying the brush spacing. However, when I changed the brush spacing to 0.01, its strokes still have some roughness compared to using the blur engine, and there will be some lag at this time. Below is a comparison between using the blur engine and the smudging engine:


Actually, I have been using this MR for some time now, and I have improved the speed by replacing the original Gaussian blur with the regular blur in Krita.

According to the MR author, replacing Gaussian blur with box blur will make the speed faster. I don’t know if the regular blur in Krita is box blur, but it seems sufficient for me to use.

Another noteworthy point is that the developers believe the reason why this MR is slow is because they blurred the entire dab during testing, but in reality, this is not necessary. Here is a rendering of a straight line drawn using the blur engine in Clip Studio Paint, with a pen tip spacing of 150%:

It can be roughly seen that its blur radius is only 1/3 of the brush size. Since the Easy Paint Tool SAI seems unable to adjust the pen tip spacing, the blur width of a single dab cannot be clearly seen. The effect image after a single mouse click:

It can be seen that the fuzzy radius is approximately 1/2.

Anyway, I have been using this MR in Krita for a while now and I feel it’s great. I also hope it can be merged so that more people can use it.

Can you please post these observations in the MR is they are not already in there. It would be great if the author of the MR finishes this.

Okay, I tried using AI to help with the box blurring, the following is the application code in the paint brush engine:

void KisColorSmudgeStrategyBase::blendInBackgroundWithBlurring(KisFixedPaintDeviceSP dst, KisColorSmudgeSourceSP src,
                                                               const QRect &neededRect, const QRect &srcRect, const QRect &dstRect,
                                                               const quint8 smudgeRateOpacity, const qreal smudgeRadiusValue)
{
    const bool opaqueCopy = (m_smearOp->id() == COMPOSITE_COPY || m_smearOp->id() == COMPOSITE_COPY_SPECTRAL) && smudgeRateOpacity == OPACITY_OPAQUE_U8;

    // Copy the original data into the blurring device
    KisPainter p(m_filterDevice);
    p.setCompositeOpId(COMPOSITE_COPY);
    src->bitBlt(&p, neededRect.topLeft(), neededRect);

    // Blur
    KisTransaction transaction(m_filterDevice);
    KisLodTransformScalar t(m_filterDevice);
    const int halfSize = t.scale(((qreal)srcRect.width()) * smudgeRadiusValue / 2.0);
    const int kernelSize = 2 * halfSize + 1;

    Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> boxBlurKernel(kernelSize, kernelSize);
    qreal kernelValue = 1.0 / (kernelSize * kernelSize);
    boxBlurKernel.fill(kernelValue);
    KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(boxBlurKernel, 0, 1);

    KisConvolutionPainter convolutionPainter(m_filterDevice);

    QBitArray channelFlags = QBitArray(m_filterDevice->colorSpace()->channelCount(), true);
    convolutionPainter.setChannelFlags(channelFlags);
    convolutionPainter.setProgress(nullptr);

    QPoint srcTopLeft = srcRect.topLeft();
    convolutionPainter.applyMatrix(kernel, m_filterDevice, srcTopLeft, srcTopLeft, srcRect.size(), BORDER_REPEAT);

    transaction.end();

    if (opaqueCopy) {
        // Write blur directly to dst
        m_filterDevice->readBytes(dst->data(), srcRect);
        m_filterDevice->clear();
    } else {
        // Copy the original data to the destination
        src->readBytes(dst->data(), dstRect);

        KisFixedPaintDevice tempDevice(src->colorSpace(), m_memoryAllocator);
        tempDevice.setRect(dstRect);
        tempDevice.lazyGrowBufferWithoutInitialization();

        // Write blur to temp device
        m_filterDevice->readBytes(tempDevice.data(), srcRect);
        m_filterDevice->clear();

        // Blend the blur with the destination
        m_smearOp->composite(dst->data(), dstRect.width() * dst->pixelSize(),
                             tempDevice.data(), dstRect.width() * tempDevice.pixelSize(), // stride should be random non-zero
                             0, 0,
                             1, dstRect.width() * dstRect.height(),
                             smudgeRateOpacity);
    }
}

In recent experiments, it was found that the “color running” of CSP is basically Krita’s “smearing” (except that it will blend with the canvas color, even if color blending is not turned on)
For the “intensity of blur” option in “color running”, it will only blur the first dab. This is very different from the blurry MR on the right. Therefore, there is no significant performance consumption

Still waiting for this to end up in Krita

yesss😔

Up for just any update

This post mentions voting; how do I vote for a request? :slight_smile:

I too ran into this when trying to replicate a SAI brush; no matter how I changed the settings I couldn’t get a brush that blends smoothly on light pressure and adds paint on hard pressure. I got the basic function easily enough (blending vs adding color) but no matter what the blending had some painterly texture even with the softest brush tip. If it could just add a bit of blur as well, to hide the brush stroke, it would be perfect.

I don’t mind terribly for most part as I kinda like a painterly look and it is very subtle, but there’s a few instances were a blur would be wanted and the sharp but subtle edge of the brush stroke in the middle of blending annoys me as I try to make edges disappear, not move around. I could select the area and use an airbrush, but it would give a better workflow if the brush could fix that last bit of texture smoothing.

(still not switching from Krita because I still find it much nicer to work with all in all, it’s just a small annoyance. And if I had to chose between always perfectly smooth but never painterly, and always painterly and never smooth - I definetly would chose the always painterly as it is now)

If this is a real question (sorry, hard to read tone through text!), then you can vote right next to the topic title!

The “up arrow” is what you should press to vote for something! ^^

Thank you!
It is a real question, I never noticed the voting arrow :slight_smile:

The voting function was recently changed into this new representation.
And it has its advantages, because now it is always reach- and click-able, before that change you had to scroll up to the initial posting for casting your vote, now you can vote from whereever you are in a topic.

Michelist

For what it’s worth, I paint in a realistic style, worked with Photoshop and Clip Studio before, and in my opinion, the current default Blender Blur brush in Krita is by far the best blending brush I ever used and EASILY beats the Smudge and Blur Tools both in Photoshop and in CSP for achieving realistic rendering and shading results.

That’s, I think, the issue though; not everyone is going for realism all the time. It’s all about being a flexible enough tool to cover a lot of things at once; if I wanted to make a comic “properly”, I’d still return to CSP (unfortunately). If I want a very specific tool (art history brush/ clone brush with sampling that doesn’t change even if you draw over it), I’ll turn to Photoshop because nothing else has those.

I’m of course not “calling out” the devs or anything, but just that each software has their own specific complexities and I can see why people come into Krita, in a way, demanding their feature gets put in. If all experience you have is CSP, then you think it’s the only proper way to do things. Understanding that perhaps the person which demands a feature comes from a very limited perspective, and also understanding that you might also not fully see the benefits of that feature, because your workflow depends on different tooling, is key to finding understanding between the two parties.

Devolving conversations about features into endless “this software does it the good way, and Krita does it the worst way only” and “no, because I work differently and your feature is thus useless to me personally” is just really silly at this point.

I agree; if I have to chose I prefer Kritas way.

..but ideally I want both.

wakocima: I think a big differense is demanding vs asking. I don’t think people mind asking for a feature, but when it’s demanded it gives a bad taste. Because 1. the entitlement and 2 others may prefer the krita way…
Here I do see the issue and I can agree that blurring and smearing is two different things. Which is why I want both…

hope I’m not intruding, if anyone really needs a “blurring” brush, you can use the filter brush (in my case is really slow) or you can tweak the smear brush making with length smear value at minimum. It’s not as instant and might take you some “scrubbing” back and forth while drawing but it seem to be performing better than the filter brush (for me at least)

If someone interested - in the experimental build Krita with clipping (test build) - #136 by uvhat with clipping layers support i made an equivalent of blur brush, it is similar to IllustStudio or Сlip Studio blur brush. It is updated to current (5.3.2.1) state.

You can try how it works and think is it feeling close to blur brush from CSP?

This doesn’t touch smudge engine and smudge brushes like in approach current thread, it adds new options for filter blur brush as closest equivalent in Krita.

Example video:

Description of feature: GitHub - uvhat/krita_clipping_layers: Krita mod with clipping layers feature · GitHub

Fast blur brush is a single commit, the code is rather dirty and draft (not ready for merge requests) but demonstrates the main idea. You can build it yourself using 0008-Implementation-of-fast-box-blur-filter.patch from info archive.

The key changes are quite simple - sliding window blur box filter + accumulation of dabs results in the same stroke + link kernel size with brush size to achieve correct pressure control.

I didn’t test it enough, maybe it has some issues. If you find something wrong with it, let me know please. As example, i’m not sure that can works fine with instant preview mode.

I have tested that, and it does work as intended. Gaussian blur does look perfect, but yeah, the performance is not optimal compared to CSP, and I would love to see the fast blur version. Also, it mentions that fast blur it use box filter, it is same as dual Kawase blur? If not, maybe this could help

CSP actually do the same box blur. I test it on fixed width line with maximum hardness brushes and think this is really just box blur without any sophistic. It is obvious even if we just make simple test on small round dot with blur apply with hardest mask - this gives us just square result of clear box blur. Here is example of blur brush with hard mask result from CSP:

I used sliding window box blur just because it is fast on CPU. Krita has own box blur implementation, but it uses common straight algorithm with box convolution kernel requires all 0(r2 x w x h) ops compared to sliding window box bluring.

Sliding window it is just 2 ops per pixel: previous sum of window pixels + next to window pixel - previous to window pixel. And 2 pass - vertical and horizontal. And after this - usual gaussian mask to merge with destination image. Performance is just linear to data O( w x h). The only kernel-size related operation is prefilling start sum of window for row or column and it cost almost nothing.

In GD usually make blur effects through combining result of texture mipmas because GPUs can generate many LODs of texture on fly, downsampling/upsampling ops in this case are almost free in performance. This is described in article.

Kawase filter is GPU-specific algorithm, required mipmaps pyramid. It has linear performance too, same as sliding window box blur (fixed 5+8 reads per pixel), but requires additional efforts for upsampling/downsampling, this operation is very heavy without texture blocks on CPU.

If it need, i can add 3 pass approach (with checkbox for feel difference) - it will make 3 passes of same sliding window box blur which are almost perfectly approximate gaussian curve, but still same fast. I actually have tried this, you can find traces of this experiment in the diff file in void KisBlurFilter::processBoxBlur() but i got not very good feeling from it, it was a bit different from CSP result (as from test i made, it using just 1 pass box blur, without any sophistication) on 1px width test line. Or you can achieve almost same effect by just reducing dabs distance with lowering kernel width (17% kernel width to brush width is default for CSP/IS and in my attempt, for 20-25% dabs spacing, closer dabs would require lower kernel width because same area will be processed more often).

Also, both CSP and IS have rather non-obvious behavior, not existed in Krita, that works for all kinds of brushes masks in CSP and IllustStudio/MangaStudio, including blur brush masks - they reduce max opacity of every dab if step space distance becomes lower than ~33%. This makes drawing and bluring softer too with default values. In Krita we need adjusting opacity and flow to current spacing interval of dabs manually to achieve same result.

Just wondering, how effective if I upgraded my CPU? Currently, I’m planning to upgrade my CPU, but bit in a dilemma about what I should focus on. Should I go for better multicore performance or more on single core perfomance.