Smudge Tweaks testers needed

Tags: #<Tag:0x00007fd756594900>


I would not say that :slight_smile: . while I love the HCY color picker which lets me just think in brightness and chroma for painting, I don’t think HCY is the best option here (at least for changing the brightness via keys). A very saturated, dark color should not keep full saturation upon brightening up. Your CIECAM implementation seems to do it in a good way. I don’t know if there is a simpler solution?

Yes, compared to the initial pull request, this has grown a quite fast :upside_down_face:. It might be a good idea to use the issue tracker in your forks for all the little problems that came up so far, what do you think? I currently see:

  • subtractive / spectral / pow -> black is too strong / white is too weak
  • subtractive / spectral / pow -> dark colors are falsely brightened up (e.g. while smudging black on black)
  • smudge bucket order problem (for flat brushes and pens without tilt support)
  • CIECAM mapping problems
  • dynamic brush radius indicator

How would I go about contributing code that depends on your current work? Would I create a fork of your fork, and then create pull requests? Or is there a simpler/cleaner way?


Yes, please feel free. Yep, just fork my repo on, then git clone it locally (or ideally just add the remote to you existing local repo. Then github will have a button to create a pull requests when you push commits to your own repo.

Well, when you use HCY and increase the Y channel, the saturation channel (but not the chroma channel) will drop once you get to a certain threshold. But, yeah, it seems like it starts to drop immediately when using the CIECAM value channel.

Please check out this branch, it uses CIECAM16 and you’ll need the latest colour-science module. Oh, use official libmypaint for this branch. Unfortunately colour-science package only support colorfulness and chroma, and lightness (JMh, JCh). So, don’t try to use saturation and brightness or it will give you an error.

To try to find errors I think this is handy- disable the dynamic step size and set it to something like 0.5. Then draw spirals while shifting the hue, etc. Ideally the gradient will be very smooth. The way the gamut logic is set up is that it will always preserve the lightness and hue channels, but sacrifice the chroma/colorfulness channel as much as needed. But even that can be adjusted later on, maybe you’d be ok with shifting the value channel to maintain a particular chroma/colorfulness . . . But the other important thing to remember is that if you start off with a pretty unsaturated color you won’t have to do any gamut mapping at all and it will be very very smooth.


I cant really access that colour-science home page :frowning: (Error: SSL_ERROR_NO_CYPHER_OVERLAP) but I just installed it via pip install colour-science (got v0.3.11)

I can’t run mypaint however:

python2.7: mypaint-brush-settings.c:75: mypaint_brush_input_info: Zusicherung »id < MYPAINT_BRUSH_INPUTS_COUNT« nicht erfüllt.
Demo: cleaning up '/tmp/demo-VxQYOs'...
Traceback (most recent call last):
  File "", line 603, in <module>
  File "/usr/lib/python2.7/site-packages/setuptools/", line 129, in setup
    return distutils.core.setup(**attrs)
  File "/usr/lib/python2.7/distutils/", line 151, in setup
  File "/usr/lib/python2.7/distutils/", line 953, in run_commands
  File "/usr/lib/python2.7/distutils/", line 972, in run_command
  File "", line 200, in run
  File "/usr/lib/python2.7/", line 190, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['/usr/bin/python2.7', '/tmp/demo-VxQYOs/bin/mypaint', '-c', '/tmp/demo-VxQYOs/_config']' returned non-zero exit status -6

I used the original libmypaint’s up-to-date master branch


Make sure you clean your build tree when you switch between the versions of libmypaint… so make install libmypaint and then for mypaint do

python clean --all
python demo

I’m trying to understand why this would be…

Ok, I think I figured it out. MyPaint’s HCY’ “chroma” scale is NOT the same as the CIE’s definition for Chroma. See here:
So when the HCY’ chroma scale is dropping that doesn’t necessarily mean the CIE Chroma is actually dropping.


If you set your step-size to static and tune it to “10”, you can make nice comparisons like the above. J is lightness, M is CIE Colorfulness, and C is CIE Chroma. The M, C and hue (h) channels are static in this image. Only lightness is increased in steps of 10 units. It’s worth repeating- the CIE Colorfulness and CIE Chroma on these scales does not go up or down at all.

So, take this image into various programs and color pick the scales from dark to light. It might be surprising. In Krita, Gimp, Photoshop, and Mypaint- HSV, HSB, and HSY’ (I think MyPaint’s HCY’ is identical to HSY’) show decreasing saturation, which (loosely) aligns with CIE’s definition of saturation. While the HSI and HSL models both show increasing saturation, and Gimp’s LCH scale also shows increasing Chroma.

Also interesting is that Photoshop shows the Hue angle bouncing around a lot, while Krita is 100% static for all of their sliders (which means all their sliders use the same model for the Hue angle, which also agrees with CIECAM). In Gimp, the Hue angle for LCH bounces slightly and the hue angle for HSV bounces independently of the LCH hue angle (so they are maintaining independent models, which I think is a good thing).

For completeness I loaded the image into Gimp and added an LCH and HSV example, also 10 step units of L and V respectively. LCH also dropped CIE Chroma when increasing the lightness, and even more-so lost CIE Colorfulness, which is disappointing. I included HSV just to show how different it is :slight_smile:


So I guess the question is, as an artist, do you want to your brush color to (try to) maintain the “colorfulness” (as defined by CIE) when you increase/decrease the lightness? I would argue that, in general, yes. So I think that CIECAM JMh is probably a good default. In other words, I think “Colorfulness” is the more useful dimension to think about when adjusting the color, and keeps the ideas of hue, colorfulness, and lightness much more separated into independent channels.


Here’s an idea borrowed from GIMP to have functional CIECAM sliders, where the lovely magenta shows out of gamut colors that you can’t pick:


There seem to be some color jumps when brightening up (Lightness,Colorfullness,D65):

In the console I saw some CIECAM runtime-warnings that are probably related to this. I also noticed that brightening pure black results in red and darkening pure white in cyan:

I think I really have to take that back. In my painting workflow I guess I simply want to adjust the lightness and have the saturation/hue unchanged (as much as possible). The below shows the results of the HCY’ slider which are colors that I would expect for use in painting:

So I looked into the old code of the color adjustment keys where these comments are saying “TODO: use HCY”. How do you think about having both HCY’ keys and CIECAM keys? I might try to implement HCY / HSY keys in my fork and report back if it works.

Upon testing the color picking while dragging the cursor around I noticed that there is a significant delay that often shows a wrong color in the preview and in the color picker until I drag the curse a bit further. In the original branch the correct color (preview and color picker) displayes instantly.

Btw, For checking color anywhere on screen I use this nifty tool called “ColorGrab”

It only shows HSV and HSL besides RGB and CMYK though, so if you know a tool like this that can handle more/other color spaces, let me know.

That’s nice! What does the gradient to the unreachable magenta mean, or is that a technical problem?


Yeah, I’ve found some jumps too. I think it is related to the difficulty in gamut mapping from such extremes. I’m thinking of an approach that stores the last good color for each direction you are moving, so that as you increase lightness it can restart the process from the last point instead of the beginning…

Some of this might be because pure white is D65 which actually has a bluish color to it… I’ll have to think about this problem more :slight_smile:

The value looks fine but notice how some colors are more colorful than others, but you only changed the Luma slider, right?

I actually implemented the HCY first (hence the name of the branch), but I rebased and lost those old commits. I can restore that from a backup though and make a new branch. I think it would be nice to have a preference to pick from HSV, HSY’, CIECAM, etc…

Yeah, I am doing some throttling to help performance since the ciecam is pretty slow. Might be some bugs here too

If you try the newer GIMP releases, you can see the same kind of thing for the LCH sliders, which has the same technical problem of using absolute measurements for these scales that are not possible for every combination of L,C,H. It’s a double-edged sword… more “meaningful” scales but also more difficult to represent.


Hmm can’t find that HCY branch at the moment, but I found this:
It lets you pick H,C, and Y channels independently. I was disappointed that HCY’ (Y’ is luma) is not really that meaningful as far as maintaining lightness. That’s why I abandoned it. But it did have the advantage of being very fast and simple :slight_smile:

Edit oops I think a rebase broke something on that pickHCY branch…


Imho hsv is pretty much covered by hsy but yes, having the options (e.g. simply as separate keyboard shortcuts) would definitly be nice!

I only changed the luma slider, yep. In HSY they all have the same saturation. The CIECAM colorfulness differs, I suppose. I’ll really have to test the CIECAM controls in action to see how it feels.

In what way are CIECAM (or other) calculations done already when picking up color?

I personally prefer merging over rebasing :grin:. Anyway, let me know if you happen to find the old HSY code anywhere.


That makes sense to just keep them as keyboard shortcuts, so you can mix and match models as you want instead of flipping a global preference

Well, the way I have it set up is that when you “pick” a full color you are grabbing the R,G, and B channels. I take those values and convert it to CIECAM channels like J, M, h. So it’s the same process as before but much more cpu intensive. Before, it only had to do RGB–>HSV, so it didn’t matter if you sampled the canvas 500 times per second. So I think the weirdness with the color being “wrong” momentarily has something to do with the lag from the extra cpu usage and frames being skipped perhaps.

You want to rebase your branch to get it “up to date” with the upstream master branch (which may have changed when you were making commits to your own branch) and put your commits back on top of that. So typically you’ll want to rebase -i HEAD~20 (or however many commits you’ve made), and clean them up, squash the ones that are worthless typo fixes, etc etc. Then you’ll do a git rebase upstream/master to get everything up to date. It’s not very fun when there are conflicts, but I think this is generally the best practice before making a pull request.

Good news, I had forgotten about git reflog and didn’t realize it went back so far. I went back to before I rebased and squashed out the HCY adjusters. I made a new branch for where I left off

So that will be HCY for the adjusters as well as the color picking channels H, C and Y independently. It is refreshing how fast the darn thing is, but I’ve documented the issues a bit here:
regarding the issues of luma vs luminance.


Bunch of updates on the ciecam stuff. You’ll need the bleeding edge version of colour-science:
git clone git://
cd colour
pip install .


Both of these seem to be fixed. I had major errors in the gamut mapping logic and I think an update to colour helped fix the black–>red and white–>cyan issues. So far the errors that happen seem to be benign and I should squash them eventually. During the optimization loop it might try invalid saturation values but those will be rejected by the loss function. Thanks for testing!


With the latest updates and the current version of colour-science there seem to be no more weird color “jumps”, also the hue seems pretty stable while lightening and darkening. When turning the hue, the saturation drops initially (maybe the initial color was out of gamut range?), but then stays stable as far as I can see.

When lightening pure black / darkening pure white the colors still slightly shift towards red/cyan but it is barely noticable at all and should not bother anyone while painting.

Any idea when those fixes will be officially released in the colour-science package?

I find that random “flickering” grey a bit distracting/confusing and would suggest chopping off the slider without any gradient (from valid to invalid, like in GIMP) to a color like magenta, which is easily distinguishable from most colors, or maybe I didn’t quite understand your answer right.

Another improvement might be to find the nearest valid point when clicking in an unpickable area. This might be especially important if you want to drag the slider to the maximum. Right now I have to click multiple times to find that point.

Great job so far, I guess one of the next steps is to optimize performance which so far was my only concern about the CIECAM functionalities :slight_smile:


Well, the initial color is always going to be in-gamut. But, assuming you start off fully saturated there is a good chance that the next hue rotation is going to “bump” against the sRGB gamut triangle and require desaturation. When this happens totally depends on where your starting position is. If you start off with a pure primary like Blue (0,0,1), it’s going to happen right away and will be pretty obvious. But if you start at a middle-value and middle-saturated color, you shouldn’t have any bumps in saturation. Think of it like trying to fit a perfect circle inside a triangle-- small circles fit nicely but large circles need 3 sides to be flattened off to fit. Thing is with “colorfulness” we’re not dealing with perfect circles within the CIE diagram but some oddball shape. I should really figure matplotlib and plot colorfulness for a given lightness. . .

No but I’m sure it won’t be too long.

Using magenta isn’t the best. I received some good feedback on Twitter on this ;-). The obvious one is it doesn’t work well if you are actually picking magenta colors… but also, it is a very strong color and probably seriously interferes with color perception (think simultaneous contrast effect). That said, the random greys aren’t much better. Ideally we’d have non-color data like stripes or something . Or, as an option, the ability to squash these regions entirely so you don’t even notice them.

I do agree we need a clean chop off w/o gradients for the edges. The sliders are built with Cario’s linear-gradients so I don’t know how do to that yet. Might need a whole new widget for this and the stripes. Take a look at and

This is definitely doable, I just need to build out a few more gamut mapping functions to preserve alternate axes. Using the loss function it should actually know when to jump you left versus right assuming the gap is in the middle. Although, right now you can sorta drag to a maximum but it’s not obvious that you’ve reached it.

Another preference I want to add is which sliders groups to show, instead of all-or-nothing. . .


So, this is actually smudge_tweaks related. A lot of the spectral code uses loops, so I was looking at auto vectorization. I had to enable some compiler flags, but now a whole bunch of loops are vectorized. The speed difference is pretty remarkable. Here is my build script which includes the CFLAGS for my cpu. You’ll want to look at different options if you have an AMD processor, etc. I usually execute it from within libmypaint or mypaint via sh ../ This might be a good article to read too too for more optimizations:

#exit if any error
set -e

cd ../libmypaint/
export LIBPROFILER_CFLAGS="-lprofiler -ltcmalloc"
export LIBPROFILER_LIBS=/usr/lib/

export CFLAGS='-fopenmp -O3 -ftree-vectorize -fopt-info-all -mavx2 -march=corei7-avx -funsafe-math-optimizations -funsafe-loop-optimizations'
if [ "$1" == "debug" ]
	./configure --enable-openmp --enable-profiling --enable-debug --enable-gperftools  --enable-introspection=yes
./configure  --enable-openmp 
make clean
make -j8
sudo make install
sudo ldconfig
cd ../mypaint/

python  clean --all
rm -vf lib/*_wrap.c*
if [ "$1" == "debug" ]
	python build --debug
	python build 

sudo python managed_install
if [ "$1" == "debug" ]
	LD_PRELOAD=/usr/lib/ CPUPROFILE=/tmp/ mypaint
	google-pprof --web /usr/local/bin/mypaint /tmp/


Nice build script! Some while ago I tried building with openmp enabled, and while it utilized all cpu cores it was mostly slower than single-threaded. But with these flags its actually faster.

…also, a quick slider mockup with stipes:


I still need to do some actual benchmarks, but that’s interesting I didn’t realize openmp might make things slower. It was definitely faster with a big airbrush when zoomed way out, but I suppose it might slow down normal usage when zoomed in…

Mockup looks great with the stripes. Now, how can we do it? :slight_smile:

I made a few updates to the illuminant stuff. I made it more coherent, I think. If you change the illuminant to something other than D65, you won’t be able to get to pure white (255, 255, 255), because that is the white for D65. It will be the brightest color that is still identified as the illuminant color. If your illuminant is red then the “definition of white” is a red color.

For some reason it is really hard to get to absolute pure white 255,255,255 with D65, though. I’ll have to investigate that but I’m not too concerned at the moment, it gets pretty darn close enough.

I have a few other ideas I think will be fairly easy:

Two new UIColor types. LinearRGB and “Paint”. LinearRGB will be a copy of the RGBColor basically, but with a power function to make the blending linear light. Actually, probably better to just add an optional argument to RGBColor to specify a power function. The “Paint” UIColor will be the spectral upsampling and subtractive mode just like in the smudge code. With these two color options you’ll be able to fill gaps in the palette editor with these linear or paint modes as well.

The next thing I want to do is a new color pick mode that blends the canvas color with the brush color interactively. A preference should allow any UIColor model for the blending (including perceptual RGB, LinearRGB, HSV, CIECAM, and spectral “paint”, etc). When you enter this mode, it will use grab the color and x,y coordinates of where you grabbed and then calculate the euclidean distance as you drag around (dist = numpy.linalg.norm(a-b)). This distance will be the blend ratio of the two colors. This way you can drag back and forth and tune the color precisely before you exit the color pick mode. I think dist=0 will be 100% brush color and dist=100 pixels will be 100% canvas color. Maybe instead of 100 pixels that should be some percentage of the window size. . .


Fiddled with the sliders a bit…

This basically paints many many little gradients as stripes first and then applies the actual color gradient on top (using rgba). Its very hacky but might be worth looking into further. I’d basically need alpha information for every color…


That looks pretty great! More samples would definitely help but I can’t see how can get super-high accuracy without making it really, really slow. Now I’m wondering if fuzzy edges might be better after all, since it’s sort of accurate in a way-- we don’t know exactly where these edges are :slight_smile:

You’re right, since only the CIECAMSliders pass the gamutmapping=“highlight” option, we could just return (0, 0, 0, 0) here and here and your code just render it normally. Very cool!


I’ve added the same spectral code from smudge tweaks into MyPaint as a UIColor called PigmentColor, only accessible via the palette editor for now. The closest model to compare to is surprisingly plain old sRGB since it is a perceptual blend in RGB. For each set of two rows, the top is RGB and the bottom is Pigment:

It is pretty clear that black “pigment” paint is very strong. Before I call that a bug, I’d like to learn more about the physical simulation. In real life, is black paint much much stronger than white paint?