This forum has been archived. All content is frozen. Please use KDE Discuss instead.

Can you switch both fg/bg colors and last brush together?

Tags: None
(comma "," separated)
viktorsl
Registered Member
Posts
10
Karma
1
Hello,

I'd like to ask is it possible to trigger switching foreground and background color (default on X) at the same time as switching last two brushes (default on /)?

Or in any other way to assign color to a brush?

I sketch with two brushes and I'd like to quickly switch between them but each needs a different color/value (currently I have 2 colors in palette docker and I switch brueshes with / and then switch foreground and background color but that's 2 keys. It's not much for a keyboard but it's a lot for 8 buttons of my graphical display :0.

I can't find anything in manual or Krita itself that would make this possible for me, i just don't know if there is a way?

If not would it be possible with a python script? I'm neither python nor c++ programmer so a bit out of my league so far I found that you probably can connect custom actions to existing actions/signals (?) when they are triggered but I have no idea how or even where to go with this?

Thank you very much, I'm completely lost here.
User avatar
rksk16it
Registered Member
Posts
21
Karma
0
OS
This is cool ! I actually needed something like this, but it never occurred to me that I can combine these two actions !!

I have put together an addon which can accomplish this. You can download the files here : https://drive.google.com/file/d/1RMbwE74CNHHpVOE1n8z5V2IyeNcfhBhX/view?usp=sharing

It contains a zip file with two folders :
- to_be_put_in_pykrita
- to_be_put_in_actions

Go to krita's resource folder on your platform (from its resource management window), and I refer to this folder as KRITA_RESOURCE.

Put the contents of "to_be_put_in_pykrita" (only the contents, not this folder itself) in the folder : KRITA_RESOURCE/pykrita

Then, put the contents of "to_be_put_actions" in the folder: KRITA_RESOURCE/actions

If any of those folders in KRITA_RESOURCE dont exist, then create them.

After its done, assuming no errors (shouldnt be any, I tested it), then you will find this "Brush Color Switch" in the keyboard configuration, you can assign any key to that.
viktorsl
Registered Member
Posts
10
Karma
1
rksk16it wrote:This is cool ! I actually needed something like this, but it never occurred to me that I can combine these two actions !!

I have put together an addon which can accomplish this. You can download the files here : https://drive.google.com/file/d/1RMbwE74CNHHpVOE1n8z5V2IyeNcfhBhX/view?usp=sharing

It contains a zip file with two folders :
- to_be_put_in_pykrita
- to_be_put_in_actions

Go to krita's resource folder on your platform (from its resource management window), and I refer to this folder as KRITA_RESOURCE.

Put the contents of "to_be_put_in_pykrita" (only the contents, not this folder itself) in the folder : KRITA_RESOURCE/pykrita

Then, put the contents of "to_be_put_actions" in the folder: KRITA_RESOURCE/actions

If any of those folders in KRITA_RESOURCE dont exist, then create them.

After its done, assuming no errors (shouldnt be any, I tested it), then you will find this "Brush Color Switch" in the keyboard configuration, you can assign any key to that.



I'll test it asap (well after work). In the meantime I just made outside application script but it's a chore because I always have to start it and kill it later and if I have 2 apps open at the same time or more it messes up with my writting and/or commands in other apps.

You sir are my hero, having it straight in krita is a life saver nothing less than that ;-).

Thank you very much.
User avatar
rksk16it
Registered Member
Posts
21
Karma
0
OS
There is one problem though, I wanted to make it such that it can be used as a drop in replacement for "previous preset shortcut". The problem happens when you are using pen/eraser combo and want to switch back and forth, and dont really care about the color of eraser. When you swap fg/bg color, the eraser mode gets turned off. I havent yet tested with specific eraser brushes, but normal brushes with eraser mode turned on is what I use most of the time.

I tried to fix it by checking for eraser mode, it worked for a while but at one point krita got hung. It was probably some deadlock. I'll have to see if I can find some other way(s) to fix the situation.
viktorsl
Registered Member
Posts
10
Karma
1
rksk16it wrote:There is one problem though, I wanted to make it such that it can be used as a drop in replacement for "previous preset shortcut". The problem happens when you are using pen/eraser combo and want to switch back and forth, and dont really care about the color of eraser. When you swap fg/bg color, the eraser mode gets turned off. I havent yet tested with specific eraser brushes, but normal brushes with eraser mode turned on is what I use most of the time.

I tried to fix it by checking for eraser mode, it worked for a while but at one point krita got hung. It was probably some deadlock. I'll have to see if I can find some other way(s) to fix the situation.



rksk16it wrote:There is one problem though, I wanted to make it such that it can be used as a drop in replacement for "previous preset shortcut". The problem happens when you are using pen/eraser combo and want to switch back and forth, and dont really care about the color of eraser. When you swap fg/bg color, the eraser mode gets turned off. I havent yet tested with specific eraser brushes, but normal brushes with eraser mode turned on is what I use most of the time.

I tried to fix it by checking for eraser mode, it worked for a while but at one point krita got hung. It was probably some deadlock. I'll have to see if I can find some other way(s) to fix the situation.



Well there are 2 parts to this, one is that waitForDone() call freezes Krita (though I assume it's more like cycled in an endless loop waiting for some trigger which it's not getting from this) I don't know how properly use this funciton there seems to be nothing useful about this specific function in docs at least from what I saw (seems like it needs to be documented so far the entry doesn't says anything but suggest to be careful no link to anything further - or it just doesn't link in my case). So let's remove that it doesnt' seem to be doing anything useful anyway.

The other part is the order of commands, this has 2 options too:

1. If eraser is turned on with one brush keep it on with the other, in that case for me it works as this:
Code: Select all
from krita import *

switch_brush_action_name = "previous_preset"
switch_color_action_name = "toggle_fg_bg"
erase_action_name = "erase_action"

class BrushColSwitchExtension(Extension):
  def __init__(self,parent):
    super().__init__(parent)

  def createActions(self,window):
    self.bc_switch = window.createAction("bc_switch", "Brush Color Switch")

    krita = Krita.instance()
    switch_brush_action = krita.action(switch_brush_action_name)
    switch_color_action = krita.action(switch_color_action_name)
    erase_action = krita.action(erase_action_name)
   
    @self.bc_switch.triggered.connect
    def on_trigger():
      is_eraser = erase_action.isChecked()

      switch_color_action.trigger()
      switch_brush_action.trigger()

      if is_eraser:
        erase_action.trigger()
  def setup(self):
    pass

Krita.instance().addExtension(BrushColSwitchExtension(Krita.instance()))


2. Keep it consistent with how / (slash - previous brush) works in Krita, meaning it remembers the eraser state of both brushes separately, for that we can adjust it as this:

Code: Select all
from krita import *

switch_brush_action_name = "previous_preset"
switch_color_action_name = "toggle_fg_bg"
erase_action_name = "erase_action"

class BrushColSwitchExtension(Extension):
  def __init__(self,parent):
    super().__init__(parent)

  def createActions(self,window):
    self.bc_switch = window.createAction("bc_switch", "Brush Color Switch")

    krita = Krita.instance()
    switch_brush_action = krita.action(switch_brush_action_name)
    switch_color_action = krita.action(switch_color_action_name)
    erase_action = krita.action(erase_action_name)
    self.is_eraser = False
   
    @self.bc_switch.triggered.connect
    def on_trigger():
      if self.is_eraser:
        eraser = True
      else:
        eraser = False

      self.is_eraser = erase_action.isChecked()

      switch_color_action.trigger()
      switch_brush_action.trigger()
     
      if eraser:
        erase_action.trigger()

  def setup(self):
    pass

Krita.instance().addExtension(BrushColSwitchExtension(Krita.instance()))


I tested both and both seem to be doing fine, can you check on your end? I dont' really understand this that much as I'm not python programmer and never did anythign with Krita plugins either so yeah it works so I'm pretty happy with it but please let me know if you figure out some problem or a better way to do this, ok?

One more thing, I added self.is_eraser, I don't exatly now if self here refers to the plugins class BrushColSwitchExtension or does it refer somewhere else? If it's the plugin class BrushColSwitchExtension then we are fine as the new property is contained but if it's Krita class or anythin outside then we need to change that part (I don't know how classes work in Krita with all these defs and such so I hope you can shed some light on this for me please.

Lastly, would you be ok with uploading this to github or kde so other people can use it in the future too (if not it's up to you, it's your code so I'm not going to upload it but it would be probably more practical to have the code elsewhere other than the google drive ;-)).

Thanks for the help it's great, now it works perfect so far let me know here if you find some problems with it as I mentioned above please :-).

Have a nice day.
User avatar
rksk16it
Registered Member
Posts
21
Karma
0
OS
The waitForDone() might be important, I explain it in a bit. Regarding two modes you said, yeah it makes sense. A better option I see is to have two actions for each mode, one which maintains the / button behaviour and other which maintains eraser mode.

Regarding "self", in this case it wasnt really necessary. The self refers to actual instance/object of the class, like "this" in C++/Java. In python, unlike C++/Java, usage of self.var_name is mandatory if var_name is a member variable of the class, otherwise its considered a local variable to that function code. And in my code the is_eraser is indeed a local variable, its created and then used immediately then and there, no need to keep it around by attaching to self.

Now, regarding waitForDone(). I just made another thread https://forum.kde.org/viewtopic.php?f=139&t=162449 where I described code to mirror the image and then scroll it such that the pixel on which the user was drawing upon, stays under the drawing cursor. I was doing the similar thing there, triggering the horizontal mirror action and then scrolling. Though it didnt work, the image ended up in weird position. I imagined that Krita code must be using Qt's event system to queue some operations, and its probably important that they finish before I scroll. Using waitForDone() fixed the problem. Which is why I was calling waitForDone() after triggering every action, because I dont know which action might be needing it and which ones dont. Of course, I can lookup the code to determine it, but then code can change. One action may work today without needing to call waitForDone() before triggering another action, but tomorrow it may not.

But at this point, I am gonna look into the code for waitForDone() to see what exactly it does. I expect it must be calling Qt to process all the pending events, but I think there is more done there.

Regarding github, yeah no issues there, I will do that. Though I cannot guarantee my immediate availability if some issue arises. I may not be able to check on the repo anything more than once per 3-4 days maybe.
viktorsl
Registered Member
Posts
10
Karma
1
rksk16it wrote:The waitForDone() might be important, I explain it in a bit. Regarding two modes you said, yeah it makes sense. A better option I see is to have two actions for each mode, one which maintains the / button behaviour and other which maintains eraser mode.

Regarding "self", in this case it wasnt really necessary. The self refers to actual instance/object of the class, like "this" in C++/Java. In python, unlike C++/Java, usage of self.var_name is mandatory if var_name is a member variable of the class, otherwise its considered a local variable to that function code. And in my code the is_eraser is indeed a local variable, its created and then used immediately then and there, no need to keep it around by attaching to self.

Now, regarding waitForDone(). I just made another thread https://forum.kde.org/viewtopic.php?f=139&t=162449 where I described code to mirror the image and then scroll it such that the pixel on which the user was drawing upon, stays under the drawing cursor. I was doing the similar thing there, triggering the horizontal mirror action and then scrolling. Though it didnt work, the image ended up in weird position. I imagined that Krita code must be using Qt's event system to queue some operations, and its probably important that they finish before I scroll. Using waitForDone() fixed the problem. Which is why I was calling waitForDone() after triggering every action, because I dont know which action might be needing it and which ones dont. Of course, I can lookup the code to determine it, but then code can change. One action may work today without needing to call waitForDone() before triggering another action, but tomorrow it may not.

But at this point, I am gonna look into the code for waitForDone() to see what exactly it does. I expect it must be calling Qt to process all the pending events, but I think there is more done there.

Regarding github, yeah no issues there, I will do that. Though I cannot guarantee my immediate availability if some issue arises. I may not be able to check on the repo anything more than once per 3-4 days maybe.



To be honest I couldn't make it work it is_eraser initialized outside the function definition so I just checked if I can slap it on the class and see if that will make it work as an outside function (my guess is I could use some global var but I wanted to avoid that as I don't know the scope of python in this and don't want ot mess something Krita related up, is you have time would you mind showing me what you mean that it could work evne withtou the self.is_eraser and going with just is_eraser outside the function definition scope? Or is there a better way to handle this? Considering i don't know if Krita remembers different states when switching brushe this was the only decent way I coudl think of without adding too much clutter. :-).

About the waitForDone() that seems to go deeper so I won't really be checking it much further than what I've already did as I'm getting lost pretty quickly there. But I just tried to debug it line by line to find what cause Krita to freeze and the only part that had any effect was the waitForDone() call. So I removed it moved somethign around to see more and so far there doesn't seem to be a problem, so far my memory is holding fine too, performance doesn't seem get a hit either so I don't know, for now I'll leave it out until you manage to find some more about this subject.

So far I'm keeping the second variant as it's consistent with Krita's native behaviour on brush switch.

I don't think it's that hot with the github, if you place it there at least it will be easily visible and people can connect it to Krita in a usual environment so you probably don't need to keep an eye on it that much (even 3-4 days period seems to be too much for this, there is not really much happening so I'd not worry about it too much if I were you. Well this is just my two cents in this.

Thanks a lot really, it made things so much more comfortable
User avatar
rksk16it
Registered Member
Posts
21
Karma
0
OS
I created two actions for each of the modes you described: '/' behaviour mode, and eraser keeping mode. The code is here https://github.com/rkspsm/BrushColorSwitch.

I removed all doc.waitForDone() as on inspecting code, it seemed that its intention is to be used when the actual pixel data is changed, which actually happened with my other addon as it was flipping the image. Here image is not modified at any stage, so I guess doc.waitForDone() isnt needed. And it seemed to work smoothly.

Regarding is_eraser variable, in the old code, its completely my own variable, krita has no control over it, and I didnt intend it to be preserved across different calls to the function. Its purpose was very temporary, just to store the toggle state of eraser. I needed to use the previous state before triggering switching actions, and after those actions the state might have changed. This made me to fetch the state before and then use it in an if conditional. There is nothing more to that variable.
viktorsl
Registered Member
Posts
10
Karma
1
rksk16it wrote:I created two actions for each of the modes you described: '/' behaviour mode, and eraser keeping mode. The code is here https://github.com/rkspsm/BrushColorSwitch.

I removed all doc.waitForDone() as on inspecting code, it seemed that its intention is to be used when the actual pixel data is changed, which actually happened with my other addon as it was flipping the image. Here image is not modified at any stage, so I guess doc.waitForDone() isnt needed. And it seemed to work smoothly.

Regarding is_eraser variable, in the old code, its completely my own variable, krita has no control over it, and I didnt intend it to be preserved across different calls to the function. Its purpose was very temporary, just to store the toggle state of eraser. I needed to use the previous state before triggering switching actions, and after those actions the state might have changed. This made me to fetch the state before and then use it in an if conditional. There is nothing more to that variable.



Yeah, waitForDone seems ot be working on image from the docs.

I'll check the adjustments tomorrow. The only part I'd like to ask now is does:
Code: Select all
switch_brush_action_name = "previous_preset"
switch_color_action_name = "toggle_fg_bg"
erase_action_name = "erase_action"

need to be outside the class definition? Considering where it's used wouldn't it be cleaner to smash it inside or is that something related to something else?

Thanks a lot, great to see this working :-).
User avatar
rksk16it
Registered Member
Posts
21
Karma
0
OS
I declared them outside because they are used as global constants. In this small code though, you can put them inside the class, I just prefer global constants to be outside, unless they are really class specific, in which case I make them static class members. And even static class members are just glorified global constants, namespaced within the class.

In python though, unlike some other languages, the global variables are not really global for whole krita or even other python extensions. They are global only within the module they are declared. Which means if you have some other extensions with global variables of same name, there will not be any name clashes. There are more details, but I suggest reading up on python modules for proper description.
viktorsl
Registered Member
Posts
10
Karma
1
rksk16it wrote:I declared them outside because they are used as global constants. In this small code though, you can put them inside the class, I just prefer global constants to be outside, unless they are really class specific, in which case I make them static class members. And even static class members are just glorified global constants, namespaced within the class.

In python though, unlike some other languages, the global variables are not really global for whole krita or even other python extensions. They are global only within the module they are declared. Which means if you have some other extensions with global variables of same name, there will not be any name clashes. There are more details, but I suggest reading up on python modules for proper description.


I see thanks, I read up on python a little bit but module were certainly not the first parts on my list.

I guess that's all, really thank you a lot for your help with this.

Have a nice day.


Bookmarks



Who is online

Registered users: Bing [Bot], Google [Bot]