Fill layers appear poorly optimized

In ClipStudio and Firealpaca/Medibang, it’s possible to create layers that have only one single color. These work similar to Krita’s fill layers, except they start blank like a normal paint layer. Their main purpose is to be used with artists’ workflows that make use of dozens of flat-color or single-color layers, as opposed to those that prefer to paint everything with fewer layers.

ClipStudio also has a separate fill layer like Krita’s. Unlike Krita’s, ClipStudio’s fill layer comes with a mask that you can delete completely.

After trying to use fill layers in Krita, I’ve come to the conclusion that they’re really poorly optimized compared to normal paint layers, which is ironic considering that using a 32bit layer to draw in a single color is what should be the poorly optimized choice here.

The main problem is that fill layers have a built-in 8 bit mask that begins opaque and fills the entire layer. It’s not possible to delete the mask, which means Krita ALWAYS uses the mask when filling, always stores it in memory, and always saves it to disk. If you could delete the mask to mean “fill EVERYTHING with a color, unmaskedly,” performance could be improved when a fill layer is placed on top of a drawing with “inherit alpha” enabled. You wouldn’t need a mask in this case because it’s supposed to fill over the alpha you already drew, so Krita wouldn’t need to create a temporary image the size of the entire canvas with the fill applied before blending it on the layer stack.

With normal paint layers, Krita seems to be optimized to “clip” the layer to non-transparent pixels. If you drew one tiny red circle in a huge canvas, most of the layer is transparent, so Krita doesn’t store, save, or tries to render the transparent pixels. It treats the layer as a small image placed at a position on a huge canvas rather than a huge image that’s mostly transparent but covers the entire canvas. This doesn’t seem to be the case with fill layers for some reason. I assume it’s because the mask is internally grayscale, and black isn’t clipped like alpha is. I drew a tiny dot on a 1000x1000px layer on a normal layer. The memory usage meter says 128KiB. I click select → select from opaque → replace, create a fill layer, same color, it looks literally the same thing, and I delete the normal layer. The memory usage meter says 4.2MiB now. It doesn’t make sense for something 1/4th of the color channels take 40 times the memory. In fact, 4MiB means 1 megapixel (1000x1000) times 4 color channels, which means Krita just created a canvas-sized full-color buffer from the tiny dot to render it. There’s nothing optimized here at all.

I don’t know if Krita compresses a normal paint layer that happens to be single-color paint for storage. It doesn’t matter much, though, because the main reason artists use such 8bit paint layers is because you don’t have to remember what color the layer you’re painting on is supposed to be. If you draw black on a skin layer, it draws the skin color, on the hair layer, it draws the hair color, and so on, and you can easily change the colors without having to lock alpha, and it avoid situations such as having the skin color selected and accidentally drawing with that color in the hair layer, because with 8 bit it would draw the hair color and you’d realized you made a mistake right away.

For reference, this sort of workflow ends up looking like this (in Japanese: one layer for skin, one for skin shadow, etc., all set to 1bit depth and colorized with a separate setting. The black spots over the drawing is saying you just draw with black and it changes color automatically.): https://pbs.twimg.com/media/D0JagqAVAAA2zKt.jpg

The fact Krita lacks support for such workflow is unfortunate, but more unfortunate is the fact that fill layers can’t seem to even fill an area with a single color better than a normal paint layer. And before 5.0 they were a hassle to just set a color for. They probably only make sense with patterns or something more complex at this point in time.

3 Likes

It’s interesting what you said about workflow. I also hope to see it in krita one day in the future. That might be a new layer type though.

1 Like

It is an interesting workflow and problem to solve. I tried to find workaround but may be these will be not satisfactory for this.

I add a fill layer with chosen colour. A fill layer is just a mask like all other filter layers or masks. So I invert this layer with CTRL + I (it basically fills the mask with black) now there is no colour on canvas. I add several coloured fill layers like this. I then paint on each of them with pure white. If I want slight variation I increase or decrease the value by L and K key. I can change the colour easily with I just change the colour of the fill layer in properties

I just select the appropriate layer and paint with existing colour, no need to change the colour. Since the layer is a type of mask, I can also paint with gray to get opacity etc.

I can also add another extra transparency mask to remove things completely. And there is also another way which is a bit lengthy, you add fill layer and clip it to another layer where you paint with any colour.

I know this is not exactly the same but I sharing in hope that it helps.

Also Sidenote: May I request you to translate what is written in the image you linked.

4 Likes

After some experiments, I found that it is not 1-channel, but 4+1-channel
1.
KR63){D)8W}S29WR8)`D5Y
1~KO(D3_PK$4$KG)OBFMEB
2.
image
image
3.
image
image
4.
image
image

This is a bit weird. The built-in transparency mask not only increases the consumption, but also makes things like filters completely ineffective.

The translation is as follows (my notes in parentheses):

“For your favorite characters that you draw often” a quick way to color using Layer Color (a layer property in CSP feature in which you choose a color and the layer/layer group becomes that color as if it were converted to grayscale then the black was changed to the color of your choice)

The layer color function is often used to change the color of a sketch (typically to cyan), but it can also be used when coloring. The advantage of this way to color is that “when coloring it isn’t necessary to charge (the brush) color (you can color everything with black (0, 0, 0)).” Cumbersome work involving color such as switching the color palette and then “ah, I forgot to color that spot, I need to sample the color from over there…” becomes unnecessary. You just need to paint everything with black, so it’s easy and saves time.

The names of the layers are as follows: yellow shadow, yellow, white shadow, white, red shadow, red, skin shadow, skin. The red line on the left side means clip to layer below.

Right of layers: set the color of each layer with Layer Color.

Effect (the rightmost button is the layer color). Layer color (white-ish skin color). Color expression: monochrome (1bit layer, color or transparent, although CSP also has 2bit layers that are black, white or transparent, 8 bit that are either fully black or white plus 8bit alpha, and 16bit that are black to white, plus 8bit alpha.)

Blue box: if you set the color expression to “monochrome,” it looks like “anime style” (Japanese term for flat shading). If you set to “grayscale” you can change the brightness of the black, so soft shading is also possible.

Green ball: shadow layer’s color expression: “monochrome.”
Skin ball: shadow layer’s color expression: “gratscale.”
Black ball: the actual black color on the layer (drawn with airbrush).

Below layers: after you set the color of the shadow and clip it, you color the shadow with just black, too.

Bottom-left: if you save the layer set as a Material (CSP feature), when you need to color you won’t need to set each the color of the layers (CSP lets you drag-drop materials like copy-pasting them off the document to an application storage.) It’s convenient because you can create a layer set for a character you like that you draw often.
(on the other hand if you don’t draw a character often, creating a layer set sounds inconvenient so I don’t recommend it.)

*How to make it into a Material
1: put all color layers of the layer set into one folder.
2: drag and drop the folder into the Material docker.
That’s all.
When using, drag and drop from the Material docker to the layer stack.

Right side: just paint it black. East-peasy!

1 Like

Thank you so much for the translation work.

So in CSP you paint in black, here in my workaround I inverted and painted in black on the fill layer.

Yes, it’s so bad that even if you have a fill layer that fills the entire canvas, converting it to a paint layer saves memory. I don’t think there is a single case where a fill layer outperforms a paint layer

No. The poster used black, but in CSP any color, including pure white, would work.

Basically the layer isn’t a grayscale mask. It behaves a a paint layer, and what makes it opaque are opaque brush strokes. You can erase with the eraser. It just happens that Grayscale layers don’t have ANY RGB color. They only have alpha. Whatever is painted becomes black. Then with the “Layer Color” effect, you can change this black to another color. It’s a bit more complicated than that because the “Grayscale” layer can also have a white color, which you can colorize separately. Basically it becomes an actual grayscale layer with an alpha channel, and if you set what color is black and what color is white, you have the same result as you’d get in Krita by using a filter mask with the Gradient filter on a grayscale layer.

I also want to note that in firealpaca/medibang, painting with white erases an 8bit/1bit layer, while painting with black makes it opaque instead.

It just doesn’t make much sense, intuitively, for white to make things opaque because the canvas is white by default

I don’t think any workaround will really work. The way people do it in SAI for example, that lacks such features, is to simply lock the layer’s alpha and use CTRL+F or another keyboard shortcut to fill everything with a color. This is error prone, but the errors are nothing more than an annoyance you can CTRL+Z. The only way to improve this workflow is a layer designed to be single-color, and in which drawing ANY color results in the SAME color, so you don’t have to worry about what color you’re drawing.

The main issue of this thread isn’t that Krita doesn’t support this workflow, but that Fill layers appear to perform way worse than anything you’d expect.

I mean, if you use a handful of fill layers in a project, regardless of what you’re doing to them, regardless of your use case, they’re going to eat all your memory.

Okay I understand.

As shown by @TheTwo the fill layers take up the memory required by a paint layer + a transparency mask. Filters masks are also a kind of masks so this sound logical to me.

Uh… wait, does this mean that every single mask in Krita uses as much memory as the entire canvas size in 8 bit?

This sounds kind of wasteful if your masks are really small compared to the entire canvas… like it shouldn’t be the case that masks perform worse than creating a paint layer under whatever you’re trying to mask, painting the mask there, and then setting what you’re trying to mask to inherit alpha.

I don’t know exactly how it is coded and works internally. But from my observation and working experience each filter mask or filter layer or fill layer is a mask, you can paint on it with grayscale to hide unhide the effect of filter or fill. So a fill layer is basically a colour fill + a mask.

From what I understood (but I’m not sure), data are stored in 64x64 pixels size tiles.

If you have a layer 10000x10000pixels and only have a line drawn from 10,10 to 20,20, then only one tile (4096 pixels) is used in memory to keep pixels information.

But after, you need projections to render canvas.
I don’t know if all projections use a full canvas size or data bounds size or only used tiles, but projection use more memory (a group layer for example have a projection, even if there’s no “real” pixels data)

But for a fill layer, I suppose that as the layer fills everything, at least the projection use all canvas size and then more memory… :thinking:

Grum999

data are stored in 64x64 pixels size tiles

That doesn’t seem to be the case with fill layers.

I suppose that as the layer fills everything

As I mentioned above, if you have a fill layer that only fills a tiny dot, it still uses as much memory as a fill layer that fills the entire canvas. In BOTH cases, converting the fill layer to a paint layer saves memory, so the fill layer doesn’t have the same optimizations a paint layer has, and doesn’t have any optimization a paint layer doesn’t have, either. It just doesn’t have any optimizations at all in this regard.

Fill layer always fills the entire canvas. As I said earlier fill layer is a paint layer (filled with a colour to entire canvas) + a transparency mask. When you convert it to normal paint layer it loses the transparency mask.

Fill layer always fills the entire canvas.

It looks like that’s literally what is happening. The fill layer creates a paint layer that’s filled with a single color before applying the transparency mask.

The issue is that this is horribly inefficient.

Krita already knows that:

  1. it’s all going to be the same color.
  2. it’s not actually going to fill the entire canvas, only the masked area.

And yet it creates a layer internally the size of the entire canvas, using 8bit RGBA as if any pixel could be of any random color.

I already said this multiple times before, but this is so horribly inefficient that converting that fill layer to a paint layer ALWAYS saves memory. Fill layer = 8bit mask + 32bit rgba ALWAYS the size of the entire canvas. Paint layer = 32bit rgba UP TO the size of the entire canvas.

1 Like

Fill layers and other mask type layers are designed to fill all the canvas. The workflow you described needs a different type of layer I think.

Your work-flow needs a layer which is somewhat like the colour overlay layer style. No matter what you paint on a layer with this layer style enabled it will fill the pixels with single colour. But layer style have performance issues.

https://imgur.com/pK6jBXr

This doesn’t have the overhead as the fill layer.

If you want a parallel to fill layer check out photoshops solid color adjustment layer. That too like Krita is a layer filled on entire canvas with colour + plus a mask. This type of layers fill the canvas automatically when resized etc. So they are not designed for your intended workflow, but can be used which leads to inefficiency in memory footprint as you note.

P.S. sorry for so many edits

What I mean is that it can only lead to this sort of inefficiency if it’s not optimized.

I don’t know how the layer stack was programmed, but one could, for example in OpenGL, store the mask as a GL_RED format texture and switch to a fragment shader program that uses the fill color as a vec4 uniform when rendering. The only reason for it use 5 channels worth of memory is that Krita doesn’t switch shaders, using the same fragment shader for everything so you need to convert the fill layer to a paint layer internally and keep both in memory for it to work.

Again, even disregarding that, it doesn’t explain why mask-type layers don’t make use of the tile optimization that RGBA layers have. Why RGBA layers are optimzied to not store transparent pixels that cover the entire canvas but mask layers lack this optimization?

I understand that you’re well-meaning in your responses, but this is a technical issue regarding how data is store and used. Just because “it fills the whole canvas” in practice (and it really doesn’t as you could make a tiny mask that only colors a small circle in the canvas) that doesn’t mean it needs to be implemented in such way that it always uses as much memory as it can, always performing as badly as the worst case scenario in which it does indeed fill the entire canvas with a multi-colored pattern, and never optimizing for a much simpler scenario in which it fills a small part of the canvas with a single flat color.

However, unlike with Clip Studio, you could learn how the layer stack works in Krita. Please go to Graphics / Krita · GitLab.

Anyway, all pixel data is stored in a paint device. This is the same for layers and for masks, the only difference is the colorspace for masks is fixed to 8 bits/single channel. The pixel data is in CPU memory, not GPU memory, so OpenGL is irrelevant.

In

There is

It seems that FillLayer of a solid color and a pattern is definitely the same internally, if storing pattern in the fillLayer uses 4 channels RGBA, storing a single color will also uses 4 channels RGBA (plus a mask channel, so total 5 channels)

in PS there are different fillLayers:

I love using mask coloring workflow, so I can observe that when I draw on Krita of 4K size, it eats up the same memory when I draw on PS of 8K size

2 Likes

That’s not how it works… You cannot reliably infer anything from Krita’s scripting API, you would have to really dig into the actual code. Nor can we conclude anything about Photoshop’s implementation of fill layers by its menu.

In Krita a KisGeneratorLayer uses one of several generator (check out the type hierarchy for KisGenerator to generate a projection that is composited in the image stack. Every layer has a projection (though for paint layers without masks the projection might be the same objects as the paint device).

Actually generating the projection on the fly would be really expensive, since it would mean an extra, possibly complex, function call for every pixel. It’s what we tried to begin with, and it didn’t work out.

As always in computing, you need to trade between performance and memory usage, and here memory usage is worth it.

3 Likes

There is a memory use advantage when using Background layers:
(The Layers Docker doesn’t show that the Fill layers are painted the same as the masks.)

Changing the colour of a Background Layer is not difficult but is not as convenient as changing the colour of a Paint Layer or a Fill Layer.

2 Likes