CMYK Color Blending Mode

I have already discussed the ability to load more than one color on the same brush and why the feature would be useful on the following thread (Feature request: to use two or more colors at the same time at the same brush tip - #17 by Rigognos). I also mentioned the usefulness of implementing a mixer palette when developing the previous feature on the same thread.The following feature may be necessary to implement as a blending mode before adding back a color mixing palette and creating a feature enabling loading a brush with more than one color.

Krita already has several blending modes for brushes that can be selected at the top menu bar, but I have yet to find a blending mode that satisfactorily mimics the behavior of pigment on a canvas. Such a blending mode might require more processing power than other blending modes, but it would be desirable when blending colors on the canvas or on a separate scratch pad/ mixer palette.

In order to explain the blending mode I’m requesting, I will try to provide examples from color theory. The following is a YouTube video on color theory demonstrating the behavior of additive color (RGB) versus subtractive color (CMYK)

The primary light colors are red, blue, and green. A computer monitor creates color on its display by varying the brightness of red, blue, and green light in each pixel to create intermediate colors. Pigment colors are mixed diferently for a printer and for artists mixing colors on a palette. The primary pigment colors are cyan, magenta, and yellow. Since pigments absorb light rather than emitting light, they work in the reverse direction of a coputer monitor. When pigments are mixed together they “subtract” from wavelengths of light reflected by the original pigments yielding a new color. I am hoping there is a way to mimic the behavior of subtractive color blending on a computer screen as this would make color blending easier on a computer screen for artists accustomed to using traditional painting media. I’m hoping @RamonM can better demonstrate CMYK color theory with his oil paints.
Here are some images of RGB color theory versus CMYK color theory from the Wikipedia article on color theory:

1 Like

So, this is actually a super-simplified version of what is going on. CMYK does not emulate pigment based mixing, the primary pigments are also not Cyan, Magenta and Yellow, they’re just the most efficient for process colors with out current technologies, this is why you have printers who have 6, or 8 base pigments, or even things like spot colors where the ink is made from other pigments than the ones for cyan, magenta and yellow.

Pigment based mixing emulation up till now has used either spectral based mixing, which uses 36 channels instead of RGB’s 3 channels (meaning your images would be 12 times as big in the ram). This on top of being very very big, spectral colors also have the issue that it is not self-evident to find the spectral coordinates for a given RGB color.

Another one is the Kubelka-Munk reflection model, which works on top of spectral mixing, which would be to actually figure out the blending between different opacities of given pigments. We also had another one, that I lost the name of.

Thing is, it is not very easy to get pigment style mixing going, and I assume the primary reason you want it is because you really want blue plus yellow to be green. MyPaint does have some kind of spectral based model going, but even though it managed to get it faster than any of our experiments, the version that has it is still a lot slower than the previous version. We always keep an eye out for natural media stuff, but in our experience most solutions become really really slow, and we realized long ago that this isn’t what people who paint professionally need from a program. So unless we find a fast enough solution, it is unlikely to get into Krita.

6 Likes

Spectral mixing does not necessarily need 36 channels. MyPaint uses 10 right now, and I’ve had pretty good success with as little as 7 channels. Right now MyPaint is using a temporary buffer to do the spectral mixing, so it doesn’t use any more RAM, but it is WAY slower, as you mention. But really it is surprising that it works at all, since it is upsampling each src and dst pixel to 10 channels before every single OVER operation, and then down-sampling back to RGB. It’s ridiculous.

But there IS another way that I wish I had discovered sooner, which is to expand the model to 10 persistent color channels. MyPaint uses Numpy arrays for everything so this was largely a search and replace operation, but by doing this we definitely DO use a lot more RAM (about 3X more). I’ve been testing this on my personal dumpster-fire fork.

The other thing MyPaint is doing right now (my fault!) that is horrible for performance is the weighted geometric mean, which requires a bazillion power function calls not to mention un-premultiplying. The weighted geometric mean is a pretty decent approximation of how two paints mix, but of course you do needs lots of color channels. But a while ago it dawned on me (OK after reading Wikipedia) that the weighted geometric mean was identical to the weighted arithmetic mean of the logarithms. In other words, if our data was already log encoded from the start, then the standard OVER operation would give us our pigment mixing with barely any overhead beyond the additional channel memory bandwidth.

So essentially, to get a really optimized pigment blend going, you have to start right after you pick a brush color. You take that linear RGB (before you scale it w/ alpha or anything) and you upsample each primary to its spectral representation. R*spectral_red_array, etc. You do have to avoid 0.0 by adding an epsilon, unfortunately. Then you sum those three N-length arrays column-wise to have one spectral distribution. It’s exactly the same concept as summing two RGB colors, but it’s more than 3 channels. Where do you get those spectral primaries that match your colorspace’s primaries, besides yanking sRGB primaries from MyPaint? That’s a bit tricky, but there is a ton of research ongoing (it’s called spectral reflectance recovery). It’s pretty easy for sRGB but gets trickier for larger spaces.

Now that you have an N channel color that represents your RGB, you can log() encode it (any kind of log) and then scale that logged data with your alpha/opacity so it becomes some sort of premultiplied log color data. At this point you can treat it precisely the same way as premultiplied linear RGBA, but only for the OVER operator. There are a couple others that work pretty well but are not identical in the algorithms. For instance a multiply operation would just sum the two log values.

After you are done compositing all your log layers, which is pretty fast since you do not have to flip and flop at any point, assuming you restrict yourself to the compatible blend modes like Normal and Multiply, you have to get back to linear. So you just exp() the final flattened data, and then perform what is essentially an RGB->XYZ transformation, but instead of a 3x3 (RGB) we have N channels, so it is an 3xN matrix. These are the CIE 1931 Color Matching Functions, and it really is an identical concept and procedure to go from Spectral->XYZ as it is from RGB->XYZ. In MyPaint the Color Matching Functions and the XYZ->sRGB matrices have been combined before-hand to save some ops, just as Scott Burns shows (Scott got me started on this journey and has been very helpful!)

So yeah, I have been thinking how on Earth this could be integrated into a pipeline along with everything else in existence that we already have (legacy and linear blend modes, etc), and I don’t think it can be without a LOT of crummy flip flopping. I think a compromise could be to force the “pigment/spectral” layers to be at the root of the compositing stack. In a UI it might just be a layer type that automatically “sinks” to the bottom of the stack below any other non-spectral layer. Or it could be a separate layer stack entirely. And those layers would only have a couple of different blend modes available. Would that be annoying? Sure, but you could just totally ignore it and continue using the existing standard layers and blend modes.

That way, everything else could remain as-is and supported. The stack would just start off as spectral (assuming you have a spectral/pigment layer) and eventually work its way up the stack and become XYZ (or RGB), or whatever the interchange model is for layers (does Krita have per-layer color space support?)

Anyway, I hope this sparks some ideas or maybe I could get some feedback on how to do things better, or someone can tell me what on Earth this Premultiplied Log data really represents. I think Krita could totally do this, store the data in EXR and be ready for the industry when it moves to spectral formats. Or maybe not, I could totally see the toolkit limiting functionality. I think I was lucky that MyPaint used Numpy arrays. If it was using GDKPixBufs things would be a lot different. . .

Cheers!

7 Likes

@Brien_Dieterle, thank you for your first post. Do you have any experience coding? Maybe you can communicate with some of the developers to find out a test version of your proposal would work in Krita. Hopefully this feature can be implemented as just another blending mode without causing compatibility issues. I’m not a programmer so I have no idea how any of this would work.

1 Like

We actually were really into emulating analog media around 2008. We had a special layer that would handle watercolor things following some academic research, but found that drying was a chore and it messed with undo. Emanuele Tamponi had implemented spectral color layers, that is layers, with between 8 and 36 channels, but we had a lot of trouble reconciling that with transparency. We had a color mixer that mixed using the kubelka-munk model, which is basically what you’re asking for…

But all of that was based on Cyrille Berger’s OpenGTL library, a real open source version of https://github.com/ampas/CTL, which has a license that is incompatible with GPL. When Cyrille stopped maintaining OpenGTL, the code started to bitrot, and then we removed the functionality.

During this process we learned that, actually, painting applications are a tool all by themselves. Imitating analog media is all very well, but people don’t want to wait two weeks for their digital oil painting to dry… And as for color mixing; spectral colors are kind of fun, but anyone who thinks it’s based on analog media is deluding themselves. Despite what everyone has learned at school, when working with real paints, you’re not mixing colors, you’re mixing pigments, and the mixing properties of those pigments are as much chemical and physical as optical.

8 Likes

Yes :wink:

Also I believe that we mostly use Vc library for that sort of calculations.

1 Like

I think that is resolved more or less, I don’t have any problems handling alpha as long as it is premultiplied with the logged color data as I described above.

There’s no reason to add a bunch of gimmicks to make painting more difficult. Using spectral actually makes digital painting easier and simply look a lot nicer. One of these looks less broken and was a lot easier to control the blending, can you guess which is nonlinear sRGB/spectral and which is nonlinear sRGB? :slight_smile:


It’s not just about yellow and blue, it’s about the response when blending blue and white, black and white, blue and black, black and yellow-- everything. It’s just much more natural and easier to work with the colors in this encoding. Dark colors are stronger, light colors are weaker, it feels a lot like the way you’d expect paint to feel without simulated headaches from turpentine exposure.

This is totally possible, maybe just try to jam it at the bottom of the pipeline and maybe it won’t break everything else.

5 Likes

Hi, @Brien_Dieterle!

Thank you for interesting details! :slight_smile:

Yes, Krita supports per-layer colorspace, though it is not very visible from the GUI. And, technically, such temporary upsampling can be implemented in the Krita’s color space library quite naturally. It will be slow, but t will fit the design quite well. We have separation of “layer colors pace” and “painting color space”, that is we can easily ask engine to upsample the layer before painting. Right now this mechanism is used for painting on alpha masks.

As far as I remember, Kubelka-Munk theory was not only about “spectral blending”, but also about translucency and reflectancy of pigments. Does your model touches this problem in some way?

Well, speaking truly, I have a feeling that the first brush just has wrong settings. I guess it is possible to get the effect of the second image (except of yellow+blue=green) in Krita just in RGB color space. Just by adjusting Color Rate and Smudge Rate curves.

Of course, the yellow+blue problem is not solvable in RGB, but perhaps there is a cheaper way to get that in digital form?

3 Likes

No problem, I’m glad you find this interesting!

That’s pretty slick. I suppose it does the same thing to the brush, and hopefully the smudge buffer and anything else that might be affecting painting on that layer?

Not at all, it is probably the most basic approximation. I think if you looked into it, it might be a Beer Lambert equation. Basically the attenuation of light as it goes through a substance.

I’m not so sure, I think the response is dependent on the particular colors, too. So if you adjust the curves just right it might feel the same for white+black, but that would mess up the way it feels for other colors. I know if you do linear gradient with this method darker colors will dominate lighter colors across most of the gradient. Since that is how real paint work I think that’s a key component to why it feels more natural and expected. But you may be right, I didn’t try adjusting the curves.

Also, did you notice blue+white skew towards violet w/ sRGB, but skews slightly towards cyan with the spectral? I think most would agree the latter is more pleasing. The interesting thing is that the skew depends on the shape of the spectral curves, which can vary a lot depending on how they were calculated. So, there is opportunity for customizing how your colors behave, if one was so inclined.

I don’t think there is a robust way without having more primaries. I suppose they don’t even need to be on the spectral locus, but no reason not to place them there. I’ve tried HCY/HSV and CMY but they don’t seem natural, or they have weird issues.

3 Likes

Hi, @Brien_Dieterle!

As far as I can tell, the main “decisive factor” of your algorithm is how sRGB color is split into virtual pigments, right? That is, different ways to split it will mean different blending behavior, right?

1 Like

@dkazakov that definitely effects the way colors mix. You can pick a whole variety of different primaries/wavelengths and then use a solver (even in Excel as Scott Burns shows) to produce smooth curves (or not) that are metameric with the particular XYZ that you are targeting (sRGB Red, for instance). Afterward, that new N channel primary is totally interchangeable with the original (they are the same XYZ color, after all); but only in additive linear model. It’s the nonlinear/log or subtractive model where they behave much differently when mixed.

Ideally you’d use more and more wavelengths/channels, 36+, which we will get to eventually as hardware advances.

2 Likes

Over the past week, I have been trying to paint a picture of some wild squash I found a while ago. From my experience so far, a spectral blending mode would make it easier to shade dark areas of a painting with complementary colors. I tend to use colored pencils when I work with physical media, so I’m used to layering complementary colors to shade dark areas.

I’ve been using Artrage for 8 years (and I love it!), but I am quite impressed with many of Krita’s features. And the only thing that prevents me from using Krita for more of my painting workflow is the lack of realistic paint colour mixing. Were Krita capable of even MyPaint-like colour mixing (obviously as an option in Preferences), I could easily see it becoming my painting software of choice.

In my opinion, concerning the specifics of a possible mixing algorithm, quite a bit of realism can and should be sacrificed for performance – but having digital paint mix colours in a way that’s close enough to real paint is essential to making my paintings look organic. From what little testing I’ve done in MyPaint, its paint mixing algorithm results in paintings that look enough like the colours were mixed in pigment; the resulting colours don’t need to be perfect, but at least a loose approximation of pigment-mixed colour is something that many painters such as myself can’t do without.

2 Likes

Hello and welcome,

As said some post above yours, MyPaint has an spectral blending and a merge request to add it to Krita is currently open

When it’s going to be fully implemented I don’t know, but for now it may be a good alternative.

Cheers.

3 Likes