Programming Draw Tools?

Hi. I’m interested in creating some scripts for animation purposes. Since a lot of animations consists of repetitious actions /it seemed like coding would be a great way to go about it. Right now I’m just trying to cut my teeth a bit, but can’t seem to find any resources related to this.

Right now I’m just trying to create a script to draw some rectangles. So–

how would you draw a rectangle? And could you simply move to the next frame by coding in a “left arrow key” user input?

any help would be appreciated. Especially resources for access of the built in tools

I guess QPainter would be best to use, remember that in Python scripting you have access to basically all of Qt.

1 Like

Hi

This is tricky, Krita doesn’t provides API to easily build animation from scripts

There’s 2 possible methods:

  1. Generate all frames and import them in Krita
  2. Generate empty frames, import them in Krita, update frames content

Here an example for first solution (the simplest):

import tempfile
import shutil
import os.path

from krita import *
from PyQt5.Qt import *

def createDoc():
    """Create a new document and return document"""
    doc=Krita.instance().createDocument(800, 600, "Testing animation", "RGBA", "U8", "", 300.0)
    doc.setFramesPerSecond(24)
    Krita.instance().activeWindow().addView(doc)
    
    return doc 
    
def insertFrames(doc, nbFrames):
    """Create a new layer with empty frames"""
    # create a temporary directory
    tmpPath = tempfile.mkdtemp()
 
    # prepare list of files  
    files=[]
    for imgNumber in range(nbFrames):
        # save one image per expected frame in document
        fileName=os.path.join(tmpPath, f"tmp{imgNumber:04}.png")
        files.append(fileName)
        # generate frame
        image=setFrame(doc, imgNumber)
        # save frame as a file
        image.save(fileName)
 
    # import all frames in new layer
    doc.importAnimation(files, 0, 1)
 
    # delete temporary directory (and files)  
    shutil.rmtree(tmpPath)
    
def setFrame(doc, number):
    """Draw and set frame content"""
    # create an empty image with current document size
    pixmap=QPixmap(doc.bounds().size())
    # set image as fully transparent
    pixmap.fill(QColor(Qt.transparent))
    
    # define black border, 2 pixels witdh
    pen=QPen(Qt.black)
    pen.setWidth(2)
    
    # define background color from Hue/Saturation/Value
    color=QColor.fromHsl(number, 255, 128, 255)
    brush=QBrush(color)

    # create painter
    painter=QPainter()
    # start to draw
    painter.begin(pixmap)

    # define pen & fill color
    painter.setPen(pen)
    painter.setBrush(brush)
    
    # do transformation in space (rotate center of a rectangle draw at the center of image)
    painter.translate(400,300)
    painter.rotate(number)
    # draw rectangle
    painter.drawRect(QRect(-200,-150,400,300))
    painter.end()

    # return image    
    return pixmap.toImage()
    
    
   
   
def main():
    doc=createDoc()
    insertFrames(doc, 360)
    
main()  

Will give as result:

For the second solution, it’s more complicated and probably slower but idea is:

  • Create empty files frames
  • Import them ==> generate an animated layer with empty content (no other way to create it)
  • Process frames

Grum999

2 Likes

That is awesome, thank you so much! I can’t wait to play around with it

Wouldn’t a combination of “next frame” and “add_blank_frame” actions make it easier (no importing needed)?

Hi

I’m not a big fan of “action” trigger, even if sometime it’s the only way to execute things.
I really prefer to use API when possible.

Concerning this case, I tried your solution and for me it was harder to code than my first solution due to many crashes occurred…
There’s still something wrong with the waitForDone() method…

Here’s the code with triggered action:

from krita import *
from PyQt5.Qt import *


def sleep(value):
    loop = QEventLoop()
    QTimer.singleShot(value, loop.quit)
    loop.exec()


def createDoc():
    """Create a new document and return document"""
    doc=Krita.instance().createDocument(800, 600, "Testing animation", "RGBA", "U8", "", 300.0)
    doc.setFramesPerSecond(24)
    Krita.instance().activeWindow().addView(doc)
    
    return doc 
    
    
def insertFrame(doc, layer, imgNumber):
    """Insert a frame in document"""
    Krita.instance().action('insert_keyframe_right').trigger()
    sleep(250)
    doc.waitForDone()
    
    image=setFrame(doc, imgNumber)
    
    ptr = image.bits()
    ptr.setsize(image.byteCount())

    layer.setPixelData(QByteArray(ptr.asstring()), 0, 0, image.width(), image.height())
  
    
def setFrame(doc, number):
    """Draw and set frame content"""
    # create an empty image with current document size
    pixmap=QPixmap(doc.bounds().size())
    # set image as fully transparent
    pixmap.fill(QColor(Qt.transparent))
    
    # define black border, 2 pixels witdh
    pen=QPen(Qt.black)
    pen.setWidth(2)
    
    # define background color from Hue/Saturation/Value
    color=QColor.fromHsl(number, 255, 128, 255)
    brush=QBrush(color)

    # create painter
    painter=QPainter()
    # start to draw
    painter.begin(pixmap)

    # define pen & fill color
    painter.setPen(pen)
    painter.setBrush(brush)
    
    # do transformation in space (rotate center of a rectangle draw at the center of image)
    painter.translate(400,300)
    painter.rotate(number)
    # draw rectangle
    painter.drawRect(QRect(-200,-150,400,300))
    painter.end()

    # return image    
    return pixmap.toImage()
    
    
def main():
    doc=createDoc()

    layer=doc.createNode("Animated layer", "paintlayer")
    doc.rootNode().addChildNode(layer, None)
    for i in range(360):
        insertFrame(doc, layer, i)
     
main()  

As you can see, there’s a sleep() of 250ms after the Krita.instance().action('insert_keyframe_right').trigger() and before the doc.waitForDone()

  • With the sleep() call, it’s loooong to generate animation
    250ms * 360, it’s ~90s to generate animation (first method need ~11s to generate animation)

  • Without the sleep() call or if I reduce this sleep value, Krita badly crash after the following warning on console:

SAFE ASSERT (krita): "!m_d->waitingOnImages.contains(image)" in file /home/appimage/workspace/Krita_Release_Appimage_Build/krita/libs/image/KisBusyWaitBroker.cpp, line 64
SAFE ASSERT (krita): "m_d->waitingOnImages.contains(image)" in file /home/appimage/workspace/Krita_Release_Appimage_Build/krita/libs/image/KisBusyWaitBroker.cpp, line 80
SAFE ASSERT (krita): "!m_d->waitingOnImages.contains(image)" in file /home/appimage/workspace/Krita_Release_Appimage_Build/krita/libs/image/KisBusyWaitBroker.cpp, line 64
SAFE ASSERT (krita): "m_d->waitingOnImages.contains(image)" in file /home/appimage/workspace/Krita_Release_Appimage_Build/krita/libs/image/KisBusyWaitBroker.cpp, line 80
ASSERT (krita): "row >= 0" in file /home/appimage/workspace/Krita_Release_Appimage_Build/krita/plugins/dockers/animation/kis_animation_curve_channel_list_model.cpp, line 143

Grum999

2 Likes