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.

1 Like

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

6 Likes

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?

I was looking for a function like this after having a positive experience with it in Rebelle. I tried to install the plugin on mac but somehow the plugin does not show up in the program after restarting. It is not in the plugins dropdown menu and not in the preferences plugin list.

Is this a Mac problem?

Hi, the new version (just released) works with Krita 5.2.9. I hope it works on Mac but I haven’t had a chance to test it.

There are two possible reasons, but I guess it is the first one because you did not put your action and your desktop file into the root folder KritaColorPlus-main of your plugin, but in sub-folders buried in the root folder KritaColorPlus-main. If you had chosen this stack, it would probably work:


* KritaColorPlus (folder)
  * KritaColorPlus (folder containing `__init__.py` and your plugin-structure with sub-folder(s) but not with a different name to the root like your `recent_color`)
  * KritaColorPlus.action (in which you, by the way, can stack all actions in a single file, instead of x `action` files¹)
  * KritaColorPlus.desktop

The second reason I think of is probably not the reason, but anyway:
It is probably because you created an action file for every single action¹, having its own name each, not being connected to the plugin, instead of creating an action file with the name of the plugin containing all actions.

But since I do not code (anymore since the 90s) beside some script-juggling, I can not tell it for sure.

Michelist

¹ Maybe it would also work if you named your battery of action files connected though the plugins name and according to the scheme:


* KritaColorPlus_action_1.action
* KritaColorPlus_action_2.action
* KritaColorPlus_action_3.action
* ...
1 Like