package main import ( "fmt" "math" "syscall/js" ) type selection struct { startX int startY int endX int endY int } type mbRender struct { doc js.Value ctx js.Value backCtx []js.Value curCtx int width float64 height float64 minX float64 minY float64 maxX float64 maxY float64 colorMode string imageData js.Value mandelbrot chan Mandelbrot mandelbrotValue Mandelbrot mandelbrotProgress chan float64 mandelbrotProgressValue float64 mouseDown bool hasSelection bool selection selection } type Mandelbrot []byte func (mb *mbRender) getMandelbrot(maxIterations uint) { go func() { mb.mandelbrot <- mb.CalculateMandelbrot(maxIterations) }() } func (mb *mbRender) start() { mb.doc = js.Global().Get("document") canvasEl := mb.doc.Call("getElementById", "canvas") //mb.width = mb.doc.Get("body").Get("clientWidth").Float() //mb.height = mb.doc.Get("body").Get("clientHeight").Float() mb.width = 1000.0 mb.height = 1000.0 mb.minX = -2.0 mb.minY = -2.0 mb.maxX = 2 mb.maxY = 2 canvasEl.Set("width", mb.width) canvasEl.Set("height", mb.height) mb.ctx = canvasEl.Call("getContext", "2d") mb.imageData = mb.ctx.Call("getImageData", 0, 0, mb.width, mb.height) mb.mandelbrot = make(chan Mandelbrot) mb.getMandelbrot(1000) mouseDownEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} { evt := args[0] mb.mouseDown = true mb.hasSelection = true mb.selection = selection{ startX: evt.Get("clientX").Int() - canvasEl.Get("offsetLeft").Int(), startY: evt.Get("clientY").Int() - canvasEl.Get("offsetTop").Int(), endX: evt.Get("clientX").Int() - canvasEl.Get("offsetLeft").Int(), endY: evt.Get("clientY").Int() - canvasEl.Get("offsetTop").Int(), } return nil }) defer mouseDownEvt.Release() mouseMoveEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} { evt := args[0] if mb.mouseDown { posX := evt.Get("clientX").Int() - canvasEl.Get("offsetLeft").Int() posY := evt.Get("clientY").Int() - canvasEl.Get("offsetTop").Int() dx := posX - mb.selection.startX dy := posY - mb.selection.startY if math.Abs(float64(dx)) > math.Abs(float64(dy)) { mb.selection.endX = mb.selection.startX + dx mb.selection.endY = mb.selection.startY + dx } else { mb.selection.endX = mb.selection.startX + dy mb.selection.endY = mb.selection.startY + dy } } return nil }) defer mouseMoveEvt.Release() mouseUpEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} { mb.mouseDown = false mb.hasSelection = false newMinX := CalcReal(float64(mb.selection.startX), mb.width, mb.minX, mb.maxX) newMinY := CalcReal(float64(mb.selection.startY), mb.height, mb.minY, mb.maxY) newMaxX := CalcReal(float64(mb.selection.endX), mb.width, mb.minX, mb.maxX) newMaxY := CalcReal(float64(mb.selection.endY), mb.height, mb.minY, mb.maxY) mb.minX = newMinX mb.minY = newMinY mb.maxX = newMaxX mb.maxY = newMaxY mb.getMandelbrot(uint(math.Min(1000*1/math.Sqrt((mb.maxX-mb.minX)), 100000))) return nil }) defer mouseUpEvt.Release() canvasEl.Call("addEventListener", "mousedown", mouseDownEvt) canvasEl.Call("addEventListener", "mousemove", mouseMoveEvt) canvasEl.Call("addEventListener", "mouseup", mouseUpEvt) var renderFrame js.Func renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} { // mb.ctx.Call("clearRect", 0, 0, mb.width, mb.height) select { case x, ok := <-mb.mandelbrot: if ok { mb.mandelbrotValue = x } default: } select { case x, ok := <-mb.mandelbrotProgress: if ok { mb.mandelbrotProgressValue = x } default: } value := js.Global().Get("Uint8Array").New(len(mb.mandelbrotValue)) js.CopyBytesToJS(value, mb.mandelbrotValue) mb.imageData.Get("data").Call("set", value) mb.ctx.Call("putImageData", mb.imageData, 0, 0) if mb.hasSelection { mb.ctx.Set("fillStyle", "rgba(0, 0, 255, 0.8)") mb.ctx.Call("fillRect", mb.selection.startX, mb.selection.startY, mb.selection.endX-mb.selection.startX, mb.selection.endY-mb.selection.startY) } js.Global().Call("requestAnimationFrame", renderFrame) return nil }) done := make(chan struct{}, 0) js.Global().Call("requestAnimationFrame", renderFrame) <-done } func (m Mandelbrot) SetPixel(width int, x int, y int, color []byte) { start := y*int(width)*4 + x*4 for i := 0; i < 4; i++ { m[start+i] = color[i] } } func (mb mbRender) GetColor(iteration uint, maxIterations uint) []byte { intensity := (math.Pi / 2.0) * float64(iteration) / float64(maxIterations) return []byte{ byte(math.Sin(intensity) * 256), byte(math.Sin(intensity*2) * 256), byte(math.Cos(intensity) * 256), 255, } // HSL - red /*c := colorful.Hsl(float64(iteration)/256, 1.0, float64(iteration)/(float64(iteration)+8.0)) return []byte{ byte(c.R * 255), byte(c.G * 255), byte(c.B * 255), 255, }*/ } func CalcReal(x, width, minX, maxX float64) float64 { return (x*(maxX-minX))/width + minX } func CalcImaginary(y, height, minY, maxY float64) float64 { return (y*(maxY-minY))/height + minY } func (mb *mbRender) CalculateMandelbrot(maxIterations uint) Mandelbrot { fmt.Println("max iterations", maxIterations) mandelbrot := make(Mandelbrot, int(mb.width*mb.height*4)) mb.mandelbrotProgress = make(chan float64) for row := 0; row < int(mb.height); row++ { for col := 0; col < int(mb.width); col++ { cRe := CalcReal(float64(col), mb.width, mb.minX, mb.maxX) cIm := CalcImaginary(float64(row), mb.height, mb.minY, mb.maxY) var x float64 var y float64 var iteration uint for ; x*x+y*y <= 4 && iteration < maxIterations; iteration++ { newX := x*x - y*y + cRe y = 2*x*y + cIm x = newX } mandelbrot.SetPixel(int(mb.width), col, row, mb.GetColor(iteration, maxIterations)) } go func() { mb.mandelbrotProgress <- float64(row) / mb.height }() } return mandelbrot } func main() { fmt.Println("Hallo fögel") mb := mbRender{} mb.start() }