-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrender.go
More file actions
112 lines (90 loc) · 2.67 KB
/
render.go
File metadata and controls
112 lines (90 loc) · 2.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package noisy
import (
"fmt"
"image"
"image/color"
"image/png"
"os"
"sort"
)
// Gradient is an unsorted map of colors, with positions [-1;1] as keys.
type Gradient map[float64]color.RGBA
func clampValue(value, lowerBound, upperBound int) int {
if value < lowerBound {
return lowerBound
} else if value > upperBound {
return upperBound
} else {
return value
}
}
// GetColor returns a color.RGBA for a given position.
//
// The color is computed with lerp, to get the closest one from the Gradient map.
// An error is returned if the Gradient does not contain at least 2 colors.
func (gradient Gradient) GetColor(position float64) (color.RGBA, error) {
if len(gradient) < 2 {
return color.RGBA{}, fmt.Errorf("a Gradient must have at least 2 values")
}
keys := []float64{}
for k := range gradient {
keys = append(keys, k)
}
sort.Float64s(keys)
indexPos := 0
for _, k := range keys {
if position < k {
break
}
indexPos++
}
index0 := clampValue(indexPos-1, 0, len(gradient)-1)
index1 := clampValue(indexPos, 0, len(gradient)-1)
if index0 == index1 {
return gradient[keys[index1]], nil
}
input0 := keys[index0]
input1 := keys[index1]
alpha := (position - input0) / (input1 - input0)
color0 := gradient[keys[index0]]
color1 := gradient[keys[index1]]
return linearInterpColor(color0, color1, alpha), nil
}
func blendChannel(channel0, channel1 uint8, alpha float64) uint8 {
c0 := float64(channel0) / 255.0
c1 := float64(channel1) / 255.0
return uint8(((c1 * alpha) + (c0 * (1.0 - alpha))) * 255.0)
}
func linearInterpColor(color0, color1 color.RGBA, alpha float64) color.RGBA {
a := blendChannel(color0.A, color1.A, alpha)
r := blendChannel(color0.R, color1.R, alpha)
g := blendChannel(color0.G, color1.G, alpha)
b := blendChannel(color0.B, color1.B, alpha)
return color.RGBA{r, g, b, a}
}
// RenderImg generates an image from source, stored in filename.
//
// The gradient parameter defines the colors to build the image.
// The parameters width & height defines the size. The image should be squared, to avoid deformations.
func RenderImg(source SourceInterface, gradient Gradient, filename string, width int, height int) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
img := image.NewRGBA(image.Rectangle{
Min: image.Point{0, 0},
Max: image.Point{width, height},
})
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
value := source.GetValue(float64(x)/float64(width), float64(y)/float64(height), 0.0)
pixelColor, err := gradient.GetColor(value)
if err != nil {
return err
}
img.Set(x, height-y-1, pixelColor)
}
}
return png.Encode(file, img.SubImage(img.Bounds()))
}