Node management through API chaged between Krita 5.0.6 and Krita 5.1.0

Hi

From this problem (Channels to Layers - #25 by Grum999), it seems node management through API doesn’t work like before.

I was able to find commit from which node management has been changed

Building from commit (Fix initialization of image link in the pasted nodes (81f9b1aa) · Commits · Graphics / Krita · GitLab), python script doesn’t generate expected result

Building from previous commit (Allow the brush settings lists to respond to drag inputs (75abd1e9) · Commits · Graphics / Krita · GitLab), python script generate expected result

@dkazakov (as you’ve made the commit :sweat_smile:) before creating a bug, I would like to know if it’s a normal behavior or not, especially due to your commit message I’m not sure this patch is safe enough for 5.0.x branch. :slight_smile:

Before commit

(Krita 5.0.6)

  • Create a group layer
  • Create nodes inside group layer, with blending mode
  • Merge node inside group layer
  • Add group layer to document
    = OK
Scripter example
from krita import *
from PyQt5.Qt import *
from PyQt5 import QtCore
from PyQt5.QtCore import QByteArray, QPoint
from PyQt5.QtGui import QColor, QImage, QPixmap

def buildCircleImage(w, h):
    """Generate a QImage to use for example"""
    img=QImage(w, h, QImage.Format_ARGB32_Premultiplied)
    img.fill(Qt.transparent)
    pxmTgt = QPixmap.fromImage(img)
    
    canvas = QPainter(pxmTgt)
    canvas.setRenderHint(QPainter.Antialiasing)

    canvas.setPen(QPen(QColor(255,0,0), 25))
    canvas.drawEllipse(QRect(20,20,w-40, h-40));
    canvas.end()
    return pxmTgt.toImage()

def setLayerFromQImage(layerNode, image):
    """Set QImage as layer content"""
    position = QPoint(0, 0)
    ptr = image.bits()
    ptr.setsize(image.byteCount())

    layerNode.setPixelData(QByteArray(ptr.asstring()), position.x(), position.y(), image.width(), image.height())

dWidth=500
dHeight=500

newDocument = Krita.instance().createDocument(dWidth, dHeight, "Test", "RGBA", "U8", "", 300.0)
Krita.instance().activeWindow().addView(newDocument)

# create group layer
parentGroupLayer = newDocument.createGroupLayer(f'Group layer')

# create circle layer
newPLayer = newDocument.createNode(f"PaintLayer-Circle", 'paintlayer')
setLayerFromQImage(newPLayer, buildCircleImage(dWidth, dHeight))
parentGroupLayer.addChildNode(newPLayer, None)

# create fill layer
infoObject = InfoObject();
infoObject.setProperty("color", QColor(255,255,0))
selection = Selection()
selection.select(0, 0, dWidth, dHeight, 255)

newFLayer = newDocument.createFillLayer(f"FillColor-Color", "color", infoObject, selection)
parentGroupLayer.addChildNode(newFLayer, newPLayer)
# mandatory as when provided to createFillLayer(), infoObject is not applied
# must also be applied after node has been added to parent...
newFLayer.setGenerator("color", infoObject)
newFLayer.setBlendingMode('multiply')


newLayer = newFLayer.mergeDown()


# add group layer to document
newDocument.rootNode().addChildNode(parentGroupLayer, None)


newDocument.refreshProjection()

Result:

After commit

(Krita 5.1.0)

  • Create a group layer
  • Create nodes inside group layer, with blending mode
  • Merge node inside group layer
  • Add group layer to document
    = KO
Same scripter code example

Same script than previous one generate this picture

Workaround

  • Create a group layer
  • Add group layer to document
  • Create nodes inside group layer, with blending mode
  • Merge node inside group layer
    = OK
Workaround scripter code example

Modified script (add group layer to document before starting to add child) produce expected result

from krita import *
from PyQt5.Qt import *
from PyQt5 import QtCore
from PyQt5.QtCore import QByteArray, QPoint
from PyQt5.QtGui import QColor, QImage, QPixmap

def buildCircleImage(w, h):
    """Generate a QImage to use for example"""
    img=QImage(w, h, QImage.Format_ARGB32_Premultiplied)
    img.fill(Qt.transparent)
    pxmTgt = QPixmap.fromImage(img)
    
    canvas = QPainter(pxmTgt)
    canvas.setRenderHint(QPainter.Antialiasing)

    canvas.setPen(QPen(QColor(255,0,0), 25))
    canvas.drawEllipse(QRect(20,20,w-40, h-40));
    canvas.end()
    return pxmTgt.toImage()

def setLayerFromQImage(layerNode, image):
    """Set QImage as layer content"""
    position = QPoint(0, 0)
    ptr = image.bits()
    ptr.setsize(image.byteCount())

    layerNode.setPixelData(QByteArray(ptr.asstring()), position.x(), position.y(), image.width(), image.height())

dWidth=500
dHeight=500

newDocument = Krita.instance().createDocument(dWidth, dHeight, "Test", "RGBA", "U8", "", 300.0)
Krita.instance().activeWindow().addView(newDocument)

# create group layer
parentGroupLayer = newDocument.createGroupLayer(f'Group layer')
# add group layer to document
newDocument.rootNode().addChildNode(parentGroupLayer, None)


# create circle layer
newPLayer = newDocument.createNode(f"PaintLayer-Circle", 'paintlayer')
setLayerFromQImage(newPLayer, buildCircleImage(dWidth, dHeight))
parentGroupLayer.addChildNode(newPLayer, None)

# create fill layer
infoObject = InfoObject();
infoObject.setProperty("color", QColor(255,255,0))
selection = Selection()
selection.select(0, 0, dWidth, dHeight, 255)

newFLayer = newDocument.createFillLayer(f"FillColor-Color", "color", infoObject, selection)
parentGroupLayer.addChildNode(newFLayer, newPLayer)
# mandatory as when provided to createFillLayer(), infoObject is not applied
# must also be applied after node has been added to parent...
newFLayer.setGenerator("color", infoObject)
newFLayer.setBlendingMode('multiply')


newLayer = newFLayer.mergeDown()

newDocument.refreshProjection()

If plugins have to be modified, it might not be to hard…
But before modifying plugins, I’m wondering if it’s normal.
On my side, I sometimes work on temporary nodes, created on document but not added to root document because adding it to delete seems useless to me…

Grum999

Well, I have to say that commit provoked the issue with the tools that use color labeled layers as reference always using the first frame as reference, and the issue with copy/paste that always copied the first frame instead the current one. The fix is supposed to be correct, but that provokes malfunction probably because of relying on the previous wrong behavior. So one useful question is: are you using animated layers?
EDIT: ok looking at the code I see you are not using animated layers. So it seems the issue is of different nature perhaps.

Hi, @Grum999!

In general, merging node outside of the image stack has always been a kind of undefined behavior. The problem is that there are several properties that are requested by the layer from the image, such as, default bounds, animation timeframe and so on. Some other layers, like Fill Layers require image’s infrastructure to run regeneration jobs in the background.

I cannot explain why your particular case stopped working (I spent about 20mins on that and then gave up). But your script also triggers a safe assert in Krita’s layers docker. So I believe that you should just make sure that you do any merging actions on layers that are a part of some image.

Just to make it clear. Theoretically, we can make some parts of the code work when the layer is not yet attaced to the image, but I’m not sure we should invest time into that.

Hi @dkazakov

Thanks for reply!

No need to spent too much time on problem.
If it was something like “of course it’s from here” and took 2minutes to fix, then I would be happy.
But I know it could complex behind all underlying interactions.

I also understand that layer operations are intricate with document, but as node even if not added to stack seems to be linked to document, I was thinking that was good to do like I did :slight_smile:

So no problem, I’ll take in account this node possibility is not available anymore and try to check plugins to apply workaround (then add group node to root before doing manipulation)
Main impact for me by adding layer to root, there will be some flickering in layer stack now and probably some slowness :upside_down_face:

Grum999

1 Like