Gap Closing Selection Script (WIP)

The code has been simplified slightly and an exception handling when clicking outside the canvas has been added. Selection accuracy remains the same.

from struct import iter_unpack
from krita import *
from PyQt5.QtCore import (
        Qt,
        QPoint,
        QPointF,
        QEvent)
from PyQt5.QtGui import (
        QImage,
        QPainter,
        QColor,
        QMouseEvent,
        QCursor)
from PyQt5.QtWidgets import (
        QWidget,
        QApplication)
import time


def find_current_canvas():

    q_window = application.activeWindow().qwindow()
    q_stacked_widget = q_window.centralWidget()
    q_mdi_area = q_stacked_widget.currentWidget()
    q_mdi_sub_window = q_mdi_area.currentSubWindow()
    view = q_mdi_sub_window.widget()
    for c in view.children():
        if c.metaObject().className() == 'KisCanvasController':
            viewport = c.viewport()
            canvas = viewport.findChild(QWidget)
            return canvas

def click_canvas():#click action

    global_pos = pos#これが現在のカーソル位置
    posF = QPointF(canvas.mapFromGlobal(global_pos))

    mouse_press = QMouseEvent(
            QEvent.MouseButtonPress,
            posF,
            Qt.LeftButton,
            Qt.LeftButton,
            Qt.NoModifier)

    mouse_release = QMouseEvent(
            QEvent.MouseButtonRelease,
            posF,
            Qt.LeftButton,
            Qt.LeftButton,
            Qt.NoModifier)

    if not canvas.isActiveWindow():
        canvas.activateWindow()
    QApplication.sendEvent(canvas, mouse_press)
    QApplication.sendEvent(canvas, mouse_release)

    activeDoc.waitForDone()

def edgeDetectFilter(node):

    filt = application.filter('edge detection')
    configParameters = filt.configuration()
    configParameters.setProperties({
            'horizRadius': 1,
            'lockAspect': True,
            'output': 'pythagorean',
            'transparency': True,
            'type': 'prewitt',
            'vertRadius': 1})
    filt.setConfiguration(configParameters)
    filt.apply(node, 0, 0, w, h)
    activeDoc.waitForDone()

def createSelection(node, filterFunc):#Converting an image to  selection

    pixelData = bytes(node.projectionPixelData(0, 0, w, h))
    selectionData = bytearray(w * h)

    for i, rgba in enumerate(iter_unpack('>BBBB', pixelData)):
        selectionData[i] = filterFunc(*rgba)

    newSelection = Selection()
    newSelection.setPixelData(selectionData, 0, 0, w, h)
    return newSelection
#使い方selection = createSelection(node, transparentFilter)
def transparentFilter(r, g, b, a):#不透明度の閾値を数値で決める
    return 255 if a > 1 else 0#aが何より大きかったら完全に選択するか

def fillColor(node):#選択範囲に関係なく全面が色づく

    image = QImage(w, h, QImage.Format_ARGB32)
    image.fill(Qt.red)

    pixel_ptr = image.constBits()
    pixels = bytes(pixel_ptr.asarray(image.byteCount()))
    node.setPixelData(pixels, 0, 0, w, h)
    activeDoc.waitForDone()

def makeProcessedLine():

    rootCopy = activeDoc.createNode("rootCopy", "paintlayer")
    rootNodepix = rootNode.projectionPixelData(0, 0, w, h)
    rootCopy.setPixelData(rootNodepix, 0, 0, w, h)#ノード全体に画像貼り付け
    #Make the entire image a line drawing
    edgeDetectFilter(rootCopy)#エッジを出す
    #To Selection
    ProcessedLineSe = createSelection(rootCopy, transparentFilter)

    return ProcessedLineSe

def reactsToGap():
    #Scope to fill in the gap location
    reactsToGapSe = processedLineSe.duplicate()
    eraseLineSe = processedLineSe.duplicate()

    reactsToGapSe.grow(1, 1)#この辺で調整
    reactsToGapSe.grow(1, 1)
    reactsToGapSe.grow(1, 1)

    activeDoc.setSelection(reactsToGapSe)
    reactsToGapSe.shrink(4, 4, True)

    reactsToGapSe.subtract(eraseLineSe)
    reactsToGapSe.grow(2, 2)

    return reactsToGapSe

def exclusionZone():
    #Scope for eliminating unwanted parts of the lid.
    selectionPL1 = processedLineSe.duplicate()
    selectionPL2 = processedLineSe.duplicate()
    selectionPL3 = processedLineSe.duplicate()

    #Apex
    activeDoc.setSelection(selectionPL1)
    selectionPL1.grow(3, 3)
    selectionPL1.shrink(6, 6, True)

    activeDoc.setSelection(selectionPL2)
    selectionPL2.grow(7, 7)#このへんの数字はいじりようがある
    selectionPL2.shrink(3, 3, True)

    selectionPL3.grow(1, 1)
    selectionPL2.subtract(selectionPL3)

    selectionPL1.add(selectionPL2)

    return selectionPL1

def fillExpantion():#Expand the selection inside the line

    s = activeDoc.selection()

    if s == None:
        pass

    else:
        s2 = s.duplicate()
        for i in range(6):
            s2.subtract(processedLineSe)
            s2.grow(1, 1)

        s2.intersect(reactsToGapSe)#蓋と拡大する範囲の交錯部分をプラス
        s2.intersect(allSe)

        s.add(s2)
        activeDoc.setSelection(s)

def makeCloser():
    #赤いレイヤを用意 clip red layer
    redNode = activeDoc.createNode("redNode", "paintlayer")
    fillColor(redNode)
    allSe.copy(redNode)

    closerNode = activeDoc.createNode("closerNode", "paintlayer")
    closerNode.setBlendingMode("not_converse")#否定逆論理

    processedLineSe2 = processedLineSe.duplicate()
    activeDoc.setSelection(processedLineSe2)#最も狭い隙間を閉じるもの
    processedLineSe2.grow(1, 1)
    processedLineSe2.shrink(2, 2, True)

    reactsToGapSe.grow(1, 1)
    reactsToGapSe.add(processedLineSe2)
    reactsToGapSe.subtract(exSe)#フタ完成

    reactsToGapSe.paste(closerNode, 0, 0)#クローザーを設置
    rootNode.addChildNode(closerNode, topNode)

    application.action('deselect').trigger()#変な選択あるから一旦掃除
    activeDoc.waitForDone()

    click_canvas()
    fillExpantion()

    closerNode.remove()


application = Krita.instance()
activeDoc = application.activeDocument()
activeLayer = activeDoc.activeNode()
rootNode = activeDoc.rootNode()
topNode = activeDoc.topLevelNodes().pop()

w = activeDoc.width()
h = activeDoc.height()

allSe = Selection()
allSe.select(0, 0, w, h, 255)
# time.sleep(2)
pos = QCursor.pos()#起動時点のカーソル位置

#Avoid having the same tool twice in a row.
application.action('PanTool').trigger()#連続で同じツールを持たないよう回避
application.action('KisToolSelectContiguous').trigger()#select tool

canvas = find_current_canvas()
processedLineSe = makeProcessedLine()
reactsToGapSe = reactsToGap()
exSe = exclusionZone()
makeCloser()

activeDoc.setActiveNode(activeLayer)

#Make an ant march appear.
application.action('invert_selection').trigger()#アリの行進
application.action('invert_selection').trigger()


This code makes the “closerNode” appear on the screen and temporarily closes the gap. The weakness of this code is that it works like a keyboard macro, automatically clicking the mouse to select a continuous area. It would be faster if the parts that fill the gap did not have to appear on the screen…
Also, I still don’t understand why it doesn’t work when the active node is a group layer.

5 Likes