I realized the spectral mixing of mypaint in krita

There are also other findings that the rotation option in the smudge brush engine can greatly affect brush performance

1 Like

Ha, ok. please accept my apologies then for my sarcastic tone in my last comment. I had no ideas some meaning/sens of your original sentence were lost in translation.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.

This is a very immature version, so it is not recommendedThis is a very immature version, so it is not recommended

1 Like

If you want to do this, you cannot assume an sRGB colour space, certainly not a linear one. Therefore, you can’t use hardcoded tables and matrices; you need to compute them using the primaries and whitepoint from the colour profile associated with the image, and take into account the gamma / EOTF in the to- and from- spectral functions.

As Brien_Dieterle mentioned earlier in this thread, he has a python implementation. However I don’t think it will be very useful to you as an example for writing the required code in c++, but it may be useful to compare your results.

Instead, look at Scott Burns’ site. He has a very nice explanation as well as sample code that will be easier to convert to c++. The calculations are described here: http://scottburns.us/subtractive-color-mixture-5/ (note the updates at the end of the page) and here: Fast RGB to Spectrum Conversion for Reflectances – Project Docs

3 Likes

Thank you for sharing. In fact, I’m not a programmer, so I don’t know how to deal with many things. I submitted this request before to thank a friend who shared oklab color space for me. Now I want to spend more time on painting practice. If I have free time, I will try to understand them. Thank you again for sharing!

1 Like

I hope I didn’t scare you, that wasn’t my intention. You do need to understand the basic ideas of the colourscience behind the pigment mixing, but it’s not that complicated. (Some of the math behind creating the lookup tables is, but you could ask for help with that part if you can’t work out how to do it in c++.)

1 Like

After some reading and some coding, I can run Brien Dieterle’s spectrum generator and other spectrum-related bits using C++ and compatibly licensed libraries. Unfortunately, it’s far too slow to generate the spectra inside of Krita as needed (e.g. when opening an image).

So, inclusion of the spectral blending in Krita could work like this:

  • Include a set of spectra for built-in and standardised RGB colour spaces.
  • Include a separate tool to generate spectra for any other RGB colour space, e.g. using ICC profiles as input.
  • spectral blending modes are disabled unless suitable spectra are present.

Would this general approach be acceptable? If it is, then consider this an initial proposal:

Spectral blending in Krita

As the name implies, the spectral blending works by constructing a spectrum for each of the colours to be blended, blends these spectra, and converts the resulting spectrum back to a colour value.

UI / UX:

Other applications providing similar blending, e.g. Mypaint, Rebelle use a special layer mode to enforce spectral / pigment blending when using a brush on a layer with it enabled. That behaviour doesn’t seem a good fit for Krita.

Instead, a reasonable initial implementation may be this:

  • spectral blending modes are made available for both brushes and layers. COMPOSITE_SPECTRAL_OVER is simply named “Spectral” and is listed as part of the favourites by default to make it easily accessible and discoverable.
  • the smudge engine switches to using the spectral modes internally when the brush blending mode is set to Spectral.

This makes using the spectral blending straightforward, but does require the user to explicitly switch the blending mode manually, separately for layer and brush blending. A special default setting for the brush blending that switches between normal and spectral blending modes depending on the blending mode of the active layer may be considered to create a user experience closer to that offered by Mypaint or Rebelle without complicating or infringing on Krita’s established workflow.

Technical

  • Implements various calculations to allow conversion of colour values to and from their spectral components, specifically including an RGB to spectral components conversion using pre-calculated spectra for the RGB primaries to perform blending, as described by Scott Burns and Brien Dieterle [*1].

  • No additional libraries are required for Krita itself to link against for implementing this proposal. Eigen [*2] (already used by Krita) is used for all vector, matrix, and linear algebra. Colorimetric data from CIE is included in the source files [*3]. Generation of the spectral profiles as described in [*1] does require an additional library for non-linear optimisation, but is separate from Krita’s main executable.

  • Limited to RGB modes. In principle this process is not limited to RGB, but the computational burden is such that a shortcut is used to get acceptable performance.
    The shortcut is what limits this proposal to RGB, although the spectral calculations may be used in other contexts where performance requirements are not as strict.

  • Implements spectral blending in at least two blending modes;
    COMPOSITE_SPECTRAL_OVER, the spectral version of the normal blending mode, which can be used to simulate glazing when painting with any brush or blending layers and is required by the smudge engine.
    COMPOSITE_SPECTRAL_COPY, which is required by the smudge engine.
    A small number of other spectral blending modes may be considered.

[1]: http://scottburns.us/color-science-projects/ and http://github.com/FocalPaint/spectralSolver
[2]: https://eigen.tuxfamily.org/index.php?title=Main_Page
[3]: Technical Resources | CIE

3 Likes

Yes, I think the biggest problem is the speed of spectral calculation, but I haven’t tried the spectral solution of FocalPaint; In fact, if we only use spectral blending in linear rgb space, we can achieve acceptable performance by modifying the spacing of the smudging brushes, but it will be very slow if we use it in 8-bit

1 Like

If you have time, maybe you can try this. Using lut conversion should solve the performance problem. For me, these codes are too difficult
https://github.com/joeedh/pigment-painter

1 Like

Mixbox generates very large lookup-tables derived from mixing real-world pigments (Kubelka-Munk with measured spectra of 4 pigments that together can mix any basically any colour). That approach has its own set of problems when dealing with different colour-spaces and colour-depths.

1 Like

Eigen can use various SIMD instructions to speed up at least part of the calculations, that will help.

1 Like

I don’t think we should ignore it if we can. Many blending modes/filters only work in 8bit space. For most people’s choice, if there is a faster way, it should be adopted. And it can prepare for the linear space in the future. Just like a few days ago, when someone was doing blending mode, he made the version of linear space and gamma 2.2 at the same time.

1 Like

I was talking about mixbox, not 8-bit colour-depth. Besides, linearisation is a separate issue and not even just related to gamma – neither sRGB, rec.709 HDTV or HDR use gamma, and other standards use various gamma values, e.g. P3 uses 2.6. It is also very unlikely to be the performance bottleneck for spectral blending.

1 Like

My description is not quite accurate. My main idea is that if the speed and effect of the mixbox are significantly stronger, it may be appropriate to implement it for the commonly used environment (sRGB elle-V2-srgbtrc. icc). Hereafter, consider “different colour spaces and colour depths”.

(ps.This is just my personal idea. I don’t know the technical details very well. The UI/UX design is great,hope you can realize it!)

1 Like

It’s true that mixbox is going to faster, but as I said, it has its own set of issues. Also there is no open implementation, at least not a complete one. Finally, for the spectral blending proposed here, using AVX for vector and matrix math really can make a big difference. Even if using Eigen’s optimisations don’t kick in, we have modern C++ now, so there’s this: https://en.cppreference.com/w/cpp/experimental/simd.

3 Likes

I tried to use the spectral solution of focal paint to calculate the srgb conversion matrix, and made a comparison with the mixbox pigment:


Spectrum above, pigment below

This is one of the pictures generated by the calculation:

The maximum number of iterations I set is 1500. I don’t know whether the result is correct. Here are some codes:

#include <math.h>

static const double T_MATRIX[3][12] = {
{0.026492621896506, 0.022003993049561, 0.001603522499762,
  -0.083407186626009, -0.160485586315245, -0.139888278077828,
  -0.061658165014736, 0.261461390320221, 0.532217327201109,
  0.452813201663640, 0.070406380500551, 0.052323463551253},
{-0.033321255696260, -0.032826404335223, -0.017465130548145,
  0.071094032921934, 0.259995010318080, 0.287301254816468,
  0.308414876604338, 0.197107281842086, -0.004195792066587,
  -0.018517602766212, -0.007209794232495, -0.005435519895686},
{0.354397952108205, 0.393679890355713, 0.327669800431984,
  0.080366645719529, -0.019256614110030, -0.026504732689542,
  -0.034782337160045, -0.029624843890751, -0.010214895818947,
  -0.006840606003622, -0.000527749788140, -0.000382490787487}
};

static const double spectral_r[12] = {0.012397579245876, 0.009041991274249, 0.037031841770377,
 0.000559619165367, 0.001403879218334, 0.003413600200518,
 0.003662715513914, 0.121342497297871, 0.978985576309555,
 0.901725942232003, 0.171410925030869, 0.497722094238182};

static const double spectral_g[12] = {0.027462483418840, 0.000276659636407, 0.148892151117023,
 0.554378607401809, 0.912576962037066, 0.955492921801916,
 0.937015702733367, 0.881222180612152, 0.004672232896176,
 0.163813493886854, 0.751096674359955, 0.466680834646172};

static const double spectral_b[12] = {0.935872968136978, 0.957637007887858, 0.784603747897508,
 0.477070110292183, 0.079365294180901, 0.039745059644301,
 0.031868308776113, 0.005813536688569, 0.007455462540296,
 0.007257305436748, 0.045492370159752, 0.024869810893464};

static double clamp(double x, double min, double max)
{
    return x < min ? min : (max < x ? max : x);
}

static void rgb_to_spectral(double r, double g, double b, double *spectral)
{
    double offset = 1.0 - 0.0001;
    r = r * offset + 0.0001;
    g = g * offset + 0.0001;
    b = b * offset + 0.0001;

    double spec_r[12] = {0};
    for (int i = 0; i < 12; i++) {
        spec_r[i] = spectral_r[i] * r;
    }
    double spec_g[12] = {0};
    for (int i = 0; i < 12; i++) {
        spec_g[i] = spectral_g[i] * g;
    }
    double spec_b[12] = {0};
    for (int i = 0; i < 12; i++) {
        spec_b[i] = spectral_b[i] * b;
    }

    for (int i = 0; i < 12; i++) {
        spectral[i] = spec_r[i] + spec_g[i] + spec_b[i];
    }
}

static void spectral_to_rgb(double *spectral, double *rgb)
{
    double offset = 1.0 - 0.0001;
    for (int i = 0; i < 12; i++) {
        rgb[0] += T_MATRIX[0][i] * spectral[i];
        rgb[1] += T_MATRIX[1][i] * spectral[i];
        rgb[2] += T_MATRIX[2][i] * spectral[i];
    }
    for (int i = 0; i < 3; i++) {
        rgb[i] = clamp((rgb[i] - 0.0001) / offset, 0.0, 1.0);
    }
}

void Spectral_Mix(double sr, double sg, double sb, double sw, double& dr, double& dg, double& db) {
    double spec_a[12] = {0};
    double spec_b[12] = {0};

    rgb_to_spectral(sr, sg, sb, spec_a);
    rgb_to_spectral(dr, dg, db, spec_b);

    double spectralmix[12] = {0};
    for (int i=0; i < 12; i++) {
        spectralmix[i] = exp(log(spec_b[i]) * (1.0 - sw) + log(spec_a[i]) * sw);
    }

    double result[3] = {0};
    spectral_to_rgb(spectralmix, result);

    dr = result[0];
    dg = result[1];
    db = result[2];
}

3 Likes

Running the solver with different colour spaces and/or some adjusted settings can give a visibly different result. Tuning that is also something I will be looking into further.

For now though, I will be working on getting the colorimetric stuff organised and then integrate it into Krita. Perfomance and parameter tuning are things you do when everything else is working.

4 Likes

I tried mypaint and it looks like ‘pigment’ is just a blending mode, it doesn’t force the brush to do anything. I used yellow and blue to get the same green colour for “pigment”, “normal” and “multiply”. So they should be the same as the scheme you have in mind for krita.

1 Like