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.