I realized the spectral mixing of mypaint in krita

@rvanwijnen: Thanks for your hint! Now that you mentioned this, I downloaded this image to view it with decent software instead of a browser, and now it is also clear to see!
I must say that I like the lower results better, except for the red to green gradient on the bottom right, there the upper gradient appeals more to me.

The idea from @TheTwo is not the baddest idea, therefor I give → :+1:

Michelist

I disagree, but feel free to implement what feels best.
There’s a lot of artefacting in the image though, it’s hard to see with all the banding going on.

Okay, it’s mainly about the speed issue that Urzeye mentioned earlier. I thought it was caused by its’ 81 channel '. I don’t mind choosing a theoretically better one when the brush painting speed is all normal.

This shows the difference between WGM and KM

4 Likes

Is your source for both versions available somewhere?
I don’t need the whole Krita source but just the mix part for both implementations

I have the feeling the comparison is between spectral 1 and spectral 2 in this image.

In my experience the Weighted Geometric mean is not capable of creating the saturation that is shown here

This is the address of the source code: https://invent.kde.org/graphics/krita/-/merge_requests/1783/diffs
I used his data with your mix, your mix is faster because it doesn’t need to use the pow function, which can seriously affect performance, which is why the mypaint spectrum mix was not merged initially. mypaint also uses a weighted geometric average, which still doesn’t work even though there are only 8 data per channel

They are now about the same speed, with spectral.js being 7 channels of 38 data per channel and three channels of 81 data per channel in the merge request that has been submitted

This is Spectral with the 81 length data from Matt?
This is not using the Weighted Geometric mean but Kubelka-Munk.
This explains the image being very simular.

Do you have an implementation with the 7 channel Spectral version?

You are mixing things which is not recommended.

What does this do?

float spectrumToLuminance(float* spectrum) {
float illum = 0.f;
for (int i = 0; i < (int)81; i++) {
illum += CMF_X[i] * spectrum[i];
}

return illum;

}

Luminance is calculated with the Y.

Sorry for the late reply, there is no KM implementation for mypaint, as the mypaint calculation pre-calculates the required matrix between the spectral to RGB conversions, there is no XYZ conversion, so KM cannot be used

Yes, it should use Y instead of Z, because previously spectral.js used the X conversion from RGB to XYZ matrix for the calculation, so it’s used here too, I haven’t modified it yet, depending on whether people prefer the effect of spectral.js

I’ve reached the post limit so I had to make a new account.

No it didn’t.

Spectral first used the perceptual luminance (0.2126R + 0.7152G + 0.0722*B) but I changed this to use the Y value in the XYZ.
You cannot use X or Z for the luminance, they have nothing to do with the brightness.

Don’t take this the wrong way but your implementation is wrong.

What has mypaint to do with this? This is about implementating it in Krita right?
You implemented Kubelka-Munk in Krita with the dataset from Matt and this wrong.
You can use Spectral 1 which has 3(x37) channels or you can implement Spectral 2 which has 7(x38) channels.

I’m happy to help but I have no experience with Krita and I will have to find the time to look at this.

Is there a way I can download the full source?

3 Likes

Mypaint was probably the first software to implement spectral blending, way before even Mixbox, this thread was originally opened to implement the same functionality in krita, thank you very much indeed, without your spectral.js it would probably have taken a long time to implement spectral blending in krita, yes, you can download the full source code using this link: https://invent.kde.org/killy/krita/-/archive/spectral_blending_mod/krita-spectral_blending_mod.zip

Hello everyone, this is the python code, which is available in krita’s Tools->Scripts->Script Debugging Tools

from math import sqrt

R = [0.327457414, 0.323750578, 0.313439461, 0.288879383, 0.239205681, 0.189702037, 0.121746068, 0.074578271, 0.044433159, 0.028928632, 0.022316653, 0.016911307, 0.014181107, 0.013053143, 0.011986164, 0.011288715, 0.010906066, 0.010400713, 0.010637360, 0.010907663, 0.011032712, 0.011310657, 0.011154642, 0.010148770, 0.008918582, 0.007685576, 0.006705708, 0.005995806, 0.005537257, 0.005193784, 0.005025362, 0.005136363, 0.005433200, 0.005819986, 0.006400573, 0.007449529, 0.008583636, 0.010395762, 0.013565434, 0.019384516, 0.032084071, 0.074356038, 0.624393724, 0.918310033, 0.949253030, 0.958187833, 0.958187751, 0.958187625, 0.955679061, 0.958006155, 0.954101573, 0.947607606, 0.938681328, 0.924466683, 0.904606025, 0.880412199, 0.847787873, 0.805779127, 0.752531854, 0.686439397, 0.618694571, 0.540264444, 0.472964416, 0.432701597, 0.405358046, 0.385491835, 0.370983585, 0.357608702, 0.348712800, 0.344880119, 0.341917877, 0.339531093, 0.337169504, 0.336172019, 0.335167443, 0.334421625, 0.334008760, 0.333915793, 0.333818455, 0.333672775, 0.333569513]

G = [0.331861713, 0.329688188, 0.327860022, 0.319173580, 0.294322584, 0.258697065, 0.188894319, 0.125388382, 0.078687060, 0.053143271, 0.042288146, 0.033318346, 0.029755948, 0.030331251, 0.030988572, 0.031686355, 0.034669962, 0.034551957, 0.040684806, 0.054460037, 0.080905287, 0.146348303, 0.379679643, 0.766744269, 0.876214748, 0.918491656, 0.940655563, 0.953731885, 0.961643280, 0.967200020, 0.970989746, 0.972852304, 0.973116594, 0.973351069, 0.973351116, 0.972261080, 0.973351022, 0.973148495, 0.971061306, 0.966371306, 0.954941968, 0.913578990, 0.364348804, 0.071507243, 0.041230434, 0.032423874, 0.031924630, 0.031276033, 0.032630370, 0.029530872, 0.031561761, 0.035674218, 0.041403005, 0.050604260, 0.063434300, 0.078918245, 0.099542743, 0.125595760, 0.157590910, 0.195398239, 0.231474475, 0.268852136, 0.296029164, 0.309754994, 0.317815883, 0.322990347, 0.326353848, 0.329143902, 0.330808727, 0.331482690, 0.331984550, 0.332341173, 0.332912009, 0.332919280, 0.333027673, 0.333179705, 0.333247031, 0.333259349, 0.333275050, 0.333294328, 0.333309425]

B = [0.340680792, 0.346561187, 0.358700493, 0.391947027, 0.466471731, 0.551600896, 0.689359611, 0.800033347, 0.876879781, 0.917928097, 0.935395201, 0.949770347, 0.956062945, 0.956615607, 0.957025265, 0.957024931, 0.954423973, 0.955047329, 0.948677833, 0.934632300, 0.908062000, 0.842341039, 0.609165715, 0.223106961, 0.114866670, 0.073822768, 0.052638729, 0.040272309, 0.032819463, 0.027606196, 0.023984891, 0.022011333, 0.021450205, 0.020828945, 0.020248311, 0.020289391, 0.018065342, 0.016455742, 0.015373260, 0.014244178, 0.012973962, 0.012064974, 0.011257478, 0.010182725, 0.009516535, 0.009388293, 0.009887619, 0.010536342, 0.011690569, 0.012462973, 0.014336665, 0.016718175, 0.019915666, 0.024929056, 0.031959674, 0.040669554, 0.052669382, 0.068625111, 0.089877232, 0.118162359, 0.149830947, 0.190883409, 0.231006403, 0.257543385, 0.276826039, 0.291517773, 0.302662506, 0.313247301, 0.320478325, 0.323636995, 0.326097309, 0.328127369, 0.329917976, 0.330907901, 0.331803633, 0.332396627, 0.332740781, 0.332820857, 0.332901731, 0.333025967, 0.333111083]

X = [0.000032348, 0.000055345, 0.000109712, 0.000248676, 0.000560325, 0.000955933, 0.001883435, 0.003396138, 0.005940683, 0.009151623, 0.011644017, 0.014886349, 0.017280890, 0.018269793, 0.018613167, 0.017704928, 0.016210296, 0.013821903, 0.010617328, 0.007758482, 0.005245854, 0.003081053, 0.001648031, 0.000758713, 0.000253535, 0.000123299, 0.000474370, 0.001463582, 0.003137073, 0.005509365, 0.008432888, 0.011327476, 0.014345805, 0.017738600, 0.021338869, 0.024718236, 0.028129296, 0.031510755, 0.034737572, 0.038293448, 0.041529413, 0.042708709, 0.043065991, 0.044671789, 0.045236160, 0.044428484, 0.042504837, 0.039361202, 0.035455761, 0.030395676, 0.025316176, 0.021408237, 0.017738193, 0.013975302, 0.010734839, 0.008290878, 0.006258656, 0.004659214, 0.003402523, 0.002415889, 0.001732401, 0.001151995, 0.000748856, 0.000529624, 0.000384871, 0.000280078, 0.000203686, 0.000132161, 0.000084501, 0.000063740, 0.000047616, 0.000034298, 0.000024514, 0.000015617, 0.000009990, 0.000006116, 0.000003646, 0.000003134, 0.000002624, 0.000001817, 0.000001260]

Y = [0.000000922, 0.000001584, 0.000003103, 0.000007054, 0.000015506, 0.000026382, 0.000052378, 0.000095370, 0.000176832, 0.000311062, 0.000475768, 0.000763124, 0.001141210, 0.001564213, 0.002103808, 0.002666572, 0.003344628, 0.004067856, 0.004944536, 0.006147819, 0.007625247, 0.009001248, 0.010709887, 0.013347152, 0.016712607, 0.020924893, 0.025656760, 0.030589357, 0.035203447, 0.039872520, 0.043922355, 0.045904502, 0.047127747, 0.048343480, 0.048981677, 0.048273090, 0.047079310, 0.045454635, 0.043393477, 0.041606911, 0.039430961, 0.035625505, 0.031765522, 0.029376763, 0.026872545, 0.024083841, 0.021324489, 0.018506141, 0.015809755, 0.012985110, 0.010443317, 0.008572776, 0.006930529, 0.005353067, 0.004051597, 0.003093441, 0.002315209, 0.001713760, 0.001245775, 0.000881268, 0.000629695, 0.000417379, 0.000270842, 0.000191353, 0.000138986, 0.000101140, 0.000073559, 0.000047731, 0.000030518, 0.000023020, 0.000017195, 0.000012381, 0.000008846, 0.000005643, 0.000003611, 0.000002212, 0.000001318, 0.000001125, 0.000000948, 0.000000647, 0.000000450]

Z = [0.000152519, 0.000261131, 0.000518438, 0.001177069, 0.002656749, 0.004542641, 0.008977810, 0.016243540, 0.028540741, 0.044275240, 0.056829693, 0.073546267, 0.086685286, 0.093569306, 0.098109991, 0.096891008, 0.093047543, 0.084114895, 0.069980019, 0.056886438, 0.044590306, 0.032761780, 0.023949742, 0.018234915, 0.014073774, 0.010906837, 0.008069383, 0.005617940, 0.003879817, 0.002877839, 0.002148221, 0.001497284, 0.001002823, 0.000660821, 0.000430765, 0.000277570, 0.000184532, 0.000127734, 0.000095721, 0.000081814, 0.000074783, 0.000061100, 0.000046159, 0.000042275, 0.000034070, 0.000025495, 0.000014414, 0.000010067, 0.000007884, 0.000004045, 0.000001970, 0.000001185, 0.000000792, 0.000000387, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000]

def concentration(sr, sg, sb, sw, dr, dg, db):
    sl = 0.212648173 * sr + 0.715179242 * sg + 0.072172585 * sb
    dl = 0.212648173 * sr + 0.715179242 * sg + 0.072172585 * sb

    dw = 1.0 - sw

    sw = sl * sw * sw
    dw = dl * dw * dw

    return sw / (sw + dw)

def rgb_spectral(r, g, b):
    spectral = [0.0 for x in range(0, 81)]
    for i in range(81):
        spectral[i] = R[i] * r + G[i] * g + B[i] * b
    return spectral

def mixSpectral(spectral_s, spectral_d, sw):
    for i in range(81):
        ks = inv = 0.0

        inv = (1.0 - spectral_s[i])
        ks += sw * (inv * inv / (2.0 * spectral_s[i]))

        inv = (1.0 - spectral_d[i])
        ks += (1.0 - sw) * (inv * inv / (2.0 * spectral_d[i]))

        spectral_d[i] = 1.0 + ks - sqrt(ks * ks + 2.0 * ks)
    return spectral_d

def spectral_rgb(spectral):
    x = y = z = 0.0
    for i in range(81):
        x += X[i] * spectral[i]
        y += Y[i] * spectral[i]
        z += Z[i] * spectral[i]

    r =  3.240830229 * x - 1.537316904 * y - 0.498589266 * z
    g = -0.969229321 * x + 1.875939794 * y + 0.041554444 * z
    b =  0.055645287 * x - 0.204032720 * y + 1.057260459 * z

    return [r, g, b]

def mixRGB(sr, sg, sb, t, dr, dg, db):
    sr = max(sr, 0.0001)
    sg = max(sg, 0.0001)
    sb = max(sb, 0.0001)
    dr = max(dr, 0.0001)
    dg = max(dg, 0.0001)
    db = max(db, 0.0001)

    spectral_s = rgb_spectral(sr, sg, sb)
    spectral_d = rgb_spectral(dr, dg, db)
    t = concentration(sr, sg, sb, t, dr, dg, db)
    spectral = mixSpectral(spectral_s, spectral_d, t)
    return spectral_rgb(spectral)

print(mixRGB(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0))

1 Like

Thank you for your help in this thread and also thank you very much for the sprectral.js

Having two duplicate account is against the terms of service, so I am merging your new account with the old one. You will eventually in short time be able to post any number of post, until then I urge you to be patient and have only one account. If you need any help in the forum please ping me I will help you.

Hope you understand. Thank you

1 Like

This is amazing!! :sob:
I’ve updated my python plugin to use the spectral mixing logic:

video here: https://www.youtube.com/watch?v=WPAEikoMY4w

2 Likes

WOW, this is soo exciting to see this thread!
I’d say that the top ones look the best for their saturation, except the magenta to yellow which seems to loose some saturation in the process :smiley:

Can’t wait to see this fully implemented :D!
Thanks a lot for your hard work!

Guys, this implementation is wrong. Please wait till I find the time to make a right implementation.

@urzeye Please stop using this, I will help you implement the right code.

5 Likes

The top and bottom are basicly the same, it’s the difference between the 3 channel and 7 channel Spectral.js.
It uses a different data set which results in slight variance.

1 Like