qTipTip / Pylette

A Python library for extracting color palettes from supplied images.
https://qtiptip.github.io/Pylette/
MIT License
88 stars 11 forks source link

CIELAB color space #2

Open Leterax opened 2 years ago

Leterax commented 2 years ago

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 commented 2 years ago

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
}
qTipTip commented 7 months ago

Hello @Leterax !

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