Pylette icon indicating copy to clipboard operation
Pylette copied to clipboard

CIELAB color space

Open Leterax opened this issue 2 years ago • 2 comments

Have you thought of using the CIELAB color space instead of HSV/RGB? I have had pretty good success using this, but have only implemented it in golang, not python. The LAB colorspace has the advantage of being perceptually uniform.

Leterax avatar Jun 26 '22 16:06 Leterax

Here is some code to convert RGB to LAB:

func Rgb2lab(color RGB) LAB {
	return xyz2lab(rgb2xyz(color))
}

// rgb2xyz converts from the RGB to the XYZ colorspace
func rgb2xyz(color RGB) XYZ {
	r := float32(color.R) / 255.
	g := float32(color.G) / 255.
	b := float32(color.B) / 255.

	if r > 0.04045 {
		r = math32.Pow((r+0.055)/1.055, 2.4)
	} else {
		r = r / 12.92
	}

	if g > 0.04045 {
		g = math32.Pow((g+0.055)/1.055, 2.4)
	} else {
		g = g / 12.92
	}

	if b > 0.04045 {
		b = math32.Pow((b+0.055)/1.055, 2.4)
	} else {
		b = b / 12.92
	}

	r = r * 100
	g = g * 100
	b = b * 100

	X := r*0.4124 + g*0.3576 + b*0.1805
	Y := r*0.2126 + g*0.7152 + b*0.0722
	Z := r*0.0193 + g*0.1192 + b*0.9505

	return XYZ{X, Y, Z}
}

// xyz2lab converts from the XYZ to the LAB colorspace
func xyz2lab(color XYZ) LAB {
	x := color.X / 94.811
	y := color.Y / 100.000
	z := color.Z / 107.304

	if x > 0.00885 {
		x = math32.Pow(x, 1./3.)
	} else {
		x = (7.787 * x) + (16. / 116.)
	}

	if y > 0.00885 {
		y = math32.Pow(y, 1./3.)
	} else {
		y = (7.787 * y) + (16. / 116.)
	}

	if z > 0.00885 {
		z = math32.Pow(z, 1./3.)
	} else {
		z = (7.787 * z) + (16. / 116.)
	}

	L := (116. * y) - 16.
	a := 500. * (x - y)
	b := 200. * (y - z)

	return LAB{L, a, b}
}

And two different distance functions:

// DeltaE76 calculates the simpler DeltaE76 between two LAB colors
func DeltaE76(colorA, colorB LAB) float32 {
	return math32.Sqrt(math32.Pow(colorB.L-colorA.L, 2.) + math32.Pow(colorB.A-colorA.A, 2.) + math32.Pow(colorB.B-colorA.B, 2.))
}

// DeltaE calculates the DeltaE between two LAB colors
func DeltaE(colorA LAB, colorB LAB) float32 {
	xC1 := math32.Sqrt(math32.Pow(colorA.A, 2) + math32.Pow(colorA.B, 2))
	xC2 := math32.Sqrt(math32.Pow(colorB.A, 2) + math32.Pow(colorB.B, 2))
	xDL := colorB.L - colorA.L
	xDC := xC2 - xC1

	xDE := math32.Sqrt(((colorA.L - colorB.L) * (colorA.L - colorB.L)) + ((colorA.A - colorB.A) * (colorA.A - colorB.A)) + ((colorA.B - colorB.B) * (colorA.B - colorB.B)))

	xDH := (xDE * xDE) - (xDL * xDL) - (xDC * xDC)

	if xDH > 0 {
		xDH = math32.Sqrt(xDH)
	} else {
		xDH = 0
	}
	xSC := 1 + (0.045 * xC1)
	xSH := 1 + (0.015 * xC1)

	xDC /= xSC
	xDH /= xSH

	dE := math32.Sqrt(math32.Pow(xDL, 2) + math32.Pow(xDC, 2) + math32.Pow(xDH, 2))
	return dE
}

Leterax avatar Jun 26 '22 16:06 Leterax

Hello @Leterax !

Thanks a lot for chiming in! I'll look at this. The CIELAB color space is new to me!

qTipTip avatar Nov 22 '23 08:11 qTipTip