Bug 426349

Summary: Layers manipulation is buggy
Product: [Applications] krita Reporter: grum999
Component: ScriptingAssignee: Krita Bugs <krita-bugs-null>
Status: CONFIRMED ---    
Severity: normal CC: tamtamy.tymona
Priority: NOR    
Version: nightly build (please specify the git hash!)   
Target Milestone: ---   
Platform: Other   
OS: Linux   
Latest Commit: Version Fixed In:
Sentry Crash Report:
Attachments: Improved Python script showing all three cases

Description grum999 2020-09-09 19:51:08 UTC
SUMMARY

Detailled description of problem is available on krita-artist.org
https://krita-artists.org/t/layers-manipulation-is-buggy/11566?u=grum999

In few words, with Krita Plus (4.4.0) and Next (5.0.0), some layer manipulation made from script are totaly asynchronous and then, as script execution is fast, when an instructions on layers is made, the next one are executed before the previous ones are finished.

@Tiar may have found origin of problem:
https://krita-artists.org/t/layers-manipulation-is-buggy/11566/7?u=grum999
>> It looks like a regression from the multi-threaded fill layers:
>> 3f85089fa36ad371ce1322117c0c420e806b580f is the first bad commit
>> commit 3f85089fa36ad371ce1322117c0c420e806b580f



STEPS TO REPRODUCE
Please read topic in Krita artist (sorry, but formatting in KDE bugtracking system is not good enough to be able to copy all explanation properly provided on krita-artist.org)

OBSERVED RESULT
When execution of script is made with Krita 4.3.0, layers content is correct
When execution of script is made with Krita Plus and/or Next, layers content:
- is correct when a delay (sleep) is applied after each layer instruction
- is not correct when no delay is applied after each layer instruction

EXPECTED RESULT
Layers content must be correct when no delay is applied after each layer instruction (like it was with Krita 4.3.0)

SOFTWARE/OS VERSIONS
Windows: not tested
macOS: not tested

Qt

  Version (compiled): 5.12.9
  Version (loaded): 5.12.9

OS Information

  Build ABI: x86_64-little_endian-lp64
  Build CPU: x86_64
  CPU: x86_64
  Kernel Type: linux
  Kernel Version: 4.19.0-10-amd64
  Pretty Productname: Debian GNU/Linux 10 (buster)
  Product Type: debian
  Product Version: 10
  Desktop: KDE

ADDITIONAL INFORMATION
-
Comment 1 grum999 2020-09-09 19:52:08 UTC
I'm not sure about Component / Version to set (4.4.0 and 5.0.0 are not in version list)

Grum999
Comment 2 Tiar 2020-09-09 20:15:11 UTC
It's technically a regression caused by making the Fill Layer multi-threaded, but I think just adding waitForDone() in the FillLayer.cpp should solve this. I haven't checked it yet though.

(If you want @Grum check it, please see if adding waitForDone() instead of sleep() would make it work).

d52e55f6cc0567ac75c8
Comment 3 grum999 2020-09-09 20:25:14 UTC
In script example, I've replaced 
> sleep(sv)

by 
> newDocument.waitForDone()


There's no change in results, it's still incorrect


Grum999
Comment 4 Tiar 2020-09-10 13:10:28 UTC
Created attachment 131537 [details]
Improved Python script showing all three cases

I attached an improved script that shows all three cases:
- using doc.waitForDone()
- using 1 ms sleep (not enough for Krita to finish generating)
- using 125 ms sleep (enough)
First two cases give bad results.

Steps to reproduce:
1. Open Krita.
2. Go to Tools -> Scripts -> Scripter.
3. Paste the script and execute.
Comment 5 grum999 2020-09-10 18:26:13 UTC
I've made some other tests.

On my side it seems that to get expected results, call to sleepOrWait() is needed only after:
- a call to layer.setGenerator()
- a call to layer.setBlendingMode()

When other call to sleepOrWait() function are in comment, expected result is still OK.

With this I can't affirm that problem doesn't occurs for mergeDown(), addChildNode() or other layer/document manipulation, but this tends to confirm that problem, as suspected bu @Tiar, is relative to fill layer.


Grum999
Comment 6 Dmitry Kazakov 2020-09-16 11:38:12 UTC
Git commit 74dbabc3636fd5a1c18967d9fed105fb2ddaf7b2 by Dmitry Kazakov.
Committed on 16/09/2020 at 11:37.
Pushed by dkazakov into branch 'master'.

Fix Python API for Node::addChildNode()

The function should use the command, run it in a stroke and wait
for it to finish. That ensures that 1) updates are issued; 2) undo
item is created; 3) no threading issues arise.

M  +12   -4    libs/libkis/Node.cpp

https://invent.kde.org/graphics/krita/commit/74dbabc3636fd5a1c18967d9fed105fb2ddaf7b2
Comment 7 Dmitry Kazakov 2020-09-16 11:38:20 UTC
Git commit 57dbf8a946d022a86855b95f278f1013f29d3148 by Dmitry Kazakov.
Committed on 16/09/2020 at 11:37.
Pushed by dkazakov into branch 'master'.

Fix Python API for Node::setBlendingMode()

The function should use the command, run it in a stroke and wait
for it to finish. That ensures that 1) updates are issued; 2) undo
item is created; 3) no threading issues arise.

M  +9    -1    libs/libkis/Node.cpp

https://invent.kde.org/graphics/krita/commit/57dbf8a946d022a86855b95f278f1013f29d3148
Comment 8 Dmitry Kazakov 2020-09-16 11:38:28 UTC
Git commit 623c5114e009485391be9fbe943a1d9cd4da6bd3 by Dmitry Kazakov.
Committed on 16/09/2020 at 11:37.
Pushed by dkazakov into branch 'master'.

Fix KisGeneratorLayer support KisDelayedUpdateNodeInterface

It is needed to make sure the layer is updated when some synchronous
non-gui provoked actions are requested. E.g. saving or changing via
python API.

The path also fixes FillLayer's Pythong API to be synchronous using
this fix.

M  +15   -0    libs/image/generator/kis_generator_layer.cpp
M  +7    -1    libs/image/generator/kis_generator_layer.h
M  +6    -0    libs/libkis/FillLayer.cpp

https://invent.kde.org/graphics/krita/commit/623c5114e009485391be9fbe943a1d9cd4da6bd3
Comment 9 Dmitry Kazakov 2020-09-16 11:39:37 UTC
After my patches the following script works fine:

===========================================================

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)


parentGroupLayer = newDocument.createGroupLayer('Group layer')
newDocument.rootNode().addChildNode(parentGroupLayer, None)

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)
    
    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)
    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('add')
                
    newLayer = newFLayer.mergeDown()
            
    # returned layer from merge can't be used, so get the last one
    currentProcessedLayer = parentGroupLayer.childNodes()[-1]
    currentProcessedLayer.setBlendingMode('multiply')

===========================================================

Though other methods of Python API still have the same problems
Comment 10 Dmitry Kazakov 2020-09-16 12:20:40 UTC
Git commit 39bf729d88e70168e1e9ce58cac08fcb5f11f6f3 by Dmitry Kazakov.
Committed on 16/09/2020 at 12:19.
Pushed by dkazakov into branch 'krita/4.3'.

Fix KisGeneratorLayer support KisDelayedUpdateNodeInterface

It is needed to make sure the layer is updated when some synchronous
non-gui provoked actions are requested. E.g. saving or changing via
python API.

The path also fixes FillLayer's Pythong API to be synchronous using
this fix.

# Conflicts:
#	libs/libkis/FillLayer.cpp

M  +15   -0    libs/image/generator/kis_generator_layer.cpp
M  +7    -1    libs/image/generator/kis_generator_layer.h
M  +7    -0    libs/libkis/FillLayer.cpp

https://invent.kde.org/graphics/krita/commit/39bf729d88e70168e1e9ce58cac08fcb5f11f6f3
Comment 11 Dmitry Kazakov 2020-09-16 12:20:48 UTC
Git commit 7be91fc9922182bf02a3fc95feb9462ae8948f72 by Dmitry Kazakov.
Committed on 16/09/2020 at 12:20.
Pushed by dkazakov into branch 'krita/4.3'.

Fix Python API for Node::setBlendingMode()

The function should use the command, run it in a stroke and wait
for it to finish. That ensures that 1) updates are issued; 2) undo
item is created; 3) no threading issues arise.

M  +9    -1    libs/libkis/Node.cpp

https://invent.kde.org/graphics/krita/commit/7be91fc9922182bf02a3fc95feb9462ae8948f72
Comment 12 Dmitry Kazakov 2020-09-16 12:20:56 UTC
Git commit 7013fef0227028316c8788e828e28a66f026c08d by Dmitry Kazakov.
Committed on 16/09/2020 at 12:20.
Pushed by dkazakov into branch 'krita/4.3'.

Fix Python API for Node::addChildNode()

The function should use the command, run it in a stroke and wait
for it to finish. That ensures that 1) updates are issued; 2) undo
item is created; 3) no threading issues arise.

M  +12   -4    libs/libkis/Node.cpp

https://invent.kde.org/graphics/krita/commit/7013fef0227028316c8788e828e28a66f026c08d
Comment 13 Dmitry Kazakov 2020-09-16 12:33:41 UTC
Now it is just a bug, not a regression
Comment 14 Dmitry Kazakov 2020-09-16 12:35:51 UTC
Reassigning back to Amyspark :)