I wonder if it is from krita 5.2 ? krita’s contiguous selection has become faster. I have coded the method differently due to this. Various problems have been solved and the selection can now be performed in about 1 second. Selection expansion, feathering, and selection modes such as “add” and “subtract” are also supported.
You can register it in ten scripts and start it at any position to try it out. (I recommend testing with krita 5.2 or later).
from krita import *
from PyQt5.QtCore import QPoint, QPointF, QEvent, QTimer
from PyQt5.QtGui import QImage,QMouseEvent,QCursor
def find_current_canvas():
q_window = application.activeWindow().qwindow()
q_stacked_widget = q_window.centralWidget()
canvas = q_stacked_widget.findChild(QOpenGLWidget)
return canvas
def click_canvas():
mouse_press = QMouseEvent(
QEvent.MouseButtonPress,
posF,
Qt.LeftButton,
Qt.LeftButton,
Qt.NoModifier)
mouse_release = QMouseEvent(
QEvent.MouseButtonRelease,
posF,
Qt.LeftButton,
Qt.LeftButton,
Qt.NoModifier)
QApplication.sendEvent(canvas, mouse_press)
QApplication.sendEvent(canvas, mouse_release)
def clipFillColor():
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)
# clip red color
redNode = activeDoc.createNode("red", "paintlayer")
fillColor(redNode)
allSe.copy(redNode)
redNode.remove()
def makeSelection():
global lineSe
startSe = activeDoc.selection()#selection by first Click
application.action('deselect').trigger()
invertSe = startSe.duplicate()
invertSe.invert()
lineSe = invertSe.duplicate()
lineSe.border(1,1)#selection of line art
# selection that close gaps
gapSe = lineSe.duplicate()
gapSe.grow(5,5)
gapSe.shrink(5,5,True)
gapSe.grow(1,1)
gapSe.border(1,1)
# selection that can make hole in the gap selection
needleSe = lineSe.duplicate()
needleSe.grow(5,5)
needleSe.subtract(lineSe)
needleSe.shrink(3,3,True)
needleSe.grow(1,1)
# make hole in the gapSelection
gapSe.subtract(needleSe)
gapSe.paste(closerNode, 0, 0)# gap closer
if aaBool:# restore state and ready for secondClick
aaBox.setChecked(True)
def expand():
def parameter(selection):
# grow of selectoin
if box_grow_v >= 0:
selection.grow(box_grow_v, box_grow_v)
elif box_grow_v < 0:
selection.shrink(abs(box_grow_v), abs(box_grow_v), True)
# feathering of selection
if box_feather_v > 0:
selection.feather(box_feather_v)
return selection
global compSe
if activeDoc.selection():
currentSe = activeDoc.selection()
se = currentSe.duplicate()
se.subtract(lineSe)
for i in range(3):# fill inside
se.grow(1,1)
se.subtract(lineSe)
se.intersect(allSe)
compSe = parameter(se)
def mixSelection():
global compSe
global preSe
# Compatible with selection mode
if preSe != None:
if controlMode == 2:
preSe = compSe
elif controlMode == 3:
preSe.intersect(compSe)
elif controlMode == 4:
preSe.add(compSe)
elif controlMode == 5:
preSe.subtract(compSe)
elif controlMode == 6:
preSe.symmetricdifference(compSe)
compSe = preSe
def afterCare():
# restore state
buttons[controlMode].setChecked(True)
closerNode.remove()
activeDoc.setActiveNode(activeLayer)
box_grow.setValue(box_grow_v)
box_feather.setValue(box_feather_v)
def visualizer():
# to visualize the selection(krita has bag?)
if compSe == None:
activeDoc.setSelection(preSe)
else:
activeDoc.setSelection(compSe)
application.action('invert_selection').trigger()
application.action('invert_selection').trigger()
def settingTimer(ms, func):
timer = QTimer()
timer.setInterval(ms)
timer.setSingleShot(True)
timer.timeout.connect(func)
return timer
application = Krita.instance()
activeDoc = application.activeDocument()
activeLayer = activeDoc.activeNode()
rootNode = activeDoc.rootNode()
w = activeDoc.width()
h = activeDoc.height()
allSe = Selection()
allSe.select(0, 0, w, h, 255)
lineSe = Selection()
compSe = Selection()
compSe = None
if activeDoc.selection() != None:
preSe = activeDoc.selection()# memory original selection
application.action('deselect').trigger()
else:
preSe = None
canvas = find_current_canvas()
if not canvas.isActiveWindow():
canvas.activateWindow()
# click point
pos = QCursor.pos()
posF = QPointF(canvas.mapFromGlobal(pos))
# pick contiguous selection tool
tooldock = next((w for w in application.dockers() if w.objectName() == 'ToolBox'), None)
sebutton = tooldock.findChild(QToolButton,'KisToolSelectContiguous')
sebutton.click()
# the tool option docker
qdock = next((w for w in application.dockers() if w.objectName() == 'sharedtooldocker'), None)
wobj = qdock.findChild(QWidget,'KisToolSelectContiguousoption widget')
buttons = wobj.findChildren(QToolButton)
boxs = wobj.findChildren(QSpinBox)# input box
aaBox = wobj.findChild(QCheckBox)# antialiasing check box
n = 2
while n < 7:
if buttons[n].isChecked():
num = n
n += 1
controlMode = num # original mode
buttons[2].setChecked(True)# pick "replace"
# input to the box
box_grow = boxs[0]
box_feather = boxs[1]
box_grow_v = box_grow.value()
box_feather_v = box_feather.value()
box_grow.setValue(0)
box_feather.setValue(0)
# check antiAliasing
aaBool = aaBox.isChecked()
if aaBool:
aaBox.setChecked(False)# set AA "False" for running speed
timer1 = settingTimer(100, visualizer)# selection
closerNode = activeDoc.createNode("closerNode", "paintlayer")
closerNode.setBlendingMode("not_converse")
rootNode.addChildNode(closerNode, None)# add layer for closer
def runAll():
clipFillColor()# clip red color
# activeDoc.waitForDone()
click_canvas()# firstClick
activeDoc.waitForDone()
if activeDoc.selection():
makeSelection()
activeDoc.waitForDone()
click_canvas()# secondClick
activeDoc.waitForDone()
expand()
# activeDoc.waitForDone()
mixSelection()
# activeDoc.waitForDone()
afterCare()
# activeDoc.waitForDone()
timer1.start()# Time difference required to visualize the selection
runAll()
Problems
-
Selection takes a long time if the canvas size is over 2000px, and may not work well in some environments, since PC specs also affect the speed.
-
Selection can only be made on all visible layers. It is theoretically possible to select “only the current layer” or “only the color labels,” but this would be a complex operation and would be slow to execute.
-
To have the ability to change the gap size, I would need to work on creating a dedicated docker or adding a button to the selection tool options. This is also not impossible, but for me it would be time consuming.
I think it would be difficult to improve the speed any further, because the operation of forcing Krita to click to create a selection is slow. It might be faster if a function was added to the Python API to create a contiguous selection from an arbitrary cursor position, or if this code could be executed with C++, but that is not possible for me at this time.
Still, I think I’m at a more usable level than I was at first.