aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go
diff options
context:
space:
mode:
authorTulir Asokan <tulir@maunium.net>2019-01-11 23:28:47 +0200
committerTulir Asokan <tulir@maunium.net>2019-01-11 23:28:47 +0200
commit331597b9f8a7942cbcb233a328301e4d5bf94fb0 (patch)
tree5ec624585ebf66c63549a098acb6f7421f1193a7 /vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go
parent2fc3378b717f40f37f3a188b68407887242d9c06 (diff)
Switch to Go modules and make other changes
Diffstat (limited to 'vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go')
-rw-r--r--vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go185
1 files changed, 0 insertions, 185 deletions
diff --git a/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go b/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go
deleted file mode 100644
index 0154ac9..0000000
--- a/vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go
+++ /dev/null
@@ -1,185 +0,0 @@
-// Largely inspired by the descriptions in http://lab.medialab.sciences-po.fr/iwanthue/
-// but written from scratch.
-
-package colorful
-
-import (
- "fmt"
- "math"
- "math/rand"
-)
-
-// The algorithm works in L*a*b* color space and converts to RGB in the end.
-// L* in [0..1], a* and b* in [-1..1]
-type lab_t struct {
- L, A, B float64
-}
-
-type SoftPaletteSettings struct {
- // A function which can be used to restrict the allowed color-space.
- CheckColor func(l, a, b float64) bool
-
- // The higher, the better quality but the slower. Usually two figures.
- Iterations int
-
- // Use up to 160000 or 8000 samples of the L*a*b* space (and thus calls to CheckColor).
- // Set this to true only if your CheckColor shapes the Lab space weirdly.
- ManySamples bool
-}
-
-// Yeah, windows-stype Foo, FooEx, screw you golang...
-// Uses K-means to cluster the color-space and return the means of the clusters
-// as a new palette of distinctive colors. Falls back to K-medoid if the mean
-// happens to fall outside of the color-space, which can only happen if you
-// specify a CheckColor function.
-func SoftPaletteEx(colorsCount int, settings SoftPaletteSettings) ([]Color, error) {
-
- // Checks whether it's a valid RGB and also fulfills the potentially provided constraint.
- check := func(col lab_t) bool {
- c := Lab(col.L, col.A, col.B)
- return c.IsValid() && (settings.CheckColor == nil || settings.CheckColor(col.L, col.A, col.B))
- }
-
- // Sample the color space. These will be the points k-means is run on.
- dl := 0.05
- dab := 0.1
- if settings.ManySamples {
- dl = 0.01
- dab = 0.05
- }
-
- samples := make([]lab_t, 0, int(1.0/dl*2.0/dab*2.0/dab))
- for l := 0.0; l <= 1.0; l += dl {
- for a := -1.0; a <= 1.0; a += dab {
- for b := -1.0; b <= 1.0; b += dab {
- if check(lab_t{l, a, b}) {
- samples = append(samples, lab_t{l, a, b})
- }
- }
- }
- }
-
- // That would cause some infinite loops down there...
- if len(samples) < colorsCount {
- return nil, fmt.Errorf("palettegen: more colors requested (%v) than samples available (%v). Your requested color count may be wrong, you might want to use many samples or your constraint function makes the valid color space too small.", colorsCount, len(samples))
- } else if len(samples) == colorsCount {
- return labs2cols(samples), nil // Oops?
- }
-
- // We take the initial means out of the samples, so they are in fact medoids.
- // This helps us avoid infinite loops or arbitrary cutoffs with too restrictive constraints.
- means := make([]lab_t, colorsCount)
- for i := 0; i < colorsCount; i++ {
- for means[i] = samples[rand.Intn(len(samples))]; in(means, i, means[i]); means[i] = samples[rand.Intn(len(samples))] {
- }
- }
-
- clusters := make([]int, len(samples))
- samples_used := make([]bool, len(samples))
-
- // The actual k-means/medoid iterations
- for i := 0; i < settings.Iterations; i++ {
- // Reassing the samples to clusters, i.e. to their closest mean.
- // By the way, also check if any sample is used as a medoid and if so, mark that.
- for isample, sample := range samples {
- samples_used[isample] = false
- mindist := math.Inf(+1)
- for imean, mean := range means {
- dist := lab_dist(sample, mean)
- if dist < mindist {
- mindist = dist
- clusters[isample] = imean
- }
-
- // Mark samples which are used as a medoid.
- if lab_eq(sample, mean) {
- samples_used[isample] = true
- }
- }
- }
-
- // Compute new means according to the samples.
- for imean := range means {
- // The new mean is the average of all samples belonging to it..
- nsamples := 0
- newmean := lab_t{0.0, 0.0, 0.0}
- for isample, sample := range samples {
- if clusters[isample] == imean {
- nsamples++
- newmean.L += sample.L
- newmean.A += sample.A
- newmean.B += sample.B
- }
- }
- if nsamples > 0 {
- newmean.L /= float64(nsamples)
- newmean.A /= float64(nsamples)
- newmean.B /= float64(nsamples)
- } else {
- // That mean doesn't have any samples? Get a new mean from the sample list!
- var inewmean int
- for inewmean = rand.Intn(len(samples_used)); samples_used[inewmean]; inewmean = rand.Intn(len(samples_used)) {
- }
- newmean = samples[inewmean]
- samples_used[inewmean] = true
- }
-
- // But now we still need to check whether the new mean is an allowed color.
- if nsamples > 0 && check(newmean) {
- // It does, life's good (TM)
- means[imean] = newmean
- } else {
- // New mean isn't an allowed color or doesn't have any samples!
- // Switch to medoid mode and pick the closest (unused) sample.
- // This should always find something thanks to len(samples) >= colorsCount
- mindist := math.Inf(+1)
- for isample, sample := range samples {
- if !samples_used[isample] {
- dist := lab_dist(sample, newmean)
- if dist < mindist {
- mindist = dist
- newmean = sample
- }
- }
- }
- }
- }
- }
- return labs2cols(means), nil
-}
-
-// A wrapper which uses common parameters.
-func SoftPalette(colorsCount int) ([]Color, error) {
- return SoftPaletteEx(colorsCount, SoftPaletteSettings{nil, 50, false})
-}
-
-func in(haystack []lab_t, upto int, needle lab_t) bool {
- for i := 0; i < upto && i < len(haystack); i++ {
- if haystack[i] == needle {
- return true
- }
- }
- return false
-}
-
-const LAB_DELTA = 1e-6
-
-func lab_eq(lab1, lab2 lab_t) bool {
- return math.Abs(lab1.L-lab2.L) < LAB_DELTA &&
- math.Abs(lab1.A-lab2.A) < LAB_DELTA &&
- math.Abs(lab1.B-lab2.B) < LAB_DELTA
-}
-
-// That's faster than using colorful's DistanceLab since we would have to
-// convert back and forth for that. Here is no conversion.
-func lab_dist(lab1, lab2 lab_t) float64 {
- return math.Sqrt(sq(lab1.L-lab2.L) + sq(lab1.A-lab2.A) + sq(lab1.B-lab2.B))
-}
-
-func labs2cols(labs []lab_t) (cols []Color) {
- cols = make([]Color, len(labs))
- for k, v := range labs {
- cols[k] = Lab(v.L, v.A, v.B)
- }
- return cols
-}