/
mandelbrot.ink
132 lines (115 loc) · 2.83 KB
/
mandelbrot.ink
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
` generate a rendering of the Mandelbrot set `
std := load('std')
bmp := load('bmp').bmp
log := std.log
f := std.format
map := std.map
reduce := std.reduce
range := std.range
wf := std.writeFile
` graph position `
CENTERX := ~0.540015
CENTERY := 0.59468
` rendering configurations `
WIDTH := 300
HEIGHT := 300
SCALE := 50000 ` pixels for 1 unit `
MAXITER := 600
` set the correct "escape" threshold for sequence `
ESCAPE := (WIDTH > HEIGHT :: {
true -> WIDTH / SCALE
false -> HEIGHT / SCALE
})
ESCAPE := (ESCAPE < 2 :: {
true -> 2
false -> ESCAPE
})
` complex arithmetic functions `
cpxAbsSq := z => z.0 * z.0 + z.1 * z.1
cpxAdd := (z, w) => [z.0 + w.0, z.1 + w.1]
cpxMul := (z, w) => [
z.0 * w.0 - z.1 * w.1
z.0 * w.1 + z.1 * w.0
]
` given a number [0, WIDTH * HEIGHT), return a, (a, b) pair in a + bi
whose mandelbrot set value is to be computed `
idxToCpx := i => (
x := (i % WIDTH) - WIDTH / 2
y := floor(i / WIDTH) - HEIGHT / 2
[x / SCALE + CENTERX, y / SCALE + CENTERY]
)
` compute divergence speed for a given a + bi,
returns a number [0, 1)`
lb := 1 / ln(ESCAPE) ` log base`
lhb := ln(0.5) * lb ` log half-base `
mcompute := coord => (
thresholdSq := ESCAPE * ESCAPE
(sub := (last, iter) => cpxAbsSq(last) > thresholdSq :: {
` provably diverges `
true -> (
` smoothed rendering from https://csl.name/post/mandelbrot-rendering/ `
fractional := 5 + iter - lhb - ln(ln(cpxAbsSq(last))) * lb
fractional / MAXITER
)
` maybe converges? `
false -> iter :: {
` give up `
MAXITER -> 1
` try again `
_ -> sub(cpxAdd(cpxMul(last, last), coord), iter + 1)
}
})([0, 0], 0)
)
` hsl to rgb color converter, for rendering the exterior of the set `
hsl := (h, s, l) => (
` ported from https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion `
h2rgb := (p, q, t) => (
` wrap to [0, 1) `
t := (t < 1 :: {
true -> t + 1
false -> t
})
t := (t > 1 :: {
true -> t - 1
false -> t
})
[t < 1 / 6, t < 1 / 2, t < 2 / 3] :: {
[true, _, _] -> p + (q - p) * 6 * t
[_, true, _] -> q
[_, _, true] -> p + (q - p) * (2 / 3 - t) * 6
_ -> p
}
)
q := (l < 0.5 :: {
true -> l * (1 + s)
false -> l + s - l * s
})
p := 2 * l - q
[
255 * h2rgb(p, q, h + 1 / 3)
255 * h2rgb(p, q, h)
255 * h2rgb(p, q, h - 1 / 3)
]
)
` map [0, 1) output from mcompute to RGB values `
mrgb := n => n :: {
1 -> [20, 20, 20]
_ -> hsl(n, 0.8, 0.6)
}
` generate image `
startTime := time()
total := WIDTH * HEIGHT
file := bmp(WIDTH, HEIGHT, map(range(0, WIDTH * HEIGHT, 1), x => (
x % floor(total / 50) :: {
0 -> log(f('{{ progress }}% rendered {{ rate }} pixels/sec', {
progress: floor(x / total * 100)
rate: floor(x / (time() - startTime))
}))
}
mrgb(mcompute(idxToCpx(x)))
)))
` save file `
wf('mandelbrot.bmp', file, result => result :: {
true -> log('done!')
() -> log('error writing file!')
})