├── LICENSE ├── README.md ├── assembly.c ├── assembly.h ├── demos ├── README.md ├── fire.go ├── generate.go ├── images │ ├── fire.png │ └── noise.png ├── noise.go └── patterns.go ├── go.mod ├── go.sum ├── ledsgo.go ├── ledsgo_test.go ├── namedcolors.go ├── noise.go ├── noise_test.go ├── palette.go └── strip.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2020 FastLED and Ayke van Laethem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Color utilities for LED animations 2 | 3 | This package is a collection of some utility functions for working with 4 | color. It is primarily intended for LED animations on microcontrollers using 5 | [TinyGo](https://tinygo.org/), therefore it has been optimized for devices 6 | without FPU. 7 | 8 | It is inspired by [FastLED](http://fastled.io/) but does not implement any 9 | drivers for LED strips to keep development focused on fast animations. 10 | 11 | ## Noise functions 12 | 13 | This package contains a number of Simplex noise functions. 14 | [Simplex noise](https://en.wikipedia.org/wiki/Simplex_noise) is very similar 15 | to Perlin noise and produces naturally looking gradients as you might 16 | encounter in nature. It is commonly used as a building block for animations, 17 | especially in procedurally generated games. 18 | 19 | Be warned that Simplex noise is 20 | [patented](https://patents.google.com/patent/US6867776) (set to expire on 21 | 2022-01-18) so use at your own risk for computer graphics. This patent may or 22 | may not apply to LED animations, I don't know. 23 | 24 | ## Animation demos 25 | 26 | There is a [demos](./demos) subpackage which contains a number of simple 27 | animations that can be directly applied to surfaces implementing the 28 | [`Displayer`](https://godoc.org/github.com/aykevl/ledsgo/demos#Displayer) 29 | interface. 30 | 31 | ## License 32 | 33 | This package is licensed under the MIT license, just like the FastLED library. 34 | See the LICENSE file for details. Some code has been copied from the FastLED 35 | library, this is indicated in the code. 36 | -------------------------------------------------------------------------------- /assembly.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Fast but slightly inaccurate multiply for AVR. 4 | // The result is the same on all architectures to ease testing (tested by 5 | // comparing many random numbers). 6 | // 7 | // TODO: it would be nice if LLVM could be improved to emit optimized 8 | // instructions for the standard C expressions, instead of whatever it does now. 9 | uint16_t ledsgo_mul16(uint16_t x, uint16_t y) { 10 | uint16_t result = 0; 11 | #if __AVR__ 12 | __asm__ ( 13 | // result = (x >> 8) * (y >> 8) 14 | "mul %B[x], %B[y]\n\t" 15 | "movw %A[result], r0\n\t" 16 | 17 | // result += ((x & 0xff) * (y >> 8)) >> 8 18 | "mul %A[x], %B[y]\n\t" 19 | "add %A[result], r1\n\t" 20 | "clr r1\n\t" 21 | "adc %B[result], r1\n\t" 22 | 23 | // result += ((x >> 8) * (y & 0xff)) >> 8; 24 | "mul %B[x], %A[y]\n\t" 25 | "add %A[result], r1\n\t" 26 | "clr r1\n\t" 27 | "adc %B[result], r1\n\t" 28 | 29 | // r1 has already been cleared 30 | 31 | : [result]"=&r"(result) 32 | : [x]"d"(x), 33 | [y]"d"(y) 34 | ); 35 | #else 36 | result += (x >> 8) * (y >> 8); 37 | result += ((x & 0xff) * (y >> 8)) >> 8; 38 | result += ((x >> 8) * (y & 0xff)) >> 8; 39 | #endif 40 | return result; 41 | } 42 | -------------------------------------------------------------------------------- /assembly.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint16_t ledsgo_mul16(uint16_t x, uint16_t y); 4 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # LED animation demos 2 | 3 | This package contains a number of LED animations. Note that the resolution is 4 | low (32x32) to match LED panels such as common hub75 LED panels. 5 | 6 | ## Fire 7 | 8 | A simple fire-like animation. 9 | 10 | ![Fire](./images/fire.png) 11 | 12 | ## Noise 13 | 14 | Map 3D simplex noise to an image using the noise output as the hue of the 15 | pixel color. 16 | 17 | ![Noise](./images/noise.png) 18 | -------------------------------------------------------------------------------- /demos/fire.go: -------------------------------------------------------------------------------- 1 | package demos 2 | 3 | import ( 4 | "image/color" 5 | "time" 6 | 7 | "github.com/aykevl/ledsgo" 8 | ) 9 | 10 | // Fire shows an animation that looks somewhat like fire. The 'now' time 11 | // indicates which instance of the animation is generated. 12 | func Fire(display Displayer, now time.Time) { 13 | width, height := display.Size() 14 | const pointsPerCircle = 12 // how many LEDs there are per turn of the torch 15 | const speed = 12 // higher means faster 16 | var cooling = 256 / height // higher means faster cooling 17 | var detail = 12800 / width // higher means more detailed flames 18 | for x := int16(0); x < width; x++ { 19 | for y := int16(0); y < width; y++ { 20 | heat := ledsgo.Noise2(int32(y*detail)+int32((now.UnixNano()>>20)*speed), int32(x*detail))/256 + 128 21 | heat -= int16((height-1)-y) * cooling 22 | if heat < 0 { 23 | heat = 0 24 | } 25 | display.SetPixel(x, y, heatMap(uint8(heat))) 26 | } 27 | } 28 | } 29 | 30 | func heatMap(index uint8) color.RGBA { 31 | if index < 128 { 32 | return color.RGBA{index * 2, 0, 0, 255} 33 | } 34 | if index < 224 { 35 | return color.RGBA{255, uint8(uint32(index-128) * 8 / 3), 0, 255} 36 | } 37 | return color.RGBA{255, 255, (index - 224) * 8, 255} 38 | } 39 | -------------------------------------------------------------------------------- /demos/generate.go: -------------------------------------------------------------------------------- 1 | // +build none 2 | 3 | // This file is used in `go generate` to update the images directory. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "image" 10 | "image/color" 11 | "math" 12 | "os" 13 | "path/filepath" 14 | "time" 15 | 16 | "github.com/aykevl/ledsgo/demos" 17 | "github.com/kettek/apng" 18 | ) 19 | 20 | const ( 21 | width = 32 22 | height = 32 23 | scale = 4 24 | ) 25 | 26 | func main() { 27 | if len(os.Args) != 2 { 28 | fmt.Fprintln(os.Stderr, "provide exactly one argumet: the directory to store the resulting images") 29 | os.Exit(1) 30 | } 31 | dir := os.Args[1] // destination directory 32 | 33 | saveAnimation(demos.Fire, filepath.Join(dir, "fire.png")) 34 | saveAnimation(demos.Noise, filepath.Join(dir, "noise.png")) 35 | } 36 | 37 | func saveAnimation(draw func(demos.Displayer, time.Time), path string) { 38 | fmt.Println("generating:", path) 39 | const frameRate = 30 // frames per second 40 | const duration = 3 // seconds 41 | const frames = frameRate * duration 42 | const frameTime = time.Second / frameRate 43 | display := &imageDisplayer{ 44 | Scale: scale, 45 | } 46 | for i := 0; i < frames; i++ { 47 | t := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Add(frameTime * time.Duration(i)) 48 | display.NextFrame(frameRate) 49 | draw(display, t) 50 | } 51 | 52 | // Write image to directory. 53 | err := display.Save(path) 54 | if err != nil { 55 | fmt.Fprintln(os.Stderr, "failed to save image:", err) 56 | } 57 | } 58 | 59 | type imageDisplayer struct { 60 | frame *image.RGBA 61 | img apng.APNG 62 | Scale int 63 | } 64 | 65 | func (d *imageDisplayer) Size() (width, height int16) { 66 | rect := d.frame.Bounds() 67 | return int16(rect.Max.X / d.Scale), int16(rect.Max.Y / d.Scale) 68 | } 69 | 70 | func (d *imageDisplayer) SetPixel(x, y int16, c color.RGBA) { 71 | c = gammaEncode(c) 72 | for ix := int(x) * d.Scale; ix < int(x+1)*d.Scale; ix++ { 73 | for iy := int(y) * d.Scale; iy < int(y+1)*d.Scale; iy++ { 74 | d.frame.Set(ix, int(iy), c) 75 | } 76 | } 77 | } 78 | 79 | func (d *imageDisplayer) Display() error { 80 | return nil // nop 81 | } 82 | 83 | func (d *imageDisplayer) NextFrame(frameRate uint16) { 84 | if d.frame != nil { 85 | d.img.Frames = append(d.img.Frames, apng.Frame{ 86 | Image: d.frame, 87 | DelayNumerator: 1, 88 | DelayDenominator: frameRate, 89 | }) 90 | } 91 | d.frame = image.NewRGBA(image.Rect(0, 0, width*scale, height*scale)) 92 | } 93 | 94 | func (d *imageDisplayer) Save(filename string) error { 95 | f, err := os.Create(filename) 96 | if err != nil { 97 | return err 98 | } 99 | defer f.Close() 100 | 101 | return apng.Encode(f, d.img) 102 | } 103 | 104 | // gammaEncode encodes a color value into sRGB, the color space often used in 105 | // computer graphics. 106 | func gammaEncode(c color.RGBA) color.RGBA { 107 | const gamma = 1 / 2.2 108 | return color.RGBA{ 109 | R: uint8(math.Pow(float64(c.R)/255, gamma) * 255.5), 110 | G: uint8(math.Pow(float64(c.G)/255, gamma) * 255.5), 111 | B: uint8(math.Pow(float64(c.B)/255, gamma) * 255.5), 112 | A: c.A, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /demos/images/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/ledsgo/1580ae804c3fd19fe7c9c1563f221d114c822b8c/demos/images/fire.png -------------------------------------------------------------------------------- /demos/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/ledsgo/1580ae804c3fd19fe7c9c1563f221d114c822b8c/demos/images/noise.png -------------------------------------------------------------------------------- /demos/noise.go: -------------------------------------------------------------------------------- 1 | package demos 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/ledsgo" 7 | ) 8 | 9 | // Noise shows noise mapped to a rainbow function. The 'now' time indicates 10 | // which instance of the animation is generated. 11 | func Noise(display Displayer, now time.Time) { 12 | const spread = 6 // higher means the noise gets more detailed 13 | const speed = 20 // higher means slower 14 | width, height := display.Size() 15 | for x := int16(0); x < width; x++ { 16 | for y := int16(0); y < height; y++ { 17 | hue := uint16(ledsgo.Noise3(int32(now.UnixNano()>>speed), int32(x<>16 == 0 10 | 11 | // Color encodes a HSV color. 12 | // 13 | // The hue is 16-bits to get better looking colors, as HSV→RGB conversions 14 | // generally can use more than 8 hue bits for their conversion. Saturation and 15 | // value are both just 8 bits because saturation is not that often used and 16 | // value does not gain much precision with extra bits. This encoding has been 17 | // chosen to have the best colors while still fitting in 32 bits. 18 | type Color struct { 19 | H uint16 // hue 20 | S uint8 // saturation 21 | V uint8 // value 22 | } 23 | 24 | // Return the RGB version of this color, calculated with the common HSV 25 | // conversion: 26 | // https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors 27 | func (c Color) Spectrum() color.RGBA { 28 | sectionWidth := uint32((1<<16)/3 + 1) // one third of the hue space 29 | section := uint32(c.H) / sectionWidth 30 | colorValue := (uint32(c.H) - section*sectionWidth) * 256 / sectionWidth 31 | var r, g, b uint32 32 | switch section { 33 | case 0: 34 | r = 0xff - colorValue 35 | g = colorValue 36 | case 1: 37 | g = 0xff - colorValue 38 | b = colorValue 39 | case 2: 40 | b = 0xff - colorValue 41 | r = colorValue 42 | } 43 | sat := (0xff - uint32(c.S)) * uint32(c.V) / 256 / 3 44 | r = r*uint32(c.V)*(uint32(c.S))/(1<<16) + sat 45 | g = g*uint32(c.V)*(uint32(c.S))/(1<<16) + sat 46 | b = b*uint32(c.V)*(uint32(c.S))/(1<<16) + sat 47 | return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff} 48 | } 49 | 50 | // Rainbow color conversion. This function was copied from the FastLED library, 51 | // for details see 52 | // https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors#color-map-rainbow-vs-spectrum. 53 | // The main difference is that yellow is somewhat brighter, which may look 54 | // better in LEDs. 55 | func (c Color) Rainbow() color.RGBA { 56 | // Yellow has a higher inherent brightness than any other color; 'pure' 57 | // yellow is perceived to be 93% as bright as white. In order to make 58 | // yellow appear the correct relative brightness, it has to be rendered 59 | // brighter than all other colors. 60 | // Level Y1 is a moderate boost, the default. 61 | // Level Y2 is a strong boost. 62 | const Y1 = true 63 | const Y2 = false 64 | 65 | hue := uint8(c.H >> 8) 66 | sat := c.S 67 | val := c.V 68 | 69 | offset := hue & 0x1F // 0..31 70 | 71 | offset8 := offset * 8 72 | 73 | third := scale8(offset8, (256 / 3)) // max = 85 74 | 75 | var r, g, b uint8 76 | 77 | if hue&0x80 == 0 { 78 | // 0XX 79 | if hue&0x40 == 0 { 80 | // 00X 81 | //section 0-1 82 | if hue&0x20 == 0 { 83 | // 000 84 | //case 0: // R -> O 85 | r = 255 - third 86 | g = third 87 | b = 0 88 | } else { 89 | // 001 90 | //case 1: // O -> Y 91 | if Y1 { 92 | r = 171 93 | g = 85 + third 94 | b = 0 95 | } 96 | if Y2 { 97 | r = 170 + third 98 | twothirds := scale8(offset8, ((256 * 2) / 3)) // max=170 99 | g = 85 + twothirds 100 | b = 0 101 | } 102 | } 103 | } else { 104 | //01X 105 | // section 2-3 106 | if hue&0x20 == 0 { 107 | // 010 108 | //case 2: // Y -> G 109 | if Y1 { 110 | twothirds := scale8(offset8, ((256 * 2) / 3)) // max=170 111 | r = 171 - twothirds 112 | g = 170 + third 113 | b = 0 114 | } 115 | if Y2 { 116 | r = 255 - offset8 117 | g = 255 118 | b = 0 119 | } 120 | } else { 121 | // 011 122 | // case 3: // G -> A 123 | r = 0 124 | g = 255 - third 125 | b = third 126 | } 127 | } 128 | } else { 129 | // section 4-7 130 | // 1XX 131 | if hue&0x40 == 0 { 132 | // 10X 133 | if hue&0x20 == 0 { 134 | // 100 135 | //case 4: // A -> B 136 | r = 0 137 | //uint8_t twothirds = (third << 1); 138 | twothirds := scale8(offset8, ((256 * 2) / 3)) // max=170 139 | g = 171 - twothirds //170? 140 | b = 85 + twothirds 141 | } else { 142 | // 101 143 | //case 5: // B -> P 144 | r = third 145 | g = 0 146 | b = 255 - third 147 | } 148 | } else { 149 | if hue&0x20 == 0 { 150 | // 110 151 | //case 6: // P -- K 152 | r = 85 + third 153 | g = 0 154 | b = 171 - third 155 | } else { 156 | // 111 157 | //case 7: // K -> R 158 | r = 170 + third 159 | g = 0 160 | b = 85 - third 161 | } 162 | } 163 | } 164 | 165 | // Scale down colors if we're desaturated at all 166 | // and add the brightness_floor to r, g, and b. 167 | if sat != 255 { 168 | if sat == 0 { 169 | r = 255 170 | b = 255 171 | g = 255 172 | } else { 173 | //nscale8x3_video( r, g, b, sat); 174 | if r != 0 { 175 | r = scale8(r, sat) 176 | } 177 | if g != 0 { 178 | g = scale8(g, sat) 179 | } 180 | if b != 0 { 181 | b = scale8(b, sat) 182 | } 183 | 184 | desat := 255 - sat 185 | desat = scale8(desat, desat) 186 | 187 | brightness_floor := desat 188 | r += brightness_floor 189 | g += brightness_floor 190 | b += brightness_floor 191 | } 192 | } 193 | 194 | // Now scale everything down if we're at value < 255. 195 | if val != 255 { 196 | val = scale8_video(val, val) 197 | if val == 0 { 198 | r = 0 199 | g = 0 200 | b = 0 201 | } else { 202 | // nscale8x3_video( r, g, b, val); 203 | if r != 0 { 204 | r = scale8(r, val) 205 | } 206 | if g != 0 { 207 | g = scale8(g, val) 208 | } 209 | if b != 0 { 210 | b = scale8(b, val) 211 | } 212 | } 213 | } 214 | 215 | return color.RGBA{r, g, b, 0xff} 216 | } 217 | 218 | // Scale one byte by a second one, which is treated as the numerator of a 219 | // fraction whose denominator is 256. 220 | // In other words, it computes i * (scale / 256) 221 | func scale8(i, scale uint8) uint8 { 222 | return uint8((uint16(i) * (1 + uint16(scale))) >> 8) 223 | } 224 | 225 | // The "video" version of scale8 guarantees that the output will only be zero if 226 | // one or both of the inputs are zero. If both inputs are non-zero, the output 227 | // is guaranteed to be non-zero. This makes for better 'video'/LED dimming, at 228 | // the cost of several additional cycles. 229 | func scale8_video(i, scale uint8) uint8 { 230 | result := ((int(i) * int(scale)) >> 8) 231 | if i != 0 && scale != 0 { 232 | result += 1 233 | } 234 | return uint8(result) 235 | } 236 | 237 | // Blend the top value into the bottom value, with the given alpha value. 238 | func blend(bottom, top, topAlpha uint8) uint8 { 239 | if is16bit { 240 | // Version optimized for AVR. 241 | bottomPart := uint16(bottom) * uint16(255-topAlpha) 242 | topPart := uint16(top) * uint16(topAlpha) 243 | return uint8((bottomPart + topPart + 255) / 256) 244 | } 245 | // Version optimized for 32-bit and higher. 246 | bottomPart := int(bottom) * (255 - int(topAlpha)) 247 | topPart := int(top) * int(topAlpha) 248 | return uint8((bottomPart + topPart + 255) / 256) 249 | } 250 | 251 | // ApplyAlpha scales the color with the given alpha. It can be used to reduce 252 | // the intensity of a given color. The color is assumed to be linear, not sRGB. 253 | func ApplyAlpha(c color.RGBA, alpha uint8) color.RGBA { 254 | return color.RGBA{ 255 | R: uint8(uint32(c.R) * uint32(alpha) / 0xff), 256 | G: uint8(uint32(c.G) * uint32(alpha) / 0xff), 257 | B: uint8(uint32(c.B) * uint32(alpha) / 0xff), 258 | A: uint8(uint32(c.A) * uint32(alpha) / 0xff), 259 | } 260 | } 261 | 262 | // Blend blends two colors together, assuming the colors are linear (not sRGB). 263 | // The bottom alpha is assumed to be 0xff. The top alpha is used to blend the 264 | // two colors together. 265 | func Blend(bottom, top color.RGBA) color.RGBA { 266 | return color.RGBA{ 267 | R: blend(bottom.R, top.R, top.A), 268 | G: blend(bottom.G, top.G, top.A), 269 | B: blend(bottom.B, top.B, top.A), 270 | A: 255, 271 | } 272 | } 273 | 274 | // Sqrt is a fast integer square root function that returns a result at most one 275 | // off of the intended result. It can be used in place of a floating-point 276 | // square root function. Negative values are accepted and result in 0. 277 | func Sqrt(x int) int { 278 | // Original code copied from: 279 | // https://stackoverflow.com/questions/34187171/fast-integer-square-root-approximation/#34187992 280 | 281 | if x < 0 { 282 | // Avoid division by 0. 283 | // Also avoid negative numbers. Returning 0 is incorrect but should be 284 | // fine for most animation purposes. 285 | return 0 286 | } 287 | 288 | a := 1024 289 | b := x / a 290 | a = (a + b) / 2 291 | b = x / a 292 | a = (a + b) / 2 293 | b = x / a 294 | a = (a + b) / 2 295 | b = x / a 296 | a = (a + b) / 2 297 | b = x / a 298 | a = (a + b) / 2 299 | b = x / a 300 | a = (a + b) / 2 301 | b = x / a 302 | a = (a + b) / 2 303 | b = x / a 304 | a = (a + b) / 2 305 | b = x / a 306 | a = (a + b) / 2 307 | b = x / a 308 | a = (a + b) / 2 309 | b = x / a 310 | a = (a + b) / 2 311 | 312 | return a 313 | } 314 | -------------------------------------------------------------------------------- /ledsgo_test.go: -------------------------------------------------------------------------------- 1 | package ledsgo 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "testing" 7 | ) 8 | 9 | func TestSqrt(t *testing.T) { 10 | // Test all positive 32-bit integers with the following loop: 11 | // for i := (1 << 31) - 1; i >= 0; i-- { 12 | // Test a large number of positive 32-bit integers. 13 | for i := 0; i < 100000; i++ { 14 | n := int(rand.Int31()) 15 | n1 := Sqrt(n) 16 | n2 := int(math.Sqrt(float64(n))) 17 | diff := n1 - n2 18 | if diff < 0 { 19 | diff = -diff 20 | } 21 | if diff > 1 { 22 | t.Fatalf("sqrt failed: i=%d n1=%d n2=%d diff=%d\n", i, n1, n2, diff) 23 | } 24 | } 25 | } 26 | 27 | func TestBlend(t *testing.T) { 28 | // Test a few common cases that must always be correct. 29 | for _, tc := range []struct { 30 | bottom uint8 31 | top uint8 32 | alpha uint8 33 | result uint8 34 | }{ 35 | // 0..255 36 | {0, 255, 0, 0}, 37 | {0, 255, 255, 255}, 38 | {0, 255, 15, 15}, 39 | {0, 255, 240, 240}, 40 | 41 | // 255..0 42 | {255, 0, 0, 255}, 43 | {255, 0, 255, 0}, 44 | {255, 0, 15, 240}, 45 | {255, 0, 240, 15}, 46 | } { 47 | testBlend(t, tc.bottom, tc.top, tc.alpha, tc.result) 48 | } 49 | if t.Failed() { 50 | return // don't clutter the test output 51 | } 52 | 53 | // Test the 0..255 scale exhaustively. 54 | for i := 0; i <= 255; i++ { 55 | testBlend(t, 0, 255, uint8(i), uint8(i)) 56 | testBlend(t, 255, 0, uint8(i), uint8(255-i)) 57 | } 58 | 59 | // Compare test results against ideally rounded value (using floats). 60 | // TODO: improve blending so it does a better job at rounding. Now, it can 61 | // be slightly off due to integer division. 62 | diffSum := 0.0 63 | const tests = 1e6 64 | for i := 0; i < tests; i++ { 65 | top := uint8(rand.Uint32()) 66 | bottom := uint8(rand.Uint32()) 67 | alpha := uint8(rand.Uint32()) 68 | floatAlpha := float64(alpha) / 255 69 | ideal := float64(top)*floatAlpha + float64(bottom)*(1-floatAlpha) 70 | actual := blend(bottom, top, alpha) 71 | diff := float64(actual) - ideal 72 | diffSum += math.Abs(diff) 73 | if math.Abs(diff) >= 1.0 { 74 | t.Errorf("top %3d bottom %3d alpha %3d: got %3d expected %.1f (diff: %.3f)", top, bottom, alpha, actual, ideal, diff) 75 | } 76 | } 77 | t.Logf("diff avg: %.2f", diffSum/tests) 78 | } 79 | 80 | func testBlend(t *testing.T, bottom, top, alpha, expected uint8) { 81 | actual := blend(bottom, top, alpha) 82 | if expected != actual { 83 | t.Errorf("bottom %3d top %3d alpha %3d: expected %3d, got %d", bottom, top, alpha, expected, actual) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /namedcolors.go: -------------------------------------------------------------------------------- 1 | package ledsgo 2 | 3 | import "image/color" 4 | 5 | // Colors copied from the FastLED library, originating from HTML colors. 6 | // Warning: these colors are in sRGB (as used on the web), not in linear colors 7 | // as used in this package. They are copied for easier porting from FastLED but 8 | // please realize that the color you'll see on a screen often won't match actual 9 | // LED colors. 10 | var ( 11 | AliceBlue = color.RGBA{0xF0, 0xF8, 0xFF, 0xFF} 12 | Amethyst = color.RGBA{0x99, 0x66, 0xCC, 0xFF} 13 | AntiqueWhite = color.RGBA{0xFA, 0xEB, 0xD7, 0xFF} 14 | Aqua = color.RGBA{0x00, 0xFF, 0xFF, 0xFF} 15 | Aquamarine = color.RGBA{0x7F, 0xFF, 0xD4, 0xFF} 16 | Azure = color.RGBA{0xF0, 0xFF, 0xFF, 0xFF} 17 | Beige = color.RGBA{0xF5, 0xF5, 0xDC, 0xFF} 18 | Bisque = color.RGBA{0xFF, 0xE4, 0xC4, 0xFF} 19 | Black = color.RGBA{0x00, 0x00, 0x00, 0xFF} 20 | BlanchedAlmond = color.RGBA{0xFF, 0xEB, 0xCD, 0xFF} 21 | Blue = color.RGBA{0x00, 0x00, 0xFF, 0xFF} 22 | BlueViolet = color.RGBA{0x8A, 0x2B, 0xE2, 0xFF} 23 | Brown = color.RGBA{0xA5, 0x2A, 0x2A, 0xFF} 24 | BurlyWood = color.RGBA{0xDE, 0xB8, 0x87, 0xFF} 25 | CadetBlue = color.RGBA{0x5F, 0x9E, 0xA0, 0xFF} 26 | Chartreuse = color.RGBA{0x7F, 0xFF, 0x00, 0xFF} 27 | Chocolate = color.RGBA{0xD2, 0x69, 0x1E, 0xFF} 28 | Coral = color.RGBA{0xFF, 0x7F, 0x50, 0xFF} 29 | CornflowerBlue = color.RGBA{0x64, 0x95, 0xED, 0xFF} 30 | Cornsilk = color.RGBA{0xFF, 0xF8, 0xDC, 0xFF} 31 | Crimson = color.RGBA{0xDC, 0x14, 0x3C, 0xFF} 32 | Cyan = color.RGBA{0x00, 0xFF, 0xFF, 0xFF} 33 | DarkBlue = color.RGBA{0x00, 0x00, 0x8B, 0xFF} 34 | DarkCyan = color.RGBA{0x00, 0x8B, 0x8B, 0xFF} 35 | DarkGoldenrod = color.RGBA{0xB8, 0x86, 0x0B, 0xFF} 36 | DarkGray = color.RGBA{0xA9, 0xA9, 0xA9, 0xFF} 37 | DarkGrey = color.RGBA{0xA9, 0xA9, 0xA9, 0xFF} 38 | DarkGreen = color.RGBA{0x00, 0x64, 0x00, 0xFF} 39 | DarkKhaki = color.RGBA{0xBD, 0xB7, 0x6B, 0xFF} 40 | DarkMagenta = color.RGBA{0x8B, 0x00, 0x8B, 0xFF} 41 | DarkOliveGreen = color.RGBA{0x55, 0x6B, 0x2F, 0xFF} 42 | DarkOrange = color.RGBA{0xFF, 0x8C, 0x00, 0xFF} 43 | DarkOrchid = color.RGBA{0x99, 0x32, 0xCC, 0xFF} 44 | DarkRed = color.RGBA{0x8B, 0x00, 0x00, 0xFF} 45 | DarkSalmon = color.RGBA{0xE9, 0x96, 0x7A, 0xFF} 46 | DarkSeaGreen = color.RGBA{0x8F, 0xBC, 0x8F, 0xFF} 47 | DarkSlateBlue = color.RGBA{0x48, 0x3D, 0x8B, 0xFF} 48 | DarkSlateGray = color.RGBA{0x2F, 0x4F, 0x4F, 0xFF} 49 | DarkSlateGrey = color.RGBA{0x2F, 0x4F, 0x4F, 0xFF} 50 | DarkTurquoise = color.RGBA{0x00, 0xCE, 0xD1, 0xFF} 51 | DarkViolet = color.RGBA{0x94, 0x00, 0xD3, 0xFF} 52 | DeepPink = color.RGBA{0xFF, 0x14, 0x93, 0xFF} 53 | DeepSkyBlue = color.RGBA{0x00, 0xBF, 0xFF, 0xFF} 54 | DimGray = color.RGBA{0x69, 0x69, 0x69, 0xFF} 55 | DimGrey = color.RGBA{0x69, 0x69, 0x69, 0xFF} 56 | DodgerBlue = color.RGBA{0x1E, 0x90, 0xFF, 0xFF} 57 | FireBrick = color.RGBA{0xB2, 0x22, 0x22, 0xFF} 58 | FloralWhite = color.RGBA{0xFF, 0xFA, 0xF0, 0xFF} 59 | ForestGreen = color.RGBA{0x22, 0x8B, 0x22, 0xFF} 60 | Fuchsia = color.RGBA{0xFF, 0x00, 0xFF, 0xFF} 61 | Gainsboro = color.RGBA{0xDC, 0xDC, 0xDC, 0xFF} 62 | GhostWhite = color.RGBA{0xF8, 0xF8, 0xFF, 0xFF} 63 | Gold = color.RGBA{0xFF, 0xD7, 0x00, 0xFF} 64 | Goldenrod = color.RGBA{0xDA, 0xA5, 0x20, 0xFF} 65 | Gray = color.RGBA{0x80, 0x80, 0x80, 0xFF} 66 | Grey = color.RGBA{0x80, 0x80, 0x80, 0xFF} 67 | Green = color.RGBA{0x00, 0x80, 0x00, 0xFF} 68 | GreenYellow = color.RGBA{0xAD, 0xFF, 0x2F, 0xFF} 69 | Honeydew = color.RGBA{0xF0, 0xFF, 0xF0, 0xFF} 70 | HotPink = color.RGBA{0xFF, 0x69, 0xB4, 0xFF} 71 | IndianRed = color.RGBA{0xCD, 0x5C, 0x5C, 0xFF} 72 | Indigo = color.RGBA{0x4B, 0x00, 0x82, 0xFF} 73 | Ivory = color.RGBA{0xFF, 0xFF, 0xF0, 0xFF} 74 | Khaki = color.RGBA{0xF0, 0xE6, 0x8C, 0xFF} 75 | Lavender = color.RGBA{0xE6, 0xE6, 0xFA, 0xFF} 76 | LavenderBlush = color.RGBA{0xFF, 0xF0, 0xF5, 0xFF} 77 | LawnGreen = color.RGBA{0x7C, 0xFC, 0x00, 0xFF} 78 | LemonChiffon = color.RGBA{0xFF, 0xFA, 0xCD, 0xFF} 79 | LightBlue = color.RGBA{0xAD, 0xD8, 0xE6, 0xFF} 80 | LightCoral = color.RGBA{0xF0, 0x80, 0x80, 0xFF} 81 | LightCyan = color.RGBA{0xE0, 0xFF, 0xFF, 0xFF} 82 | LightGoldenrodYellow = color.RGBA{0xFA, 0xFA, 0xD2, 0xFF} 83 | LightGreen = color.RGBA{0x90, 0xEE, 0x90, 0xFF} 84 | LightGrey = color.RGBA{0xD3, 0xD3, 0xD3, 0xFF} 85 | LightPink = color.RGBA{0xFF, 0xB6, 0xC1, 0xFF} 86 | LightSalmon = color.RGBA{0xFF, 0xA0, 0x7A, 0xFF} 87 | LightSeaGreen = color.RGBA{0x20, 0xB2, 0xAA, 0xFF} 88 | LightSkyBlue = color.RGBA{0x87, 0xCE, 0xFA, 0xFF} 89 | LightSlateGray = color.RGBA{0x77, 0x88, 0x99, 0xFF} 90 | LightSlateGrey = color.RGBA{0x77, 0x88, 0x99, 0xFF} 91 | LightSteelBlue = color.RGBA{0xB0, 0xC4, 0xDE, 0xFF} 92 | LightYellow = color.RGBA{0xFF, 0xFF, 0xE0, 0xFF} 93 | Lime = color.RGBA{0x00, 0xFF, 0x00, 0xFF} 94 | LimeGreen = color.RGBA{0x32, 0xCD, 0x32, 0xFF} 95 | Linen = color.RGBA{0xFA, 0xF0, 0xE6, 0xFF} 96 | Magenta = color.RGBA{0xFF, 0x00, 0xFF, 0xFF} 97 | Maroon = color.RGBA{0x80, 0x00, 0x00, 0xFF} 98 | MediumAquamarine = color.RGBA{0x66, 0xCD, 0xAA, 0xFF} 99 | MediumBlue = color.RGBA{0x00, 0x00, 0xCD, 0xFF} 100 | MediumOrchid = color.RGBA{0xBA, 0x55, 0xD3, 0xFF} 101 | MediumPurple = color.RGBA{0x93, 0x70, 0xDB, 0xFF} 102 | MediumSeaGreen = color.RGBA{0x3C, 0xB3, 0x71, 0xFF} 103 | MediumSlateBlue = color.RGBA{0x7B, 0x68, 0xEE, 0xFF} 104 | MediumSpringGreen = color.RGBA{0x00, 0xFA, 0x9A, 0xFF} 105 | MediumTurquoise = color.RGBA{0x48, 0xD1, 0xCC, 0xFF} 106 | MediumVioletRed = color.RGBA{0xC7, 0x15, 0x85, 0xFF} 107 | MidnightBlue = color.RGBA{0x19, 0x19, 0x70, 0xFF} 108 | MintCream = color.RGBA{0xF5, 0xFF, 0xFA, 0xFF} 109 | MistyRose = color.RGBA{0xFF, 0xE4, 0xE1, 0xFF} 110 | Moccasin = color.RGBA{0xFF, 0xE4, 0xB5, 0xFF} 111 | NavajoWhite = color.RGBA{0xFF, 0xDE, 0xAD, 0xFF} 112 | Navy = color.RGBA{0x00, 0x00, 0x80, 0xFF} 113 | OldLace = color.RGBA{0xFD, 0xF5, 0xE6, 0xFF} 114 | Olive = color.RGBA{0x80, 0x80, 0x00, 0xFF} 115 | OliveDrab = color.RGBA{0x6B, 0x8E, 0x23, 0xFF} 116 | Orange = color.RGBA{0xFF, 0xA5, 0x00, 0xFF} 117 | OrangeRed = color.RGBA{0xFF, 0x45, 0x00, 0xFF} 118 | Orchid = color.RGBA{0xDA, 0x70, 0xD6, 0xFF} 119 | PaleGoldenrod = color.RGBA{0xEE, 0xE8, 0xAA, 0xFF} 120 | PaleGreen = color.RGBA{0x98, 0xFB, 0x98, 0xFF} 121 | PaleTurquoise = color.RGBA{0xAF, 0xEE, 0xEE, 0xFF} 122 | PaleVioletRed = color.RGBA{0xDB, 0x70, 0x93, 0xFF} 123 | PapayaWhip = color.RGBA{0xFF, 0xEF, 0xD5, 0xFF} 124 | PeachPuff = color.RGBA{0xFF, 0xDA, 0xB9, 0xFF} 125 | Peru = color.RGBA{0xCD, 0x85, 0x3F, 0xFF} 126 | Pink = color.RGBA{0xFF, 0xC0, 0xCB, 0xFF} 127 | Plaid = color.RGBA{0xCC, 0x55, 0x33, 0xFF} 128 | Plum = color.RGBA{0xDD, 0xA0, 0xDD, 0xFF} 129 | PowderBlue = color.RGBA{0xB0, 0xE0, 0xE6, 0xFF} 130 | Purple = color.RGBA{0x80, 0x00, 0x80, 0xFF} 131 | Red = color.RGBA{0xFF, 0x00, 0x00, 0xFF} 132 | RosyBrown = color.RGBA{0xBC, 0x8F, 0x8F, 0xFF} 133 | RoyalBlue = color.RGBA{0x41, 0x69, 0xE1, 0xFF} 134 | SaddleBrown = color.RGBA{0x8B, 0x45, 0x13, 0xFF} 135 | Salmon = color.RGBA{0xFA, 0x80, 0x72, 0xFF} 136 | SandyBrown = color.RGBA{0xF4, 0xA4, 0x60, 0xFF} 137 | SeaGreen = color.RGBA{0x2E, 0x8B, 0x57, 0xFF} 138 | Seashell = color.RGBA{0xFF, 0xF5, 0xEE, 0xFF} 139 | Sienna = color.RGBA{0xA0, 0x52, 0x2D, 0xFF} 140 | Silver = color.RGBA{0xC0, 0xC0, 0xC0, 0xFF} 141 | SkyBlue = color.RGBA{0x87, 0xCE, 0xEB, 0xFF} 142 | SlateBlue = color.RGBA{0x6A, 0x5A, 0xCD, 0xFF} 143 | SlateGray = color.RGBA{0x70, 0x80, 0x90, 0xFF} 144 | SlateGrey = color.RGBA{0x70, 0x80, 0x90, 0xFF} 145 | Snow = color.RGBA{0xFF, 0xFA, 0xFA, 0xFF} 146 | SpringGreen = color.RGBA{0x00, 0xFF, 0x7F, 0xFF} 147 | SteelBlue = color.RGBA{0x46, 0x82, 0xB4, 0xFF} 148 | Tan = color.RGBA{0xD2, 0xB4, 0x8C, 0xFF} 149 | Teal = color.RGBA{0x00, 0x80, 0x80, 0xFF} 150 | Thistle = color.RGBA{0xD8, 0xBF, 0xD8, 0xFF} 151 | Tomato = color.RGBA{0xFF, 0x63, 0x47, 0xFF} 152 | Turquoise = color.RGBA{0x40, 0xE0, 0xD0, 0xFF} 153 | Violet = color.RGBA{0xEE, 0x82, 0xEE, 0xFF} 154 | Wheat = color.RGBA{0xF5, 0xDE, 0xB3, 0xFF} 155 | White = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF} 156 | WhiteSmoke = color.RGBA{0xF5, 0xF5, 0xF5, 0xFF} 157 | Yellow = color.RGBA{0xFF, 0xFF, 0x00, 0xFF} 158 | YellowGreen = color.RGBA{0x9A, 0xCD, 0x32, 0xFF} 159 | ) 160 | -------------------------------------------------------------------------------- /noise.go: -------------------------------------------------------------------------------- 1 | package ledsgo 2 | 3 | // This file implements simplex noise, which is an improved Perlin noise. This 4 | // implementation is a fixed-point version that avoids all uses of floating 5 | // point while still being compatible with the floating point version. Note: not 6 | // all inputs have been tested in Noise2 and up, so there might be inputs that 7 | // overflow int16. For Noise2, exhaustive testing might be possible but 8 | // computationally expensive (2**64 combinations). For Noise3 and up it is 9 | // impossible to check all inputs (2**96 inputs). 10 | // 11 | // Warning: there are patents on simplex noise for certain uses, which probably 12 | // doesn't include LED animations (no guarantee). 13 | // See: 14 | // https://patents.stackexchange.com/questions/18573 15 | 16 | // Original author: Stefan Gustavson, converted to Go by Lars Pensjö, converted 17 | // to fixed-point by Ayke van Laethem. 18 | // https://github.com/larspensjo/Go-simplex-noise/blob/master/simplexnoise/simplexnoise.go 19 | // 20 | // The code in this file has been placed in the public domain. You can do 21 | // whatever you want with it. Attribution is appreciated but not required. 22 | 23 | // Notation: 24 | // Every fixed-point calculation has a line comment saying how many bits in the 25 | // given integer are used for the fractional part. For example: 26 | // 27 | // n := a + b // .12 28 | // 29 | // means the result of this operation has the floating point 12 bits from the 30 | // right. It can be converted to a floating point using: 31 | // 32 | // nf := float64(n) / (1 << 12) 33 | 34 | // #include "assembly.h" 35 | import "C" 36 | 37 | // Permutation table. This is just a random jumble of all numbers. 38 | // This needs to be exactly the same for all instances on all platforms, 39 | // so it's easiest to just keep it as static explicit data. 40 | var perm = [256]uint8{ 41 | 151, 160, 137, 91, 90, 15, 42 | 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 43 | 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 44 | 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 45 | 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 46 | 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 47 | 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 48 | 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 49 | 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 50 | 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 51 | 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 52 | 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 53 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 54 | } 55 | 56 | // A lookup table to traverse the simplex around a given point in 4D. 57 | // Details can be found where this table is used, in the 4D noise method. 58 | // TODO: This should not be required, backport it from Bill's GLSL code! 59 | var simplex = [64][4]uint8{ 60 | {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 0, 0, 0}, {0, 2, 3, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 2, 3, 0}, 61 | {0, 2, 1, 3}, {0, 0, 0, 0}, {0, 3, 1, 2}, {0, 3, 2, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 3, 2, 0}, 62 | {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, 63 | {1, 2, 0, 3}, {0, 0, 0, 0}, {1, 3, 0, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, 64 | {1, 0, 2, 3}, {1, 0, 3, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 0, 3, 1}, {0, 0, 0, 0}, {2, 1, 3, 0}, 65 | {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, 66 | {2, 0, 1, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {0, 0, 0, 0}, {3, 1, 2, 0}, 67 | {2, 1, 0, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 1, 0, 2}, {0, 0, 0, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}, 68 | } 69 | 70 | // Helper functions to compute gradients-dot-residualvectors (1D to 4D) 71 | // Note that these generate gradients of more than unit length. To make 72 | // a close match with the value range of classic Perlin noise, the final 73 | // noise values need to be rescaled to fit nicely within [-1,1]. 74 | // (The simplex noise functions as such also have different scaling.) 75 | 76 | func q(cond bool, v1 int32, v2 int32) int32 { 77 | if cond { 78 | return v1 79 | } 80 | return v2 81 | } 82 | 83 | func bool2uint32(b bool) uint32 { 84 | if b { 85 | return 1 86 | } else { 87 | return 0 88 | } 89 | } 90 | 91 | // hash is 0..0xff, x is 0.12 fixed point 92 | // returns *.12 fixed-point value 93 | func grad1(hash uint8, x int32) int32 { 94 | h := hash & 15 95 | grad := int32(1 + h&7) // Gradient value 1.0, 2.0, ..., 8.0 96 | if h&8 != 0 { 97 | grad = -grad // Set a random sign for the gradient 98 | } 99 | return grad * x // Multiply the gradient with the distance (integer * 0.12 = *.12) 100 | } 101 | 102 | // Same as grad1, but doesn't multiply with the x parameter. 103 | func grad1AVR(hash uint8) int16 { 104 | h := hash & 15 105 | grad := int16(1 + h&7) // Gradient value 1.0, 2.0, ..., 8.0 106 | if h&8 != 0 { 107 | grad = -grad // Set a random sign for the gradient 108 | } 109 | return grad 110 | } 111 | 112 | func grad2(hash uint8, x, y int32) int32 { 113 | h := hash & 7 // Convert low 3 bits of hash code 114 | u := q(h < 4, x, y) // into 8 simple gradient directions, 115 | v := q(h < 4, y, x) // and compute the dot product with (x,y). 116 | return q(h&1 != 0, -u, u) + q(h&2 != 0, -2*v, 2*v) 117 | } 118 | 119 | func grad3(hash uint8, x, y, z int32) int32 { 120 | h := hash & 15 // Convert low 4 bits of hash code into 12 simple 121 | u := q(h < 8, x, y) // gradient directions, and compute dot product. 122 | v := q(h < 4, y, q(h == 12 || h == 14, x, z)) // Fix repeats at h = 12 to 15 123 | return q(h&1 != 0, -u, u) + q(h&2 != 0, -v, v) 124 | } 125 | 126 | func grad4(hash uint8, x, y, z, t int32) int32 { 127 | h := hash & 31 // Convert low 5 bits of hash code into 32 simple 128 | u := q(h < 24, x, y) // gradient directions, and compute dot product. 129 | v := q(h < 16, y, z) 130 | w := q(h < 8, z, t) 131 | return q(h&1 != 0, -u, u) + q(h&2 != 0, -v, v) + q(h&4 != 0, -w, w) 132 | } 133 | 134 | // Fast multiplication that works on 16-bit integers. The result is 135 | // approximately the following: 136 | // 137 | // uint16((uint32(x) * uint32(y)) >> 16) 138 | func mul16AVR(x, y uint16) uint16 { 139 | return uint16(C.ledsgo_mul16(C.uint16_t(x), C.uint16_t(y))) 140 | } 141 | 142 | // 1D simplex noise. 143 | // 144 | // The x input is a 20.12 fixed-point value. The result covers the full range of 145 | // a uint16, averaging around 32768. 146 | // Only the low 20 bits of x are used. 147 | func Noise1(x uint32) uint16 { 148 | i0 := x >> 12 149 | i1 := i0 + 1 150 | x0 := int32(x & 0xfff) // .12 151 | x1 := int32(x0 - 0x1000) // .12 152 | 153 | t0 := 0x8000 - (x0*x0)>>9 // .15 154 | t0 = (t0 * t0) >> 15 // .15 155 | t0 = (t0 * t0) >> 15 // .15 156 | n0 := (t0 * grad1(perm[i0&0xff], x0)) >> 12 // .15 * .12 = .15 157 | 158 | t1 := 0x8000 - (x1*x1)>>9 // .15 159 | t1 = (t1 * t1) >> 15 // .15 160 | t1 = (t1 * t1) >> 15 // .15 161 | n1 := (t1 * grad1(perm[i1&0xff], x1)) >> 12 // .15 * .12 = .15 162 | 163 | n := n0 + n1 // .15 164 | n += 2503 // .15: fix offset, adjust to +0.03 165 | n = (n * 26694) >> 16 // .15: fix scale to fit in [-1,1] 166 | return uint16(n + 0x8000) 167 | } 168 | 169 | // Fast 1D simplex noise for 8-bit microcontrollers (may or may not be faster on 170 | // 32-bit microcontrollers). It is less precise than Noise1. 171 | // 172 | // The x input is a 8.8 fixed-point value. The result covers the full range of a 173 | // uint16, averaging around 32768. The output is only slightly more precise than 174 | // a uint8, though. 175 | func Noise1AVR(x uint16) uint16 { 176 | i0 := x >> 8 177 | i1 := i0 + 1 178 | x0 := x & 0xff // .8 179 | x1 := 0x100 - x0 // .8 180 | 181 | t0 := 0x4000 - mul16AVR(x0<<7, x0<<7) 182 | t0 = mul16AVR(t0*2, t0*2) 183 | t0 = mul16AVR(t0*2, t0*2) 184 | n0 := int16(mul16AVR(t0, x0<<7)) 185 | n0 *= grad1AVR(perm[i0&0xff]) // multiply by value -8..8 186 | 187 | t1 := 0x4000 - mul16AVR(x1<<7, x1<<7) 188 | t1 = mul16AVR(t1*2, t1*2) 189 | t1 = mul16AVR(t1*2, t1*2) 190 | n1 := int16(mul16AVR(t1, x1<<7)) 191 | n1 *= -(grad1AVR(perm[i1&0xff])) // multiply by value -8..8 192 | 193 | n := uint16(n0 + n1) 194 | n += 0x8000 - 12032 // change range to start at 0 195 | n = mul16AVR(n, 53409) << 1 // adjust range to end very close to 0xffff 196 | return n 197 | } 198 | 199 | // 2D simplex noise. 200 | // 201 | // The x and y inputs are 20.12 fixed-point value. The result covers the full 202 | // range of a uint16, averaging around 32768. 203 | func Noise2(x, y uint32) uint16 { 204 | const F2 = 1572067135 // .32: F2 = 0.5*(sqrt(3.0)-1.0) 205 | const G2 = 907633384 // .32: G2 = (3.0-Math.sqrt(3.0))/6.0 206 | 207 | // Skew the input space to determine which simplex cell we're in 208 | s := uint32(((uint64(x) + uint64(y)) * F2) >> 32) // (.12 + .12) * .32 = .12: Hairy factor for 2D 209 | i := (x>>1 + s>>1) >> 11 // .0 210 | j := (y>>1 + s>>1) >> 11 // .0 211 | 212 | t := (uint64(i) + uint64(j)) * G2 // .32 213 | X0 := uint64(i)<<32 - t // .32: Unskew the cell origin back to (x,y) space 214 | Y0 := uint64(j)<<32 - t // .32 215 | x0 := int32(uint64(x)<<2 - X0>>18) // .14: The x,y distances from the cell origin 216 | y0 := int32(uint64(y)<<2 - Y0>>18) // .14 217 | 218 | // For the 2D case, the simplex shape is an equilateral triangle. 219 | // Determine which simplex we are in. 220 | var i1, j1 uint32 // Offsets for second (middle) corner of simplex in (i,j) coords 221 | if x0 > y0 { 222 | i1 = 1 223 | j1 = 0 // lower triangle, XY order: (0,0)->(1,0)->(1,1) 224 | } else { 225 | i1 = 0 226 | j1 = 1 227 | } // upper triangle, YX order: (0,0)->(0,1)->(1,1) 228 | 229 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 230 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 231 | // c = (3-sqrt(3))/6 232 | 233 | x1 := x0 - int32(i1)<<14 + G2>>18 // .14: Offsets for middle corner in (x,y) unskewed coords 234 | y1 := y0 - int32(j1)<<14 + G2>>18 // .14 235 | x2 := x0 - (1 << 14) + 2*G2>>18 // .14: Offsets for last corner in (x,y) unskewed coords 236 | y2 := y0 - (1 << 14) + 2*G2>>18 // .14 237 | 238 | var n0, n1, n2 int32 // Noise contributions from the three corners 239 | 240 | // Calculate the contribution from the three corners 241 | t0 := ((1 << 27) - x0*x0 - y0*y0) >> 12 // .16 242 | if t0 > 0 { 243 | t0 = (t0 * t0) >> 16 // .16 244 | t0 = (t0 * t0) >> 16 // .16 245 | n0 = t0 * grad2(perm[(i+uint32(perm[j&0xff]))&0xff], x0, y0) // .16 * .14 = .30 246 | } 247 | 248 | t1 := ((1 << 27) - x1*x1 - y1*y1) >> 12 // .16 249 | if t1 > 0 { 250 | t1 = (t1 * t1) >> 16 // .16 251 | t1 = (t1 * t1) >> 16 // .16 252 | n1 = t1 * grad2(perm[(i+i1+uint32(perm[(j+j1)&0xff]))&0xff], x1, y1) // .16 * .14 = .30 253 | } 254 | 255 | t2 := ((1 << 27) - x2*x2 - y2*y2) >> 12 // .16 256 | if t2 > 0 { 257 | t2 = (t2 * t2) >> 16 // .16 258 | t2 = (t2 * t2) >> 16 // .16 259 | n2 = t2 * grad2(perm[(i+1+uint32(perm[(j+1)&0xff]))&0xff], x2, y2) // .16 * .14 = .30 260 | } 261 | 262 | // Add contributions from each corner to get the final noise value. 263 | // The result is scaled to return values in the interval [-1,1]. 264 | n := n0 + n1 + n2 // .30 265 | n = ((n >> 8) * 23163) >> 16 // fix scale to fit exactly in an int16 266 | return uint16(n) + 0x8000 267 | } 268 | 269 | // 3D simplex noise. 270 | // 271 | // The x, y and z inputs are 20.12 fixed-point value. The result covers the full 272 | // range of a uint16, averaging around 32768. 273 | func Noise3(x, y, z uint32) uint16 { 274 | // Simple skewing factors for the 3D case 275 | const F3 = 1431655764 // .32: 0.333333333 276 | const G3 = 715827884 // .32: 0.166666667 277 | 278 | // Skew the input space to determine which simplex cell we're in 279 | s := uint32(((uint64(x) + uint64(y) + uint64(z)) * F3) >> 32) // .12 + .32 = .12: Very nice and simple skew factor for 3D 280 | i := (x>>1 + s>>1) >> 11 // .0 281 | j := (y>>1 + s>>1) >> 11 // .0 282 | k := (z>>1 + s>>1) >> 11 // .0 283 | 284 | t := (uint64(i) + uint64(j) + uint64(k)) * G3 // .32 285 | X0 := uint64(i)<<32 - t // .32: Unskew the cell origin back to (x,y) space 286 | Y0 := uint64(j)<<32 - t // .32 287 | Z0 := uint64(k)<<32 - t // .32 288 | x0 := int32(uint64(x)<<2 - X0>>18) // .14: The x,y distances from the cell origin 289 | y0 := int32(uint64(y)<<2 - Y0>>18) // .14 290 | z0 := int32(uint64(z)<<2 - Z0>>18) // .14 291 | 292 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 293 | // Determine which simplex we are in. 294 | var i1, j1, k1 uint32 // Offsets for second corner of simplex in (i,j,k) coords 295 | var i2, j2, k2 uint32 // Offsets for third corner of simplex in (i,j,k) coords 296 | 297 | // This code would benefit from a backport from the GLSL version! 298 | if x0 >= y0 { 299 | if y0 >= z0 { 300 | i1 = 1 301 | j1 = 0 302 | k1 = 0 303 | i2 = 1 304 | j2 = 1 305 | k2 = 0 // X Y Z order 306 | } else if x0 >= z0 { 307 | i1 = 1 308 | j1 = 0 309 | k1 = 0 310 | i2 = 1 311 | j2 = 0 312 | k2 = 1 // X Z Y order 313 | } else { 314 | i1 = 0 315 | j1 = 0 316 | k1 = 1 317 | i2 = 1 318 | j2 = 0 319 | k2 = 1 // Z X Y order 320 | } 321 | } else { // x0>18 // .14: Offsets for second corner in (x,y,z) coords 352 | y1 := y0 - int32(j1)<<14 + G3>>18 // .14 353 | z1 := z0 - int32(k1)<<14 + G3>>18 // .14 354 | x2 := x0 - int32(i2)<<14 + 2*G3>>18 // .14: Offsets for third corner in (x,y,z) coords 355 | y2 := y0 - int32(j2)<<14 + 2*G3>>18 // .14 356 | z2 := z0 - int32(k2)<<14 + 2*G3>>18 // .14 357 | x3 := x0 - (1 << 14) + 3*G3>>18 // .14: Offsets for last corner in (x,y,z) coords 358 | y3 := y0 - (1 << 14) + 3*G3>>18 // .14 359 | z3 := z0 - (1 << 14) + 3*G3>>18 // .14 360 | 361 | // Calculate the contribution from the four corners 362 | var n0, n1, n2, n3 int32 // .30 363 | const fix0_6 = 161061274 // .28: 0.6 364 | 365 | t0 := (fix0_6 - x0*x0 - y0*y0 - z0*z0) >> 12 // .16 366 | if t0 > 0 { 367 | t0 = (t0 * t0) >> 16 // .16 368 | t0 = (t0 * t0) >> 16 // .16 369 | // .16 * .14 = .30 370 | n0 = t0 * grad3(perm[(i+uint32(perm[(j+uint32(perm[k&0xff]))&0xff]))&0xff], x0, y0, z0) 371 | } 372 | 373 | t1 := (fix0_6 - x1*x1 - y1*y1 - z1*z1) >> 12 // .16 374 | if t1 > 0 { 375 | t1 = (t1 * t1) >> 16 // .16 376 | t1 = (t1 * t1) >> 16 // .16 377 | // .16 * .14 = .30 378 | n1 = t1 * grad3(perm[(i+i1+uint32(perm[(j+j1+uint32(perm[(k+k1)&0xff]))&0xff]))&0xff], x1, y1, z1) 379 | } 380 | 381 | t2 := (fix0_6 - x2*x2 - y2*y2 - z2*z2) >> 12 // .16 382 | if t2 > 0 { 383 | t2 = (t2 * t2) >> 16 // .16 384 | t2 = (t2 * t2) >> 16 // .16 385 | // .16 * .14 = .30 386 | n2 = t2 * grad3(perm[(i+i2+uint32(perm[(j+j2+uint32(perm[(k+k2)&0xff]))&0xff]))&0xff], x2, y2, z2) 387 | } 388 | 389 | t3 := (fix0_6 - x3*x3 - y3*y3 - z3*z3) >> 12 // .16 390 | if t3 > 0 { 391 | t3 = (t3 * t3) >> 16 // .16 392 | t3 = (t3 * t3) >> 16 // .16 393 | // .16 * .14 = .30 394 | n3 = t3 * grad3(perm[(i+1+uint32(perm[(j+1+uint32(perm[(k+1)&0xff]))&0xff]))&0xff], x3, y3, z3) 395 | } 396 | 397 | // Add contributions from each corner to get the final noise value. 398 | // The result is scaled to stay just inside [-1,1] 399 | n := n0 + n1 + n2 + n3 // .30 400 | n = ((n >> 8) * 16748) >> 16 // fix scale to fit exactly in an int16 401 | return uint16(n) + 0x8000 402 | } 403 | 404 | // 4D simplex noise. 405 | // 406 | // The x, y, z and w inputs are 20.12 fixed-point value. The result covers the 407 | // full range of a uint16, averaging around 32768. 408 | func Noise4(x, y, z, w uint32) uint16 { 409 | // The skewing and unskewing factors are hairy again for the 4D case 410 | const F4 = 331804471 // .30: (Math.sqrt(5.0)-1.0)/4.0 = 0.30901699437494745 411 | const G4 = 593549882 // .32: (5.0-Math.sqrt(5.0))/20.0 = 0.1381966011250105 412 | 413 | // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're 414 | // in. 415 | s := uint32(((uint64(x) + uint64(y) + uint64(z) + uint64(w)) * F4) >> 32) // .12 + .30 = .10: Factor for 4D skewing. 416 | i := (x>>2 + s) >> 10 // .0 417 | j := (y>>2 + s) >> 10 // .0 418 | k := (z>>2 + s) >> 10 // .0 419 | l := (w>>2 + s) >> 10 // .0 420 | 421 | t := ((uint64(i) + uint64(j) + uint64(k) + uint64(l)) * G4) >> 18 // .14 422 | X0 := uint64(i)<<14 - t // .14: Unskew the cell origin back to (x,y,z,w) space 423 | Y0 := uint64(j)<<14 - t // .14 424 | Z0 := uint64(k)<<14 - t // .14 425 | W0 := uint64(l)<<14 - t // .14 426 | x0 := int32(uint64(x)<<2 - X0) // .14: The x,y,z,w distances from the cell origin 427 | y0 := int32(uint64(y)<<2 - Y0) // .14 428 | z0 := int32(uint64(z)<<2 - Z0) // .14 429 | w0 := int32(uint64(w)<<2 - W0) // .14 430 | 431 | // For the 4D case, the simplex is a 4D shape I won't even try to describe. 432 | // To find out which of the 24 possible simplices we're in, we need to 433 | // determine the magnitude ordering of x0, y0, z0 and w0. 434 | // The method below is a good way of finding the ordering of x,y,z,w and 435 | // then find the correct traversal order for the simplex we’re in. 436 | // First, six pair-wise comparisons are performed between each possible pair 437 | // of the four coordinates, and the results are used to add up binary bits 438 | // for an integer index. 439 | var c int 440 | if x0 > y0 { 441 | c += 32 442 | } 443 | if x0 > z0 { 444 | c += 16 445 | } 446 | if y0 > z0 { 447 | c += 8 448 | } 449 | if x0 > w0 { 450 | c += 4 451 | } 452 | if y0 > w0 { 453 | c += 2 454 | } 455 | if z0 > w0 { 456 | c += 1 457 | } 458 | 459 | // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. 460 | // Many values of c will never occur, since e.g. x>y>z>w makes x= 3) 466 | j1 := bool2uint32(simplex[c][1] >= 3) 467 | k1 := bool2uint32(simplex[c][2] >= 3) 468 | l1 := bool2uint32(simplex[c][3] >= 3) 469 | // The number 2 in the "simplex" array is at the second largest coordinate. 470 | // The integer offsets for the third simplex corner 471 | i2 := bool2uint32(simplex[c][0] >= 2) 472 | j2 := bool2uint32(simplex[c][1] >= 2) 473 | k2 := bool2uint32(simplex[c][2] >= 2) 474 | l2 := bool2uint32(simplex[c][3] >= 2) 475 | // The number 1 in the "simplex" array is at the second smallest coordinate. 476 | // The integer offsets for the fourth simplex corner 477 | i3 := bool2uint32(simplex[c][0] >= 1) 478 | j3 := bool2uint32(simplex[c][1] >= 1) 479 | k3 := bool2uint32(simplex[c][2] >= 1) 480 | l3 := bool2uint32(simplex[c][3] >= 1) 481 | // The fifth corner has all coordinate offsets = 1, so no need to look that up. 482 | 483 | x1 := x0 - int32(i1)<<14 + G4>>18 // .14: Offsets for second corner in (x,y,z,w) coords 484 | y1 := y0 - int32(j1)<<14 + G4>>18 485 | z1 := z0 - int32(k1)<<14 + G4>>18 486 | w1 := w0 - int32(l1)<<14 + G4>>18 487 | x2 := x0 - int32(i2)<<14 + 2*G4>>18 // .14: Offsets for third corner in (x,y,z,w) coords 488 | y2 := y0 - int32(j2)<<14 + 2*G4>>18 489 | z2 := z0 - int32(k2)<<14 + 2*G4>>18 490 | w2 := w0 - int32(l2)<<14 + 2*G4>>18 491 | x3 := x0 - int32(i3)<<14 + 3*G4>>18 // .14: Offsets for fourth corner in (x,y,z,w) coords 492 | y3 := y0 - int32(j3)<<14 + 3*G4>>18 493 | z3 := z0 - int32(k3)<<14 + 3*G4>>18 494 | w3 := w0 - int32(l3)<<14 + 3*G4>>18 495 | x4 := x0 - (1 << 14) + 4*G4>>18 // .14: Offsets for last corner in (x,y,z,w) coords 496 | y4 := y0 - (1 << 14) + 4*G4>>18 497 | z4 := z0 - (1 << 14) + 4*G4>>18 498 | w4 := w0 - (1 << 14) + 4*G4>>18 499 | 500 | var n0, n1, n2, n3, n4 int32 // Noise contributions from the five corners 501 | const fix0_6 = 161061274 // .28: 0.6 502 | 503 | // Calculate the contribution from the five corners 504 | t0 := (fix0_6 - x0*x0 - y0*y0 - z0*z0 - w0*w0) >> 12 // .16 505 | if t0 > 0 { 506 | t0 = (t0 * t0) >> 16 507 | t0 = (t0 * t0) >> 16 508 | // .16 * .14 = .30 509 | n0 = t0 * grad4(perm[(i+uint32(perm[(j+uint32(perm[(k+uint32(perm[l&0xff]))&0xff]))&0xff]))&0xff], x0, y0, z0, w0) 510 | } 511 | 512 | t1 := (fix0_6 - x1*x1 - y1*y1 - z1*z1 - w1*w1) >> 12 // .16 513 | if t1 > 0 { 514 | t1 = (t1 * t1) >> 16 515 | t1 = (t1 * t1) >> 16 516 | // .16 * .14 = .30 517 | n1 = t1 * grad4(perm[(i+i1+uint32(perm[(j+j1+uint32(perm[(k+k1+uint32(perm[(l+l1)&0xff]))&0xff]))&0xff]))&0xff], x1, y1, z1, w1) 518 | } 519 | 520 | t2 := (fix0_6 - x2*x2 - y2*y2 - z2*z2 - w2*w2) >> 12 // .16 521 | if t2 > 0 { 522 | t2 = (t2 * t2) >> 16 523 | t2 = (t2 * t2) >> 16 524 | // .16 * .14 = .30 525 | n2 = t2 * grad4(perm[(i+i2+uint32(perm[(j+j2+uint32(perm[(k+k2+uint32(perm[(l+l2)&0xff]))&0xff]))&0xff]))&0xff], x2, y2, z2, w2) 526 | } 527 | 528 | t3 := (fix0_6 - x3*x3 - y3*y3 - z3*z3 - w3*w3) >> 12 // .16 529 | if t3 > 0 { 530 | t3 = (t3 * t3) >> 16 531 | t3 = (t3 * t3) >> 16 532 | // .16 * .14 = .30 533 | n3 = t3 * grad4(perm[(i+i3+uint32(perm[(j+j3+uint32(perm[(k+k3+uint32(perm[(l+l3)&0xff]))&0xff]))&0xff]))&0xff], x3, y3, z3, w3) 534 | } 535 | 536 | t4 := (fix0_6 - x4*x4 - y4*y4 - z4*z4 - w4*w4) >> 12 // .16 537 | if t4 > 0 { 538 | t4 = (t4 * t4) >> 16 539 | t4 = (t4 * t4) >> 16 540 | // .16 * .14 = .30 541 | n4 = t4 * grad4(perm[(i+1+uint32(perm[(j+1+uint32(perm[(k+1+uint32(perm[(l+1)&0xff]))&0xff]))&0xff]))&0xff], x4, y4, z4, w4) 542 | } 543 | 544 | n := n0 + n1 + n2 + n3 + n4 // .30 545 | n = ((n >> 8) * 13832) >> 16 // fix scale 546 | return uint16(n) + 0x8000 547 | } 548 | -------------------------------------------------------------------------------- /noise_test.go: -------------------------------------------------------------------------------- 1 | package ledsgo 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/aykevl/Go-simplex-noise/simplexnoise" 9 | ) 10 | 11 | func TestNoise1(t *testing.T) { 12 | numTests := int64(10000000) // ~0.25s 13 | //numTests = 0xffffffff // for exhaustive testing (takes ~100 seconds) 14 | rangemax := 0.0 15 | rangemin := 0.0 16 | rangesum := 0.0 17 | diffsum := 0.0 18 | diffmax := 0.0 19 | diffmin := 0.0 20 | for x := int64(0); x < numTests; x++ { // .12 21 | n1 := simplexnoise.Noise1(float64(x) / 0x1000) 22 | n2 := float64(int16(Noise1(uint32(x))-0x8000)) / 0x8000 23 | rangesum += n2 24 | if n2 > rangemax { 25 | rangemax = n2 26 | } 27 | if n2 < rangemin { 28 | rangemin = n2 29 | } 30 | diff := n1 - n2 31 | diffsum += math.Abs(diff) 32 | if diff > diffmax { 33 | diffmax = diff 34 | } 35 | if diff < diffmin { 36 | diffmin = diff 37 | } 38 | //t.Logf("%d: %+2.6f %+2.6f %+2.6f", x, n1, n2, diff) 39 | } 40 | rangeavg := rangesum / float64(numTests) 41 | diffavg := diffsum / float64(numTests) 42 | // Note: the average output is off by ~0.03, which is expected and similar 43 | // to the floating point implementation. 44 | t.Logf("number of tests: %d", numTests) 45 | t.Logf("range: avg %+2.6f max %+2.6f min %+2.6f", rangeavg, rangemax, rangemin) 46 | t.Logf("diff: avg %+2.6f max %+2.6f min %+2.6f", diffavg, diffmax, diffmin) 47 | if diffavg >= 0.00006 { 48 | t.Errorf("diff between float and fixed-point is too big: %f", diffavg) 49 | } 50 | if diffmax > 0.0003 { 51 | t.Errorf("max is too high: %f", diffmax) 52 | } 53 | if diffmin < -0.0003 { 54 | t.Errorf("min is too low: %f", diffmin) 55 | } 56 | } 57 | 58 | func TestFastNoise1(t *testing.T) { 59 | numTests := int64(1 << 16) 60 | rangemax := 0.0 61 | rangemin := 0.0 62 | rangesum := 0.0 63 | diffsum := 0.0 64 | diffmax := 0.0 65 | diffmin := 0.0 66 | const maxdiff = 0.0018 67 | diffMaxTooHigh := 0 68 | diffMinTooLow := 0 69 | for x := int64(0); x <= numTests; x++ { 70 | n1 := simplexnoise.Noise1(float64(x) / 0x100) 71 | n2 := float64(int16(Noise1AVR(uint16(x))-0x8000)) / 0x8000 72 | rangesum += n2 73 | if n2 > rangemax { 74 | rangemax = n2 75 | } 76 | if n2 < rangemin { 77 | rangemin = n2 78 | } 79 | diff := n1 - n2 80 | diffsum += math.Abs(diff) 81 | if diff > maxdiff { 82 | diffMaxTooHigh++ 83 | } 84 | if diff < -maxdiff { 85 | diffMinTooLow++ 86 | } 87 | if diff > diffmax { 88 | diffmax = diff 89 | } 90 | if diff < diffmin { 91 | diffmin = diff 92 | } 93 | } 94 | rangeavg := rangesum / float64(numTests) 95 | diffavg := diffsum / float64(numTests) 96 | // Note: the average output is off by ~0.03, which is expected and similar 97 | // to the floating point implementation. 98 | if diffavg >= 0.0004 { 99 | t.Errorf("diffavg between float and fixed-point is too big: %f", diffavg) 100 | } 101 | if diffmax > maxdiff { 102 | t.Errorf("diffmax is too high: %f (%d times, %.1f%%)", diffmax, diffMaxTooHigh, float64(diffMaxTooHigh)/float64(numTests)*100) 103 | } 104 | if diffmin < -maxdiff { 105 | t.Errorf("diffmin is too low: %f (%d times, %.1f%%)", diffmin, diffMinTooLow, float64(diffMinTooLow)/float64(numTests)*100) 106 | } 107 | t.Logf("number of tests: %d", numTests) 108 | t.Logf("range: avg %+2.6f max %+2.6f min %+2.6f", rangeavg, rangemax, rangemin) 109 | t.Logf("diff: avg %+2.6f max %+2.6f min %+2.6f", diffavg, diffmax, diffmin) 110 | } 111 | 112 | func TestMul(t *testing.T) { 113 | t.Parallel() 114 | 115 | for x := uint64(0); x <= 0xffff; x++ { 116 | for y := uint64(0); y <= 0xffff; y++ { 117 | result1 := (x * y) >> 16 118 | result2 := uint64(mul16AVR(uint16(x), uint16(y))) 119 | diff := result1 - result2 120 | if uint64(diff) > 2 { 121 | t.Errorf("invalid: %d * %d = %d, got %d (diff %d)", x, y, result1, result2, diff) 122 | } 123 | } 124 | } 125 | } 126 | 127 | func TestNoise2(t *testing.T) { 128 | r := rand.NewSource(0) 129 | numTestsSub := 4000 // ~2s 130 | //numTestsSub = 16000 // ~30s 131 | //numTestsSub = 128000 // ~30m 132 | numTests := numTestsSub * numTestsSub 133 | rangemax := 0.0 134 | rangemin := 0.0 135 | rangesum := 0.0 136 | diffsum := 0.0 137 | diffmax := 0.0 138 | diffmin := 0.0 139 | for i := 0; i < numTestsSub; i++ { // .12 140 | for j := 0; j < numTestsSub; j++ { // .12 141 | x := uint32(r.Int63()) 142 | y := uint32(r.Int63()) 143 | n1 := simplexnoise.Noise2(float64(x)/0x1000, float64(y)/0x1000) 144 | n2 := float64(int16(Noise2(x, y)-0x8000)) / 0x8000 145 | rangesum += n2 146 | if n2 > rangemax { 147 | rangemax = n2 148 | } 149 | if n2 < rangemin { 150 | rangemin = n2 151 | } 152 | diff := n1 - n2 153 | diffsum += math.Abs(diff) 154 | if diff > diffmax { 155 | diffmax = diff 156 | if diffmax > 0.1 { 157 | t.Errorf("diffmax at x=%d; y=%d: %f", x, y, diffmax) 158 | } 159 | } 160 | if diff < diffmin { 161 | diffmin = diff 162 | if diffmin < -0.1 { 163 | t.Errorf("diffmin at x=%d; y=%d: %f", x, y, diffmin) 164 | } 165 | } 166 | if i%32 == 0 && j%32 == 0 { 167 | //t.Logf("%11d: %+2.6f %+2.6f %+2.6f", x, n1, n2, diff) 168 | } 169 | } 170 | } 171 | rangeavg := rangesum / float64(numTests) 172 | diffavg := diffsum / float64(numTests) 173 | t.Logf("number of tests: %d", numTests) 174 | t.Logf("range: avg %+2.6f max %+2.6f min %+2.6f", rangeavg, rangemax, rangemin) 175 | t.Logf("diff: avg %+2.6f max %+2.6f min %+2.6f", diffavg, diffmax, diffmin) 176 | if diffavg >= 0.0008 { 177 | t.Errorf("diff avg between float and fixed-point is too big: %f", diffavg) 178 | } 179 | if diffmax > 0.005 { 180 | t.Errorf("diff max is too high: %f", diffmax) 181 | } 182 | if diffmin < -0.005 { 183 | t.Errorf("diff min is too low: %f", diffmin) 184 | } 185 | } 186 | 187 | func TestNoise3(t *testing.T) { 188 | r := rand.NewSource(0) 189 | numTestsSub := 200 // ~2s 190 | //numTestsSub = 800 // ~130s 191 | numTests := numTestsSub * numTestsSub * numTestsSub 192 | rangemax := 0.0 193 | rangemin := 0.0 194 | rangesum := 0.0 195 | diffsum := 0.0 196 | diffmax := 0.0 197 | diffmin := 0.0 198 | for i := 0; i < numTestsSub; i++ { // .12 199 | for j := 0; j < numTestsSub; j++ { // .12 200 | for k := 0; k < numTestsSub; k++ { // .12 201 | x := uint32(r.Int63()) 202 | y := uint32(r.Int63()) 203 | z := uint32(r.Int63()) 204 | n1 := simplexnoise.Noise3(float64(x)/0x1000, float64(y)/0x1000, float64(z)/0x1000) 205 | n2 := float64(int16(Noise3(x, y, z)+0x8000)) / 0x8000 206 | rangesum += n2 207 | if n2 > rangemax { 208 | rangemax = n2 209 | } 210 | if n2 < rangemin { 211 | rangemin = n2 212 | } 213 | diff := n1 - n2 214 | diffsum += math.Abs(diff) 215 | if diff > diffmax { 216 | diffmax = diff 217 | if diffmax > 0.1 { 218 | t.Errorf("diffmax at x=%d; y=%d; z=%d: %f", x, y, z, diffmax) 219 | } 220 | } 221 | if diff < diffmin { 222 | diffmin = diff 223 | if diffmin < -0.1 { 224 | t.Errorf("diffmin at x=%d; y=%d; z=%d: %f", x, y, z, diffmin) 225 | } 226 | } 227 | if i%32 == 0 && j%32 == 0 && k%32 == 0 { 228 | //t.Logf("%+2.6f %+2.6f %+2.6f", n1, n2, diff) 229 | } 230 | } 231 | } 232 | } 233 | rangeavg := rangesum / float64(numTests) 234 | diffavg := diffsum / float64(numTests) 235 | t.Logf("number of tests: %d", numTests) 236 | t.Logf("range: avg %+2.6f max %+2.6f min %+2.6f", rangeavg, rangemax, rangemin) 237 | t.Logf("diff: avg %+2.6f max %+2.6f min %+2.6f", diffavg, diffmax, diffmin) 238 | if diffavg >= 0.0006 { 239 | t.Errorf("diff avg between float and fixed-point is too big: %f", diffavg) 240 | } 241 | if diffmax > 0.008 { 242 | t.Errorf("diff max is too high: %f", diffmax) 243 | } 244 | if diffmin < -0.008 { 245 | t.Errorf("diff min is too low: %f", diffmin) 246 | } 247 | } 248 | 249 | func TestNoise4(t *testing.T) { 250 | r := rand.NewSource(0) 251 | numTestsSub := 48 // ~2s 252 | //numTestsSub = 100 // ~40s 253 | numTests := numTestsSub * numTestsSub * numTestsSub * numTestsSub 254 | rangemax := 0.0 255 | rangemin := 0.0 256 | rangesum := 0.0 257 | diffsum := 0.0 258 | diffmax := 0.0 259 | diffmin := 0.0 260 | for i := 0; i < numTestsSub; i++ { // .12 261 | for j := 0; j < numTestsSub; j++ { // .12 262 | for k := 0; k < numTestsSub; k++ { // .12 263 | for l := 0; l < numTestsSub; l++ { // .12 264 | x := uint32(r.Int63()) 265 | y := uint32(r.Int63()) 266 | z := uint32(r.Int63()) 267 | w := uint32(r.Int63()) 268 | n1 := simplexnoise.Noise4(float64(x)/0x1000, float64(y)/0x1000, float64(z)/0x1000, float64(w)/0x1000) 269 | n2 := float64(int16(Noise4(x, y, z, w)-0x8000)) / 0x8000 270 | rangesum += n2 271 | if n2 > rangemax { 272 | rangemax = n2 273 | } 274 | if n2 < rangemin { 275 | rangemin = n2 276 | } 277 | diff := n1 - n2 278 | diffsum += math.Abs(diff) 279 | if diff > diffmax { 280 | diffmax = diff 281 | if diffmax > 0.1 { 282 | t.Errorf("diffmax at x=%d; y=%d; z=%d; w=%d: %f", x, y, z, w, diffmax) 283 | } 284 | } 285 | if diff < diffmin { 286 | diffmin = diff 287 | if diffmin < -0.1 { 288 | t.Errorf("diffmin at x=%d; y=%d; z=%d; w=%d: %f", x, y, z, w, diffmin) 289 | } 290 | } 291 | } 292 | } 293 | } 294 | } 295 | rangeavg := rangesum / float64(numTests) 296 | diffavg := diffsum / float64(numTests) 297 | t.Logf("number of tests: %d", numTests) 298 | t.Logf("range: avg %+2.6f max %+2.6f min %+2.6f", rangeavg, rangemax, rangemin) 299 | t.Logf("diff: avg %+2.6f max %+2.6f min %+2.6f", diffavg, diffmax, diffmin) 300 | if diffavg >= 0.0006 { 301 | t.Errorf("diff avg between float and fixed-point is too big: %f", diffavg) 302 | } 303 | if diffmax > 0.008 { 304 | t.Errorf("diff max is too high: %f", diffmax) 305 | } 306 | if diffmin < -0.008 { 307 | t.Errorf("diff min is too low: %f", diffmin) 308 | } 309 | } 310 | 311 | // avoid compiler optimizations 312 | var ( 313 | resultUint16 uint16 314 | resultFloat64 float64 315 | ) 316 | 317 | func BenchmarkNoise1(b *testing.B) { 318 | var r uint16 319 | for n := 0; n < b.N; n++ { 320 | r = Noise1(uint32(n)) 321 | } 322 | resultUint16 = r 323 | } 324 | 325 | func BenchmarkNoise1Float(b *testing.B) { 326 | var r float64 327 | for n := 0; n < b.N; n++ { 328 | r = simplexnoise.Noise1(float64(n)) 329 | } 330 | resultFloat64 = r 331 | } 332 | 333 | func BenchmarkNoise2(b *testing.B) { 334 | var r uint16 335 | for n := 0; n < b.N; n++ { 336 | r = Noise2(uint32(n), uint32(n)) 337 | } 338 | resultUint16 = r 339 | } 340 | 341 | func BenchmarkNoise2Float(b *testing.B) { 342 | var r float64 343 | for n := 0; n < b.N; n++ { 344 | r = simplexnoise.Noise2(float64(n), float64(n)) 345 | } 346 | resultFloat64 = r 347 | } 348 | 349 | func BenchmarkNoise3(b *testing.B) { 350 | var r uint16 351 | for n := 0; n < b.N; n++ { 352 | r = Noise3(uint32(n), uint32(n), uint32(n)) 353 | } 354 | resultUint16 = r 355 | } 356 | 357 | func BenchmarkNoise3Float(b *testing.B) { 358 | var r float64 359 | for n := 0; n < b.N; n++ { 360 | r = simplexnoise.Noise3(float64(n), float64(n), float64(n)) 361 | } 362 | resultFloat64 = r 363 | } 364 | 365 | func BenchmarkNoise4(b *testing.B) { 366 | var r uint16 367 | for n := 0; n < b.N; n++ { 368 | r = Noise4(uint32(n), uint32(n), uint32(n), uint32(n)) 369 | } 370 | resultUint16 = r 371 | } 372 | 373 | func BenchmarkNoise4Float(b *testing.B) { 374 | var r float64 375 | for n := 0; n < b.N; n++ { 376 | r = simplexnoise.Noise4(float64(n), float64(n), float64(n), float64(n)) 377 | } 378 | resultFloat64 = r 379 | } 380 | -------------------------------------------------------------------------------- /palette.go: -------------------------------------------------------------------------------- 1 | package ledsgo 2 | 3 | import ( 4 | "image/color" 5 | ) 6 | 7 | // Palette16 is a 16-color palette on a continuous scale, from which a color can 8 | // be picked. 9 | type Palette16 [16]color.RGBA 10 | 11 | // ColorAt returns a color from the palette at the 16-bit index (0..65535) 12 | // position. Colors not exactly from one position are interpolated. Colors close 13 | // to the top wrap around to the bottom, so you can imagine the palette as being 14 | // a custom color ring akin to a hue color ring in a HSV color picker. 15 | // 16 | // This method is similar to ColorFromPalette in FastLED except that it uses a 17 | // 16-bit index instead of a 8-bit index for better color accuracy. 18 | func (p *Palette16) ColorAt(position uint16) color.RGBA { 19 | index := position >> 12 20 | blendPosition := uint8(position >> 4) 21 | 22 | bottom := &p[index] 23 | 24 | var top *color.RGBA 25 | if index >= 15 { 26 | top = &p[0] 27 | } else { 28 | top = &p[index+1] 29 | } 30 | 31 | return color.RGBA{ 32 | R: blend(bottom.R, top.R, blendPosition), 33 | G: blend(bottom.G, top.G, blendPosition), 34 | B: blend(bottom.B, top.B, blendPosition), 35 | A: 0xff, 36 | } 37 | } 38 | 39 | // A number of palettes copied from FastLED, see: 40 | // https://github.com/FastLED/FastLED/blob/master/colorpalettes.cpp 41 | var ( 42 | CloudColors = Palette16{ 43 | Blue, 44 | DarkBlue, 45 | DarkBlue, 46 | DarkBlue, 47 | 48 | DarkBlue, 49 | DarkBlue, 50 | DarkBlue, 51 | DarkBlue, 52 | 53 | Blue, 54 | DarkBlue, 55 | SkyBlue, 56 | SkyBlue, 57 | 58 | LightBlue, 59 | White, 60 | LightBlue, 61 | SkyBlue, 62 | } 63 | 64 | LavaColors = Palette16{ 65 | Black, 66 | Maroon, 67 | Black, 68 | Maroon, 69 | 70 | DarkRed, 71 | Maroon, 72 | DarkRed, 73 | 74 | DarkRed, 75 | DarkRed, 76 | Red, 77 | Orange, 78 | 79 | White, 80 | Orange, 81 | Red, 82 | DarkRed, 83 | } 84 | 85 | OceanColors = Palette16{ 86 | MidnightBlue, 87 | DarkBlue, 88 | MidnightBlue, 89 | Navy, 90 | 91 | DarkBlue, 92 | MediumBlue, 93 | SeaGreen, 94 | Teal, 95 | 96 | CadetBlue, 97 | Blue, 98 | DarkCyan, 99 | CornflowerBlue, 100 | 101 | Aquamarine, 102 | SeaGreen, 103 | Aqua, 104 | LightSkyBlue, 105 | } 106 | 107 | ForestColors = Palette16{ 108 | DarkGreen, 109 | DarkGreen, 110 | DarkOliveGreen, 111 | DarkGreen, 112 | 113 | Green, 114 | ForestGreen, 115 | OliveDrab, 116 | Green, 117 | 118 | SeaGreen, 119 | MediumAquamarine, 120 | LimeGreen, 121 | YellowGreen, 122 | 123 | LightGreen, 124 | LawnGreen, 125 | MediumAquamarine, 126 | ForestGreen, 127 | } 128 | 129 | // HSV Rainbow 130 | RainbowColors = Palette16{ 131 | color.RGBA{0xFF, 0x00, 0x00, 0xFF}, color.RGBA{0xD5, 0x2A, 0x00, 0xFF}, color.RGBA{0xAB, 0x55, 0x00, 0xFF}, color.RGBA{0xAB, 0x7F, 0x00, 0xFF}, 132 | color.RGBA{0xAB, 0xAB, 0x00, 0xFF}, color.RGBA{0x56, 0xD5, 0x00, 0xFF}, color.RGBA{0x00, 0xFF, 0x00, 0xFF}, color.RGBA{0x00, 0xD5, 0x2A, 0xFF}, 133 | color.RGBA{0x00, 0xAB, 0x55, 0xFF}, color.RGBA{0x00, 0x56, 0xAA, 0xFF}, color.RGBA{0x00, 0x00, 0xFF, 0xFF}, color.RGBA{0x2A, 0x00, 0xD5, 0xFF}, 134 | color.RGBA{0x55, 0x00, 0xAB, 0xFF}, color.RGBA{0x7F, 0x00, 0x81, 0xFF}, color.RGBA{0xAB, 0x00, 0x55, 0xFF}, color.RGBA{0xD5, 0x00, 0x2B, 0xFF}, 135 | } 136 | 137 | // HSV Rainbow colors with alternatating stripes of black 138 | RainbowStripeColors = Palette16{ 139 | color.RGBA{0xFF, 0x00, 0x00, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, color.RGBA{0xAB, 0x55, 0x00, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, 140 | color.RGBA{0xAB, 0xAB, 0x00, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, color.RGBA{0x00, 0xFF, 0x00, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, 141 | color.RGBA{0x00, 0xAB, 0x55, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, color.RGBA{0x00, 0x00, 0xFF, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, 142 | color.RGBA{0x55, 0x00, 0xAB, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, color.RGBA{0xAB, 0x00, 0x55, 0xFF}, color.RGBA{0x00, 0x00, 0x00, 0xFF}, 143 | } 144 | 145 | // HSV color ramp: blue purple ping red orange yellow (and back) 146 | // Basically, everything but the greens, which tend to make 147 | // people's skin look unhealthy. This palette is good for 148 | // lighting at a club or party, where it'll be shining on people. 149 | PartyColors = Palette16{ 150 | color.RGBA{0x55, 0x00, 0xAB, 0xFF}, color.RGBA{0x84, 0x00, 0x7C, 0xFF}, color.RGBA{0xB5, 0x00, 0x4B, 0xFF}, color.RGBA{0xE5, 0x00, 0x1B, 0xFF}, 151 | color.RGBA{0xE8, 0x17, 0x00, 0xFF}, color.RGBA{0xB8, 0x47, 0x00, 0xFF}, color.RGBA{0xAB, 0x77, 0x00, 0xFF}, color.RGBA{0xAB, 0xAB, 0x00, 0xFF}, 152 | color.RGBA{0xAB, 0x55, 0x00, 0xFF}, color.RGBA{0xDD, 0x22, 0x00, 0xFF}, color.RGBA{0xF2, 0x00, 0x0E, 0xFF}, color.RGBA{0xC2, 0x00, 0x3E, 0xFF}, 153 | color.RGBA{0x8F, 0x00, 0x71, 0xFF}, color.RGBA{0x5F, 0x00, 0xA1, 0xFF}, color.RGBA{0x2F, 0x00, 0xD0, 0xFF}, color.RGBA{0x00, 0x07, 0xF9, 0xFF}, 154 | } 155 | 156 | // Approximate "black body radiation" palette, akin to 157 | // the FastLED 'HeatColor' function. 158 | // Recommend that you use values 0-240 rather than 159 | // the usual 0-255, as the last 15 colors will be 160 | // 'wrapping around' from the hot end to the cold end, 161 | // which looks wrong. 162 | HeatColors = Palette16{ 163 | color.RGBA{0x00, 0x00, 0x00, 0xFF}, 164 | color.RGBA{0x33, 0x00, 0x00, 0xFF}, color.RGBA{0x66, 0x00, 0x00, 0xFF}, color.RGBA{0x99, 0x00, 0x00, 0xFF}, color.RGBA{0xCC, 0x00, 0x00, 0xFF}, color.RGBA{0xFF, 0x00, 0x00, 0xFF}, 165 | color.RGBA{0xFF, 0x33, 0x00, 0xFF}, color.RGBA{0xFF, 0x66, 0x00, 0xFF}, color.RGBA{0xFF, 0x99, 0x00, 0xFF}, color.RGBA{0xFF, 0xCC, 0x00, 0xFF}, color.RGBA{0xFF, 0xFF, 0x00, 0xFF}, 166 | color.RGBA{0xFF, 0xFF, 0x33, 0xFF}, color.RGBA{0xFF, 0xFF, 0x66, 0xFF}, color.RGBA{0xFF, 0xFF, 0x99, 0xFF}, color.RGBA{0xFF, 0xFF, 0xCC, 0xFF}, color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}, 167 | } 168 | ) 169 | -------------------------------------------------------------------------------- /strip.go: -------------------------------------------------------------------------------- 1 | package ledsgo 2 | 3 | import ( 4 | "image/color" 5 | ) 6 | 7 | // The LEDs of a LED strip. Colors are represented in RGB form: 0x00rrggbb. 8 | type Strip []color.RGBA 9 | 10 | // Fill the LED strip with a color range, using the HSV spectrum conversion. 11 | // The start color is the color for the first LED. All other colors have the 12 | // same saturation and value but increased (and wrapped) hue. 13 | func (s Strip) FillSpectrum(start Color, hueinc uint16) { 14 | for i := range s { 15 | s[i] = start.Spectrum() 16 | start.H += hueinc 17 | } 18 | } 19 | 20 | // FillSolid sets all colors to the given value. 21 | func (s Strip) FillSolid(color color.RGBA) { 22 | for i := range s { 23 | s[i] = color 24 | } 25 | } 26 | --------------------------------------------------------------------------------