diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe7af13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +gomandel +*.png +*.jpg +go.sum +go.mod +.vscode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a938a58 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ + +FROM ubi8/go-toolset:1.18 as build + +### Copy source code for building the application +COPY ./mandel.go . + +### Download dependencies and build +RUN go mod init gomandel && \ + go mod tidy -e && \ + go build . + +FROM ubi8/ubi-micro +COPY --from=build /opt/app-root/src/gomandel . + +EXPOSE 8080 +ENTRYPOINT ["./gomandel"] +CMD ["--server", "--xres=1024", "--yres=1024" ] diff --git a/example.png b/example.png deleted file mode 100644 index 9068044..0000000 Binary files a/example.png and /dev/null differ diff --git a/mandel.go b/mandel.go index 38c93f6..1793a71 100644 --- a/mandel.go +++ b/mandel.go @@ -7,19 +7,27 @@ import ( "image/color" "image/color/palette" "image/png" + "log" "math" + "math/rand" + "net/http" "os" "sort" + "strconv" "sync" "github.com/nfnt/resize" ) +const RADIUS_MIN = 0.0005 +const RADIUS_MAX = 1.0 + var IT, xres, yres, aa int var xpos, ypos, radius float64 var out_filename, palette_string string -var invert bool +var invert, server_mode, randomize bool var focusstring string +var port string func init() { flag.IntVar(&IT, "IT", 512, "maximum number of iterations") @@ -33,11 +41,18 @@ func init() { flag.StringVar(&palette_string, "palette", "plan9", "One of: plan9|websafe|gameboy|retro|alternate") flag.StringVar(&focusstring, "focus", "", "sequence of focus command. Select quadrant (numbered 1-4). e.g.: 1423. Read code to understand") flag.BoolVar(&invert, "invert", false, "Inverts colouring") + flag.BoolVar(&server_mode, "server", false, "Enable web server mode (i.e., deliver resulting image over the web)") + flag.StringVar(&port, "port", "8080", "listening port when server_mode == true") + flag.BoolVar(&randomize, "random", false, "Use a random value for the radius parameter") flag.Parse() - Gray = make([]color.Color, 255 * 3) + if randomize { + randomize_params() + } + + Gray = make([]color.Color, 255*3) for i := 0; i < 255*3; i++ { - Gray[i] = color.RGBA{uint8(i / 3), uint8((i+1) / 3), uint8((i+2) / 3), 255} + Gray[i] = color.RGBA{uint8(i / 3), uint8((i + 1) / 3), uint8((i + 2) / 3), 255} } Alternate = make([]color.Color, 20) @@ -57,10 +72,10 @@ func init() { Alternate[i] = color.RGBA{0xea, 0xfb, 0xc5, 255} } } - + BlackWhite = make([]color.Color, 0) for i := 0; i < 20; i++ { - if i % 2 == 0 { + if i%2 == 0 { BlackWhite = append(BlackWhite, color.RGBA{0, 0, 0, 255}) } else { BlackWhite = append(BlackWhite, color.RGBA{255, 255, 255, 255}) @@ -72,15 +87,15 @@ func it(ca, cb float64) (int, float64) { var a, b float64 = 0, 0 for i := 0; i < IT; i++ { as, bs := a*a, b*b - if as + bs > 4 { + if as+bs > 4 { return i, as + bs } //if as + bs < .00001 { // return .00001 //} - a, b = as - bs + ca, 2 * a * b + cb + a, b = as-bs+ca, 2*a*b+cb } - return IT, a * a + b * b + return IT, a*a + b*b } var Gameboy = []color.Color{ @@ -111,7 +126,8 @@ var Retro = []color.Color{ var Gray, Alternate, BlackWhite []color.Color -func main() { +func create_image() img.Image { + width, height := xres*aa, yres*aa ratio := float64(height) / float64(width) //xpos, ypos, zoom_width := -.16070135, 1.0375665, 1.0e-7 @@ -121,9 +137,9 @@ func main() { //xpos, ypos, zoom_width := .232223859135, .559654166164, .00000000004 y_radius := float64(radius * ratio) - temp_radius, temp_y_radius := radius / 4.0, y_radius / 4.0 + temp_radius, temp_y_radius := radius/4.0, y_radius/4.0 for _, command := range focusstring { - switch(string(command)) { + switch string(command) { case "1": xpos -= temp_radius ypos += temp_radius @@ -145,25 +161,23 @@ func main() { case "d": xpos += temp_radius case "r": - temp_radius, temp_y_radius = radius / 4, y_radius / 4 + temp_radius, temp_y_radius = radius/4, y_radius/4 case "z": radius /= 2 y_radius /= 2 - temp_radius, temp_y_radius = radius / 4, y_radius / 4 + temp_radius, temp_y_radius = radius/4, y_radius/4 default: - return + return nil } temp_radius /= 2 temp_y_radius /= 2 } - - xmin, xmax := xpos - radius / 2.0, xpos + radius / 2.0 - ymin, ymax := ypos - y_radius / 2.0, ypos + y_radius / 2.0 + xmin, xmax := xpos-radius/2.0, xpos+radius/2.0 + ymin, ymax := ypos-y_radius/2.0, ypos+y_radius/2.0 + + single_values := make([]float64, width*height) - - single_values := make([]float64, width * height) - fmt.Print("Mandelling...") var wg sync.WaitGroup @@ -173,11 +187,11 @@ func main() { go func(y int) { defer wg.Done() for x := 0; x < width; x++ { - a := (float64(x) / float64(width)) * (xmax - xmin) + xmin - b := (float64(y) / float64(height)) * (ymin - ymax) + ymax + a := (float64(x)/float64(width))*(xmax-xmin) + xmin + b := (float64(y)/float64(height))*(ymin-ymax) + ymax stop_it, norm := it(a, b) - smooth_val := float64(IT - stop_it) + math.Log(norm) - i := y * width + x + smooth_val := float64(IT-stop_it) + math.Log(norm) + i := y*width + x if invert { single_values[i] = smooth_val } else { @@ -194,17 +208,15 @@ func main() { sorted_values[i] = single_values[i] } sort.Float64s(sorted_values) - - fmt.Println("Done") + fmt.Println("Done") cont := make([]color.Color, 10000) for i, _ := range cont { //val := float64(i) / float64(len(cont)) - val := i * 256 / len(cont) + val := i * 256 / len(cont) cont[i] = color.RGBA{uint8(val), 0, uint8(255 - val), uint8(255)} } - var pal []color.Color palette_map := make(map[string][]color.Color) @@ -245,12 +257,77 @@ func main() { } fmt.Println("Done") - fmt.Println("Resizing...") + fmt.Println("Resizing...") image_resized := resize.Resize(uint(xres), uint(yres), image, resize.Lanczos3) fmt.Println("Done") - out_file, _ := os.Create(out_filename) - png.Encode(out_file, image_resized) - fmt.Println("Finished writing to:", out_filename) - fmt.Printf("--r %v --x %v --y %v\n", radius, (xmin + xmax) / 2, (ymin + ymax) / 2) + return image_resized + +} + +func handler(w http.ResponseWriter, r *http.Request) { + + // Overide (if present) global vars from query parameters + params := r.URL.Query() + for k, v := range params { + switch k { + // TODO: add error handling code + case "IT": + IT, _ = strconv.Atoi(v[0]) + case "xres": + xres, _ = strconv.Atoi(v[0]) + case "yres": + yres, _ = strconv.Atoi(v[0]) + case "aa": + aa, _ = strconv.Atoi(v[0]) + // // case "xpos": + // // xpos, _ = strconv.ParseFloat(v[0], 64) + // // case "ypos": + // // ypos, _ = strconv.ParseFloat(v[0], 64) + // case "radius": + // radius, _ = strconv.ParseFloat(v[0], 64) + case "palette": + palette_string = v[0] + case "focus": + focusstring = v[0] + case "invert": + invert, _ = strconv.ParseBool(v[0]) + // case "random": + // randomize, _ = strconv.ParseBool(v[0]) + // if randomize { + // randomize_params() + // } + } + } + + randomize_params() + image_resized := create_image() + png.Encode(w, image_resized) + +} + +func randomize_params() { + radius = RADIUS_MIN + rand.Float64()*(RADIUS_MAX-RADIUS_MIN) + // radius = rand.Float64() * RADIUS_MAX + xpos = rand.Float64() + ypos = rand.Float64() +} + +func main() { + + if server_mode { + + http.HandleFunc("/", handler) + log.Printf("Serving fractals on port %v\n", port) + addr := fmt.Sprintf("0.0.0.0:%s", port) + log.Fatal(http.ListenAndServe(addr, nil)) + + } else { + image_resized := create_image() + out_file, _ := os.Create(out_filename) + png.Encode(out_file, image_resized) + fmt.Println("Finished writing to:", out_filename) + // fmt.Printf("--r %v --x %v --y %v\n", radius, (xmin+xmax)/2, (ymin+ymax)/2) + + } } diff --git a/object1.png b/object1.png deleted file mode 100644 index 86b78c7..0000000 Binary files a/object1.png and /dev/null differ diff --git a/spiral.jpg b/spiral.jpg deleted file mode 100644 index b42afe5..0000000 Binary files a/spiral.jpg and /dev/null differ diff --git a/spiral.png b/spiral.png deleted file mode 100644 index 440c551..0000000 Binary files a/spiral.png and /dev/null differ