Hot to get the mouse position in a plugin?

Hi, how do I get the current mouse coordinates (in layer space)?

I found out how to get the color at given coordinates (activeNode.pixelData), but how do I get the current mouse coordinates?

Thanks

Original post follows:

Hi, in a python plugin, how do I get the color under the cursor position? (from the merged image or from current layer, it does not matter a lot)

I’m trying to create an action to set the foreground color to the average of the current color and the color under cursor.

You can do that by adjusting the Tool Options docker for the Colour Sampler Tool to have a Blend Value of 50%.
Then, using the Ctrl key colour picker (while painting) will give a resulting colour of 50% of the current colour plus 50% of the selected colour under the cursor.

It may be that you definitely want the experience of developing a plugin for this, in which case - I wish you success :slight_smile:

Thanks but that wouldn’t work, as I would lose the ability to pick the full color with a single click.
There need to be two different shortcuts, one for picking the full color, one for mixing.

1 Like

So far I found QtGui.QCursor.pos(). Now I have to translate into node coordinates…

I guess I need to call QWidget.mapFromGlobal(), but I need a widget. Do I need to convert the Canvas to a Widget? How?

Getting closer, but this gives me the coordinates relative to the main window. I need coordinates relative to the layer:


pos = QtGui.QCursor.pos()
pos2 = Krita.instance().activeWindow().qwindow().mapFromGlobal(pos);

This works for me. (there might be off-by-one in some where…)


from krita import (
        Krita,)

from PyQt5.QtCore import (
        Qt,
        QEvent,
        QPointF,
        QRect,
        QTimer)

from PyQt5.QtGui import (
        QTransform,
        QPainter,
        QBrush,
        QColor,
        QPolygonF,
        QInputEvent,
        QCursor)

from PyQt5.QtWidgets import (
        QWidget,
        QMdiArea,
        QTextEdit,
        QAbstractScrollArea)


def get_q_view(view):
    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:
            return q_mdi_view.widget()


def get_q_canvas(q_view):
    scroll_area = q_view.findChild(QAbstractScrollArea)
    viewport = scroll_area.viewport()
    for child in viewport.children():
        cls_name = child.metaObject().className()
        if cls_name.startswith('Kis') and ('Canvas' in cls_name):
            return child


def get_transform(view):
    def _offset(scroller):
        mid = (scroller.minimum() + scroller.maximum()) / 2.0
        return -(scroller.value() - mid)
    canvas = view.canvas()
    document = view.document()
    q_view = get_q_view(view)
    area = q_view.findChild(QAbstractScrollArea)
    zoom = (canvas.zoomLevel() * 72.0) / document.resolution()
    transform = QTransform()
    transform.translate(
            _offset(area.horizontalScrollBar()),
            _offset(area.verticalScrollBar()))
    transform.rotate(canvas.rotation())
    transform.scale(zoom, zoom)
    return transform
    

def get_cursor_in_document_coords():
    app = Krita.instance()
    view = app.activeWindow().activeView()
    if view.document():
        q_view = get_q_view(view)
        q_canvas = get_q_canvas(q_view)    
        transform = get_transform(view)
        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))


class MyLogger(QTextEdit):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self._ticker = QTimer(parent=self)
        self._ticker.setInterval(1000)  # ms
        self._ticker.timeout.connect(self._on_tick)
        self._ticker.start()
        
    def _on_tick(self):
        app = Krita.instance()
        view = app.activeWindow().activeView()
        document = view.document()
        if document:
            center = QPointF(0.5 * document.width(), 0.5 * document.height())
            p = get_cursor_in_document_coords()
            doc_pos = p + center
            self.append(f'cursor at: x={doc_pos.x()}, y={doc_pos.y()}')


my_logger = MyLogger()
my_logger.resize(400, 600)
my_logger.show()

/AkiR

1 Like

It’s working! I can’t believe you also took the transform into account! Thanks man, you are a savior! I’ll post the code for the plugin as soon as finished.

Here’s the plugin. The “mix” action mixes the foreground color with the color under cursor, 50% average. I’ve bound it to the “c” key. Thanks again!

from krita import *

from krita import (
        Krita,)

from PyQt5.QtCore import (
        Qt,
        QEvent,
        QPointF,
        QRect,
        QTimer)

from PyQt5.QtGui import (
        QTransform,
        QPainter,
        QBrush,
        QColor,
        QPolygonF,
        QInputEvent,
        QCursor)

from PyQt5.QtWidgets import (
        QWidget,
        QMdiArea,
        QTextEdit,
        QAbstractScrollArea)


# from PyQt5 import QtWidgets, QtCore, QtGui, uic
import os.path
import time
import datetime
import xml



def get_q_view(view):
    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:
            return q_mdi_view.widget()


def get_q_canvas(q_view):
    scroll_area = q_view.findChild(QAbstractScrollArea)
    viewport = scroll_area.viewport()
    for child in viewport.children():
        cls_name = child.metaObject().className()
        if cls_name.startswith('Kis') and ('Canvas' in cls_name):
            return child


def get_transform(view):
    def _offset(scroller):
        mid = (scroller.minimum() + scroller.maximum()) / 2.0
        return -(scroller.value() - mid)
    canvas = view.canvas()
    document = view.document()
    q_view = get_q_view(view)
    area = q_view.findChild(QAbstractScrollArea)
    zoom = (canvas.zoomLevel() * 72.0) / document.resolution()
    transform = QTransform()
    transform.translate(
            _offset(area.horizontalScrollBar()),
            _offset(area.verticalScrollBar()))
    transform.rotate(canvas.rotation())
    transform.scale(zoom, zoom)
    return transform
    

def get_cursor_in_document_coords():
    app = Krita.instance()
    view = app.activeWindow().activeView()
    if view.document():
        q_view = get_q_view(view)
        q_canvas = get_q_canvas(q_view)    
        transform = get_transform(view)
        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))


# from PyQt5.QtCore import (
        # Qt,)

# from PyQt5.QtWidgets import (
        # QTextEdit,
        # QTableView)





def listEqual(l1,l2):
   if(len(l1)==len(l2) and len(l1)==sum([1 for i,j in zip(l1,l2) if i==j])):
      return True
   else:
      return False
      
      
class MyExtension(Extension):

    def __init__(self, parent):
        # This is initialising the parent, always important when subclassing.
        super().__init__(parent)
        
        
        print("LastColor init ok")


        
    def setup(self):
        
        
        
        
        self.currentColor = [0,0,0,0]
        self.previousColor = [0,0,0,0]
        self.inited = False
        
        print("LastColor setup ok")
        
        
        
    
    def swap(self):
        try:
            # doc  = Krita.instance().activeWindow().activeView().document()
            # print(doc)
            
            # sel  = doc.selection()
            # print(sel )
            
            # x = sel.x()
            # print(x)
    
            pos = QtGui.QCursor.pos()
            print("cursor absolute = ", pos)

            win = Krita.instance().activeWindow().qwindow().mapFromGlobal(pos);

            print("cursor mapped = ", win)
    
            if not self.inited:
                print ("swap: initializing...")
                
                
                
                
                
                
                
                
                
                app = Krita.instance()
                history_docker = next((d for d in app.dockers() if d.objectName() == 'History'), None)
                kis_undo_view = next((v for v in history_docker.findChildren(QListView) if v.metaObject().className() == 'KisUndoView'), None)
                s_model = kis_undo_view.selectionModel()
                s_model.currentChanged.connect(self._on_history_was_made)
                
                self.inited = True;
                print ("swap: initialized")
            else:
                
                acView = Krita.instance().activeWindow().activeView()

                col = acView.foregroundColor()
                comp = col.components()
                
                
                col.setComponents(self.previousColor)
                acView.setForeGroundColor(col)
                
                self.previousColor = self.currentColor
                    
        except Exception as e:
                print("errore trovato in swap:")
                print(e)
                
                


    
    def _on_history_was_made(self):
        # print ("user painted")
        try:
            
            acView = Krita.instance().activeWindow().activeView()
            if acView is not None: 
                col = acView.foregroundColor()
                if col is not None:   
                    comp = col.components()
                    if listEqual( comp, self.currentColor ):   # user did not change color
                         pass
                    else:  # user changed color
                        print("color changed")
                        self.previousColor = list.copy(self.currentColor) 
                        self.currentColor = list.copy(comp)
                

                
        except:
                print("found error")
                #self.timer_hm.stop()
                raise


    def mix(self):
        app = Krita.instance()
        win = app.activeWindow()
        if win is not None:
            view = win.activeView()
            if view is not None:
                document = view.document()
                if document:
                    center = QPointF(0.5 * document.width(), 0.5 * document.height())
                    p = get_cursor_in_document_coords()
                    doc_pos = p + center
                    print(f'cursor at: x={doc_pos.x()}, y={doc_pos.y()}')
                    
                    self.pixelBytes = document.activeNode().pixelData(doc_pos.x(), doc_pos.y(), 1, 1)
                    
                    self.imageData = QImage(self.pixelBytes, 1, 1, QImage.Format_RGBA8888)
                    self.pixelC = self.imageData.pixelColor(0,0)
                    print(f"color under cursor = {self.pixelC.red()}, {self.pixelC.green()}, {self.pixelC.blue()}")
                    
                    fg = view.foregroundColor()
                    comp = fg.components() 
                    print(f"fg color = {comp}")
     
                    comp[0] = comp[0] * 0.5 + (self.pixelC.red() / 255.0)  * 0.5
                    comp[1] = comp[1] * 0.5 + (self.pixelC.green() / 255.0)  * 0.5
                    comp[2] = comp[2] * 0.5 + (self.pixelC.blue()  / 255.0)  * 0.5
                  
                    fg.setComponents(comp)
                    print(f"setting new color: {fg}")
                    view.setForeGroundColor(fg)

        
    
    def createActions(self, window):
    


        action = window.createAction("LastColor", "LastColor")
        action.triggered.connect(self.swap)
        
        action2 = window.createAction("MixColor", "MixColor")
        action2.triggered.connect(self.mix)

# And add the extension to Krita's list of extensions:
Krita.instance().addExtension(MyExtension(Krita.instance()))
1 Like

I apologized for the silly question, but how can I make it to work?

No silly question. I just put the plugin on github. There are also the instructions to install. You need to copy a bunch of files in two folders. See here: GitHub - seguso/KritaColorPlus: Krita plugins providing shortcuts to mix color, get last color, and pick color

Let me know if there are problems :slight_smile:

1 Like

I’ll copy the instructions here as well:

INSTALLATION

In Windows:

exit Krita. Enter the folder

C:\Users\yourname\AppData\Roaming\krita\pykrita

and copy here the .desktop file and the recent_color folder: https://i.imgur.com/5SoFMpu.png

Then enter the folder

C:\Users\yourname\AppData\Roaming\krita\actions

and copy the .action files here: https://i.imgur.com/zspj4uc.png

Then start Krita. Activate the plugin by checking it here: https://i.imgur.com/CKH6wDs.png

Then reassign the shortcuts here: https://i.imgur.com/14IRqxa.png

1 Like

Thanks a lot for this plugin @seguso. Works like a charm since I blend a lot. :slight_smile:

So you tried it and it’s working? Good :slight_smile:

1 Like

It would also work to install the plugin in Krita via “Tools” >> “Scripts” >> “Import Python Plugin from File”, this makes things much more user-friendly for the less computer savvy users.
:wink:

Michelist

2 Likes

Argh! I didn’t know that :slight_smile:

1 Like

It seems this doesn’t install the .action files though. Am I missing something?