Krita Scripting – Apply Filters

Learn to apply filters using Krita Scripting Python API. You can apply filters directly or create a filter mask.

This record is a part of the materials for Krita Scripting Series. Before reading further, make sure to study materials published before.

By the moment of writing (and using Krita 5.2.2), Krita supports 50 built in filters. We can apply them programmatically.

To learn what filters you can use you can call the filters() method with the Krita application.

from pprint import pprint
from krita import Krita


def run():
    app: Krita = Krita.instance()

    fls = app.filters()
    print(len(fls))
    pprint(fls)


run()

Most of the names are the same as within the UI, so it should not be a problem to guess what filter to use.

Next what we need to know is the list of parameters that we can give the filter. The parameters names could be different from what the UI displays. Some trial and error might be needed to figure our the mapping between UI names and what we have in the configuration. To learn the parameters we first need to get an instance of the filter we want to apply. The we get filter configuration and properties of the configuration.

from pprint import pprint
from krita import Krita, Filter, InfoObject


def run():
    app: Krita = Krita.instance()

    filter: Filter = app.filter("gaussian blur")
    cfg: InfoObject = filter.configuration()
    pprint(cfg.properties())


run()

The filter is configured with an InfoObject. To know what parameters needs to be set to the InfoObject you need to get the configuration of the filter and probe the default values (line 10).

Once you have the configuration for the filter you can fiddle with values to get what you need. One simple way to learn what the possible values for the filter configurations might be is to refer to the UI version of the filter.

For the Gaussian Blur filter we have the following configuration:

{'horizRadius': 5, 'lockAspect': True, 'vertRadius': 5}

horizRadius and vertRadius are directions in which the blur should be applied. The horizontal and vertical direction respectively.

from pprint import pprint
from krita import Krita, Filter, InfoObject, View, Document


def run():
    app: Krita = Krita.instance()

    filter: Filter = app.filter("gaussian blur")
    cfg: InfoObject = filter.configuration()
    ps = cfg.properties()
    ps["horizRadius"] = 15
    ps["vertRadius"] = 15
    cfg.setProperties(ps)
    filter.setConfiguration(cfg)

    view: View = app.activeWindow().activeView()
    doc: Document = app.activeDocument()

    selected_nodes = view.selectedNodes()
    w, h = doc.width(), doc.height()

    for node in selected_nodes:
        filter.apply(node, 0, 0, w, h)

    doc.refreshProjection()


run()

The easiest way to change the filter settings is to get the configuration from the filter, extract properties, change the properties in place, then set changed properties back to configuration and then apply this configuration to the filter. These are lines from 10 all the way to line 15.

When filters are applied you need to set the node you wish to apply the filter to and the rectangle of the area you want the filter to be applied to. In our case we apply the filter to the entire image.

Filter mask

The difference between just filter and filter mask is that filter is applied to a layer what is called destructively. Once the settings is set the only way to change (or reverse) the effect of the filter is to undo the last operation.

With filter mask we can apply the filter and change its settings later. This is usually called non-destructive way.

from pprint import pprint
from krita import Krita, Filter, InfoObject, View, Document


def run():
    app: Krita = Krita.instance()

    filter: Filter = app.filter("gaussian blur")
    cfg: InfoObject = filter.configuration()
    ps = cfg.properties()
    ps["horizRadius"] = 0
    ps["vertRadius"] = 15
    cfg.setProperties(ps)
    filter.setConfiguration(cfg)

    view: View = app.activeWindow().activeView()
    doc: Document = app.activeDocument()

    selected_nodes = view.selectedNodes()
    node = selected_nodes[0]
    filter_mask = doc.createFilterMask("Filter Mask", filter, node)
    node.addChildNode(filter_mask, None)

    doc.refreshProjection()


run()

Making Filtering mask is pretty much the same as with applying the filter directly to a layer. The difference is that we use document reference to create a mask as a node (line 21). Then the filter mask is just added as a child node to the one you want to be filtered.

createFilterMask() method takes three arguments. The name of the node in the layer stack, the instance of the filter, that will be used as a filter mask, the node that will serve as a selection source.

Setting selection source to any other node does not seems to be doing anything, however, the operation will not work if you not specify it. So to be on the safe site, use the same node you want to apply the filter mask to.

Apply filter where it does not make sense

Even though you can apply filter to the layers like Vector Layer and in most cases Krita will just silently do nothing. However, this sort of programming is dangerous since in other cases Krita might just crash. To prevent the situations when the operations are performed when they do not make sense a good rule of thumb is to perform checks before doing the operation.

from pprint import pprint
from krita import Krita, Filter, InfoObject, View, Document, Node


def run():
    app: Krita = Krita.instance()

    filter: Filter = app.filter("gaussian blur")
    cfg: InfoObject = filter.configuration()
    ps = cfg.properties()
    ps["horizRadius"] = 15
    ps["vertRadius"] = 15
    cfg.setProperties(ps)
    filter.setConfiguration(cfg)

    view: View = app.activeWindow().activeView()
    doc: Document = app.activeDocument()

    selected_nodes = view.selectedNodes()
    w, h = doc.width(), doc.height()

    for node in selected_nodes:
        if node.type() != "paintlayer":
            print("Node type is not supported, skipping...")
            continue

        filter.apply(node, 0, 0, w, h)

    doc.refreshProjection()


run()

The new operation here is the if statement (line 23). Before calling the apply() method with filter we probe the layer for its type. If the type does not equal to expected paint layer we skip the iteration. The operator continue is a special kind of operation for loops. Executing this operator all the operations after it are ignored and new iteration is executed.

In other words, if the layer type is not a paint layer we will not apply any filters.

Note that you could just do the opposite and check if the layer is the paint layer type and then apply the filter.

for node in selected_nodes:
    if node.type() == "paintlayer":
        filter.apply(node, 0, 0, w, h)

Nonetheless, this kind of code is becoming a staircase hell. We recommend avoid this kind of type of programming and try to keep as few indentations as possible.

You should not see the advantage of Filter Mask over regular filter apply, since the Filter Mask can applied to any type of layer.

GMIC Filters

The natural question would be. Could we do the same with GMIC filters. Unfortunately no. There are no API for accessing these extra filters with Python.

Feedback and Suggestions

Should you have any questions or maybe you want to suggest next topic to cover. Please join our community and let us know.