mandelmapper/render.go

185 lines
4.3 KiB
Go
Raw Normal View History

2018-07-31 04:41:10 +00:00
package main
import (
"image"
"image/color"
"image/png"
"log"
2018-07-31 22:21:41 +00:00
"math"
2018-07-31 04:41:10 +00:00
"math/cmplx"
"net/http"
"runtime"
"strconv"
"strings"
"sync"
2018-07-31 22:21:41 +00:00
2018-08-02 01:51:59 +00:00
"deadbeef.codes/steven/mandelmapper/palette"
2018-07-31 04:41:10 +00:00
)
const (
Size = 128
2018-08-01 03:40:30 +00:00
Iterations = (1<<16 - 1) / 128
2018-07-31 04:41:10 +00:00
)
2018-07-31 22:21:41 +00:00
var (
colors []color.RGBA
2018-08-04 23:32:50 +00:00
queue = make(chan pixel)
2018-07-31 22:21:41 +00:00
)
2018-07-31 04:41:10 +00:00
type pixel struct {
2018-07-31 22:21:41 +00:00
out *image.RGBA
2018-07-31 04:41:10 +00:00
x, y int
tileX, tileY int64
2018-08-01 03:40:30 +00:00
tileZoom uint16
2018-07-31 04:41:10 +00:00
wg *sync.WaitGroup
}
2018-08-04 23:32:50 +00:00
func init() {
http.HandleFunc("/mandelbrot/", renderTile)
2018-07-31 04:41:10 +00:00
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
for i := 0; i < runtime.GOMAXPROCS(0); i++ {
go computeThread()
}
2018-08-01 02:18:31 +00:00
colorStep := float64(Iterations)
2018-08-04 23:39:32 +00:00
// colors = interpolateColors("Plan9", colorStep)
colors = interpolateColors("Vivid", colorStep)
2018-07-31 22:21:41 +00:00
2018-08-01 02:18:31 +00:00
log.Fatal(http.ListenAndServe(":6161", nil))
2018-07-31 04:41:10 +00:00
}
2018-08-04 23:32:50 +00:00
func computeThread() {
for p := range queue {
val := mandelbrot(
complex(
(float64(p.x)/Size+float64(p.tileX))/float64(uint(1<<(p.tileZoom-1))),
(float64(p.y)/Size+float64(p.tileY))/float64(uint(1<<(p.tileZoom-1))),
),
)
p.out.SetRGBA(p.x, p.y, colors[val])
p.wg.Done()
2018-07-31 04:41:10 +00:00
}
}
2018-07-31 22:21:41 +00:00
2018-08-04 23:32:50 +00:00
//interpolateColors accepts a color palette and number of desired colors and builds a slice of colors by interpolating the gaps
2018-07-31 22:21:41 +00:00
func interpolateColors(paletteCode string, numberOfColors float64) []color.RGBA {
var factor float64
steps := []float64{}
cols := []uint32{}
interpolated := []uint32{}
interpolatedColors := []color.RGBA{}
for _, v := range palette.ColorPalettes {
factor = 1.0 / numberOfColors
switch v.Keyword {
case paletteCode:
if paletteCode != "" {
for index, col := range v.Colors {
if col.Step == 0.0 && index != 0 {
stepRatio := float64(index+1) / float64(len(v.Colors))
step := float64(int(stepRatio*100)) / 100 // truncate to 2 decimal precision
steps = append(steps, step)
} else {
steps = append(steps, col.Step)
}
r, g, b, a := col.Color.RGBA()
r /= 0xff
g /= 0xff
b /= 0xff
a /= 0xff
uintColor := uint32(r)<<24 | uint32(g)<<16 | uint32(b)<<8 | uint32(a)
cols = append(cols, uintColor)
}
var min, max, minColor, maxColor float64
if len(v.Colors) == len(steps) && len(v.Colors) == len(cols) {
for i := 0.0; i <= 1; i += factor {
for j := 0; j < len(v.Colors)-1; j++ {
if i >= steps[j] && i < steps[j+1] {
min = steps[j]
max = steps[j+1]
minColor = float64(cols[j])
maxColor = float64(cols[j+1])
uintColor := cosineInterpolation(maxColor, minColor, (i-min)/(max-min))
interpolated = append(interpolated, uint32(uintColor))
}
}
}
}
for _, pixelValue := range interpolated {
r := pixelValue >> 24 & 0xff
g := pixelValue >> 16 & 0xff
b := pixelValue >> 8 & 0xff
a := 0xff
interpolatedColors = append(interpolatedColors, color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)})
}
}
}
}
return interpolatedColors
}
func cosineInterpolation(c1, c2, mu float64) float64 {
mu2 := (1 - math.Cos(mu*math.Pi)) / 2.0
return c1*(1-mu2) + c2*mu2
}
func linearInterpolation(c1, c2, mu uint32) uint32 {
return c1*(1-mu) + c2*mu
}
2018-08-01 03:40:30 +00:00
2018-08-04 23:32:50 +00:00
func renderTile(w http.ResponseWriter, r *http.Request) {
components := strings.Split(r.URL.Path, "/")[1:]
2018-08-04 23:32:50 +00:00
if len(components) != 4 || components[0] != "mandelbrot" || components[3][len(components[3])-4:] != ".png" {
w.WriteHeader(http.StatusNotFound)
return
}
components[3] = components[3][:len(components[3])-4]
2018-08-04 23:32:50 +00:00
tileX, err := strconv.ParseInt(components[2], 10, 64)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
tileY, err := strconv.ParseInt(components[3], 10, 64)
if err != nil {
2018-08-04 23:32:50 +00:00
w.WriteHeader(http.StatusNotFound)
return
}
tileZoom, err := strconv.ParseUint(components[1], 10, 8)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
2018-08-04 23:32:50 +00:00
var wg sync.WaitGroup
wg.Add(Size * Size)
img := image.NewRGBA(image.Rect(0, 0, Size, Size))
for x := 0; x < Size; x++ {
for y := 0; y < Size; y++ {
2018-08-04 23:32:50 +00:00
queue <- pixel{img, x, y, tileX - int64(1<<tileZoom-1) - 1, tileY, uint16(tileZoom), &wg}
}
}
wg.Wait()
w.Header().Set("Content-Type", "image/png")
png.Encode(w, img)
}
func mandelbrot(c complex128) uint16 {
var z complex128
for i := 0; i < Iterations; i++ {
z = z*z + c
if cmplx.IsNaN(z) {
return uint16(i)
}
}
return Iterations
}