I would like to fade the color of a pixel out toward white, but obviously maintain the same color. If I have a pixel (200,120,40)
, will adding 10 to each value to m
It's not that simple because in your monitor each color channel is weighted differently. I'd say the best bet is to do this in scikit image by converting to gray, dimming or brightening, and then back-converting to color. Scikit-image will take care of keeping the colors straight.
from skimage.color import gray2rgb, rgb2gray
scale_factor = 0.9 #90 percent
img_new = gray2rgb(rgb2gray(img) * scale_factor)
If you want to work directly with hue, saturation and value, check out this example:
http://scikit-image.org/docs/dev/auto_examples/plot_tinting_grayscale_images.html
The question, "to fade the color of a pixel out toward white" (not some shade of gray), is really about mixing the original pixel color with white, going from 100% original color and 0% white to 0% original color and 100% white. There's not more to it. Doing this in, for example, 101 steps would look like this:
r0= 200; // as in the question
g0= 120;
b0= 40;
for(i= 100; i >= 0; i--){
r= (i * r0 + (100 - i) * 255) / 100;
g= (i * g0 + (100 - i) * 255) / 100;
b= (i * b0 + (100 - i) * 255) / 100;
// use this color (r, g, b) somehow
}
Simply linearly interpolate between your color and white:
def lerp(a, b, t):
return a*(1 - t) + b*t
import numpy as np
white = np.array([255, 255, 255])
my_color = np.array([...])
lightened25 = lerp(my_color, white, 0.25)
Or without numpy:
lightened25 = [lerp(c, w, 0.25) for c, w in zip(my_color, white)]
As MarkM suggested, HSB (or HSL) is a simple method for doing this, but will not give perfect hue constance. If this is good enough (I assume you want your own method instead of a module) then this page has code for doing it.
In python it would look like this:
def rgb_to_hsl(rgb):
'''
Converts an rgb (0..255) tuple to hsl
'''
r, g, b = rgb
_r = r / 255 # RGB in percentage
_g = g / 255
_b = b / 255
rgbMin = min(_r, _g, _b)
rgbMax = max(_r, _g, _b)
rgbDelta = rgbMax - rgbMin
l = ( rgbMax + rgbMin ) / 2
if rgbDelta == 0: #Greyscale
h = 0
s = 0
else: # Chromatic data...
if l < 0.5: s = rgbDelta / (rgbMax + rgbMin)
else: s = rgbDelta / (2 - rgbMax - rgbMin)
deltaR = (((rgbMax - _r) / 6) + rgbDelta/2) / rgbDelta
deltaG = (((rgbMax - _g) / 6) + rgbDelta/2) / rgbDelta
deltaB = (((rgbMax - _b) / 6) + rgbDelta/2) / rgbDelta
if _r == rgbMax: h = deltaB - deltaG
elif _g == rgbMax: h = 1/3 + deltaR - deltaB
elif _b == rgbMax: h = 2/3 + deltaG - deltaR
if h < 0: h += 1
if h > 1: h -= 1
return (h, s, l)
def hsl_to_rgb(hsl):
'''
Converts a hsl tuple to rgb(0..255)
'''
h, s, l = hsl
if s == 0: #Greyscale
r = l * 255
g = l * 255
b = l * 255
else:
if l < 0.5: var_2 = l * (1 + s)
else: var_2 = l + s - (s * l)
var_1 = 2 * l - var_2
r = 255 * hue_to_RGB(var_1, var_2, h + 1/3)
g = 255 * hue_to_RGB(var_1, var_2, h)
b = 255 * hue_to_RGB(var_1, var_2, h - 1/3)
return r, g, b
def hue_to_RGB (v1, v2, vH):
'''
Helper for hsl_to_rgb
'''
if vH < 0: vH += 1
if vH > 1: vH -= 1
if (6 * vH) < 1: return v1 + (v2 - v1) * 6 * vH
if (2 * vH) < 1: return v2
if (3 * vH) < 2: return v1 + (v2 - v1) * 6 * (2/3 - vH)
return v1
Then to brighten:
def lighten(rgb):
'''
Given RGB values, returns the RGB values of the same colour slightly
brightened (towards white)
'''
h,s, l = rgb_to_hsl(rgb)
l = min(l+0.1, 1) #limit to 1
return hsl_to_rgb((h, s, l))
The benefit of this method is that the increment is a percentage of the total brightness. Modifying this to take the percentage as an input would be trivial.
You can reverse engineer the mathematical equations form this code, or see HSL to RGB.
You might want to check out this answer by denis:
RGB -> ^gamma -> Y -> L*
In color science, the common RGB values, as in html rgb( 10%, 20%, 30% ), are called "nonlinear" or Gamma corrected. "Linear" values are defined as
Rlin = R^gamma, Glin = G^gamma, Blin = B^gamma
where gamma is 2.2 for many PCs. The usual R G B are sometimes written as R' G' B' (R' = Rlin ^ (1/gamma)) (purists tongue-click) but here I'll drop the '.
Brightness on a CRT display is proportional to RGBlin = RGB ^ gamma, so 50% gray on a CRT is quite dark: .5 ^ 2.2 = 22% of maximum brightness. (LCD displays are more complex; furthermore, some graphics cards compensate for gamma.)
To get the measure of lightness called
L*
from RGB, first divide R G B by 255, and computeY = .2126 * R^gamma + .7152 * G^gamma + .0722 * B^gamma
This is
Y
in XYZ color space; it is a measure of color "luminance". (The real formulas are not exactly x^gamma, but close; stick with x^gamma for a first pass.)Finally, L* = 116 * Y ^ 1/3 - 16 "... aspires to perceptual uniformity ... closely matches human perception of lightness." -- Wikipedia Lab color space
@mark-meyer's answer is good, I answered a similar question on the StackMathematica section in great detail with examples PartOne and Part Two.
For THIS question, gradient to full white, here are examples using my gradient explorer:
Left column is staying in sRGB, next is linear xyY, then LAB, and far right is LAB LCh
You'll notice that remaining in sRGB is not substantially different than using L*a*b*
in most cases. This is partly because sRGB has a gamma curve that is different but similar to the perception curve of LAB.
You'll notice though that the LCh version has some hue shifts, depending on the starting color. In the case of purple, it requires some offsets near the mid range.
Also with LCh, the direction (clock wise or CCW) of hue rotation makes a difference.
Looking just at the for right LCh, here is magenta start, with no offsets, and the natural rotation:
Same rotation direction, but some offsets to smooth the LCh gradient.
Reversing the hue rotation and different offsets we go through purple instead of orange for LCh:
And here's from yellow, the LCh gets a green tinge without adjusting the hue offset:
But offsetting the mid hue smooths the LCh gradient:
And finally. BLUE is tricky with L*a*b*
as it often wants to shift to purple. On the other hand, in LCh blue wants to shift to cyan:
So in truth you can typically stay in sRGB for things like gradients to white, but for gradients between two saturated colors you might prefer to use LCh:
So here where colors get closer to 180° apart, the middle gets desaturated for both sRGB and LAB, as the average value in the middle is a grey - a darker murky grey for sRGB and a lighter grey for LAB. But LCh maintains saturation and instead rotates through the hues.