b = bottom
t = top

Normal Group


def normal(b,t):
	return t

\(\text{normal}(b,t) = t\)

Darken Group


def darken(b,t):
	return np.minimum(b,t)

\(\text{darken}(b,t) = \min(b,t)\)


def multiply(b,t):
	return b*t

\(\text{multiply}(b,t) = bt\)


def color_burn(b,t):
	invalid = np.geterr()['invalid']
	np.seterr(invalid='ignore')
	r = np.where(
		b == 1,1,np.where(
		t == 0,0,1-np.minimum((1-b)/t,1)))
	np.seterr(invalid=invalid)
	return r

\(\text{color_burn}(b,t) = \begin{cases} 1&\text{if }b = 1\\ 0&\text{elif }t = 0\\ 1-\min((1-b)/t,1)&\text{else} \end{cases}\)


def linear_burn(b,t):
	return np.maximum(b+t-1,0)

\(\text{linear_burn}(b,t) = \text{max}(b+t-1,0)\)


def darker_color(b,t):
	bY = rgb_to_gray(b).atleast_3d()
	tY = rgb_to_gray(t).atleast_3d()
	return np.where(bY < tY,b,t)

\(\text{darker_color}(b,t) = \begin{cases} b&\text{if }b_Y \lt t_Y\\ t&\text{else} \end{cases}\)

Lighten Group


def lighten(b,t):
	return np.maximum(b,t)

\(\text{lighten}(b,t) = \text{max}(b,t)\)


def screen(b,t):
	return b+t-b*t

\(\begin{align} \text{screen}(b,t) &= 1-(1-b)(1-t)\\ &= b+t-bt \end{align}\)


def color_dodge(b,t):
	invalid = np.geterr()['invalid']
	np.seterr(invalid='ignore')
	r = np.where(
		b == 0,0,np.where(
		t == 1,1,1-np.minimum(b/(1-t),1)))
	np.seterr(invalid=invalid)
	return r

\(\text{color_dodge}(b,t) = \begin{cases} 0&\text{if }b = 0\\ 1&\text{elif }t = 1\\ \min(b/(1-t),1)&\text{else} \end{cases}\)


def linear_dodge(b,t):
	return np.minimum(b+t,1)

\(\text{linear_dodge}(b,t) = \text{min}(b+t,1)\)


def lighter_color(b,t):
	bY = rgb_to_gray(b).atleast_3d()
	tY = rgb_to_gray(t).atleast_3d()
	return np.where(bY < tY,t,b)

\(\text{lighter_color}(b,t) = \begin{cases} t&\text{if }b_Y \lt t_Y\\ b&\text{else} \end{cases}\)

Contrast Group


def overlay(b,t):
	return hard_light(t,b)

\(\text{overlay}(b,t) = \text{hard_light}(b,t)\)


def soft_light(b,t):
	return np.where(t <= 0.5,
		2*b*t+(1-2*t)*b**2,
		2*b*(1-t)+(2*t-1)*b**0.5)

\(\text{soft_light}(b,t) = \begin{cases} 2bt+(1-2t)b^2&\text{if }t \le 0.5\\ 2b(1-t)+(2t-1)b^{0.5}&\text{else} \end{cases}\)


def hard_light(b,t):
	return np.where(t <= 0.5,
		multiply(b,2*t),
		screen(b,2*t-1))

\(\text{hard_light}(b,t) = \begin{cases} \text{multiply}(b,2t)&\text{if }t \le 0.5\\ \text{screen}(b,2t-1)&\text{else} \end{cases}\)


def vivid_light(b,t):
	return np.where(t <= 0.5,
		color_burn(b,2*t),
		color_dodge(b,2*t-1))

\(\text{vivid_light}(b,t) = \begin{cases} \text{color_burn}(b,2t)&\text{if }t \le 0.5\\ \text{color_dodge}(b,2t-1)&\text{else} \end{cases}\)


def linear_light(b,t):
	return np.where(t <= 0.5,
		linear_burn(b,2*t),
		linear_dodge(b,2*t-1))

\(\text{linear_light}(b,t) = \begin{cases} \text{linear_burn}(b,2t)&\text{if }t \le 0.5\\ \text{linear_dodge}(b,2t-1)&\text{else} \end{cases}\)


def pin_light(b,t):
	return np.where(t <= 0.5,
		darken(b,2*t),
		lighten(b,2*t-1))

\(\text{pin_light}(b,t) = \begin{cases} \text{darken}(b,2t)&\text{if }t \le 0.5\\ \text{lighten}(b,2t-1)&\text{else} \end{cases}\)


def hard_mix(b,t):
	return np.floor(b+t)

\(\text{hard_mix}(b,t) = \lfloor b+t \rfloor\)

Inversion Group




def difference(b,t):
	return np.maximum(b-t,t-b)

\(\text{difference}(b,t) = \max(b-t,t-b)\)

\(\text{exclusion}(b,t) = b+t-2bt\)

Cancellation Group


def subtract(b,t):
	return np.maximum(b-t,0)

\(\text{subtract}(b,t) = \text{max}(b-t,0)\)



\(\text{divide}(b,t) = \begin{cases} 0&\text{if }b = 0\\ 1&\text{elif }b \ge t\\ b/t&\text{else} \end{cases}\)

Component Group


def hue(b,t):
	tH = get_hsl_h(t)
	return set_hsl_h(b,tH)

def saturation(b,t):
	tS = get_hsl_s(t)
	return set_hsl_s(b,tS)

def color(b,t):
	bL = get_hsl_l(b)
	return set_hsl_l(t,bL)

def luminosity(b,t):
	return color(t,b)