mtsatellite/common/colors.go
2024-01-06 16:12:19 +01:00

154 lines
3.4 KiB
Go

// Copyright 2014, 2015 by Sascha L. Teichmann
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package common
import (
"bufio"
"fmt"
"image/color"
"log"
"os"
"sort"
"strconv"
"strings"
)
// DefaultTransparentDim sets the default dimming
// factor of transparent nodes to 2%.
const DefaultTransparentDim = 2.0 / 100.0
type Colors struct {
Colors []color.RGBA
NameIndex map[string]int32
NumTransparent int32
TransparentDim float32
}
type namedColor struct {
name string
color color.RGBA
}
type sortByAlpha []namedColor
func (colors sortByAlpha) Less(i, j int) bool {
return colors[i].color.A < colors[j].color.A
}
func (colors sortByAlpha) Len() int {
return len(colors)
}
func (colors sortByAlpha) Swap(i, j int) {
colors[i], colors[j] = colors[j], colors[i]
}
func ParseColors(filename string) (colors *Colors, err error) {
var file *os.File
if file, err = os.Open(filename); err != nil {
return
}
defer file.Close()
cols := make([]namedColor, 0, 2200)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "#") {
continue
}
c := color.RGBA{A: 0xff}
var name string
if n, _ := fmt.Sscanf(
line, "%s %d %d %d %d", &name, &c.R, &c.G, &c.B, &c.A); n > 0 {
cols = append(cols, namedColor{name: name, color: c})
}
}
err = scanner.Err()
// Sort transparent colors to front. Makes it easier to figure out
// if an index corresponds to a transparent color (i < Transparent).
sort.Sort(sortByAlpha(cols))
cs := make([]color.RGBA, len(cols))
nameIndex := make(map[string]int32, len(cols))
numTransparent := int32(0)
for i, nc := range cols {
if nc.color.A < 0xff {
numTransparent++
}
cs[i] = nc.color
nameIndex[nc.name] = int32(i)
}
colors = &Colors{
Colors: cs,
NameIndex: nameIndex,
NumTransparent: numTransparent,
TransparentDim: DefaultTransparentDim}
return
}
func (colors *Colors) IsTransparent(index int32) bool {
return index < colors.NumTransparent
}
func BlendColor(c1, c2 color.RGBA, a float32) color.RGBA {
b := float32(1) - a
return color.RGBA{
R: uint8(float32(c1.R)*a + float32(c2.R)*b),
G: uint8(float32(c1.G)*a + float32(c2.G)*b),
B: uint8(float32(c1.B)*a + float32(c2.B)*b),
A: 0xff}
}
func (colors *Colors) BlendColors(span *Span, col color.RGBA, pos int32) color.RGBA {
curr := span
// Ignore colors below pos.
for curr != nil && pos >= curr.To {
curr = curr.Next
}
if curr == nil {
return col
}
dim := colors.TransparentDim
for ; curr != nil; curr = curr.Next {
c := colors.Colors[curr.Value]
// At least alpha channel attenuation + dim% extra for each depth meter.
base := float32(c.A) / 255.0
factor := min32f(1.0, base+float32(curr.To-curr.From)*dim)
col = BlendColor(c, col, factor)
}
return col
}
func ParseColor(col string) (color.RGBA, error) {
col = strings.TrimLeft(col, "#")
rgb, err := strconv.ParseUint(col, 16, 32)
if err != nil {
return color.RGBA{}, err
}
return color.RGBA{
R: uint8(rgb >> 16),
G: uint8(rgb >> 8),
B: uint8(rgb),
A: 0xff}, nil
}
func ParseColorDefault(col string, def color.RGBA) color.RGBA {
c, err := ParseColor(col)
if err != nil {
log.Printf("WARN: cannot parse color '%s': %s\n", col, err)
return def
}
return c
}
func ColorToHex(col color.RGBA) string {
return fmt.Sprintf("#%02x%02x%02x", col.R, col.G, col.B)
}