How to use layer.setPixelData()?

Hello,

I’m trying to create my first python script with Krita. I’d like to take some pixel data from one document, rescale it and move it to another document’s layer. For now I could get the QByteArray, rescale it using a QImage, and then convert back this QImage to a QByteArray. This last byte array works fine when I try to put it again in a QImage (to save it and see the result for debug), but it fails when doing myLayer.setPixelData(pixelBytes, 0, 0,width, height) . Do you have any idea why / or where I could find some sample code for this ?

Here is the relevant part of my code :

    pixelBytes = currentDocument.pixelData(0, 0, currentDocument.width(), currentDocument.height())
    
    # Resize it through QImage
    imageData = QImage(pixelBytes, currentDocument.width(), currentDocument.height(), QImage.Format_RGBA8888).rgbSwapped()
    imageData.save(generationFolder + "img0.png") # Image is fine here
    imageData = imageData.scaled(QSize(tilesetDoc.width(), tilesetDoc.height()), Qt.IgnoreAspectRatio)
    imageData.save(generationFolder + "img1.png") # Image is fine here
    
    # Converts QPixmap to bytes
    bytes = QtCore.QByteArray()
    buffer = QtCore.QBuffer(bytes)
    buffer.open(QtCore.QIODevice.WriteOnly)
    assert imageData.save(buffer, "PNG")
    pixelBytes = QByteArray(bytes.data())
    print(type(pixelBytes))

    # Check if image is fine
    img1 = QImage()
    assert img1.loadFromData(pixelBytes)
    img1.save(generationFolder + "img2.png") # Image is fine here

    # Creates a layer for your image copy
    root = tilesetDoc.rootNode()
     
    tilesetLayer = tilesetDoc.createNode("tilesetImage", "paintlayer")
    root.addChildNode(tilesetLayer, root)
    
    # Sets image in layer
    # Checks expected size of the image as said in the libkis docs (4 channels)
    if imageData.sizeInBytes() == 4 * tilesetLayer.channels()[0].channelSize() *  tilesetDoc.width() * tilesetDoc.height() :
        print("Image size is correct")
        # tilesetLayer.setPixelData(pixelBytes, 0, 0, tilesetDoc.width() , tilesetDoc.height()) # Krita crashes

I’ve also read about this other way to get the byte array, but doens’t work either for me :

Any help would be appreciated ! :slight_smile:

Thanks :slightly_smiling_face:

Hi

You need to provide a pointer to QImage bits.

Especially this part (variable image is a QImage):

ptr = image.bits()
ptr.setsize(image.byteCount())

layer.setPixelData(QByteArray(ptr.asstring()), 0, 0, image.width(), image.height())

Grum999

Hi, thank you for your answer !

I tried to replace some code, ended up with :

    if imageData.sizeInBytes() == 4 * tilesetLayer.channels()[0].channelSize() *  tilesetDoc.width() * tilesetDoc.height() :
        print("Image size is correct")
        ptr = imageData.bits()
        ptr.setsize(imageData.byteCount())
        tilesetLayer.setPixelData(QByteArray(ptr.asstring()), 0, 0, imageData.width() , imageData.height())
    

Now Krita doesn’t crash but nothing is painted on my layer.
Have I missed something ?

Thanks ! :slight_smile:

You have to refresh projection of document.

Considering currentDocument is your document:

currentDocument.refreshProjection()

Grum999

It’s already done. The complete relevant part of code is now the following :

    # Copies your template image in our krita tileset project
    pixelBytes = currentDocument.pixelData(0, 0, currentDocument.width(), currentDocument.height())
    
    # Resize it through QImage
    imageData = QImage(pixelBytes, currentDocument.width(), currentDocument.height(), QImage.Format_RGBA8888).rgbSwapped() # the pixel data is in BGR
    imageData = imageData.scaled(QSize(tilesetDoc.width(), tilesetDoc.height()), Qt.IgnoreAspectRatio)
    
    # Creates a layer for your image copy
    root = tilesetDoc.rootNode()
     
    tilesetLayer = tilesetDoc.createNode("tilesetImage", "paintlayer")
    root.addChildNode(tilesetLayer, root)
    
    # Sets image in layer
    # Checks expected size of the image as said in the libkis docs (4 channels)
    if imageData.sizeInBytes() == 4 * tilesetLayer.channels()[0].channelSize() *  tilesetDoc.width() * tilesetDoc.height() :
        print("Image size is correct")
        ptr = imageData.bits()
        ptr.setsize(imageData.byteCount())
        
        tilesetLayer.setPixelData(QByteArray(ptr.asstring()), 0, 0, imageData.width() , imageData.height()) 
        tilesetDoc.refreshProjection()
    
    tilesetLayer.setOpacity(255)
    tilesetDoc.refreshProjection()

    # Saves your tileset document
    tilesetDoc.saveAs(generationFolder + "Tileset.kra")

Maybe it’s the most relevant but that’s hard to execute it from mind :slight_smile:

Assuming currentDocument and tilesetDoc are initialized like this, on my side I get expected results:

from PyQt5.Qt import *

currentDocument=Krita.instance().activeDocument()
tilesetDoc=Krita.instance().createDocument(400, 400, "Document name", "RGBA", "U8", "", 300.0)
Krita.instance().activeWindow().addView(tilesetDoc)

# Copies your template image in our krita tileset project
pixelBytes = currentDocument.pixelData(0, 0, currentDocument.width(), currentDocument.height())

# Resize it through QImage
imageData = QImage(pixelBytes, currentDocument.width(), currentDocument.height(), QImage.Format_ARGB32)
imageData = imageData.scaled(QSize(tilesetDoc.width(), tilesetDoc.height()), Qt.IgnoreAspectRatio)

# Creates a layer for your image copy
root = tilesetDoc.rootNode()
 
tilesetLayer = tilesetDoc.createNode("tilesetImage", "paintlayer")
root.addChildNode(tilesetLayer, None)

# Sets image in layer
# Checks expected size of the image as said in the libkis docs (4 channels)
if imageData.sizeInBytes() == 4 * tilesetLayer.channels()[0].channelSize() *  tilesetDoc.width() * tilesetDoc.height() :
    print("Image size is correct!")
    ptr = imageData.bits()
    ptr.setsize(imageData.byteCount())
    
    tilesetLayer.setPixelData(QByteArray(ptr.asstring()), 0, 0, tilesetDoc.width() , tilesetDoc.height()) 
    tilesetDoc.refreshProjection()

Notes:

  • I’ve replaced QImage.Format_RGBA8888).rgbSwapped() with QImage.Format_ARGB32 otherwise image result is negative
  • I’ve replaced root.addChildNode(tilesetLayer, root) with root.addChildNode(tilesetLayer, None) because you can’t add child above parent…

And on my side it works

Grum999

Thank you for your help, the problem was indeed solved with your code, in particular with root.addChildNode(tilesetLayer, None) ! :slight_smile: