Krita "Lightbox" Script - for easy refinement/tracing layers

Draw initial rough sketch, press a keyboard command and Krita adds a light blue tint to the drawing and a layer on top.

I wanted an action, so by loading my script into the “Ten Scripts” function I can press Ctrl+Shift+1 and call the script.

The only thing I wish it did additionally would be fading out all other painting layers before, so in theory I could infinitely use this script to make new layers without them all overlapping too much at some point.

Initial thoughts, I cleaned a bit up in here.

Right now, I use the “normalize” filter on the duplicated layer, since without any arguments it kinda does what I want. I’d like to have the whole duplicate in a specific color and a bit lighter, but it “works” still, so I count that as problem solved, just not optimal. I need to learn a lot.

Script looks like this:

Krita.instance().action(‘duplicatelayer’).trigger()
Krita.instance().action(‘krita_filter_normalize’).trigger()
Krita.instance().action(‘krita_filter_desaturate’).trigger()
Krita.instance().action(‘add_new_paint_layer’).trigger()

It works so far, but I would love to have some options. Can anyone with more Python/Krita API knowledge tell me how I can for example create a Fill Layer with a specific color and then set it to a specific blend mode?

Hope it is of any use to someone. My code is not super nice and I haven’t got a clue what I am doing, since I’m so new to scripting. But always willing to learn a bit more.

Cheers
Flo

Sometimes it’s useful to go to libs/libkis · master · Graphics / Krita · GitLab and then look at the c++ code (best is to use headers, since they’re shorter) to see the functions. (I’m not sure if the Python API website is finally fixed and up or not…)

So there is a function
FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection)
generator should be color, and then in configuration you need to have property called color with value in QVariant type set to… probably ManagedColor with the color you want.

Then you can just call setBlendingMode() on the layer.

Don’t forget to add it to the stack, btw :wink:

1 Like

Thank you so much for the heads up! I will read about it and try to tackle it further. Once I figured this out there’s a ton of other cool possibilities.

Unlimited Power! Can’t wait. hehehe

1 Like

Oh, also, important thing, selection could probably be the lineart, you need to create a Selection, then use setPixelData() on it to put your lineart there. But it probably would have to be transformed to GRAYA first…
Note: I of course haven’t checked any of what I told you :stuck_out_tongue: but I hope it works.

1 Like

Yes, halla added the meta data to have it processed by kde so the documentation now works:

3 Likes

So far, so good. It’s pretty hard for me to follow all the things like how to nest the layers and how to change specific things on specific layers. The script kinda does all the single steps, but it changes the blending mode of the wrong layer (“Background” instead of "Non-Photo Blue) and puts the Paint layer between the other layers instead of on top of the stack.

Appreciate the help, thank you all. :slight_smile:

from krita import *
app = Krita.instance()
doc = app.activeDocument()
layer = Krita.instance().activeDocument().activeNode()
infoFill = InfoObject();
infoFill.setProperty(“color”, “#1f97d9”)
select = Selection();
select.select(0, 0, doc.width(), doc.height(), 255)
fill = doc.createFillLayer(“Non-Photo Blue”, “color”,infoFill, select)
root = doc.rootNode();
child = root.childNodes();
root.addChildNode(fill, child[0])
app.action(‘add_new_paint_layer’).trigger()
layerBlueFill = doc.nodeByName(“Non-Photo Blue”)
doc.setActiveNode(layerBlueFill)
doc.activeNode().setBlendingMode(“screen”)
doc.refreshProjection()

What’s causing both problems is that doc.activeNode() is returning a different node than what should have been set in doc.setActiveNode(), and I have no idea why.
The first issue can be circumvented by calling layerBlueFill.setBlendingMode("screen") directly, at least.
(Also, a tip: use the “preformatted text” (``) option for pasting code instead of the
“blockquote” (>) option, because the latter changes quotation characters to their fancy versions ("" to “”) which doesn’t work with code.)

(By the way, to any devs reading this, is it normal for Krita to crash with Access Violation in a .dll when I break a script too badly? Or should I start a thread somewhere)

1 Like

You are overcomplicating stuff here, you should use doc.createNode() this will return to you the node so you don’t have to search for it with nodeByName

It is due to the race condition.

The python is compiled into C++, so of course if you make a mistake it can lead to a crash.

Hm. Sounds like that could’ve even caused my crashes. (At least, I think so?)

It’s not that I don’t expect it to crash, I was just curious because it seemed similar to this bug report: 433032 – Random Crash access violation , except that it’s about unknown crashes during normal usage (which I’ve also had, extremely rarely). It’s probably not important though (I just got the two things mixed up), and I don’t want to derail this thread (any further).

You mean it happened on an Undo? Possible since there is no transactions yet. So some operations are outside transactions(internally) and can result in a crash.

@freyalupen @KnowZero Thanks to both of you, it finally works. At least coming from a single Layer in a fresh document. Thank you very much.

I’ll keep on working, trying to make it work in any situation (checking if the blue Fill Layer already exists, and therefore removing the old one or not creating a new one but moving the current one. As well as updating the Ink layer names)

Update is on Github also.

from krita import *
app = Krita.instance()
doc = app.activeDocument()

infoFill = InfoObject()
infoFill.setProperty("color", "#1f97d9")
select = Selection()
select.select(0, 0, doc.width(), doc.height(), 255)

fill = doc.createFillLayer("Non-Photo Blue", "color", infoFill, select)
root = doc.rootNode()
child = root.childNodes()

#####

# Still need a check if "Non-Photo Blue" exists, if yes -> delete the old one. Or keep it and move it, whatever is easier. 
# Any maybe an iterative name for new "Ink" layers would be great.

#####

root.addChildNode(fill, child[0])

# create Fill Layer: Non-Photo Blue
layerBlueFill = doc.nodeByName("Non-Photo Blue")
layerBlueFill.setBlendingMode("screen")

# create Paint Layer: Ink
paint = doc.createNode("Ink", "paintlayer")
root.addChildNode(paint, doc.nodeByName("Non-Photo Blue"))

doc.refreshProjection()
1 Like

You can clean it up a bit more

from krita import *
app = Krita.instance()
doc = app.activeDocument()

infoFill = InfoObject()
infoFill.setProperty("color", "#1f97d9")
select = Selection()
select.select(0, 0, doc.width(), doc.height(), 255)

fill = doc.createFillLayer("Non-Photo Blue", "color", infoFill, select)
root = doc.rootNode()

# is it intentional to place it above the last layer?
root.addChildNode(fill, child[0])

# you already have fill, why do you need to search for it again?
fill.setBlendingMode("screen")


paint = doc.createNode("Ink", "paintlayer")
# again you already have the fill
root.addChildNode(paint, fill)

doc.refreshProjection()
1 Like

i tested this script by copying and pasting into the scripter, one thing i noticed is that the script creates the ink and fill layer under the layer i have selected as current is this the expected behavior? cause to me if the script intends to create a lightbox, the inks layer should be on top right? unless I misunderstood the function of the script.

I adjusted it in a way that creates the layers always on the top of everything

from krita import *
app = Krita.instance()
doc = app.activeDocument()

infoFill = InfoObject()
infoFill.setProperty("color", "#1f97d9")
select = Selection()
select.select(0, 0, doc.width(), doc.height(), 255)

fill = doc.createFillLayer("Non-Photo Blue", "color", infoFill, select)
root = doc.rootNode()
child = root.childNodes()

# grabs the last element on the list, 
# which should be the one on the top of everything
# may return an error if the list is empty 
# (though i think its impossible to happen in this case)
root.addChildNode(fill, child[-1])

# create Fill Layer: Non-Photo Blue
fill.setBlendingMode("screen")

# create Paint Layer: Ink
paint = doc.createNode("Ink", "paintlayer")
root.addChildNode(paint,fill)

doc.refreshProjection()

its also very easy to just create the layers on top of the current node. This allows for the the lightbox to be contained inside a group if the current layer is inside one, by doing:

# grabs the parent node of the current layer 
# to add the layers right above the current one
current = doc.activeNode()
topNode = current.parentNode() 
# in this case topNode replaces root

topNode.addChildNode(fill, current)

if you want to find if the “Non-Photo Blue” exists you can use the nodeByName function and i think its easier to delete and add a new one, your other option is also clone the node, delete and add again (from what i know). if you are thinking of letting the user choose the name you will need to create a qt dialog box with a text input that pops up when you activate the script. at least i think so.

1 Like

I will read it carefully and see what I can learn.

Summary of old ideas

My script is now in two parts (01 creates the layers like here, and 02 sets the “current” drawing layer opacity to 35% and creates a new painting layer). What yours now does is what I wanted in the beginning, but changed my mind: instead of adding a blue layer every time the script runs, my current version now checks if there’s a blue layer and if yes, deletes it before adding another one. So we had a the same idea, tells me that that is probably the way to go.

TL:DR; I need to find the best workflow first. → on it!

Thank you a lot for your time, it really helps me. :blush:

New script is up. Working flawlessly for how I use it.
Yay, feels powerful, thanks again everyone for your help. :smiley:

hey there i tested the new version of your script works really nice.

i just grabbed your previous version and did very minor adjustments, indeed finding if the node exists first and “moving” it is better, but it was already late and i was too tired to code that so i just gave you the steps lol.

now about your new version there are just a minor things i would like to comment:

is this your intention? cause what your code is doing right now is halving the opacity of the current layer. if its 100% it goes to 50% and so on, its not setting it to 35%. for that you can do:

# you already have the current layer in the variable current 
# no need to look for it again in the document
if not current == doc.nodeByName("Background"):
	current.setOpacity(89) #89 is 35% opacity
	current.setBlendingMode("multiply")

if you want to calculate the opacity at runtime its best to use int() to convert the float to int like this, int(current_op*0.5) . As the python interpreter says that implicit conversion from float to int is deprecated and will be removed soon, if krita updates their python version, this change will avoid breaking the code.

you could also instead of comparing the nodes do

if current.name() != "Background":

since you are already looking by the layer name in the document both ways would have the same effect

Also i saw your comment on the script about group layers the user needing to activate pass through themselves. you can check and change that from code so the user doesnt need to do anything.

# Checks if the topNode is a groupLayer and is not the root
# as the root node is a a groupLayer, this check will avoid crashes
# checks if the group layer passthrough is not enabled
# If not, it will set to true
if topNode.type() == "grouplayer" and topNode.name() != "root":
    if not topNode.passThroughMode():
        topNode.setPassThroughMode(True)

i hope these help you.

1 Like

Thank you again!

And sorry, the script is already on a newer version with 50% now, also setting the paint layer to multiply

I’ll keep it updated on Github every time I make adjustments. krita-scripts/Lightbox.py at main · flotasser/krita-scripts · GitHub

These tips are golden, thank you so much for taking the time and energy to help me!
I think I should read more about Python and general scripting mindset again. For today I’m done but tomorrow I’ll have another look at it.

2 Likes