...

Source file src/gonum.org/v1/plot/palette/moreland/luminance.go

Documentation: gonum.org/v1/plot/palette/moreland

     1  // Copyright ©2016 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package moreland
     6  
     7  import (
     8  	"fmt"
     9  	"image/color"
    10  	"math"
    11  
    12  	"gonum.org/v1/plot/palette"
    13  )
    14  
    15  // luminance is a color palette that interpolates
    16  // between control colors in a way that ensures a linear relationship
    17  // between the luminance of a color and the value it represents.
    18  type luminance struct {
    19  	// colors are the control colors to be interpolated among.
    20  	// The colors must be monotonically increasing in luminance.
    21  	colors []cieLAB
    22  
    23  	// scalars are the scalar control points associated with
    24  	// each item in colors (above). They are monotonically
    25  	// increasing values between zero and one that correspond
    26  	// to the luminance of a given control color in relation
    27  	// to the minimum and maximum luminance among all control
    28  	// colors.
    29  	scalars []float64
    30  
    31  	// alpha represents the opacity of the returned
    32  	// colors in the range (0,1). It is set to 1 by default.
    33  	alpha float64
    34  
    35  	// min and max are the minimum and maximum values of the range of scalars
    36  	// that can be mapped to colors using this ColorMap.
    37  	min, max float64
    38  }
    39  
    40  // NewLuminance creates a new Luminance ColorMap from the given controlColors.
    41  // luminance is a color palette that interpolates
    42  // between control colors in a way that ensures a linear relationship
    43  // between the luminance of a color and the value it represents.
    44  // If the luminance of the controls is not monotonically increasing, an
    45  // error will be returned.
    46  func NewLuminance(controls []color.Color) (palette.ColorMap, error) {
    47  	l := luminance{
    48  		colors:  make([]cieLAB, len(controls)),
    49  		scalars: make([]float64, len(controls)),
    50  		alpha:   1,
    51  	}
    52  	max := math.Inf(-1)
    53  	min := math.Inf(1)
    54  	for i, c := range controls {
    55  		lab := colorTosRGBA(c).cieLAB()
    56  		l.colors[i] = lab
    57  		max = math.Max(max, lab.L)
    58  		min = math.Min(min, lab.L)
    59  		if i > 0 && lab.L <= l.colors[i-1].L {
    60  			return nil, fmt.Errorf("moreland: luminance of color %d (%g) is not "+
    61  				"greater than that of color %d (%g)", i, lab.L, i-1, l.colors[i-1].L)
    62  		}
    63  	}
    64  	// Normalize scalar values to the range (0,1).
    65  	rnge := max - min
    66  	for i, c := range l.colors {
    67  		l.scalars[i] = (c.L - min) / rnge
    68  	}
    69  	// Sometimes the first and last scalars do not end up
    70  	// being exactly zero and one owing to the imperfect
    71  	// precision of floating point operations.
    72  	// Here we set them to exactly zero and one to avoid
    73  	// the possibility of the At() function returning
    74  	// an out-of-range error for values that actually
    75  	// should be in the range.
    76  	l.scalars[0] = 0
    77  	l.scalars[len(l.scalars)-1] = 1
    78  	return &l, nil
    79  }
    80  
    81  // At implements the palette.ColorMap interface for a luminance value.
    82  func (l *luminance) At(v float64) (color.Color, error) {
    83  	if err := checkRange(l.min, l.max, v); err != nil {
    84  		return nil, err
    85  	}
    86  	scalar := (v - l.min) / (l.max - l.min)
    87  	if !inUnitRange(scalar) {
    88  		return nil, fmt.Errorf("moreland: interpolation value (%g) out of range [%g,%g]", scalar, l.min, l.max)
    89  	}
    90  	i := searchFloat64s(l.scalars, scalar)
    91  	if i == 0 {
    92  		return l.colors[i].cieXYZ().rgb().sRGBA(l.alpha), nil
    93  	}
    94  	c1 := l.colors[i-1]
    95  	c2 := l.colors[i]
    96  	frac := (scalar - l.scalars[i-1]) / (l.scalars[i] - l.scalars[i-1])
    97  	o := cieLAB{
    98  		L: frac*(c2.L-c1.L) + c1.L,
    99  		A: frac*(c2.A-c1.A) + c1.A,
   100  		B: frac*(c2.B-c1.B) + c1.B,
   101  	}.cieXYZ().rgb().sRGBA(l.alpha)
   102  	o.clamp()
   103  	return o, nil
   104  }
   105  
   106  func checkRange(min, max, val float64) error {
   107  	if max == min {
   108  		return fmt.Errorf("moreland: color map max == min == %g", max)
   109  	}
   110  	if min > max {
   111  		return fmt.Errorf("moreland: color map max (%g) < min (%g)", max, min)
   112  	}
   113  	if val < min {
   114  		return palette.ErrUnderflow
   115  	}
   116  	if val > max {
   117  		return palette.ErrOverflow
   118  	}
   119  	if math.IsNaN(val) {
   120  		return palette.ErrNaN
   121  	}
   122  	return nil
   123  }
   124  
   125  // searchFloat64s acts the same as sort.SearchFloat64s, except
   126  // it uses a simple search algorithm instead of binary search.
   127  func searchFloat64s(vals []float64, val float64) int {
   128  	for j, v := range vals {
   129  		if val <= v {
   130  			return j
   131  		}
   132  	}
   133  	return len(vals)
   134  }
   135  
   136  // SetMax implements the palette.ColorMap interface for a luminance value.
   137  func (l *luminance) SetMax(v float64) {
   138  	l.max = v
   139  }
   140  
   141  // SetMin implements the palette.ColorMap interface for a luminance value.
   142  func (l *luminance) SetMin(v float64) {
   143  	l.min = v
   144  }
   145  
   146  // Max implements the palette.ColorMap interface for a luminance value.
   147  func (l *luminance) Max() float64 {
   148  	return l.max
   149  }
   150  
   151  // Min implements the palette.ColorMap interface for a luminance value.
   152  func (l *luminance) Min() float64 {
   153  	return l.min
   154  }
   155  
   156  // SetAlpha sets the opacity value of this color map. Zero is transparent
   157  // and one is completely opaque.
   158  // The function will panic is alpha is not between zero and one.
   159  func (l *luminance) SetAlpha(alpha float64) {
   160  	if !inUnitRange(alpha) {
   161  		panic(fmt.Errorf("moreland: invalid alpha: %g", alpha))
   162  	}
   163  	l.alpha = alpha
   164  }
   165  
   166  // Alpha returns the opacity value of this color map.
   167  func (l *luminance) Alpha() float64 {
   168  	return l.alpha
   169  }
   170  
   171  // Palette returns a value that fulfills the palette.Palette interface,
   172  // where n is the number of desired colors.
   173  func (l luminance) Palette(n int) palette.Palette {
   174  	if l.Max() == 0 && l.Min() == 0 {
   175  		l.SetMin(0)
   176  		l.SetMax(1)
   177  	}
   178  	delta := (l.max - l.min) / float64(n-1)
   179  	var v float64
   180  	c := make([]color.Color, n)
   181  	for i := 0; i < n; i++ {
   182  		v = l.min + delta*float64(i)
   183  		var err error
   184  		c[i], err = l.At(v)
   185  		if err != nil {
   186  			panic(err)
   187  		}
   188  	}
   189  	return plte(c)
   190  }
   191  
   192  // plte fulfils the palette.Palette interface.
   193  type plte []color.Color
   194  
   195  // Colors fulfils the palette.Palette interface.
   196  func (p plte) Colors() []color.Color {
   197  	return p
   198  }
   199  
   200  // BlackBody is a Luminance-class ColorMap based on the colors of black body radiation.
   201  // Although the colors are inspired by the wavelengths of light from
   202  // black body radiation, the actual colors used are designed to be
   203  // perceptually uniform. Colors of the desired brightness and hue are chosen,
   204  // and then the colors are adjusted such that the luminance is perceptually
   205  // linear (according to the CIE LAB color space).
   206  func BlackBody() palette.ColorMap {
   207  	return &luminance{
   208  		colors: []cieLAB{
   209  			{L: 0, A: 0, B: 0},
   210  			{L: 39.112572747719774, A: 55.92470934659227, B: 37.65159714510402},
   211  			{L: 58.45705480680232, A: 43.34389690857626, B: 65.95409116544081},
   212  			{L: 84.13253643355525, A: -6.459770854468639, B: 82.41994470228775},
   213  			{L: 100, A: 0, B: 0}},
   214  		scalars: []float64{0, 0.39112572747719776, 0.5845705480680232, 0.8413253643355525, 1},
   215  		alpha:   1,
   216  	}
   217  }
   218  
   219  // ExtendedBlackBody is a Luminance-class ColorMap based on the colors of black body radiation
   220  // with some blue and purple hues thrown in at the lower end to add some "color."
   221  // The color map is similar to the default colors used in gnuplot. Colors of
   222  // the desired brightness and hue are chosen, and then the colors are adjusted
   223  // such that the luminance is perceptually linear (according to the CIE LAB
   224  // color space).
   225  func ExtendedBlackBody() palette.ColorMap {
   226  	return &luminance{
   227  		colors: []cieLAB{
   228  			{L: 0, A: 0, B: 0},
   229  			{L: 21.873483862751876, A: 50.19882295659109, B: -74.66982659778306},
   230  			{L: 34.506542513775905, A: 75.41302687474061, B: -88.73807072507786},
   231  			{L: 47.02980511087303, A: 70.93217189227919, B: 33.59880053746508},
   232  			{L: 65.17482203230537, A: 49.14591409658836, B: 56.86480950937553},
   233  			{L: 84.13253643355525, A: -6.459770854468639, B: 82.41994470228775},
   234  			{L: 100, A: 0, B: 0},
   235  		},
   236  		scalars: []float64{0, 0.21873483862751875, 0.34506542513775906, 0.4702980511087303,
   237  			0.6517482203230537, 0.8413253643355525, 1},
   238  		alpha: 1,
   239  	}
   240  }
   241  
   242  // Kindlmann is a Luminance-class ColorMap that uses the colors
   243  // first proposed in a paper
   244  // by Kindlmann, Reinhard, and Creem. The map is basically the rainbow
   245  // color map with the luminance adjusted such that it monotonically
   246  // changes, making it much more perceptually viable.
   247  //
   248  // Citation:
   249  // Gordon Kindlmann, Erik Reinhard, and Sarah Creem. 2002. Face-based
   250  // luminance matching for perceptual colormap generation. In Proceedings
   251  // of the conference on Visualization '02 (VIS '02). IEEE Computer Society,
   252  // Washington, DC, USA, 299-306.
   253  func Kindlmann() palette.ColorMap {
   254  	return &luminance{
   255  		colors: []cieLAB{
   256  			{L: 0, A: 0, B: 0},
   257  			{L: 10.479520542426698, A: 34.05557958902206, B: -34.21934877170809},
   258  			{L: 21.03011379005111, A: 52.30473571100955, B: -61.852601228346536},
   259  			{L: 31.03098927978494, A: 23.814976212074402, B: -57.73419358300511},
   260  			{L: 40.21480513626115, A: -24.858012706049536, B: -7.322176588219942},
   261  			{L: 52.73108089333358, A: -19.064976357731634, B: -25.558178073848147},
   262  			{L: 60.007326812392634, A: -61.75624590074585, B: 56.43522875191319},
   263  			{L: 69.81578343076002, A: -58.33353084882392, B: 68.37457857626646},
   264  			{L: 79.55703752324776, A: -22.50477758899383, B: 78.57946686200843},
   265  			{L: 89.818961593653, A: 7.586705160677109, B: 15.375961528833981},
   266  			{L: 100, A: 0, B: 0},
   267  		},
   268  		scalars: []float64{0, 0.10479520542426699, 0.2103011379005111, 0.3103098927978494,
   269  			0.4021480513626115, 0.5273108089333358, 0.6000732681239264, 0.6981578343076003,
   270  			0.7955703752324775, 0.89818961593653, 1},
   271  		alpha: 1,
   272  	}
   273  }
   274  
   275  // ExtendedKindlmann is a Luminance-class ColorMap uses the colors from
   276  // Kindlmann but also
   277  // adds more hues by doing a more than 360 degree loop around the hues.
   278  // This works because the endpoints have low saturation and very
   279  // different brightness.
   280  func ExtendedKindlmann() palette.ColorMap {
   281  	return &luminance{
   282  		colors: []cieLAB{
   283  			{L: 0, A: 0, B: 0},
   284  			{L: 13.371291966477482, A: 40.39368469479174, B: -47.73239449160565},
   285  			{L: 25.072421338587574, A: -18.01441053740843, B: -5.313556572210176},
   286  			{L: 37.411516363056116, A: -43.058336774976055, B: 39.30203907343062},
   287  			{L: 49.75026355291354, A: -15.774050138318895, B: 53.507917567416094},
   288  			{L: 61.643756252245225, A: 52.67703578954919, B: 43.82595336046358},
   289  			{L: 74.93187540089825, A: 50.92061741619164, B: -30.235411697966242},
   290  			{L: 87.64732748562544, A: 14.355163639545697, B: -17.471161313826332},
   291  			{L: 100, A: 0, B: 0},
   292  		},
   293  		scalars: []float64{0, 0.13371291966477483, 0.25072421338587575, 0.37411516363056113,
   294  			0.4975026355291354, 0.6164375625224523, 0.7493187540089825, 0.8764732748562544, 1},
   295  		alpha: 1,
   296  	}
   297  }
   298  

View as plain text