Smudge Tweaks testers needed

I tried adding 0s but could not find a value that solves both problems :cry:. I also tried clamping at different positions in the code with no success.

A lower value (closer to 0) for the epsilon makes it harder for white active color to come through, and much too easy for black (e.g. where black needs 2 or 3 strokes, white needs ~50 to have the same effect)

And a higher value (closer to 1) brightens everything up.

In the video I used the original epsilon of 0.0001, the brush is not affected by pressure and smudges ~99%. After 8 strokes black on white is much stronger than white on black…

I also dont quite understand the maths about the spectral/subtractive mixing. Are there any good articles about this or even reference implementations?

Thanks again for testing that. I have to wonder if this is part of the nature of subtractive mixing-- black paint is just plain stronger? I’m sure you’ve noticed that with real paints a tiny bit of black has much more effect than a tiny bit of another color? Here’s basically what’s happening, where 0.5 is the ratio of 50/50, .01 is our black reflectance and 1.0 is our white reflectance:

0.01^0.5 * 1.0^0.5 = 0.1

So, mixing 50/50 definitely leans in blacks favor, a lot, since you might be expecting something closer to 0.5 as a result.

Most of this work is based on this article and code by Scott Burns:
http://scottburns.us/subtractive-color-mixture/
But I had to wing it as far as dealing with transparency. But recently this paper came out which says there is a program somewhere to download and experiment with:
https://hal.archives-ouvertes.fr/hal-01700606/document
and it seems to confirm the general algorithm I’m using:
image
They talk about three approaches by I’m doing the 1st approach (4.1) (much simpler)

Ah, here’s where they talk about this issue:
image

So if we take X=0… and replace 255 with 2^15… we get…sigh… 0.000030517578125 as our epsilon, which is right around where we’ve already tested.

I was confused why they are excluding the maximum value 1.0 as well, but it seems like that is only applicable to the other 2 mixing laws they talk about later. Whew!

I’m refactoring the mix_colors code to greatly simplify it and make it generic enough to use in several places without silly parameters like get=TRUE and such.

Oops, I made the classic blunder. Assuming the above values represented actual reflectance percentages of light (aka linear light), then the result of 0.1 is actually NOT that dark. Middle 50% grey is 0.18 and represented as ~0.50 via the inverse power function (aka OETF). So, 0.1 is actually more like a 38% grey (0.1^(1/2.4) = 0.3831 * 255 = sRGB 98,98,98. So I think this also highlights the importance of setting the power function before mixing :stuck_out_tongue:

Not really smudge related, but I had this idea to create a posterizing brush mode. I had no idea if it would really be useful, so I had to try it. I think it’s actually pretty cool and handy when you want to bump up the contrast of something or give it that 80’s look

For the top part I used Ramon’s Big Airbrush switched to posterize mode. For the bottom I used my halftone brush switched to posterize mode, which gave a sorta fake dithering look.

Youtube video demo too

The bummer is you have to patch MyPaint gui as well for this to work:
https://github.com/briend/mypaint/tree/smudge_tweaks

so just compile the smudge_tweaks libmypaint then git merge my branch above to mypaint gui and it should work.

Would it make sense for the base value of the Angular Offsets to influence the brush size indicator (just like Jitter does)? I’m asking because I noticed that some my brushes dont work with a base radius below ~1.5 anymore since I manually down-sized the dab size.

what is the specific order of commands to merge your fork into mypaint (have no experience with that)? Should just installing from your fork work just as well?

Honestly I don’t really know how the brush size indicator works, I’ve never looked at that code really :-P. I think it runs a simulation or something to find the size?

Oh, I suppose you just cloned from my repo to begin with? Yeah, you could just
git checkout smudge_tweaks
and build from that if you want.

Actually, you could build from my master branches on both mypaint and libmypaint. I’m going to try to keep those up to date with a few other things I’m working on.

https://github.com/briend/mypaint
https://github.com/briend/libmypaint

Normally you would add several “remotes” to that one directory. So “upstream” should be official mypaint. Then your own repo might be “origin”. And you might add my repo as “briend”. Then you can checkout any branch from any remote by just doing
git checkout briend/master
and what not. Or merge things with git merge briend/smudge_tweaks.

You add remotes by
git remote add whatevernameyouwant <url-to-the-repo>
then
git fetch <name-of-remote>

I noticed that on Linux, having dynamic dabs_per_xxx makes the initial brush not work unless you flip the stylus to the eraser or switch the mode. Then everything works fine. So, I’m looking into why that is happening

I noticed that too just yesterday :slight_smile: For me it went away after holding shift and dragging a line of by switching between mouse and stylus.

It also happened if the resulting value of dabs per xyz was too low / negative. So maybe that’s connected.

Thanks, I bet this is why it was marked constant to begin with. But, there must be a work around, since it seems really useful

Ok, both the initialize issue and negative values are fixed in my master branch and smudge_tweaks branch. I guess the dabs_per_xxx aren’t initialized soon enough so they become “naan” (not a number) and that messes things up.

Hmmm, since recently I cant build mypaint from your branch anymore :confused:

libmypaint is installed, but on running python2.7 setup.py demo i get long errors and then ImportError: No module named colorspacious

Am i missing a certain dependency that I can’t find? I use both libmypaint and mypaint from the briend-fork (both on master)

Using the unforked ones works fine.

Edit: nevermind, I installed the colorspacious package via pip

Oops, yeah, sorry about that. My branch uses colorspacious and python2-scipy for the CIECAM stuff but you probably already have scipy. If you want to explore the CIECAM color model, look at the color options under preferences, and use the keyboard shortcuts to adjust the color (Shift+a,s,d,f,q,w by default). Also, under the palette editor there is a fill-gap gradient option now for CIECAM, which IMO is much much prettier than the other options. Thanks for testing!

I think the CIECAM color adjustment is a nice addition, expecially the color temperature. I’ll have to test it a bit more, I often use the hotkeys for making my current color brighter/darker while painting already. Then again I think I also want to be able to adjust colors in a neutral/linear fashion (without hue shifting).

I currently have these settings trying to achieve this and it looks good to me:
Color Value: Brightness
Color Purity: Saturation
Light Source: D65

Is there a recommended default for these settings?

I also looked into the code about the cursor indicator circle a bit and added the following locally in mypaint in tileddrawwidget.py -> def _get_cursor_info(self):

r += base_radius * b.get_base_value('offset_angle')
r += base_radius * b.get_base_value('offset_angle_2')
r += base_radius * b.get_base_value('offset_angle_asc')
r += base_radius * b.get_base_value('offset_angle_2_asc')
r += base_radius * b.get_base_value('offset_angle_view')
r += base_radius * b.get_base_value('offset_angle_2_view')

Of cource that only works with the base values so far, but this way your cursor circle (if you have it enabled) potentially shows a correct radius.

None of the options should be shifting the hue really w/ a D65 light source at least. I kinda thought it was working but there is something very wrong with my gamut mapping code:

https://github.com/briend/mypaint/blob/HCYtools/lib/color.py#L1165-L1191

I started printing the rgb values and they are NOT being constrained to 0.0-1.0 despite the constraints set up in the minimize function. Lots of hits on google but I haven’t figured out what I’m doing wrong.

The basic idea is that if you take a color like sRGB blue 0,0,1 and want to make it “darker” while keeping the same hue angle and “colorfulness”, it becomes impossible without reaching outside of the gamut to a super-colorful “darker” blue that doesn’t exist in your colorspace, which leads to negative RGB values like 0, -2, 1 and such when converting from CIECAM back to RGB. So, you reduce the colorfulness some amount until you satisfy the constraints of 0-1, which is where a minimize/optimize function helps to minimize the loss of both lightness and colorfulness to reach the target constraints. Well, that ain’t working quite right, at all. As you’ve noticed, saturation works better because it has a different meaning than colorfulness and isn’t as sensitive to gamut issues.

I think “colorfulness” might be more useful, assuming the darn optimizer was working :slight_smile: But depending on the intent all the options could have value.

That’s pretty cool. I wonder if the dynamic values are even available though. I rarely use base value for those but rather a random or custom input. Plus there’s the offset multiplier. I hope it works though :stuck_out_tongue:

I suppose it’s not that easy, espeacially if you have to deal with random inputs (a constantly updating circle would basically “flicker” all the time for random inputs). I guess right now you can use the base value and subtract a random input instead of using random directly.

I forgot the multiplier :slight_smile:

r += base_radius * exp(b.get_base_value('offset_multiplier')) * (
    b.get_base_value('offset_angle') + b.get_base_value('offset_angle_2') + 
    b.get_base_value('offset_angle_asc') + b.get_base_value('offset_angle_2_asc') + 
    b.get_base_value('offset_angle_view') + b.get_base_value('offset_angle_2_view')
)

Maybe we could look at the dynamic inputs, but in a non-dynamic fashion. That is, find the min or max of the inputs and use that to find the maximum brush size that’s possible. For pressure, maybe it would make sense to use average of the min/max. I haven’t looked at the code yet but hopefully it is only updating when the base radius is adjusted?

I’ve updated my master branches for the gamut mapping ciecam stuff. Please give it a try w/ colorfulness, etc. It should behave much better. One thing I had to compromise on is resetting the color intent every time you make an adjustment. So, if you take a saturated color and bring it all the way to black and then lighten it again, it will be monochromatic until you bump up the color channel. It won’t lose the hue identity though. Likewise, if you take a very saturated color and rotate the hue through 360 degrees you won’t land on the same color again-- you’ll land on the saturation level that was the lowest common denominator of all the other colors you landed on. This can be cool, or annoying.

The reason I had to do this was that, when making a color darker while preserving the other attributes (colorfulness, hue), it becomes totally impossible to retain colorfulness without creating negative RGB values. The whole point of gamut mapping is to bring those back into the gamut, but these were getting so far out there that the ciecam code was choking. I might revisit this after I move to the colour-science color library but for now it’s pretty good and much more performant since it doesn’t have to work so hard.

Another nifty thing is I removed the limit for lightness/brightness, so now you can bring any color to “white” with just the lightness control. This happens because the gamut mapping code will automatically reduce the colorfulness channel to accommodate the intended lightness.

The updating code only seems to be called upon brush setting changes and window focus changes. Using max input values for the radius sounds right, for pressure I’d use the value for an input of 0 since the radius is shown while hovering (with pressure 0) anyway. I guess this gets complex fast, since you have to check all dynamic inputs for all settings that influence the actual radius. It sounds doable though :slight_smile: .

It seems to work pretty well. I can’t really tell a difference between colorfulness and chroma, both seem ok. lightness seems to be totally linear, while brightness has finer steps for darker colors?

It happens rarely, but sometimes I still seem to get a very saturated dark green after darkening e.g. an already dark red. I guess it has to do with this problem? (tested with brightness, colorfulness, D65, step size 9)

That is great, I always had to do that manually using the color picker in the old version since it just stopped at some point for quite saturated colors. One thing that bothers me is that I can’t change the hue/saturation at all in the color pickers if pure white or pure black is the current color. I think that was already the case before these CIECAM changes.

Hmm… but isn’t the idea to show a preview of how big the brush mark will be when you actually use the brush w/ pressure? Also, the default pressure for input devices like mice is 0.5. I agree it gets pretty complicated and we definitely have to make some concessions and just try to get a “ballpark” estimate. Even inputs like “jitter” can have a big effect on “effective brush size”.

I can’t say I really understand the differences between all 5 dimensions of CIECAM, I just figure it’s better to allow choices. I think if you create some swatches using the color palette fill gap you’ll see some differences side-by-side. You’d have to create a palette and then switch the preferences each time before you do the “fill gap” operation.

Probably my gamut mapping is still broken. I’m migrating to CIECAM16 and the colour-science package and trying out a different optimization strategy right now, maybe things will get better. Also, often times when you get really, really dark, the identity of the RGB color is really lost, but the GUI will still try to figure it out. This is how many programs will make any unsaturated grey color have a “red” identity. Corel Painter uses blue. So, unless your actual brush color looks green to your eyes, it’s probably not a problem. But, I’ve definitely seen an actual green due to gamut mapping failing and falling back to hard rgb clip where -5, .2, 0 get’s clipped to 0, .2, 0, for example.

This is what originally bugged me about the color adjuster keys. It was using HSV is the reason, since the Value channel is limited by the saturation level. HSL and HCY’ allow this kind of black->white with the value channel. I originally was switching the adjuster keys to use HCY’ but decided that implementing a “state of the art” color appearance model would be more useful in the long run, and here we are (boy do I regret that decision :wink:

Here’s the thread for that (we’ve gone a bit off topic from the original smudge tweaks stuff, but oh well)
https://community.mypaint.org/t/hcy-adjuster-keys-and-floating-swatch-preview