Pigment.O plugin

Unsure if it’s supposed to do this, but the active color does not seem to line up for me?
I could just have some sort of setting wrong, but I am not sure. it is set to ON.

22317d0f24d49862b3e6886d270107a8

@Sylvanticus
Activating the Luminosity Lock deactivates reception of colors from Krita. This happens because if you pick colors like that the color you will have is not the color you will picked so I de-associated them.

Just turn it off and it will align again. The luminosity lock is indicated by the top right gray square.

from googletranslate:
hi
Last week, I mentioned the problem of the slow speed of the plug-in. Several of my friends unpacked the .py file and found that this is not a bug or a performance problem.
The value of “check-timer” is very large, so there is a one-second delay. When set to 20, everything is smooth
Why not set it as the default setting? I think this will bring a good experience to users. Is there any hidden problem?

No it will not.

Not everyone has a computer good enough handle that. I have had complains where 1 second was too much to handle. I set 1second as a hard limit to not pass so it would not abuse the system but even so that is why I made the ON/P>K/OFF as a extra percaution for no win cases. Checking Krita’s colour is a bottle neck.

1 second is enough to remain fairly responsive without burdening the computer with unnecessary workload. As a user you can adjust it to your computer but for users at large I will use a longer time so it avoids lag issues. As just a color picker it should not strain your computer resources but try and be as light as possible in the background. Pigmento is already heavier than it should but in Python it can’t be lighter to my knowledge.

1 Like

Hi,

If you’re interested, rather than polling every X seconds for a color, here a small tweak to let the plugin react only when a change is made to a color.

The trick is to check for PaintEvent on the color button:
image
And if color has been changed, to emit a signal

Here the code:

  • ColorCatcher if the class you might want to use
  • Other things are here for the running example :slight_smile:
from krita import *
from PyQt5.Qt import *
from PyQt5.QtCore import (pyqtSignal as Signal)

class QFilledColor(QWidget):
    """Just a widget to display a filled rect with a color"""
    def __init__(self, parent=None):
        super(QFilledColor, self).__init__(parent)
        self.__color=None 
    
    def setColor(self, color):
        self.__color=color 
        self.update()
        
    def paintEvent(self, event):
        painter = QPainter(self)
        if self.__color:
            painter.fillRect(event.rect(), self.__color)

class ColorCatcher(QObject):
    """Catch change on color button, and emit signal if color has changed"""
    fgColorChanged=Signal(QColor)
    bgColorChanged=Signal(QColor)
    
    def __init__(self, parent=None):
        super(ColorCatcher, self).__init__(parent)
        self.__inUpdate=False
        self.__fgColor=None
        self.__bgColor=None
        self.__tweakWidget()

    def __tweakWidget(self):
        """Search for color button, and install event filter"""
        def searchSubWidget(item):
            if item.objectName() == "WdgDlgInternalColorSelector":
                item.parent().installEventFilter(self)
                return self
            elif len(item.children())>0:
                for w in item.children():
                    found=searchSubWidget(w)
                    if found:
                        return found
            return None
        return searchSubWidget(Krita.instance().activeWindow().qwindow())

    def inUpdate(self):
        return self.__inUpdate

    def eventFilter(self, source, event):
        if self.__inUpdate:
            # avoid recursives call if connected method are changing colors
            return
        if isinstance(event, QPaintEvent):
            activeView=Krita.instance().activeWindow().activeView()
            if activeView:
                color=activeView.foregroundColor()
                if color and color!=self.__fgColor:
                    self.__fgColor=color
                    self.__inUpdate=True
                    self.fgColorChanged.emit(color.colorForCanvas(activeView.canvas()))
                    self.__inUpdate=False

                color=activeView.backgroundColor()
                if color and color!=self.__bgColor:
                    self.__bgColor=color
                    self.__inUpdate=True
                    self.bgColorChanged.emit(color.colorForCanvas(activeView.canvas()))
                    self.__inUpdate=False

        return super(ColorCatcher, self).eventFilter(source, event)

def testing():
    def fgColorUpdated(color):
        fcFg.setColor(color)
    def bgColorUpdated(color):
        fcBg.setColor(color)

    dlg=QDialog(Application.activeWindow().qwindow())
    dlg.setModal(False)
    dlg.resize(QSize(400,400))

    layout = QVBoxLayout(dlg)

    fcFg=QFilledColor()
    fcBg=QFilledColor()

    layout.addWidget(fcFg)
    layout.addWidget(fcBg)

    colorCatcher=ColorCatcher()
    colorCatcher.fgColorChanged.connect(fgColorUpdated)
    colorCatcher.bgColorChanged.connect(bgColorUpdated)
        
    dlg.show()

testing()

And a running example:

Note: it doesn’t work if color button is not visible (fullscreen mode or if user have removed it from toolbar)

Grum999

1 Like

@Grum999

Thank you for the example I will test it out for sure. I just can only do that in a couple of days for real. There is stuff here I never seen before so I don’t know what they are even.

I read it on the side and I am must say I am always a bit hesitant to use eventFilter and when I have used it I am relluctant. EventFilter is like the Qtimer but for each cycle it can flood things really easily and can make Krita slowdown heavily. To avoid this I have used a dirty policy to ensure things don’t scale up.

Also reading item from a widget is a bottle neck and should be avoided and I have come to the design of only doing it once until a change is made. But it is not as tight of a bottle neck as reading Krita’s color for sure. But if the item is not visible I dunno how it will freak out and with different windows active.

The limitation of having to read from a visiable widget is a bit sucky because I need to use what I have and toggle in and out with the EventFilter and Qtimer. So the Qtimer will not be avoided fully.

Overall I have a bad feeling over this path because I have no control over the dirty state of Krita. The thing is with 1 color space working things go fast but once more color spaces are added there are alot more calculations being done for the same moment of update. If this reacts too fast to the change it might start doing too much to be handled. Linux machines are especially prone to fall under this issue. I have some checks that literally if your Linux machine I stop updating until you release your button so it only updates once and not continuously like i was doing in real time. But if the change comes from Krita there is not much I can do to check that even and if I did would void the whole purpose of this.

Events is the way Qt works.
The event filter is just a way to catch an event on an another widget.
After, if you do dirty thing with it, it’s another story. Doing things that eat CPU is clearly not recommended in this case, especially in paint event…

In my case, I check the event type (only react when the paint event occurs) and emit signal only if color has been changed.

I’ve made the choice to emit a QColor, but the ManagedColor can be emitted instead to avoid multiple conversion and let connected method doing the conversion if needed.

Yes, I agree, as indicated it’s a tweak, and tweaks are in general like dirty things :slight_smile:
The best solution will be a signal emitted directly from Krita’s API but it doesn’t exist.

Here it’s just a way to let the plugin doing things only if there’s things to do.

Yes I know.
After I’m not sure that many users remove to color button from toolbar.
But in fullscreen mode, it’s possible to keep docker for example, and then in this case I agree the trick doesn’t work anymore

Note: if button is not visible, there’s no pain event and then, filter event just filter nothing because there’s nothing to filter.
And in this case there’s no signal emitted when color is changed.

I don’t have this kind of problem on my side with Linux :man_shrugging:

Anyway, it was just an example about another possibility than timer polling.
But not a perfect solution I agree.

Grum999

I really appreciate what you did @Grum999 I am just unsure I know doing a timer pool to be very ugly but without changing Krita I donno how other ways would be better. but if I change krita might as well do it inside krita :confused: I am not proud of it at all.

If your curious how to test pigmento Linux clamp just place th advance colour selector or any other and click and hold on the sphere in the OBJ panel. In windows colours will update in krita in real time while in Linux it will only be applied in the case be when you release your mouse. If you lift the rule out you will notice a difference in performance.

I’ve made some change to test:

  • remove the control about linux/winnt to let my computer accept real time update in linux
  • remove timer and use the ColorCatcher

Test 1

Made change from Advanced Color Selector and from my own Color picker (the one I made for BuliNote)


As you can see:

  • Doing a change on Advanced Color Selector:
    – Update in real time in my color picker
    – Update in real time (and fluid) in Pigment.O
  • Doing a change from my color picker:
    – Update in real time in Advanced Color Selector
    – Update in real time (and fluid) in Pigment.O

Test 2

Made change from Pigment.O


Update is made is real time in both Advanced Color Selector and my Color Picker, but there’s a kind of latency.

Doing another comparison, just moving the hue slider:

  • On color picker: cursor on slider track the mouse without delay, everything is updated in real time on both side (color picker + Pigment.O)
  • On Pigment.O: cursor on slider doesn’t track the mouse, there’s a kind delay between mouse move and slider/panel update

Tried to do some tests on windows, but currently for an unknown reason the VM is running slow, so doing performance tests on windows in this condition is not relevant.
I’ll do it later.

I took a quick look on Pigment.O source code, I think you can do many optimization to speed up execution and probably it’s possible to improve and simplify things to got an easier maintenance :thinking:
But for someone that have started to learn how to code last year, that’s already good :wink: :+1:

Grum999

1 Like

Wow! Linux is really able to handle all that? on my Linux machine it really does not. It even goes with little lag with the OBJ panel open.

I can take another look into the slider code, that section went though a lot of different iterations.

Well the inner structure was not by design but made by trail and error because if it were for me I would have organized it different. It is bigger than it should but it makes shorter paths to the objective. I did what I could to make it as organized as possible considering the UI.

I am still a bit fearful not everyone will feel the same performance increase, I will do it but i think there will be complains :frowning: at least it is possible to work.

On my old computers, Linux is faster than windows :yum:

Looking here the results I got with a modified Pigment.O (using ColorCatcher instead of timer):

  • Modifying color hue from another source, Pigment.O sliders + panel are updated is real time and is really fluid
  • Modifying color hue directly from Pigment.O slider, sliders + panel update is laggy

So yes the problem might probably be located in how sliders have been implemented in Pigment.O
I don’t have time now to dig this, I’ll try to take a look on it next week.

Also, there’s a very bad error message in console about painter initialization.
QPainter::begin: Painter already active
I’ll try to check this too.

I know, but believe my experience, using short path will need, at the end, more time for everything:

  • bug fix
  • improvement

I can’t guarantee that performances increase will be the same for everyone but usually, even if only 50% of users perceive a real gain in performance, that’s already a good thing :wink:

Grum999

1 Like

Hi

I took a quick look about this error message.

The way QPainter() are initialised is not correct in most cases.

Example, most of QPainter are used like this:

painter = QPainter(self)
painter.begin(self)
[...]
painter.end()

There’s 2 way to create and use a QPainter:

  • Set paint device to QPainter when instancied
  • Set paint device to QPainter with begin()

In first case, this is enough:

painter=QPainter(self)
[...]

=> call to begin() is made automatically when QPainter is instancied and call to end() is made automatically when QPainter is destroyed, no need for explicit calls

In second case:

painter=QPainter()
painter.begin(self)
[...]
painter.end()

=> As paint device is not provided when QPainter is instancied, call to begin() and end() must be explicit

I’ve removed all begin/end calls where QPainter is instancied with a paint device: error message is not displayed anymore :wink:

Grum999

1 Like

Ok , found and fixed the problem :slight_smile:

I’ll explain problem with the HUE slider as example, with SIGNAL_VALUE but it’s the same for all sliders (and other signals like SIGNAL_HALF, SIGNAL_MINUS…)

In the declaration of signal connection, you’ve this:

# Channel HUE
self.hsv_1_slider.SIGNAL_VALUE.connect(self.Pigment_HSV_1_Slider_Modify)
self.layout.hsv_1_value.valueChanged.connect(self.Pigment_HSV_1_Value_Modify)

=> That’s Ok

Looking at Pigment_HSV_1_Slider_Modify, we have an update of spin box:

self.layout.hsv_1_value.setValue(send)

Looking at `Pigment_HSV_1_Value_Modify we have an update of slider:

self.hsv_1_slider.Update(self.hsv_1, self.channel_width)

Now, problem is, if you update the slider, you’ll update the spinbox, but spinbox will update the slider, and slider will update spinbox, and spinbox will update slider, and…
The ping-pong could be infinite.

But fortunately, the spinbox widget will stop to emit change once the value set is the same than actual defined value.
But you still have a ping-pong between signals with unwanted calculations and painting updates.

One solution is:

  1. Define a variable:
def Variables(self):
    # [...]
    self.__sliderUpdating=False
  1. In Pigment_XXXXX_Slider_Modify set the variable to True/False before/after changing value for spinbox
def Pigment_HSV_1_Slider_Modify(self, SIGNAL_VALUE):
    self.__sliderUpdating=True
    # [...]
    self.layout.hsv_1_value.setValue(send)
    # [...]
    self.__sliderUpdating=False
  1. In Pigment_XXXXX_Value_Modify exit immediately if change is triggered by slider:
def Pigment_HSV_1_Value_Modify(self, SIGNAL_VALUE):
    if self.__sliderUpdating:
        # already updating, exit
        return
    # [...]
    self.hsv_1_slider.Update(self.hsv_1, self.channel_width)
    # [...]

Example without fix:


In HUE slider, slider cursor is always behind the mouse cursor, and panel update is not smooth, there’s a lag in shape variation (more like a “jump” from one shape to another one)

Example with fix:


In HUE slider, slider cursor track the mouse cursor, and panel update is smooth, there’s smooth transition in shape variation

Now, looking the code implementation, it will ask some work to fix problem everywhere :anguished:

Concerning reason why on your side you don’t feel any lag in Windows, I can’t give you any explanation.
Can’t say that the problem is Linux; if there’s a difference, it’s probably more in Qt implementation that something is not managed in the same way between Windows and Linux.

Anyway, for me problem here is more about how it has been implemented in plugin than the system on which the plugin is used (but hey, I’m coding from practically 40years now so I’ve a little bit more experience about all of this… :sweat_smile:)

If you have problem to implement proposed fix or want a peer review, don’t hesitate to ask :slight_smile:

Grum999

3 Likes

Update:

  • Corrections to Speed up and Sync with Krita and User better
  • Color History added

Corrections
I did a update considering your suggestions @Grum999 .

For the speed up updating situation I just set it too 30ms for it’s check. This because loosing sight of the color widget would void it whole so to the previous method and swapping would create extra issues so I decided to try and improve this one. Also I disabled the Linux restrictions and I feel it will be bad so it is only commented out if needed.

Considering the faster rate of checks I did the best I could to lighten up all the sliders graphically by stopping to use StyleSheets and instead use Vectors only for Display. The thing is that StyleSheets despite slower are reliable and I have been holding onto them until now. So it does the same with less and lighter calculations I feel.

I did tests going from Krita to Pigmento and the other way around and both seem to be pretty responsive. However just as before closing any Krita color picker will increase responsiveness much more as it reduces lag overall for Pigmento.

Sync_Sliders

As I fixed the Slider display I also changed the UI file so it is more modular on the slider section which made me redo the menus a big deal in order to ensure proper measurements so the cursor display was correct.

Also I changed the display of the Mixers and fixed a bug where upon starting Krita, black on YUV or CMYK with just one color on the gradient the empty side would not be black but green or white because black is not (0,0,0) but using it would work perfect. I only detected it because I changed a bit the display method of the mixers when incomplete.

While i was there I shuffled the mixers around in order to be easier for them to swap interpolation methods in the future because I know there are more interpolation methods out there for colors.

For the infinite loop of the widget signals I had code to Block the unwanted signals before updating them on Pigmento_Sync however if lag is felt maybe it was not working so well. I tried to see if there was some leakage by locking it up more but I had no speed increase. So I thought that changing the status of the widget to send signals or not was perhaps too taxing and your suggestion to use a variable of control to be better and try and now it goes by much faster as it does not try to change PyQt5 and instead does the check only on Python realm. I ended up with this:

def Pigment_RGB_1_Slider_Modify(self, SIGNAL_VALUE):
    if (self.sync == None or self.sync == "rgb_1_slider"):
        self.sync = "rgb_1_slider"
        ...

and upon Mouse Release I do to allow a new input slider or of value:

self.sync = None

By using this control variable I ended up distributing if to the widget functions and not have them on the moment of sync of channels. So now the Sync of channels is much more simple.

I fixed the Painter redundancy ( QPainter::begin: Painter already active) as you told me to and now it is not crying all the time. Thank you. I really thought I would not be able to ever fix that one. Your explanation to me was much more understandable that the ones on the resource page. I had gone thought it before but I did not understand what was the difference.

Overall I think it is reacting much faster but not as fast as yours but I feel it might still do the job, but the difference does not set usable from non usable but just having to wait less to see the effect happen. I am still tense as in it pushing things to far concerning others so I am curious about that.

Color History
Also I was painting the other day and my mind crossed over a thought of something I thought I would never do and was against it as it seemed useless at the time. But now I did it. It is a little section that shows the color history change within Krita not inside Pigmento. Considering the faster performance you can get the picker tool and slide it across an image and it will sorta sample that line you draw with the picker. So that is that I can’t it imagine to be very important though. I might still re think this somehow.

10 Likes


Hello, I seem to have found some problems. When I use the transparency mask, Pigment. O can’t pick up the color on the mask, and if I change the color in krita’s color picker at this time, the color of Pigment. O won’t change either.

1 Like

thank you. it was a index out of place.
try to download it again and see if it is working it should be good now.

1 Like

Oh yes I forgot to say that it has BT.2020 now available when i did the last update.
It should be the new method for this year forward.

1 Like

Thank you!

1 Like

This is a small diagram tip on how the modifier keys react with the sliders in case you never read the manual.

The only difference is when doing the same to Hue sliders that will react different but same idea:

  • Shift - Select the Closest Pure Color because there is no Half value because it is circular.
  • Ctrl - Clamps to other amount of divisions.

Thank you so much for adding the color history function!!! :smiling_face_with_three_hearts: I used to have to keep going back to the advanced color wheel docker to access the color history. And the speed is absolutely great! The only thing I would like is to be able to do is change the colors in the color picker with just one click instead of two clicks. Like in the advanced color docker.

1 Like