Looking to create a "filter mask" script that dynamically changes bush size based on a layer's grayscale value

My goal is to create a script that, when active, allows the user to define a brush size range (e.g, 2px to 30px), and when their cursor is over a pixel in a set “mask” paint layer, it sets the brush size relative to that pixel’s saturation.

i.e. Set Brush Range (2-30px)

  • Layer V Pixel = 0; Brush Size = 30px
  • Layer V Pixel = 127; Brush Size = 17px
  • Layer V Pixel = 255; Brush Size = 2px

I could use some tips in how to best get a pixel’s data in a set, inactive, hidden layer named “Brush Size Filter.” I already have a good idea in how to set brush size.

Node.pixelData()

Note that for brush size you may have to emulate pressure or something, because as-is, if you just increase brush size it won’t work due to brush sizes are based on a single stroke. You can see this if you start drawing with your mouse and don’t let go, then hold the ] key which would increase brush size but won’t grow it. Of course this is only if you care about the size during a stroke and not just starting size.

I understand. My specific idea behind this is to assist line weighting for environmental scenes to convey distance. Instead of manually control size or pressure, you could use this script to allow you to keep the same physical pressure for your pen as you draw.

I have managed to get something working, now I’m just looking for a way to get it into a docker so I can toggle its operation.

from krita import Krita
from PyQt5.QtGui import QTransform, QCursor
from PyQt5.QtCore import QPointF
from PyQt5.QtWidgets import QMdiArea, QAbstractScrollArea, QMessageBox

ACTIONS = [{'menu_entry': '_test', 'id': '_test', 'callback': 'main'}]

SIZE_RANGE = [2, 30]
LAYER_NAME = "grayscale"

def main():

    app = Krita.instance()
    doc = app.activeDocument()
    view = app.activeWindow().activeView()
    root = doc.rootNode()

    #########################################################
    ### Get cursor canvas coordinates #######################
    def find_node_by_name(node, name):
        if node.name() == name:
            return node
        for child in node.childNodes():
            found_node = find_node_by_name(child, name)
            if found_node:
                return found_node
        return None
    def get_cursor_in_document_coords():
        if not view.document():
            return None
        window = view.window()
        q_window = window.qwindow()
        q_stacked_widget = q_window.centralWidget()
        q_mdi_area = q_stacked_widget.findChild(QMdiArea)
        for v, q_mdi_view in zip(window.views(), q_mdi_area.subWindowList()):
            if v == view:
                q_view = q_mdi_view.widget()
                break
        else:
            return None
        scroll_area = q_view.findChild(QAbstractScrollArea)
        viewport = scroll_area.viewport()
        for child in viewport.children():
            if child.metaObject().className().startswith('Kis') and 'Canvas' in child.metaObject().className():
                q_canvas = child
                break
        else:
            return None
        def _offset(scroller):
            mid = (scroller.minimum() + scroller.maximum()) / 2.0
            return -(scroller.value() - mid)
        canvas = view.canvas()
        zoom = (canvas.zoomLevel() * 72.0) / doc.resolution()
        transform = QTransform()
        transform.translate(_offset(scroll_area.horizontalScrollBar()), _offset(scroll_area.verticalScrollBar()))
        transform.rotate(canvas.rotation())
        transform.scale(zoom, zoom)
        transform_inv, _ = transform.inverted()
        global_pos = QCursor.pos()
        local_pos = q_canvas.mapFromGlobal(global_pos)
        center = q_canvas.rect().center()
        return transform_inv.map(local_pos - QPointF(center))

    center = QPointF(0.5 * doc.width(), 0.5 * doc.height())
    doc_pos = get_cursor_in_document_coords()
    if not doc_pos:
        return
    doc_pos += center

    #########################################################
    ### Convert byte to integer & extract grayscale value ###
    grayscale_node = find_node_by_name(root, LAYER_NAME)
    if not grayscale_node:
        QMessageBox.warning(None, "Layer Not Found", f"Layer '{LAYER_NAME}' does not exist.")
        return

    pixel_data = grayscale_node.pixelData(int(doc_pos.x()), int(doc_pos.y()), 1, 1)
    gray_value_int = int.from_bytes(pixel_data[0], byteorder='big', signed=False)
    mapped_value = SIZE_RANGE[0] + (gray_value_int / 255.0) * (SIZE_RANGE[1] - SIZE_RANGE[0])
    
    #print(f"gray value: {gray_value_int}")
    #print(f"mapped value: {mapped_value}")
    view.setBrushSize(round(mapped_value))

This topic was automatically closed 4 days after the last reply. New replies are no longer allowed.