Help: How to achieve "Vibrance"?

@age , I wrote this, from your code.
The filter manages a split preview as well as images with alpha channels:

#@gui Vibrance : fx_vibrance, fx_vibrance_preview(1)+
#@gui : Strength = float(1.5,0,4)
#@gui : sep = separator()
#@gui : Preview Type = choice("Full","Forward Horizontal","Forward Vertical","Backward Horizontal",
#@gui : "Backward Vertical","Duplicate Top","Duplicate Left","Duplicate Bottom","Duplicate Right",
#@gui : "Duplicate Horizontal","Duplicate Vertical","Checkered","Checkered Inverse")
#@gui : Preview Split = point(50,50,0,0,200,200,200,0,10)_0
#@gui : sep = separator()
#@gui : note = note("<small>Author: <i>Age / Pixls.us</i>.      Latest Update: <i>2022/06/28</i>.</small>")
fx_vibrance :
  foreach {
    split_opacity l[0] {
      to_rgb
      f "const vibrance = $1;
         vib(val) = (
           v = val/255;
           vv = (v - lum)*vibrance + lum;
           (vv*(1 - sat) + v*sat)*255
         );
         min = min(R,G,B)/255;
         lum = (R*0.2 + G*0.7 + B*0.1)/255;
         sat = cut(1 - min/lum,0,1);
         [ vib(R),vib(G),vib(B) ]"
      }
      a c
    }

fx_vibrance_preview :
  gui_split_preview "fx_vibrance $*",${-3--1}

I’ve already pushed it in the filter updates, so it should be available for all users of G’MIC 3.1.3+.

Just let me know about your preferred way to be credited.
Thanks! :beer:

6 Likes

In the darktable “color balance rgb” module, there is a slider “global vibrance”. I used the middle column (original) of the posted example and opened it in darktable 3.8. Besides “color balance rgb” I only activated filmic rgb with default settings.

The result below is in no way identical to the posted example but quite similar.

1 Like

Hi thanks David I’m fine with my forum’s username :muscle:t2:

1 Like

Perfect! Thanks again! I’ve posted a tweet about this :slight_smile:

3 Likes

I noticed, and he gave me his code. Based on the code provided by the person I mentioned (shame to see him go), I have made my translation for G’MIC. It’s the same except for chroma boost.

#@cli rep_ych_vibrance: 0<vibrance<=1
#@cli : Increase Chroma of Image with minimal to no impact on high-chroma areas. Based on Darktable YCH "Vibrance" code.
rep_ych_vibrance:
vibrance={cut(abs($1),0,1)}
if !$vibrance return fi
foreach {
 if s<3||s>4 continue fi
 if s==4 sh. 0,2 fi
 rgb2yiq.
 sh. 1,2
 f. "
  V=I;
  [norm(V),atan2(V[1],V[0])];"
 sh. 0
 f. "begin(
   const vibrance=$vibrance;
  );
  v=vibrance*(1-i^vibrance);
  chroma_factor=max(0,1+vibrance);
  i*chroma_factor;"
 f.. "
  polar_coordinates=I;
  r=polar_coordinates[0];
  ang=polar_coordinates[1];
  [r*cos(ang),r*sin(ang)];
  "
 k[0]
 yiq2rgb
}

This one utilize the YCH model instead of RGB model.

I noticed some details may be lost on images with already high chroma, I think that’s my fault, but oh well.

Also, I can’t tell what’s better at times. The RGB approach or the YCH approach. YCH is worse on green, but RGB is worse at other colors. I will say that YCH approaches preserve some information better, and is generally a improvement in some case.

EDIT in 6/29/2022: Polar version of YIQ is better than YUV according to my recent experiment.

2 Likes

Curious: did you convert from/to sRGB? I usually don’t because I am lazy.

Thanks @Reptorian , I move it from Testing/ to Colors/.

2 Likes

Now, I just did, but decided to go through srgb2rgb, and then rgb2srgb as that seem to preserve information some more. I also added better weighing. It still has color bleed, but that probably because of the out-of-gamut issue, and at that point, I don’t think I can do much more.

There seems to be no option to reduce Vibrance?

I’ll add it in a few, it isn’t much to do.

Note that tomorrow, I’m going on vacations for one week. There won’t be any filter update during this period (computer at the lab is powered off).

1 Like

I have made some changes. It’ll be here next week:

1 Like

Earlier in this thread I mentioned a GIMP plug-in I had created.
I have now made a simplified version that makes it possible to increase or reduce the saturation in both the high and low saturation regions of an image. It also runs somewhat quicker (but still slow, because it is written in Python 2.7xx).
It creates two masked layers above the original, one of which contains the high saturation parts, the other the low. After adjustment the layers can be merged down.
I have also “stretched” the mask in the hope that it will give a usable amount of change where the saturation range of the original image is small.
I have attached it here and hope to receive comments, suggestions and criticism!

vibrance.py.zip (1.3 KB)

2 Likes

After testing some more of Vibrance / Vibrance [YCH], and working on Vibrance [YCH] (Note that G’MIC can’t be updated during this week). It seems that Vibrance [YCH] actually preserves perceptual luminosity better than Vibrance. I actually have compared it with Ed’s Harvey Vibrance plugin for Paint.NET.

I ranked from best to worst:

  1. Ed Harvey’s Vibrance - This preserves luminosity better than Vibrance [YCH], but it isn’t that much better. And also preserves saturation better at 100%. Sometimes this performs worse than Vibrance[YCH]. Basically 52/48.
  2. Vibrance [YCH] - Performs a slightly worse than the above. However, extremely close to the above, so they’re interchangeable, and there are few cases where this is better than the above. Clearly need of a improvement.
  3. Vibrance - This one has glaring issue with really high chroma image on high vibrance.

What I think that may improve my Vibrance [YCH] is either switching to a different color space, or modify the YUV color space so that it matches one of the gmic rgb2lab illuminant. There’s another possible way to make it better, a modified version of rgb2srgb/srgb2rgb. Or even both. That way, we finally have a proper Vibrance filter as luminosity would be better perserved in some areas. However, improvement could mean trying to grasp it and then finding a better mathematical model.

Here’s the test results for all Vibrance filters.

1st image - No filter
2nd image - Ed’s Harvey’s Vibrance for Paint.NET
3rd image - G’MIC Vibrance [YCH]
4th image - G’MIC Vibrance

You can clearly see why I ranked it like this after you zoomed onto the rooster’s head.

2 Likes

Could you try the HSI color space ?

HSI isn’t based on human perception, so I cannot try that. The last image utilize HS*. The solution has to be based on modified yuv color space.

2 Likes

Yes but with a conceptually different algorithm.

Your Vibrance [YCH] could be improved with a color space where the most rgb saturated color (for example red [1,0,0]) is mapped to a value of 1.0 in the chroma/saturation channel, this is the only way to limit or not push at all colors out of gamut.

I checked, it does seem to be out-of-chroma issue. However, more because of the wrong value used. Now, it looks better than Ed’s Harvey method.

See commit here - Fix Value in Vibrance [YCH] · dtschump/gmic-community@fe55132 · GitHub

And these tests with before/after:

C:\Windows\System32>gmic 256,256,256,3,[x,y,z] rep_vibrance_ych 1
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Input image at position 0, with values '[x,y,z]' (1 image 256x256x256x3).
[gmic]-3./rep_vibrance_ych/*foreach/ 0.63235455751419067
[gmic]-3./rep_vibrance_ych/*foreach/ 0.63235485553741455
[gmic]-1./ Display image [0] = '[image of '[x,y,z]']'.
[0] = '[image of '[x,y,z]']':
  size = (256,256,256,3) [192 Mio of float32].
  data = (0,1.44314,2.88601,4.32861,5.77094,7.213,8.6548,10.0963,11.4778,12.7596,13.9606,15.1212,(...),255,255,255,255,255,255,255,255,255,255,255,255).
  min = 0, max = 255, mean = 118.538, std = 89.8106, coords_min = (0,0,0,0), coords_max = (255,1,0,0).
[gmic]-1./ End G'MIC interpreter.

C:\Windows\System32>gmic 256,256,256,3,[x,y,z] rep_vibrance_ych 1
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Input image at position 0, with values '[x,y,z]' (1 image 256x256x256x3).
[gmic]-3./rep_vibrance_ych/*foreach/ 0.63235455751419067
[gmic]-3./rep_vibrance_ych/*foreach/ 0.63235455751419067
[gmic]-1./ Display image [0] = '[image of '[x,y,z]']'.
[0] = '[image of '[x,y,z]']':
  size = (256,256,256,3) [192 Mio of float32].
  data = (0,1.44314,2.88601,4.32861,5.77094,7.213,8.6548,10.0963,11.4778,12.7596,13.9605,15.1212,(...),255,255,255,255,255,255,255,255,255,255,255,255).
  min = 0, max = 255, mean = 118.538, std = 89.8106, coords_min = (0,0,0,0), coords_max = (255,1,0,0).
[gmic]-1./ End G'MIC interpreter.

The value remaining the same is more ideal because maximum chroma doesn’t shift. New problem is color bleed rather than luminosity.

2 Likes

Some observations on the ycbcr formula:
https://en.wikipedia.org/wiki/YCbCr

The “(1/2)*” is there only to compress chroma in the -0.5,+0.5 => 0,1 range => 0,255 integer range

In theory you could multiply the chroma channel by 1.8 (to stay safe) before vibrance to have a better starting point (chroma channel similar to a saturation channel) , at the end divide chroma by 1.8 before the conversion to rgb

1 Like

The trouble with colour is that as a component it doesn’t scale the same for all hues and brightnesses. If we multiply the colour component by a factor for all colours, we are going to get some weirdness at places, especially when we are in the more colourful range. We get math, gamut and/or other unexpected oddities. Vibrance is a stopgap to the “saturation problem”.

2 Likes