Layers manipulation is buggy?

Hi,

Trying to understand why Newspaper plugin is not working anymore on Krita 4.4.0 & 5.0.0, after many tests and many hair lost, I finally found something…

I’m not sure, but practically convinced about origin of problem.

The way the plugin is working is to generate layers (color layer), apply some blending mode, merge, and process the halftone on layers…

In Krita 4.3.0 everything works fine.
In krita 4.4.0 & 5.0.0, result is like if some actions on layers were not applied or not applied properly.

So, trying to debug the process with some QMessageBox between each layer action made the final result OK; difficult to understand where is the problem…
Remembered this topic Bugs - Ok I need some Help, then I tried to put sleep (125ms) between each action and final result is OK.

Here is the sleep method applied between each layer action (ie: using Qt event compatible method)

def sleep(value):
   """Do a sleep of `value` milliseconds"""
   loop = QEventLoop()
   QTimer.singleShot(value, loop.quit)
   loop.exec()

So, what I think is, with Krita 4.4.0 something has been changed with asynchronous layer management.

And where in topic Bugs - Ok I need some Help the problem ocurred when triggering a QAction, here the problem occurs when calling API.

As I can understand that from user interface it’s not a problem because user action are not fast enough to be in this case, for a script it’s problematic: next function call is made before current calculation has been finished…
And scripting ability with layers became completely useless in this case.

Here a simple example to illustrate the problem.

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 buildQImage(color, 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)
    
    gradientb = QRadialGradient(QPointF(w/3, h/4), 2*w/3)
    gradientb.setColorAt(0, color)    
    gradientb.setColorAt(1, Qt.black)
    
    gradientf = QRadialGradient(QPointF(w/3, h/4), 2*w/3)
    gradientf.setColorAt(0, Qt.white)
    gradientf.setColorAt(0.15, Qt.transparent)
    gradientf.setColorAt(1, Qt.transparent)

    canvas = QPainter()
    canvas.begin(pxmTgt)
    canvas.setRenderHint(QPainter.Antialiasing)

    canvas.setPen(Qt.NoPen)

    canvas.setBrush(gradientb)
    canvas.drawEllipse(QRect(20,20,w-40, h-40));

    canvas.setBrush(gradientf)
    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())

def sleep(value):
    """Do a sleep of `value` milliseconds"""
    loop = QEventLoop()
    QTimer.singleShot(value, loop.quit)
    loop.exec()
    
dWidth=500
dHeight=500

# Cyan, Magenta, Yellow: colors used to generate layers
colors=[QColor(0,255,255), QColor(255,0,255), QColor(255,255,0)]
sleepValues=[0, 125]

image=buildQImage(QColor(255,0,0), dWidth, dHeight)

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


for sv in sleepValues:
    # loop over sleep values to illustrate result of asynchronous calculation

    parentGroupLayer = newDocument.createGroupLayer(f'Group layer {sv}ms')

    for i in range(len(colors)):
        # loop over colors and apply basics actions like plugin Newspaper (and Channel2Layers) does
        newPLayer = newDocument.createNode(f"PaintLayer{i}", 'paintlayer')
        setLayerFromQImage(newPLayer, image)
        parentGroupLayer.addChildNode(newPLayer, None)
    
        sleep(sv)

        infoObject = InfoObject();
        infoObject.setProperty("color", colors[i])
        selection = Selection();
        selection.select(0, 0, dWidth, dHeight, 255)

        newFLayer = newDocument.createFillLayer(f"Color{i}", "color", infoObject, selection)
        sleep(sv)
        parentGroupLayer.addChildNode(newFLayer, newPLayer)

        sleep(sv)
        # 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)

        sleep(sv)
        newFLayer.setBlendingMode('add')
        sleep(sv)
        newLayer = newFLayer.mergeDown()
        sleep(sv)
    
        # returned layer from merge can't be used, so get the last one
        currentProcessedLayer = parentGroupLayer.childNodes()[-1]
    
        currentProcessedLayer.setBlendingMode('multiply')
        sleep(sv)

    newDocument.rootNode().addChildNode(parentGroupLayer, None)


newDocument.refreshProjection()

The script will generate a picture, and will try to decompose it in Cyan, Magenta and Yellow layers.

There 2 executions:

  • One with a sleep set to 0ms
  • One with a sleep set to 125ms

With Krita 4.3.0, result is the same in the both case.
And result is correct:

With Krita 4.4.0, result is not the the same:

  • Incorrect result with a sleep of 0ms
  • Correct result (like in Krita 4.3) with a sleep of 125ms

If someone can test this and confirm the problem exists (Linux, Windows, Mac? or only my computer is concerned?) and my analysis about origin…

Note: I currently can’t tell if all functions are concerned or only a subset…

If I’m right, I currently don’t think I have to find a solution for the plugin as the problem seems to be relative to Krita’s API.
Add a sleep evereywhere is not a good solution, for me when a call to an API function is made, the function shouldn’t return a result until the function has finished to process the data.

Grum999

2 Likes

Test results on some platforms.

krita_version: 4.2.9, 
platform: Windows-10, 
QT_VERSION_STR: 5.12.7, 
PYQT_VERSION_STR: 5.13.1, 
result: Ok (look same)

krita_version: (Appimage) 4.1.5, 
platform: Linux, 
QT_VERSION_STR: 5.10.0, 
PYQT_VERSION_STR: 5.9.2, 
result: Ok (look same)

krita_version: (Appimage) 4.3.1-alpha (git 4212182), 
platform: Linux, 
QT_VERSION_STR: 5.12.9, 
PYQT_VERSION_STR: 5.13.1, 
result: Failure (look different)

/AkiR

2 Likes

It does look like a regression… and a regression for Dmitry :slight_smile: I will try to bisect this (find the exact commit that caused the trouble), if you give me some time.

1 Like

Hi @tiar

Thanks for your confirmation :slightly_smiling_face:

From a personal point of view, there’s no urgency except that it might be better to solve the problem before the 4.4.0 is officially released :sweat_smile:

Grum999

Tbh I haven’t checked it myself, it just sounds bad, it looks like there is some deeper issue there.

@Grum999 it looks like a regression from the multi-threaded fill layers:

3f85089fa36ad371ce1322117c0c420e806b580f is the first bad commit
commit 3f85089fa36ad371ce1322117c0c420e806b580f
Author: L. E. Segovia <amy@amyspark.me>
Date:   Sun Jul 5 00:41:59 2020 +0000

Split layer into patches for multithreaded render

Next commit will move the split to the stroke strategy itself.

Will you please make a bug report on that?

There is a chance it would just need more “waitForDone()” calls or something…

1 Like

Hi @tiar

Thanks!

Bug 426349 created

Grum999

There is now a fix for exactly this script, but a lot of other places in Python API need to be fixed as well: https://phabricator.kde.org/T13628