Mercurial > gemma
view pkg/models/colors.go @ 4848:8584197232ec
Handle unknown units gracefully
author | Tom Gottfried <tom@intevation.de> |
---|---|
date | Mon, 18 Nov 2019 16:38:28 +0100 |
parents | 04eba9dc917d |
children | 18d5461bec5d |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> // * Markus Kottländer <markus.kottlaender@intevation.de> package models import ( "fmt" "image/color" "math" "sort" "strconv" "strings" ) type ( ColorValue struct { Value float64 Color color.RGBA } ColorValues []ColorValue ClassBreak struct { High float64 HasHigh bool Low float64 HasLow bool Col color.RGBA } ) func (cb *ClassBreak) Color() string { return fmt.Sprintf("#%02x%02x%02x", cb.Col.R, cb.Col.G, cb.Col.B, ) } func (cc ColorValues) ClassBreaks() []ClassBreak { cbs := make([]ClassBreak, len(cc), len(cc)+1) for i := range cc { if i > 0 { cbs[i].Low = cc[i-1].Value cbs[i].HasLow = true } cbs[i].High = cc[i].Value cbs[i].HasHigh = true cbs[i].Col = cc[i].Color } if len(cc) > 0 { cbs = append(cbs, ClassBreak{ Col: cc[len(cc)-1].Color, Low: cc[len(cc)-1].Value, HasLow: true, }) } return cbs } func (cc ColorValues) Heights() []float64 { heights := make([]float64, len(cc)) for i := range cc { heights[i] = cc[i].Value } return heights } func (cc ColorValues) Clip(v float64) color.RGBA { if len(cc) == 0 { return color.RGBA{} } if v < cc[0].Value { return cc[0].Color } if v > cc[len(cc)-1].Value { return cc[len(cc)-1].Color } c, _ := cc.Interpolate(v) return c } func (cc ColorValues) Interpolate(v float64) (color.RGBA, bool) { if len(cc) == 0 || v < cc[0].Value || v > cc[len(cc)-1].Value { return color.RGBA{}, false } if len(cc) == 1 { return cc[0].Color, cc[0].Value == v } for i := 0; i < len(cc)-1; i++ { v1 := cc[i].Value v2 := cc[i+1].Value if v1 <= v && v <= v2 { // f(v1) = 0 // f(v2) = 1 // 0 = m*v1 + c <=> c = -m*v1 // 1 = m*v2 + c // (1 - 0) = m*(v2 - v1) // m = 1/(v2 - v1) for v2 != v1 if v1 == v2 { return color.RGBA{ R: uint8((uint16(cc[i].Color.R) + uint16(cc[i+1].Color.R)) / 2), G: uint8((uint16(cc[i].Color.G) + uint16(cc[i+1].Color.G)) / 2), B: uint8((uint16(cc[i].Color.B) + uint16(cc[i+1].Color.B)) / 2), A: 0xff, }, true } m := 1 / (v2 - v1) c := -m * v1 s := v*m + c interpolate := func(a, b uint8) uint8 { v := math.Round(float64(a) + (float64(b)-float64(a))*s) if v < 0 { return 0 } if v > 255 { return 255 } return uint8(v) } return color.RGBA{ R: interpolate(cc[i].Color.R, cc[i+1].Color.R), G: interpolate(cc[i].Color.G, cc[i+1].Color.G), B: interpolate(cc[i].Color.B, cc[i+1].Color.B), A: 0xff, }, true } } return color.RGBA{}, false } func ParseColorValues(s string) (ColorValues, error) { var err error parseFloat := func(s string) float64 { var v float64 if err == nil { v, err = strconv.ParseFloat(s, 64) } return v } parseColor := func(s string) color.RGBA { if err != nil { return color.RGBA{} } s = strings.Map(func(r rune) rune { if ('0' <= r && r <= '9') || ('a' <= r && r <= 'f') || ('A' <= r && r <= 'F') { return r } return -1 }, s) var v int64 v, err = strconv.ParseInt(s, 16, 64) return color.RGBA{ R: uint8(v >> 16), G: uint8(v >> 8), B: uint8(v >> 0), A: 0xff, } } lines := strings.Split(s, ",") // first pass: find defined colors var defined ColorValues for _, line := range lines { // ignore the lines w/o a color. if !strings.ContainsRune(line, ':') { continue } parts := strings.SplitN(line, ":", 2) if len(parts) < 2 { continue } value := parseFloat(parts[0]) color := parseColor(parts[1]) defined = append(defined, ColorValue{ Value: value, Color: color, }) } if err != nil { return nil, err } sort.Slice(defined, func(i, j int) bool { return defined[i].Value < defined[j].Value }) // second pass: interpolate the rest var final ColorValues for _, line := range lines { if idx := strings.IndexRune(line, ':'); idx >= 0 { line = line[:idx] } value := parseFloat(line) if color, ok := defined.Interpolate(value); ok { final = append(final, ColorValue{ Value: value, Color: color, }) } } return final, err }