diff options
| author | Pavlov Eugene <blooderast@gmail.com> | 2015-10-08 15:07:12 +0300 |
|---|---|---|
| committer | Pavlov Eugene <blooderast@gmail.com> | 2015-10-08 15:07:12 +0300 |
| commit | f04712a908761f4277a94f36117694edadbafaa8 (patch) | |
| tree | 0613d724d79dbf27dcbe79175a25b4a2a1dc9ca5 | |
| parent | 7bddc77bde6c2821f187246a687fb4adfcc6bbec (diff) | |
| download | xterm256-color-palette-f04712a908761f4277a94f36117694edadbafaa8.tar.gz | |
Prealfa
| -rw-r--r-- | chart.go | 409 | ||||
| -rw-r--r-- | color/color.go | 649 |
2 files changed, 1058 insertions, 0 deletions
diff --git a/chart.go b/chart.go new file mode 100644 index 0000000..dd4ac10 --- /dev/null +++ b/chart.go @@ -0,0 +1,409 @@ +package main + +import ( + "fmt" + "math" + + "github.com/kutuluk/xterm-color-chart/color" + "github.com/nsf/termbox-go" +) + +// обертка над termbox'ом +type Screen struct { + Buffer []termbox.Cell + Width int + Height int +} + +func (s *Screen) reScreen() { + s.Buffer = termbox.CellBuffer() + s.Width, s.Height = termbox.Size() +} + +func (s *Screen) Init() error { + err := termbox.Init() + if err == nil { + s.reScreen() + } + return err +} + +func (s *Screen) Flush() { + termbox.Flush() + s.reScreen() +} + +func (s *Screen) Clear(fg, bg termbox.Attribute) { + termbox.Clear(fg, bg) + s.reScreen() +} + +var screen Screen + +func termNative(c int) termbox.Attribute { + return termbox.Attribute(c + 1) +} + +func printString(x, y int, s string) { + // TODO срабатывает перенос на следующую строку при достижении правого края + // и ошибка при выходе из границ буфера снизу + offsetX := 0 + offsetY := 0 + for _, char := range s { + if char == '\n' { + offsetX = 0 + offsetY++ + } else { + screen.Buffer[x+offsetX+(y+offsetY)*screen.Width].Ch = char + offsetX++ + } + } +} + +func printLine(x, y int, s string) { + printLineAttr(x, y, s, termbox.ColorWhite, background) +} + +func printLineAttr(x, y int, s string, fg, bg termbox.Attribute) { + offsetX := 0 + offsetY := 0 + for _, char := range s { + if char == '\n' { + offsetX = 0 + offsetY++ + } else { + termbox.SetCell(x+offsetX, y+offsetY, char, fg, bg) + offsetX++ + } + } +} + +func DrawColor(x, y int, c int) { + + Grayscale := []byte{ + 16, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 231, + } + + for xx := 0; xx < 13; xx++ { + termbox.SetCell(x+xx, y, ' ', termbox.ColorBlack, termbox.Attribute(c)) + } + + // round(36 * (r * 5) + 6 * (g * 5) + (b * 5) + 16) + // https://gist.github.com/MicahElliott/719710 + + v := int(0.2125*float64(color.XtermRGBPalette[c].R) + 0.7154*float64(color.XtermRGBPalette[c].G) + 0.0721*float64(color.XtermRGBPalette[c].B)) + + fg := termbox.ColorRed + // if v < 128 { + // fg = termbox.ColorWhite + //fg = termbox.Attribute(231) + delta := 64 + center := 128 + if (v > (center - delta)) && (v <= center) { + fg = termbox.Attribute(Grayscale[25]) + } else if (v > center) && (v < (center + delta)) { + fg = termbox.Attribute(Grayscale[0]) + } else { + //fg = termbox.Attribute(Grayscale[12]) + fg = termbox.Attribute(Grayscale[25-int(v/11)]) + } + + printLineAttr(x+1, y, fmt.Sprintf("%03d #%s", int(c), color.XtermRGBPalette[c]), fg, termbox.Attribute(c)) +} + +func DrawBox(left, top, d, l int) { + var approxer string + switch currentComparer { + case 0: + approxer = "CIE76" + case 1: + approxer = "CIE94" + case 2: + approxer = "CIE2000" + } + + r := d / 2 + for y := 0; y < d; y++ { + for x := 0; x < d; x++ { + a := 100.0 * float64(x-r) / float64(r) + b := -100.0 * float64(y-r) / float64(r) + _, c := color.Approximate(color.LabColor{float64(l), a, b}, comparers[currentComparer]) + //termbox.SetCell(x*2+left, y+top, ' ', background, termbox.Attribute(color)) + //termbox.SetCell(x*2+left+1, y+top, ' ', background, termbox.Attribute(color)) + fg := int(background) + if y == 1 || y == 2 || y == d-2 { + fgLab := color.XtermRGBPalette[c-17].Lab() + if l > 50 { + fgLab.L -= 50 + //fgLab.L = 20 + } else { + fgLab.L += 50 + //fgLab.L = 70 + } + _, fg = color.Approximate(fgLab, color.DeltaCIE2000{}) + } + termbox.SetCell(x*2+left, y+top, ' ', termbox.Attribute(fg), termbox.Attribute(c)) + termbox.SetCell(x*2+left+1, y+top, ' ', termbox.Attribute(fg), termbox.Attribute(c)) + } + + } + + labLabel := "CIE L*a*b color space" + if len(labLabel)+4 < d*2 { + printString(left+2, top+1, labLabel) + printString(left+2, top+2, fmt.Sprintf("Lightness = %d%%", l)) + } + //printString(left+2, top+4, fmt.Sprintln("Press Up/Down key for change")) + + approxerLabel := fmt.Sprintf("Approximate by %s algorithm", approxer) + if len(approxerLabel)+4 <= d*2 { + printString(left+2, top+d-2, approxerLabel) + } + //printString(left+2, top+7, fmt.Sprintln("Press Left/Right key for change")) +} + +func DrawCircle(left, top, r int, l float64) { + + var ch [2]rune + point := func(xb, yb int) { + xl := xb / r + yl := yb / r + distance, color := color.Approximate(color.LChColor{l, math.Sqrt(float64(xl*xl) + float64(yl*yl)), 180.0 * math.Atan2(float64(xl), float64(yl))}, comparers[currentComparer]) + //distance, color := color.Palette(color.LabColor{l, float64(xb * 120.0 / r), float64(-yb * 120.0 / r)}, color.DeltaCIE94{}) + if distance < 50 { + ch[0] = ' ' + ch[1] = ' ' + } else { + //ch[0] = '¤' + //ch[1] = '¤' + ch[0] = '(' + ch[1] = ')' + } + termbox.SetCell((xb+r)*2+left, yb+r+top, ch[0], termbox.Attribute(0), termbox.Attribute(color)) + termbox.SetCell((xb+r)*2+left+1, yb+r+top, ch[1], termbox.Attribute(0), termbox.Attribute(color)) + } + + // алгоритм Брезенхэма + line := func(x, y1, y2 int) { + for yf := y1; yf <= y2; yf++ { + point(x, yf) + } + } + + x := 0 + y := r + d := 3 - 2*r + for x <= y { + line(x, -y, y) + line(-x, -y, y) + line(y, -x, x) + line(-y, -x, x) + if d < 0 { + d += 4*x + 6 + } else { + d += 4*(x-y) + 10 + y-- + } + x++ + } +} + +func DrawColorCell(x, y, c int) { + termbox.SetCell(x, y, ' ', termbox.Attribute(1), termbox.Attribute(c)) + termbox.SetCell(x+1, y, ' ', termbox.Attribute(1), termbox.Attribute(c)) +} + +// left, top - левый верхний угол отрисовки палитры +// row - количество кубов 6x6 по горизонтали +// num - признак отрисовки номеров цветов +func DrawPalette(left, top, row int, num bool) { + var numOffset int + if num { + numOffset = 4 + } + + //bh := [2]termbox.Attribute{237, background} + bh := [2]termbox.Attribute{236, background} + //th := [2]termbox.Attribute{232, 241} + th := [2]termbox.Attribute{243, 243} + + cc := 1 + + printLineAttr(left, top, fmt.Sprintln("16 system colors"), text, background) + + printLineAttr(left, top+2, fmt.Sprintf("%3d ", cc-1), th[1], bh[1]) + printLineAttr(left+numOffset+16*2, top+2, fmt.Sprintf(" %-3d", cc+14), th[1], bh[1]) + + for x := 0; x < 16; x++ { + DrawColorCell(left+numOffset+x*2, top+2, cc) + cc++ + } + + padding := 1 + if num { + // padding = 0 + } + + printLineAttr(left, top+4, fmt.Sprintln("216 colors of color cube 6x6x6"), text, background) + col := int(6 / row) // ширина + for l := 0; l < row; l++ { // l - вертикальный счетчик + for k := 0; k < col; k++ { // k - горизонтальный счетчик + for y := 0; y < 6; y++ { + h := y % 2 + if h != 0 { + h = 1 + } + printLineAttr(left+k*numOffset*2+k*(6+padding)*2, top+l*(6+padding)+y+6, fmt.Sprintf("%3d ", cc-1), th[h], bh[h]) + printLineAttr(left+k*numOffset*2+k*(6+padding)*2+16, top+l*(6+padding)+y+6, fmt.Sprintf(" %-3d", cc+4), th[h], bh[h]) + for x := 0; x < 6; x++ { + DrawColorCell(left+numOffset+k*(6+padding)*2+k*numOffset*2+x*2, top+l*(6+padding)+y+6, cc) + cc++ + } + } + } + } + + printLineAttr(left, top+row*7+6, fmt.Sprintln("24 grayscale colors"), text, background) + for y := 0; y < 2; y++ { + for x := 0; x < 12; x++ { + DrawColorCell(left+numOffset+x*2, top+row*7+y+8, cc) + cc++ + } + } + + printLineAttr(left, top+row*7+8, fmt.Sprintf("%3d ", 232), th[1], bh[1]) + printLineAttr(left+28, top+row*7+8, fmt.Sprintf(" %-3d", 243), th[1], bh[1]) + printLineAttr(left, top+row*7+9, fmt.Sprintf("%3d ", 244), th[1], bh[1]) + printLineAttr(left+28, top+row*7+9, fmt.Sprintf(" %-3d ", 255), th[1], bh[1]) +} + +func Fill(x1, y1, x2, y2 int, fg, bg termbox.Attribute) { + for y := y1; y <= y2; y++ { + for x := x1; x <= x2; x++ { + termbox.SetCell(x, y, ' ', fg, bg) + } + } +} + +var ( + text, background termbox.Attribute + lightness int + helpVisible bool +) + +func DrawChart() { + screen.Clear(text, background) + //Fill(0, 0, screen.Width-1, screen.Height-1, text, background) + + Fill(0, 0, screen.Width-1, 0, background, background+8) + printString(2, 0, "XTerm 256 color palette chart") + printString(screen.Width-19, 0, "F1 Help F10 Exit") + printLineAttr(screen.Width-19, 0, "F1", background+20, background+8) + printLineAttr(screen.Width-10, 0, "F10", background+20, background+8) + + paletteSize := 42 + + boxSize := screen.Height - 3 + w := int((screen.Width - paletteSize - 6) / 2) + if w < boxSize { + boxSize = w + } + + DrawBox(2, 2, boxSize, lightness) + // http://snag.gy/XMro8.jpg - картинка для сравнения + + helpText := []string{ + "Control keys:", + "", + "F1 - show/hide this help", + "Up/Down - change lightness", + "Left/Right - change approximation method", + "", + "CIE76 - fastest", + "CIE94 - middle", + "CIE2000 - slowest", + "", + "", + } + + xc := boxSize*2 + 4 + if xc < 2 { + xc = 2 + } + yc := 2 + + if helpVisible { + for i, line := range helpText { + printString(xc, yc+i, line) + } + } else { + DrawPalette(xc, yc, 3, true) + } + + screen.Flush() +} + +var comparers []color.Comparer +var currentComparer int + +func main() { + + err := screen.Init() + if err != nil { + panic(err) + } + defer termbox.Close() + termbox.SetOutputMode(termbox.Output256) + //termbox.SetInputMode(termbox.InputEsc) + + background = termNative(234) + text = background + 12 + lightness = 100 + + comparers = append(comparers, color.DeltaCIE76{}, color.DeltaCIE94{}, color.DeltaCIE2000{}) + + //size := int(h/2 - 3) + //DrawCircle(2, 1, size, 1.0) + + DrawChart() + + for loop := true; loop; { + switch ev := termbox.PollEvent(); ev.Type { + case termbox.EventKey: + switch ev.Key { + case termbox.KeyCtrlQ, termbox.KeyF10: + loop = false + case termbox.KeyCtrlH, termbox.KeyF1: + helpVisible = !helpVisible + DrawChart() + case termbox.KeyArrowUp: + if lightness < 100 { + lightness = lightness + 10.0 + DrawChart() + } + case termbox.KeyArrowDown: + if lightness > 0 { + lightness = lightness - 10.0 + DrawChart() + } + case termbox.KeyArrowLeft: + if currentComparer > 0 { + currentComparer-- + } else { + currentComparer = len(comparers) - 1 + } + DrawChart() + case termbox.KeyArrowRight: + if currentComparer < 2 { + currentComparer++ + } else { + currentComparer = 0 + } + DrawChart() + } + case termbox.EventResize: + DrawChart() + } + } +} diff --git a/color/color.go b/color/color.go new file mode 100644 index 0000000..a3cbd16 --- /dev/null +++ b/color/color.go @@ -0,0 +1,649 @@ +package color + +import ( + "fmt" + "math" +) + +const ( + epsilon float64 = 216.0 / 24389.0 + kappa float64 = 24389.0 / 27.0 +) + +var ( + // reference white in XYZ coordinates + // цветовые координаты излучателя белого света в пространстве XYZ + whitePoints = map[string][3]float64{ + "A": {1.09850, 1.0, 0.35585}, + "B": {0.99072, 1.0, 0.85223}, + "C": {0.98074, 1.0, 1.18232}, + "D50": {0.96422, 1.0, 0.82521}, + "D55": {0.95682, 1.0, 0.92149}, + "D65": {0.95047, 1.0, 1.08883}, + "D75": {0.94972, 1.0, 1.22638}, + "E": {1.00000, 1.0, 1.00000}, + "F2": {0.99186, 1.0, 0.67393}, + "F7": {0.95041, 1.0, 1.08747}, + "F11": {1.00962, 1.0, 0.64350}, + } + + // sRGB to XYZ conversion matrix + convertMatrix = [3][3]float64{ + {0.4124564, 0.3575761, 0.1804375}, + {0.2126729, 0.7151522, 0.0721750}, + {0.0193339, 0.1191920, 0.9503041}, + } + + // XYZ to sRGB conversion matrix + inverseMatrix = [3][3]float64{ + {3.2404542, -1.5371385, -0.4985314}, + {-0.9692660, 1.8760108, 0.0415560}, + {0.0556434, -0.2040259, 1.0572252}, + } +) + +func sqr(v float64) float64 { + return v * v +} + +type RGBColor struct { + R, G, B int // RGB-компоненты [0..255] +} + +func (rgb RGBColor) String() string { + return fmt.Sprintf("RGB:{%d %d %d}", rgb.R, rgb.G, rgb.B) +} + +func (rgb RGBColor) RGB() RGBColor { + return rgb +} + +func (rgb RGBColor) XYZ() XYZColor { + + f := func(n float64) float64 { + if n > 0.04045 { + // math.Pow(base, exponent) = math.Exp(math.Log(base) * exponent) + //return math.Exp(2.4 * math.Log((n+0.055)/1.055)) + return math.Pow(((n + 0.055) / 1.055), 2.4) + } else { + return n / 12.92 + } + } + + r := f(float64(rgb.R) / 255.0) + g := f(float64(rgb.G) / 255.0) + b := f(float64(rgb.B) / 255.0) + + // + // [X] [Xr Xg Xb] [R] + // [Y] = [Yr Yg Yb] x [G] + // [Z] [Zr Zg Zb] [B] + // + //Observer. = 2°, Illuminant = D65 + return XYZColor{ + r*0.4124 + g*0.3576 + b*0.1805, + r*0.2126 + g*0.7152 + b*0.0722, + r*0.0193 + g*0.1192 + b*0.9505, + } +} + +func (rgb RGBColor) Lab() LabColor { + return rgb.XYZ().Lab() +} + +type XYZColor struct { + X, Y, Z float64 +} + +func (xyz XYZColor) String() string { + return fmt.Sprintf("XYZ:{%4.7f %4.7f %4.7f}", xyz.X, xyz.Y, xyz.Z) +} + +func (xyz XYZColor) RGB() RGBColor { + // TODO!!! + return RGBColor{0, 0, 0} +} + +func (xyz XYZColor) XYZ() XYZColor { + return xyz +} + +// ok +func (xyz XYZColor) Lab() LabColor { + + f := func(n float64) float64 { + if n > epsilon { + return math.Pow(n, 1.0/3.0) + } else { + return (kappa*n + 16.0) / 116.0 + } + } + + // Observer= 2°, Illuminant= D65 + whitePoint := whitePoints["D65"] + + x := f(xyz.X / whitePoint[0]) + y := f(xyz.Y / whitePoint[1]) + z := f(xyz.Z / whitePoint[2]) + + return LabColor{ + (116.0 * y) - 16.0, + 500.0 * (x - y), + 200.0 * (y - z), + } +} + +type LabColor struct { + L, A, B float64 +} + +func (lab LabColor) String() string { + return fmt.Sprintf("Lab:{%4.7f %4.7f %4.7f}", lab.L, lab.A, lab.B) +} + +func (lab LabColor) RGB() RGBColor { + return lab.XYZ().RGB() +} + +// ok +func (lab LabColor) XYZ() XYZColor { + + f := func(n float64) float64 { + if n3 := n * n * n; n3 > epsilon { + return n3 + } else { + return (n*116.0 - 16.0) / kappa + } + } + + y := (lab.L + 16.0) / 116.0 + x := 0.002*lab.A + y + z := y - 0.005*lab.B + + // Observer= 2°, Illuminant= D65 + whitePoint := whitePoints["D65"] + + return XYZColor{ + f(x) * whitePoint[0], + f(y) * whitePoint[1], + f(z) * whitePoint[2], + } +} + +func (lab LabColor) Lab() LabColor { + return lab +} + +// ok +func (lab LabColor) LCh() LChColor { + c := math.Sqrt(sqr(lab.A) + sqr(lab.B)) + h := 180.0 * math.Atan2(lab.B, lab.A) / math.Pi + if h < 0.0 { + h += 360.0 + } + return LChColor{lab.L, c, h} +} + +type LChColor struct { + L, C, H float64 +} + +func (lch LChColor) RGB() RGBColor { + return lch.Lab().XYZ().RGB() +} + +func (lch LChColor) XYZ() XYZColor { + return lch.Lab().XYZ() +} + +// ok +func (lch LChColor) Lab() LabColor { + a := lch.C * math.Cos(lch.H*math.Pi/180.0) + b := lch.C * math.Sin(lch.H*math.Pi/180.0) + return LabColor{lch.L, a, b} +} + +type Colorer interface { + RGB() RGBColor + XYZ() XYZColor + Lab() LabColor +} + +type Comparer interface { + Compare(c1, c2 Colorer) float64 +} + +type DeltaCIE76 struct{} + +// DeltaCIE76.Compare computes the CIE76 color difference. +// This is just Euclidean distance in Lab space, and therefore quite fast, +// though it exhibits perceptual uniformity issues especially in the blue and desaturated regions. +func (DeltaCIE76) Compare(c1, c2 Colorer) float64 { + lab1 := c1.Lab() + lab2 := c2.Lab() + return math.Sqrt(sqr(lab1.L-lab2.L) + sqr(lab1.A-lab2.A) + sqr(lab1.B-lab2.B)) +} + +type DeltaCIE94 struct{} + +// KLCh94 is a struct for weighting factors for CIE94 ΔE calculation. +type KLCh94 struct { + KL, KC, Kh, K1, K2 float64 +} + +// KLCH94GraphicArts are the weighting factors for CIE94 used for most uses except textiles. +var KLCH94GraphicArts = KLCh94{1, 1, 1, 0.045, 0.015} + +// KLCH94Textiles are the weighting factors for CIE94 used for textiles. +var KLCH94Textiles = KLCh94{2, 1, 1, 0.048, 0.014} + +//var KLCH94 = &KLCH94GraphicArts + +// DeltaCIE94.Compare computes the CIE94 color difference of two L*a*b* colors. +// This is a distance calculation with the addition of weighting factors specified by klch. +func (DeltaCIE94) Compare(c1, c2 Colorer) float64 { + lab1 := c1.Lab() + lab2 := c2.Lab() + + dL := sqr(lab1.L - lab2.L) + + c1ab := math.Sqrt(sqr(lab1.A) + sqr(lab1.B)) + c2ab := math.Sqrt(sqr(lab2.A) + sqr(lab2.B)) + + dC := sqr(c1ab - c2ab) + + dH := sqr(lab1.A-lab2.A) + sqr(lab1.B-lab2.B) - dC + + KLCH94 := &KLCH94GraphicArts + + sC := 1.0 + KLCH94.K1*c1ab + sH := 1.0 + KLCH94.K2*c1ab + + return math.Sqrt( + dL/sqr(KLCH94.KL) + + dC/sqr(KLCH94.KC*sC) + + dH/sqr(KLCH94.Kh*sH)) +} + +type DeltaCIE2000 struct{} + +type KLCh struct { + KL, KC, Kh float64 +} + +// KLCHDefault is the most commonly used set of weighting parameters for CIEDE2000 +var KLCH = KLCh{1, 1, 1} + +// CIE2000 computes the CIEDE2000 delta-E for two L*a*b* space color coordinates +// klch is for configuring the weighting factors, but this almost always should be KLCHDefault +// Note that this implementation will exhibit slightly different behavior around the discontinuities +// of the function (these are grey colors) compared to Java and most C runtimes. The golang atan +// function has different accuracy characteristics compared to most Unix platforms and Java Strict math +func (DeltaCIE2000) Compare(col1, col2 Colorer) float64 { + lab1 := col1.Lab() + lab2 := col2.Lab() + + lBarPrime := (lab1.L + lab2.L) * 0.5 + c1 := math.Sqrt(lab1.A*lab1.A + lab1.B*lab1.B) + c2 := math.Sqrt(lab2.A*lab2.A + lab2.B*lab2.B) + cBar := (c1 + c2) * 0.5 + + cBar7 := cBar * cBar * cBar + cBar7 *= cBar7 * cBar + g := 0.5 * (1.0 - math.Sqrt(cBar7/(cBar7+6103515625.0))) // 25**7 + + a1Prime := (1.0 + g) * lab1.A + a2Prime := (1.0 + g) * lab2.A + + c1Prime := math.Sqrt(a1Prime*a1Prime + lab1.B*lab1.B) + c2Prime := math.Sqrt(a2Prime*a2Prime + lab2.B*lab2.B) + + cBarPrime := (c1Prime + c2Prime) * 0.5 + + h1Prime := math.Atan2(lab1.B, a1Prime) + if h1Prime < 0 { + h1Prime += 2 * math.Pi + + } + h2Prime := math.Atan2(lab2.B, a2Prime) + if h2Prime < 0 { + h2Prime += 2 * math.Pi + + } + + hBarPrime := (h1Prime + h2Prime) * 0.5 + dhPrime := h2Prime - h1Prime + if math.Abs(dhPrime) > math.Pi { + hBarPrime += math.Pi + if h2Prime <= h1Prime { + dhPrime += 2 * math.Pi + + } else { + dhPrime -= 2 * math.Pi + + } + + } + + t := 1.0 - + 0.17*math.Cos(hBarPrime-math.Pi/6) + + 0.24*math.Cos(2.0*hBarPrime) + + 0.32*math.Cos(3.0*hBarPrime+math.Pi/30) - + 0.20*math.Cos(4.0*hBarPrime-63.0*math.Pi/180) + + dLPrime := lab2.L - lab1.L + dCPrime := c2Prime - c1Prime + dHPrime := 2.0 * math.Sqrt(c1Prime*c2Prime) * math.Sin(dhPrime/2.0) + + lBarPrimeM50Sqr := lBarPrime - 50.0 + lBarPrimeM50Sqr *= lBarPrimeM50Sqr + sL := 1.0 + (0.015*lBarPrimeM50Sqr)/math.Sqrt(20.0+lBarPrimeM50Sqr) + sC := 1.0 + 0.045*cBarPrime + sH := 1.0 + 0.015*cBarPrime*t + + hBarPrimeM := (180/math.Pi*hBarPrime - 275.0) / 25.0 + dTheta := math.Pi / 6 * math.Exp(-hBarPrimeM*hBarPrimeM) + cBarPrime7 := cBarPrime * cBarPrime * cBarPrime + cBarPrime7 *= cBarPrime7 * cBarPrime + rC := math.Sqrt(cBarPrime7 / (cBarPrime7 + 6103515625.0)) + rT := -2.0 * rC * math.Sin(2.0*dTheta) + + return math.Sqrt( + sqr(dLPrime/(KLCH.KL*sL)) + + sqr(dCPrime/(KLCH.KC*sC)) + + sqr(dHPrime/(KLCH.Kh*sH)) + + (dCPrime/(KLCH.KC*sC))*(dHPrime/(KLCH.Kh*sH))*rT) + +} + +func Approximate(color Colorer, comparer Comparer) (float64, int) { + best := 0 + bestdist := 10000000.0 + for i, applicant := range XtermLabPalette { + if dist := comparer.Compare(color, applicant); dist < bestdist { + best, bestdist = i, dist + } + } + return bestdist, best + 17 +} + +//color = round(36 * (r * 5) + 6 * (g * 5) + (b * 5) + 16) +func HackApproximate(color Colorer) int { + c := color.RGB() + r := float64(c.R / 255) + g := float64(c.B / 255) + b := float64(c.G / 255) + return int(36*(r*5)+6*(g*5)+b*5) + 17 +} + +var ( + /* + { + {R: 0x00, G: 0x00, B: 0x00}, // "#000000" 16 index of palette + {R: 0x00, G: 0x00, B: 0x5F}, // "#00005f" + {R: 0x00, G: 0x00, B: 0x87}, // "#000087" + {R: 0x00, G: 0x00, B: 0xAF}, // "#0000af" + {R: 0x00, G: 0x00, B: 0xD7}, // "#0000d7" + {R: 0x00, G: 0x00, B: 0xFF}, // "#0000ff" + {R: 0x00, G: 0x5F, B: 0x00}, // "#005f00" + {R: 0x00, G: 0x5F, B: 0x5F}, // "#005f5f" + {R: 0x00, G: 0x5F, B: 0x87}, // "#005f87" + {R: 0x00, G: 0x5F, B: 0xAF}, // "#005faf" + {R: 0x00, G: 0x5F, B: 0xD7}, // "#005fd7" + {R: 0x00, G: 0x5F, B: 0xFF}, // "#005fff" + {R: 0x00, G: 0x87, B: 0x00}, // "#008700" + {R: 0x00, G: 0x87, B: 0x5F}, // "#00875f" + {R: 0x00, G: 0x87, B: 0x87}, // "#008787" + {R: 0x00, G: 0x87, B: 0xAF}, // "#0087af" + {R: 0x00, G: 0x87, B: 0xD7}, // "#0087d7" + {R: 0x00, G: 0x87, B: 0xFF}, // "#0087ff" + {R: 0x00, G: 0xAF, B: 0x00}, // "#00af00" + {R: 0x00, G: 0xAF, B: 0x5F}, // "#00af5f" + {R: 0x00, G: 0xAF, B: 0x87}, // "#00af87" + {R: 0x00, G: 0xAF, B: 0xAF}, // "#00afaf" + {R: 0x00, G: 0xAF, B: 0xD7}, // "#00afd7" + {R: 0x00, G: 0xAF, B: 0xFF}, // "#00afff" + {R: 0x00, G: 0xD7, B: 0x00}, // "#00d700" + {R: 0x00, G: 0xD7, B: 0x5F}, // "#00d75f" + {R: 0x00, G: 0xD7, B: 0x87}, // "#00d787" + {R: 0x00, G: 0xD7, B: 0xAF}, // "#00d7af" + {R: 0x00, G: 0xD7, B: 0xD7}, // "#00d7d7" + {R: 0x00, G: 0xD7, B: 0xFF}, // "#00d7ff" + {R: 0x00, G: 0xFF, B: 0x00}, // "#00ff00" + {R: 0x00, G: 0xFF, B: 0x5F}, // "#00ff5f" + {R: 0x00, G: 0xFF, B: 0x87}, // "#00ff87" + {R: 0x00, G: 0xFF, B: 0xAF}, // "#00ffaf" + {R: 0x00, G: 0xFF, B: 0xD7}, // "#00ffd7" + {R: 0x00, G: 0xFF, B: 0xFF}, // "#00ffff" + {R: 0x5F, G: 0x00, B: 0x00}, // "#5f0000" + {R: 0x5F, G: 0x00, B: 0x5F}, // "#5f005f" + {R: 0x5F, G: 0x00, B: 0x87}, // "#5f0087" + {R: 0x5F, G: 0x00, B: 0xAF}, // "#5f00af" + {R: 0x5F, G: 0x00, B: 0xD7}, // "#5f00d7" + {R: 0x5F, G: 0x00, B: 0xFF}, // "#5f00ff" + {R: 0x5F, G: 0x5F, B: 0x00}, // "#5f5f00" + {R: 0x5F, G: 0x5F, B: 0x5F}, // "#5f5f5f" + {R: 0x5F, G: 0x5F, B: 0x87}, // "#5f5f87" + {R: 0x5F, G: 0x5F, B: 0xAF}, // "#5f5faf" + {R: 0x5F, G: 0x5F, B: 0xD7}, // "#5f5fd7" + {R: 0x5F, G: 0x5F, B: 0xFF}, // "#5f5fff" + {R: 0x5F, G: 0x87, B: 0x00}, // "#5f8700" + {R: 0x5F, G: 0x87, B: 0x5F}, // "#5f875f" + {R: 0x5F, G: 0x87, B: 0x87}, // "#5f8787" + {R: 0x5F, G: 0x87, B: 0xAF}, // "#5f87af" + {R: 0x5F, G: 0x87, B: 0xD7}, // "#5f87d7" + {R: 0x5F, G: 0x87, B: 0xFF}, // "#5f87ff" + {R: 0x5F, G: 0xAF, B: 0x00}, // "#5faf00" + {R: 0x5F, G: 0xAF, B: 0x5F}, // "#5faf5f" + {R: 0x5F, G: 0xAF, B: 0x87}, // "#5faf87" + {R: 0x5F, G: 0xAF, B: 0xAF}, // "#5fafaf" + {R: 0x5F, G: 0xAF, B: 0xD7}, // "#5fafd7" + {R: 0x5F, G: 0xAF, B: 0xFF}, // "#5fafff" + {R: 0x5F, G: 0xD7, B: 0x00}, // "#5fd700" + {R: 0x5F, G: 0xD7, B: 0x5F}, // "#5fd75f" + {R: 0x5F, G: 0xD7, B: 0x87}, // "#5fd787" + {R: 0x5F, G: 0xD7, B: 0xAF}, // "#5fd7af" + {R: 0x5F, G: 0xD7, B: 0xD7}, // "#5fd7d7" + {R: 0x5F, G: 0xD7, B: 0xFF}, // "#5fd7ff" + {R: 0x5F, G: 0xFF, B: 0x00}, // "#5fff00" + {R: 0x5F, G: 0xFF, B: 0x5F}, // "#5fff5f" + {R: 0x5F, G: 0xFF, B: 0x87}, // "#5fff87" + {R: 0x5F, G: 0xFF, B: 0xAF}, // "#5fffaf" + {R: 0x5F, G: 0xFF, B: 0xD7}, // "#5fffd7" + {R: 0x5F, G: 0xFF, B: 0xFF}, // "#5fffff" + {R: 0x87, G: 0x00, B: 0x00}, // "#870000" + {R: 0x87, G: 0x00, B: 0x5F}, // "#87005f" + {R: 0x87, G: 0x00, B: 0x87}, // "#870087" + {R: 0x87, G: 0x00, B: 0xAF}, // "#8700af" + {R: 0x87, G: 0x00, B: 0xD7}, // "#8700d7" + {R: 0x87, G: 0x00, B: 0xFF}, // "#8700ff" + {R: 0x87, G: 0x5F, B: 0x00}, // "#875f00" + {R: 0x87, G: 0x5F, B: 0x5F}, // "#875f5f" + {R: 0x87, G: 0x5F, B: 0x87}, // "#875f87" + {R: 0x87, G: 0x5F, B: 0xAF}, // "#875faf" + {R: 0x87, G: 0x5F, B: 0xD7}, // "#875fd7" + {R: 0x87, G: 0x5F, B: 0xFF}, // "#875fff" + {R: 0x87, G: 0x87, B: 0x00}, // "#878700" + {R: 0x87, G: 0x87, B: 0x5F}, // "#87875f" + {R: 0x87, G: 0x87, B: 0x87}, // "#878787" + {R: 0x87, G: 0x87, B: 0xAF}, // "#8787af" + {R: 0x87, G: 0x87, B: 0xD7}, // "#8787d7" + {R: 0x87, G: 0x87, B: 0xFF}, // "#8787ff" + {R: 0x87, G: 0xAF, B: 0x00}, // "#87af00" + {R: 0x87, G: 0xAF, B: 0x5F}, // "#87af5f" + {R: 0x87, G: 0xAF, B: 0x87}, // "#87af87" + {R: 0x87, G: 0xAF, B: 0xAF}, // "#87afaf" + {R: 0x87, G: 0xAF, B: 0xD7}, // "#87afd7" + {R: 0x87, G: 0xAF, B: 0xFF}, // "#87afff" + {R: 0x87, G: 0xD7, B: 0x00}, // "#87d700" + {R: 0x87, G: 0xD7, B: 0x5F}, // "#87d75f" + {R: 0x87, G: 0xD7, B: 0x87}, // "#87d787" + {R: 0x87, G: 0xD7, B: 0xAF}, // "#87d7af" + {R: 0x87, G: 0xD7, B: 0xD7}, // "#87d7d7" + {R: 0x87, G: 0xD7, B: 0xFF}, // "#87d7ff" + {R: 0x87, G: 0xFF, B: 0x00}, // "#87ff00" + {R: 0x87, G: 0xFF, B: 0x5F}, // "#87ff5f" + {R: 0x87, G: 0xFF, B: 0x87}, // "#87ff87" + {R: 0x87, G: 0xFF, B: 0xAF}, // "#87ffaf" + {R: 0x87, G: 0xFF, B: 0xD7}, // "#87ffd7" + {R: 0x87, G: 0xFF, B: 0xFF}, // "#87ffff" + {R: 0xAF, G: 0x00, B: 0x00}, // "#af0000" + {R: 0xAF, G: 0x00, B: 0x5F}, // "#af005f" + {R: 0xAF, G: 0x00, B: 0x87}, // "#af0087" + {R: 0xAF, G: 0x00, B: 0xAF}, // "#af00af" + {R: 0xAF, G: 0x00, B: 0xD7}, // "#af00d7" + {R: 0xAF, G: 0x00, B: 0xFF}, // "#af00ff" + {R: 0xAF, G: 0x5F, B: 0x00}, // "#af5f00" + {R: 0xAF, G: 0x5F, B: 0x5F}, // "#af5f5f" + {R: 0xAF, G: 0x5F, B: 0x87}, // "#af5f87" + {R: 0xAF, G: 0x5F, B: 0xAF}, // "#af5faf" + {R: 0xAF, G: 0x5F, B: 0xD7}, // "#af5fd7" + {R: 0xAF, G: 0x5F, B: 0xFF}, // "#af5fff" + {R: 0xAF, G: 0x87, B: 0x00}, // "#af8700" + {R: 0xAF, G: 0x87, B: 0x5F}, // "#af875f" + {R: 0xAF, G: 0x87, B: 0x87}, // "#af8787" + {R: 0xAF, G: 0x87, B: 0xAF}, // "#af87af" + {R: 0xAF, G: 0x87, B: 0xD7}, // "#af87d7" + {R: 0xAF, G: 0x87, B: 0xFF}, // "#af87ff" + {R: 0xAF, G: 0xAF, B: 0x00}, // "#afaf00" + {R: 0xAF, G: 0xAF, B: 0x5F}, // "#afaf5f" + {R: 0xAF, G: 0xAF, B: 0x87}, // "#afaf87" + {R: 0xAF, G: 0xAF, B: 0xAF}, // "#afafaf" + {R: 0xAF, G: 0xAF, B: 0xD7}, // "#afafd7" + {R: 0xAF, G: 0xAF, B: 0xFF}, // "#afafff" + {R: 0xAF, G: 0xD7, B: 0x00}, // "#afd700" + {R: 0xAF, G: 0xD7, B: 0x5F}, // "#afd75f" + {R: 0xAF, G: 0xD7, B: 0x87}, // "#afd787" + {R: 0xAF, G: 0xD7, B: 0xAF}, // "#afd7af" + {R: 0xAF, G: 0xD7, B: 0xD7}, // "#afd7d7" + {R: 0xAF, G: 0xD7, B: 0xFF}, // "#afd7ff" + {R: 0xAF, G: 0xFF, B: 0x00}, // "#afff00" + {R: 0xAF, G: 0xFF, B: 0x5F}, // "#afff5f" + {R: 0xAF, G: 0xFF, B: 0x87}, // "#afff87" + {R: 0xAF, G: 0xFF, B: 0xAF}, // "#afffaf" + {R: 0xAF, G: 0xFF, B: 0xD7}, // "#afffd7" + {R: 0xAF, G: 0xFF, B: 0xFF}, // "#afffff" + {R: 0xD7, G: 0x00, B: 0x00}, // "#d70000" + {R: 0xD7, G: 0x00, B: 0x5F}, // "#d7005f" + {R: 0xD7, G: 0x00, B: 0x87}, // "#d70087" + {R: 0xD7, G: 0x00, B: 0xAF}, // "#d700af" + {R: 0xD7, G: 0x00, B: 0xD7}, // "#d700d7" + {R: 0xD7, G: 0x00, B: 0xFF}, // "#d700ff" + {R: 0xD7, G: 0x5F, B: 0x00}, // "#d75f00" + {R: 0xD7, G: 0x5F, B: 0x5F}, // "#d75f5f" + {R: 0xD7, G: 0x5F, B: 0x87}, // "#d75f87" + {R: 0xD7, G: 0x5F, B: 0xAF}, // "#d75faf" + {R: 0xD7, G: 0x5F, B: 0xD7}, // "#d75fd7" + {R: 0xD7, G: 0x5F, B: 0xFF}, // "#d75fff" + {R: 0xD7, G: 0x87, B: 0x00}, // "#d78700" + {R: 0xD7, G: 0x87, B: 0x5F}, // "#d7875f" + {R: 0xD7, G: 0x87, B: 0x87}, // "#d78787" + {R: 0xD7, G: 0x87, B: 0xAF}, // "#d787af" + {R: 0xD7, G: 0x87, B: 0xD7}, // "#d787d7" + {R: 0xD7, G: 0x87, B: 0xFF}, // "#d787ff" + {R: 0xD7, G: 0xAF, B: 0x00}, // "#d7af00" + {R: 0xD7, G: 0xAF, B: 0x5F}, // "#d7af5f" + {R: 0xD7, G: 0xAF, B: 0x87}, // "#d7af87" + {R: 0xD7, G: 0xAF, B: 0xAF}, // "#d7afaf" + {R: 0xD7, G: 0xAF, B: 0xD7}, // "#d7afd7" + {R: 0xD7, G: 0xAF, B: 0xFF}, // "#d7afff" + {R: 0xD7, G: 0xD7, B: 0x00}, // "#d7d700" + {R: 0xD7, G: 0xD7, B: 0x5F}, // "#d7d75f" + {R: 0xD7, G: 0xD7, B: 0x87}, // "#d7d787" + {R: 0xD7, G: 0xD7, B: 0xAF}, // "#d7d7af" + {R: 0xD7, G: 0xD7, B: 0xD7}, // "#d7d7d7" + {R: 0xD7, G: 0xD7, B: 0xFF}, // "#d7d7ff" + {R: 0xD7, G: 0xFF, B: 0x00}, // "#d7ff00" + {R: 0xD7, G: 0xFF, B: 0x5F}, // "#d7ff5f" + {R: 0xD7, G: 0xFF, B: 0x87}, // "#d7ff87" + {R: 0xD7, G: 0xFF, B: 0xAF}, // "#d7ffaf" + {R: 0xD7, G: 0xFF, B: 0xD7}, // "#d7ffd7" + {R: 0xD7, G: 0xFF, B: 0xFF}, // "#d7ffff" + {R: 0xFF, G: 0x00, B: 0x00}, // "#ff0000" + {R: 0xFF, G: 0x00, B: 0x5F}, // "#ff005f" + {R: 0xFF, G: 0x00, B: 0x87}, // "#ff0087" + {R: 0xFF, G: 0x00, B: 0xAF}, // "#ff00af" + {R: 0xFF, G: 0x00, B: 0xD7}, // "#ff00d7" + {R: 0xFF, G: 0x00, B: 0xFF}, // "#ff00ff" + {R: 0xFF, G: 0x5F, B: 0x00}, // "#ff5f00" + {R: 0xFF, G: 0x5F, B: 0x5F}, // "#ff5f5f" + {R: 0xFF, G: 0x5F, B: 0x87}, // "#ff5f87" + {R: 0xFF, G: 0x5F, B: 0xAF}, // "#ff5faf" + {R: 0xFF, G: 0x5F, B: 0xD7}, // "#ff5fd7" + {R: 0xFF, G: 0x5F, B: 0xFF}, // "#ff5fff" + {R: 0xFF, G: 0x87, B: 0x00}, // "#ff8700" + {R: 0xFF, G: 0x87, B: 0x5F}, // "#ff875f" + {R: 0xFF, G: 0x87, B: 0x87}, // "#ff8787" + {R: 0xFF, G: 0x87, B: 0xAF}, // "#ff87af" + {R: 0xFF, G: 0x87, B: 0xD7}, // "#ff87d7" + {R: 0xFF, G: 0x87, B: 0xFF}, // "#ff87ff" + {R: 0xFF, G: 0xAF, B: 0x00}, // "#ffaf00" + {R: 0xFF, G: 0xAF, B: 0x5F}, // "#ffaf5f" + {R: 0xFF, G: 0xAF, B: 0x87}, // "#ffaf87" + {R: 0xFF, G: 0xAF, B: 0xAF}, // "#ffafaf" + {R: 0xFF, G: 0xAF, B: 0xD7}, // "#ffafd7" + {R: 0xFF, G: 0xAF, B: 0xFF}, // "#ffafff" + {R: 0xFF, G: 0xD7, B: 0x00}, // "#ffd700" + {R: 0xFF, G: 0xD7, B: 0x5F}, // "#ffd75f" + {R: 0xFF, G: 0xD7, B: 0x87}, // "#ffd787" + {R: 0xFF, G: 0xD7, B: 0xAF}, // "#ffd7af" + {R: 0xFF, G: 0xD7, B: 0xD7}, // "#ffd7d7" + {R: 0xFF, G: 0xD7, B: 0xFF}, // "#ffd7ff" + {R: 0xFF, G: 0xFF, B: 0x00}, // "#ffff00" + {R: 0xFF, G: 0xFF, B: 0x5F}, // "#ffff5f" + {R: 0xFF, G: 0xFF, B: 0x87}, // "#ffff87" + {R: 0xFF, G: 0xFF, B: 0xAF}, // "#ffffaf" + {R: 0xFF, G: 0xFF, B: 0xD7}, // "#ffffd7" + {R: 0xFF, G: 0xFF, B: 0xFF}, // "#ffffff" + {R: 0x08, G: 0x08, B: 0x08}, // "#080808" + {R: 0x12, G: 0x12, B: 0x12}, // "#121212" + {R: 0x1C, G: 0x1C, B: 0x1C}, // "#1c1c1c" + {R: 0x26, G: 0x26, B: 0x26}, // "#262626" + {R: 0x30, G: 0x30, B: 0x30}, // "#303030" + {R: 0x3A, G: 0x3A, B: 0x3A}, // "#3a3a3a" + {R: 0x44, G: 0x44, B: 0x44}, // "#444444" + {R: 0x4E, G: 0x4E, B: 0x4E}, // "#4e4e4e" + {R: 0x58, G: 0x58, B: 0x58}, // "#585858" + {R: 0x60, G: 0x60, B: 0x60}, // "#606060" + {R: 0x66, G: 0x66, B: 0x66}, // "#666666" + {R: 0x76, G: 0x76, B: 0x76}, // "#767676" + {R: 0x80, G: 0x80, B: 0x80}, // "#808080" + {R: 0x8A, G: 0x8A, B: 0x8A}, // "#8a8a8a" + {R: 0x94, G: 0x94, B: 0x94}, // "#949494" + {R: 0x9E, G: 0x9E, B: 0x9E}, // "#9e9e9e" + {R: 0xA8, G: 0xA8, B: 0xA8}, // "#a8a8a8" + {R: 0xB2, G: 0xB2, B: 0xB2}, // "#b2b2b2" + {R: 0xBC, G: 0xBC, B: 0xBC}, // "#bcbcbc" + {R: 0xC6, G: 0xC6, B: 0xC6}, // "#c6c6c6" + {R: 0xD0, G: 0xD0, B: 0xD0}, // "#d0d0d0" + {R: 0xDA, G: 0xDA, B: 0xDA}, // "#dadada" + {R: 0xE4, G: 0xE4, B: 0xE4}, // "#e4e4e4" + {R: 0xEE, G: 0xEE, B: 0xEE}, // "#eeeeee" + } + */ + XtermRGBPalette [240]RGBColor + XtermLabPalette [240]LabColor +) + +func init() { + // calculate xterm 240 color RGB palette + + // calculate 6x6x6 color cube + cubeLevels := [6]int{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff} + for r := 0; r < 6; r++ { + for g := 0; g < 6; g++ { + for b := 0; b < 6; b++ { + XtermRGBPalette[r*36+g*6+b] = RGBColor{cubeLevels[r], cubeLevels[g], cubeLevels[b]} + } + } + } + // calculate grayscale ramp + for i := 0; i < 24; i++ { + XtermRGBPalette[i+216] = RGBColor{0x08 + i*0xA, 0x08 + i*0xA, 0x08 + i*0xA} + } + + // calculate xterm 240 color Lab palette + for i, color := range XtermRGBPalette { + XtermLabPalette[i] = color.Lab() + } +} |
