Python: how to apply blur to selected part of layer only?

Hi, if I do

   blurFilter.apply(backgroundLayer, 0, 0, 3000, 2000)

the filter is applied to the whole layer, ignoring the selection. how can I apply it only to the selected area? Thanks

I’m not a programmer, but it seems to me that you should apply the filter to the selection rather than the layer…

Maybe something like this?

from krita import *

# Krita Instance Objects
ki = Krita.instance()
ad = ki.activeDocument()
ss = ad.selection()

blurFilter.apply(ss, 0, 0, 3000, 2000)

The first few lines are stolen from @EyeOdin in this thread.

I’m afraid not. apply wants a Node, and Selection is not a Node.

If I try that, Krita says “argument 1 has unexpected type “Selection”” … :frowning:

Hi

Filter apply method is:
filter.apply(node, x, y, width, height)

Then you have to get current selection, and provide selection boundaries to filter method

from krita import *

application = Krita.instance()
currentDoc = application.activeDocument()
currentSelection = currentDoc.selection()

if currentSelection is not None:
    currentLayer = currentDoc.activeNode()
    
    blurFilter = application.filter('gaussian blur')

    # apply( layer, x, y, width, height  )
    blurFilter.apply(currentLayer, currentSelection.x(), currentSelection.y(), currentSelection.width(), currentSelection.height())
    currentDoc.refreshProjection()  # update UI

For a non rectangular selection… :thinking:

Grum999

Ok, but this way I can only apply the filter to a rectangular area, right? I need to apply it to a nonrectangular selection (which is my previous stroke actually. The selection is obtained with the action “select opaque”)

(I’m trying to mimic the blur which watercolor makes with the re-wet option in Rebelle)

Maybe selection mask/filter mask?

Unfortunately, yes…

As indicated in scripting lesson, filter API is still a work in progress…

https://scripting.krita.org/lessons/filters

For now the only ways I see to apply filter on a non-rectangular selection are:

  • Try to trigger action, but poor in performances compared to API usage and not possible to set filter parameters
  • As suggested by @Bleke, you’ll have to do tricky things with filter masks
  • The last possibility I can see is tricky too: create a node (not attached in document layer stack), copy selection content in this node, apply filter on this node, copy content of this updated node and paste it in the current node

If there’s a simple way to apply a filter using the current -or a given- selection I’m interested to have it too :slight_smile:

Grum999

Yep, I’m trying the copy/paste solution but without success for now. it will take some time… thanks

Here something that’s working (a mix between solutions 2 & 3)

from krita import *

application = Krita.instance()
currentDoc = application.activeDocument()

if currentDoc is not None:
    currentSelection = currentDoc.selection()

    if currentSelection is not None:
        currentLayer = currentDoc.activeNode()
        
        if currentLayer is not None and currentLayer.type() == 'paintlayer':
            blurFilter = application.filter('gaussian blur')
            
            tmpDoc = Krita.instance().createDocument(currentDoc.width(),
                                                     currentDoc.height(), 
                                                     'tmpDoc', 
                                                     currentDoc.colorModel(), 
                                                     currentDoc.colorDepth(), 
                                                     currentDoc.colorProfile(), 
                                                     currentDoc.resolution())

            tmpLayer = tmpDoc.createNode('tmpLayer', 'paintlayer')
            tmpLayer.setPixelData(currentLayer.pixelData(currentSelection.x(), 
                                                         currentSelection.y(), 
                                                         currentSelection.width(),
                                                         currentSelection.height()), 
                                  currentSelection.x(), 
                                  currentSelection.y(), 
                                  currentSelection.width(), 
                                  currentSelection.height())
                                

            tmpFilterMask = tmpDoc.createFilterMask('tmpFilterMask', blurFilter, currentSelection)
            tmpLayer.addChildNode(tmpFilterMask, None)
            
            tmpDoc.rootNode().addChildNode(tmpLayer, None)

            currentLayer.setPixelData(tmpLayer.projectionPixelData(currentSelection.x(), 
                                                        currentSelection.y(), 
                                                                currentSelection.width(), 
                                                                currentSelection.height()), 
                                    currentSelection.x(), 
                                    currentSelection.y(), 
                                    currentSelection.width(), 
                                    currentSelection.height())
                
            currentDoc.refreshProjection()  
            
            tmpDoc.close()

It’s not optimized but it works :slight_smile:

Grum999

2 Likes

Looks wonderful. I’m trying! :heart_eyes:

BTW, why did you use a temp document? Couldn’t you create a new node in the current document, without adding it to the tree? Edit: probably because of the filter mask.

Good question :slight_smile:

It’s possible to create node without adding it to tree and work on it.

But from my tests, in this case node seems to be considered as “not visible” and then projection pixel data is not updated with mask (that’s weird for me because asking a projection of a layer shouldn’t take care of visibility of layer but…)

So you have to add the temporary node to document.
But doing it on active document result with a weird flick on canvas (and layer stack) because node is visible less than a second…

So the temporary document.

There’s possibilities to improves (this: reduce size of document to selection dimension will improve performances, also maybe create/delete temp document at plugin startup/stop this need to be compared with resizing doc on the fly)

Grum999

1 Like

Yep, I’ll try to reduce the temp document dimensions!

Currently it works but it’s too slow to be used:

Yes, your document size is big and filter layer is applied on a document from same size… there’s some computations made for nothing…

Grum999

1 Like

It seems like layers can have a different size than the image.

Maybe it would be faster to create a temporary layer of the required size rather than creating an entire new document?

Didn’t tested recently, but a filter mask doesn’t apply outside canvas.

Then, having 10x10px document would be fast to create and low memory consumpting
But if you put a node of 100x100pixel on it with a filter mask, I think only the 10x10 visible area of layer will be updated…

Need to be confirmed

Grum999

No good way to do it, so here is one bad way to do it :slight_smile:

from krita import Krita
from PyQt5.QtWidgets import (
        QCheckBox,
        QApplication,
        QDoubleSpinBox)


def blur_selected(radius):
    app = Krita.instance()
    krita_filter_blur = app.action('krita_filter_gaussian blur')
    krita_filter_blur.trigger()
    QApplication.processEvents()
    blur_dialog = QApplication.activeWindow()
    preview_check = blur_dialog.findChild(QCheckBox, 'checkBoxPreview')
    preview_check.setChecked(False)
    horizontalRadius = blur_dialog.findChild(QDoubleSpinBox, 'horizontalRadius')
    horizontalRadius.setValue(radius)
    # no need to set if vertical & horizontal lock is on
    # verticalRadius = blur_dialog.findChild(QDoubleSpinBox, 'verticalRadius')
    # verticalRadius.setValue(radius)
    blur_dialog.accept()
    
blur_selected(50.0)

/AkiR

edit: better to turn preview check box off (it did get confused…)

2 Likes