Krita Scripting – Operations with Layers

Learn how to create, remove or change layers in different ways. How to add mask to a layer with a script.

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

Add Layers

We have already mentioned how to make a new layers in the previous record. However, what we have not mentioned, is what kind of layers could be created.

Well, pretty much any layer can be created with Python API. Here the list of all supported keys for each layer type:

  • paintlayer
  • grouplayer
  • filelayer
  • filterlayer
  • filllayer
  • clonelayer
  • vectorlayer
  • transparencymask
  • filtermask
  • transformmask
  • selectionmask

Let’s refresh our knowledge on how to add new layer

from krita import Krita, Document, Node


def run():
    app: Krita = Krita.instance()
    doc: Document = app.activeDocument()

    root: Node = doc.rootNode()
    print(root)
    node: Node = doc.createNode("New Paint Layer", "paintlayer")
    print(node)
    root.addChildNode(node, None)


run()

Let’s consider another example. Let’s say you want to have a new paint layer and the vector layer. The vector layer has to be below the paint layer and the paint layer has to have alpha inheritance enabled. All these two layers should be added to a group.

from krita import Krita, Document, Node


def run():
    app: Krita = Krita.instance()
    doc: Document = app.activeDocument()

    root: Node = doc.rootNode()
    group_node: Node = doc.createNode("New Group Layer", "grouplayer")
    paint_node: Node = doc.createNode("New Paint Layer", "paintlayer")
    paint_node.setInheritAlpha(True)
    vector_node: Node = doc.createNode("New Vector Layer", "vectorlayer")

    group_node.addChildNode(vector_node, None)
    group_node.addChildNode(paint_node, vector_node)

    root.addChildNode(group_node, None)


run()

Here we add layers one by one and then follow the hierarchy we want. First we add the Vector layer that will serve as a mask (line 14) to a group, then we add a paint layer above it (line 15) to a group as well. Lastly we add the group to the layer stack. It might seem logical to add a group to the layer stack and then add other layers to it. Good practice to follow is to first gather everything (or perform operations we need) and then add it to the layers stack.

One more thing. Apart of hierarchy we change the property of the paint_node to inherit alpha.

Remove Layers

The layers can also be removed with Python API. It can be helpful if you want to use some layer as a starting point and add things on top of it.

Let’s remove all the selected layers. The procedure is pretty simple, we first get the list of nodes we wish to remove. It can be done either by search or retrieved selected nodes from the View.

Then we go over each of the node and call remove() method. There is a catch though. What will happen if you select Layers Group and the layer in it?

It turns out Krita will calmly handle this situation and do nothing.

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


def run():
    app: Krita = Krita.instance()
    view: View = app.activeWindow().activeView()

    selected_nodes = view.selectedNodes()

    node: Node
    for node in selected_nodes:
        print(node.index(), node.name())
        node.remove()
        print(node.index(), node.name(), node.parentNode())
        print("--")


run()

Each node assigned index when it added to layers or moved around. You can check if the node is in layers stack by querying its index(). Another way is to call the method parentNode(). If it is None than the layer is orphan and is not displayed on the layers stack.

Multiple Layers Operations

Sometimes you will need to apply the same property or operation on the set of layers. In this case you need to get the collection of layers you want to change and then iterate over them and apply the same operation to each of the layers.

To illustrate this case we will show how to set the blending bode to all selected layers. You can find the full list of available blending modes here. However, keep in mind that the actual blending modes might change between Krita versions. So make sure the particular blending mode you are want to use from the file is actually related to the version of Krita you are using.

We will use “multiply” mode.

from krita import Krita, View, Node


def run():
    app: Krita = Krita.instance()
    view: View = app.activeWindow().activeView()

    selected_nodes = view.selectedNodes()
    blending_mode = "multiply"

    node: Node
    for node in selected_nodes:
        node.setBlendingMode(blending_mode)


run()

Most of the things are familiar to you already. The new things here are the variable at line 9 and the operation to line 13. Making a variable blending_mode makes the code more reusable and clear. Then calling the setBlendingMode() method we change the current blending mode of the layer to the one we have in the variable.

Blending mode with Actuator

If it happens that you have an Actuator plugin you can also use it as a source of additional operations for your scripts.

from PyQt5 import QtWidgets
from krita import Krita
from actuator.api.blend import BLENDING_MODES

def set_blending_mode(mode):
    if mode not in BLENDING_MODES:
        QtWidgets.QMessageBox.warning(None, "Warning", f'Unknown blending mode: "{mode}"')
        return
    win = Krita.instance().activeWindow()
    view = win.activeView()
    selected_nodes = view.selectedNodes()
    for s in selected_nodes:
        s.setBlendingMode(mode)

set_blending_mode("multiply")

The BLENDING_MODES contains all available blending modes. By using it you can perform additional verification (line 6) in case the name of the blending mode was set incorrectly.

Merging Layers With Script

Merging the layers using Python code could be a bit tedious. The API does not have an operation that will do it right away and doing it manually will require moving layers around and use mergeDown() method for the node.

In this case, getting around with the action could be a best course of action. The action you need is “merge_layer”.

Manipulate Pixel data

Changing the pixels values of the layer is a bit more advanced topic and will come in handy if you use other programming instruments apart from what Python API is offering.

However, one of simplest uses could be resetting the opacity of each pixel.

Be advised these trick is only available for the Paint Layer.

The image is usually represented (and interpreted as) the 2D array of pixels. Each pixel has a coordinate (x, y). The mapping of the coordinates start from the top left corner and have value (0, 0) and end on the bottom right corner and have coordinate value (height-1, width-1).

Krita supports different types of images which can have different number of channels and different range of values. This example will showcase how to work with RGBA type image.

from PyQt5.QtCore import QByteArray
from krita import Krita, View, Node


def make_node_opaque(node: Node, w: int, h: int):
    data: QByteArray = node.pixelData(0, 0, w, h)
    size = w * h * len(node.channels())

    print([x.name() for x in node.channels()])
    print(data.length(), size)

    for i in range(3, size, 4):
        data.replace(i, 1, bytearray([255]))

    node.setPixelData(data, 0, 0, w, h)


def run():
    app: Krita = Krita.instance()
    view: View = app.activeWindow().activeView()
    doc: Document = app.activeDocument()

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

    for node in selected_nodes:
        make_node_opaque(node, w, h)

    doc.refreshProjection()


run()

In the run (line 19) function we start with usual operations on getting references to the layers. Then in the line 28 we call the separate function to make the layer opaque.

make_node_opaque function. It is not the first time when we make a separate function for a small operation. You should start noticing how this makes the script better to read and understand. In the future we will learn how to reuse the code in different scripts. Developing habits of making separate functions will be valuable. Alright, let’s investigate each aspect of this function.

QByteArray and bytearray are new kind of classes here. These are basically representing the same thing – the array of binary data. The first one is the way of Qt and the second one is the python way. We need to work with QByteArray since Krita Python API relies on types derived from C++ version of Qt.

The first thing we do is request the data for entire layer starting from coordinate (0,0) all the way to the opposite corner (line 7).

The idea of making the image opaque is to go over each pixel and set the opacity level to maximum. Before we do this we need to know how many pixels we have at all, to know how many iterations we need to do. In this particular case our image data is stored as a single string of binary data (bytearray we mentioned before). So we need to know how many bytes our layer will take. To calculate this value we need to know how many rows and columns of pixels the image has (these are w and h variables). Then each pixel will have a number of channels. That we can learn by calling channels() method for our node. The method itself returns a list of names what element represents what channel (line 10). By measuring the length of the list we get the number of channels. This will be our total number of bytes we need for this layer.

Next step, we need to iterate over each pixel and set the value for alpha channel to be 255. In our case each pixel is stored in a consecutive string of bytes that will go in order RGBARGBARGBA… Each letter here represents a byte for the Red channel, a byte for the Green channel, Blue and respectfully for Alpha channel.

So in our case we need to go over each 4th byte and change only this value. This is where for loop will come in handy. However in this case we want to have just indices of byte and not bytes itself. For that we can use range() function that can return us a list of integers which we can then iterate over. The range function accepts three arguments: start, last and step. range(0, 3, 1) will have numbers [0, 1, 2]. The last number is not included.

To change the opacity of each pixel we start from the byte by index 3 (which is the 4th item in the array, remember that indices are starting from 0?) and the last byte we will have is the size of the image we calculated before. Since we do not need any other bytes that alpha we can skip them by setting the step to 4.

Then we can replace the bytes we want with value 255 (line 14). Unfortunately the QBytesArray is not like the list and you cannot replace the item by accessing it with square brackets. So data[i] = 255 will not work. The way to set the value to specific byte is to replace the value with another array. To do that we call the replace() method on the data variable. This method takes three arguments: the index of the byte we want to replace from, the number of bytes we want to replace and the bytearray to use as a replacement. Since we have only one value we make a bytearray with single number 255.

And the last step we need to set this pixel data back to the layer (line 16).

Something what have not seen before is the refreshProjection() method on the line 30. Some of the operations in Krita can update the canvas, after doing so we need to inform the application that something has been changed and the canvas should be updated. This is what this method is for. Usually it is a great rule of thumb to call this method every time you do anything that in one way or another displays something on the canvas.

Next

Further step is to learn how to apply filters to layers.

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.