Introduction

The Java Code

public class myMandelbrot extends Application {

    /* =========================================Variables================================================ */

    double width = 800;
    double height = 600;
    double maximumIterations = 50;
    Canvas canvas = new Canvas(width, height);
    WritableImage actualImage;
    double zoom = 250.0;
    double xPos = -470; //add 0 on both of the coordinates for the accurate plane
    double yPos = 30;
    double hue = 264.0;
    double saturation = maximumIterations;
    double brightness = 0.9;
    int R = 60;
    int G = 0;
    int B = 60;

    /* =========================================MainMethod================================================ */

    public static void main(String[] args) {
        launch(args);
    }

    /* =========================================MainStage================================================= */

    @Override
    public void start(Stage stage) {
        Group group = new Group(canvas);

        Scene scene = new Scene(group, width, height);
        scene.setOnKeyPressed(event -> {
            switch (event.getCode()) {
                case W, UP -> {
                    if (event.isShiftDown()) {
                        up(10);
                    } else {
                        up(100);
                    }
                }
                case A, LEFT -> {
                    if (event.isShiftDown()) {
                        left(10);
                    } else {
                        left(100);
                    }
                }
                case S, DOWN -> {
                    if (event.isShiftDown()) {
                        down(10);
                    } else {
                        down(100);
                    }
                }
                case D, RIGHT -> {
                    if (event.isShiftDown()) {
                        right(10);
                    } else {
                        right(100);
                    }
                }
                case EQUALS -> zoomIn();
                case MINUS -> zoomOut();
                case SPACE -> reset();
                case ESCAPE -> Platform.exit();
            }
        });     //key listener
        scene.setOnMouseClicked(event -> {
            switch (event.getButton()) {
                case PRIMARY -> {

                    zoom /= 0.7;

                    MandelbrotSet();
                }
                case SECONDARY -> {
                    zoom *= 0.7;
                    MandelbrotSet();
                }
            }
        });   //mouse listener for easier zoom

        TextField typeHeight = new TextField();
        TextField typeWidth = new TextField();
        Button button1 = new Button("Save");

        TextField typeIter = new TextField();
        Button button2 = new Button("Refresh");

        mainMenuBar(stage, group, typeHeight, typeWidth, typeIter, button1, button2);

        textFieldsImg(group, typeHeight, typeWidth, button1);

        textFieldIter(group, typeIter, button2);

        stage.widthProperty().addListener((obs, oldVal, newVal) -> {
            canvas.widthProperty().bind(stage.widthProperty());
            MandelbrotSet();
        });

        stage.heightProperty().addListener((obs, oldVal, newVal) -> {
            canvas.heightProperty().bind(stage.heightProperty());
            MandelbrotSet();
        });

        stage.setScene(scene);

        long start = System.currentTimeMillis();
        //MandelbrotSet();
        long end = System.currentTimeMillis();
        long result = (end - start);
        runTime(group, stage, result);

        stage.setTitle("Mandelbrot Set");
        stage.show();
    }

    /* =========================================Iterations================================================ */

    public int iterationChecker(double cr, double ci) {
        int iterationsOfZ = 0;
        double zr = 0.0;
        double zi = 0.0;

        while (iterationsOfZ < maximumIterations && (zr * zr) + (zi * zi) < 4) {
            double oldZr = zr;
            zr = (zr * zr) - (zi * zi) + cr;
            zi = 2 * (oldZr * zi) + ci;
            iterationsOfZ++;
        }
        return iterationsOfZ;
    }

    /* ========================================MandelbrotSet============================================== */

    public void MandelbrotSet() {
        WritableImage image = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
        actualImage = image;
        double centerY = canvas.getWidth() / 2.0;
        double centerX = canvas.getHeight() / 2.0;
        for (int x = 0; x < canvas.getWidth(); x++) {
            for (int y = 0; y < canvas.getHeight(); y++) {
                double cr = xPos / width + (x - centerY) / zoom;
                double ci = yPos / height + (y - centerX) / zoom;       //getting position of the points on the canvas

                int iterations = iterationChecker(cr, ci);

                if (iterations == maximumIterations) {  //inside the set
                    image.getPixelWriter().setColor(x, y, Color.rgb(R, G, B));
                } else if (brightness == 0.9) {  //white background
                    image.getPixelWriter().setColor(x, y, Color.hsb(hue, iterations / maximumIterations, brightness));
                } else if (hue == 300) {  //colorful background
                    image.getPixelWriter().setColor(x, y, Color.hsb(hue * iterations / maximumIterations, saturation, brightness));
                } else if (hue == 0 && saturation == 0 && brightness == 1) {
                    image.getPixelWriter().setColor(x, y, Color.hsb(hue, saturation, brightness));
                } else {   //black background
                    image.getPixelWriter().setColor(x, y, Color.hsb(hue, saturation, iterations / brightness));
                }
            }
            canvas.getGraphicsContext2D().drawImage(image, 0, 0); //x and y coordinates of the image.
        }
    }

    /* ===========================================Colors================================================== */

    public void colorLight() {
        hue = 246.0;
        saturation = maximumIterations;
        brightness = 0.9;
        R = 60;
        G = 0;
        B = 60;
        MandelbrotSet();
    }

    public void colorDark() {
        hue = 0;
        saturation = 0;
        brightness = maximumIterations;
        R = 15;
        G = 15;
        B = 15;
        MandelbrotSet();
    }

    public void colorHue() {
        hue = 300.0;
        saturation = 1.0;
        brightness = 1.0;
        R = 35;
        G = 0;
        B = 35;
        MandelbrotSet();
    }

    public void colorWhite() {
        hue = 0.0;
        saturation = 0.0;
        brightness = 1.0;
        R = 0;
        G = 0;
        B = 0;
        MandelbrotSet();
    }

    /* ==========================================Position================================================= */

    public void up(int number) {
        yPos -= (height / zoom) * number;
        MandelbrotSet();
    }

    public void down(int number) {
        yPos += (height / zoom) * number;
        MandelbrotSet();
    }

    public void left(int number) {
        xPos -= (width / zoom) * number;
        MandelbrotSet();
    }

    public void right(int number) {
        xPos += (width / zoom) * number;
        MandelbrotSet();
    }

    public void zoomIn() {
        zoom /= 0.7;
        MandelbrotSet();
    }

    public void zoomOut() {
        zoom *= 0.7;
        MandelbrotSet();
    }

    public void reset() {
        zoom = 250.0;
        xPos = -470;
        yPos = 30;
        MandelbrotSet();
    }

    /* ==========================================SaveImage================================================ */

    public void saveImage(Stage stage, WritableImage image) {
        FileChooser fc = new FileChooser();
        fc.setTitle("Save File");
        FileChooser.ExtensionFilter extensions = new FileChooser.ExtensionFilter("Images *.jpg, *.png", "*.jpg", "*.png");

        fc.getExtensionFilters().add(extensions);

        File file = fc.showSaveDialog(stage);
        if (file != null) {
            try {
                canvas.snapshot(null, image);
                RenderedImage renderedImage = SwingFXUtils.fromFXImage(image, null);
                ImageIO.write(renderedImage, "png", file);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    /* ============================================MenuBar================================================ */

    public void mainMenuBar(Stage stage, Group group, TextField typeHeight, TextField typeWidth, TextField typeIter, Button button1, Button button2) {
        MenuBar menubar = new MenuBar();
        menu1(menubar, group);
        menu2(menubar);
        menu3(stage, menubar, typeHeight, typeWidth, typeIter, button1, button2);

    }

    public void menu1(MenuBar menubar, Group group) {
        Menu menu1 = new Menu("Color");
        menubar.getMenus().add(menu1);
        group.getChildren().add(menubar);
        menubar.setPrefWidth(176);
        CheckMenuItem m1i1 = new CheckMenuItem("Light");
        CheckMenuItem m1i2 = new CheckMenuItem("Dark");
        CheckMenuItem m1i3 = new CheckMenuItem("Colorful");
        CheckMenuItem m1i4 = new CheckMenuItem("Solid White");
        m1i1.setOnAction(e -> {
            if (m1i1.isSelected()) {
                colorLight();
                m1i2.setSelected(false);
                m1i3.setSelected(false);
                m1i4.setSelected(false);
            }
        });
        m1i2.setOnAction(e -> {
            if (m1i2.isSelected()) {
                colorDark();
                m1i1.setSelected(false);
                m1i3.setSelected(false);
                m1i4.setSelected(false);
            }
        });
        m1i3.setOnAction(e -> {
            if (m1i3.isSelected()) {
                colorHue();
                m1i1.setSelected(false);
                m1i2.setSelected(false);
                m1i4.setSelected(false);
            }
        });
        m1i4.setOnAction(e -> {
            if (m1i4.isSelected()) {
                colorWhite();
                m1i1.setSelected(false);
                m1i2.setSelected(false);
                m1i3.setSelected(false);
            }
        });
        m1i1.setAccelerator(KeyCombination.keyCombination("1"));
        m1i2.setAccelerator(KeyCombination.keyCombination("2"));
        m1i3.setAccelerator(KeyCombination.keyCombination("3"));
        m1i4.setAccelerator(KeyCombination.keyCombination("4"));
        menu1.getItems().addAll(m1i1, m1i2, m1i3, m1i4);
    }

    public void menu2(MenuBar menubar) {
        Menu menu2 = new Menu("View");
        menubar.getMenus().add(menu2);
        MenuItem m2i1 = new MenuItem("Zoom in");
        MenuItem m2i2 = new MenuItem("Zoom out");
        MenuItem m2i3 = new MenuItem("Reset");
        MenuItem m2i4 = new MenuItem("Move Up");
        MenuItem m2i5 = new MenuItem("Move Down");
        MenuItem m2i6 = new MenuItem("Move Left");
        MenuItem m2i7 = new MenuItem("Move Right");

        m2i1.setAccelerator(KeyCombination.keyCombination("Plus"));
        m2i2.setAccelerator(KeyCombination.keyCombination("Minus"));
        m2i3.setAccelerator(KeyCombination.keyCombination("Space"));
        m2i4.setAccelerator(KeyCombination.keyCombination("UP"));
        m2i5.setAccelerator(KeyCombination.keyCombination("DOWN"));
        m2i6.setAccelerator(KeyCombination.keyCombination("LEFT"));
        m2i7.setAccelerator(KeyCombination.keyCombination("RIGHT"));

        m2i1.setOnAction(t -> zoomIn());
        m2i2.setOnAction(t -> zoomOut());
        m2i3.setOnAction(t -> reset());
        m2i4.setOnAction(t -> up(100));
        m2i5.setOnAction(t -> down(100));      //menubar location
        m2i6.setOnAction(t -> left(100));
        m2i7.setOnAction(t -> right(100));
        menu2.getItems().addAll(m2i1, m2i2, m2i3, new SeparatorMenuItem(), m2i4, m2i5, m2i6, m2i7);
    }

    public void menu3(Stage stage, MenuBar menubar, TextField typeHeight, TextField typeWidth, TextField typeIter, Button button1, Button button2) {
        Menu menu3 = new Menu("Image");
        menubar.getMenus().add(menu3);

        MenuItem m3i1 = new MenuItem("Set Iterations");
        MenuItem m3i2 = new MenuItem("Save Image");

        menu3.getItems().addAll(m3i1, m3i2);

        m3i2.setOnAction(e -> {
            typeHeight.setVisible(true);
            typeWidth.setVisible(true);
            button1.setVisible(true);
            button2.setVisible(false);
            typeIter.setVisible(false);
        });
        button1.setOnAction(e -> {       //setting image resolution and saving
            canvas.widthProperty().unbind();
            canvas.heightProperty().unbind();
            canvas.setWidth(Integer.parseInt(typeHeight.getText()));
            canvas.setHeight(Integer.parseInt(typeWidth.getText()));
            MandelbrotSet();

            saveImage(stage, actualImage);
            canvas.widthProperty().bind(stage.widthProperty());
            canvas.heightProperty().bind(stage.heightProperty());
            MandelbrotSet();

            typeHeight.setVisible(false);
            typeWidth.setVisible(false);
            button1.setVisible(false);
        });
        m3i1.setAccelerator(KeyCombination.keyCombination("CTRL + C"));
        m3i1.setOnAction(e -> {
            typeIter.setVisible(true);
            button2.setVisible(true);
            typeHeight.setVisible(false);
            typeWidth.setVisible(false);
            button1.setVisible(false);
        });
        button2.setOnAction(e -> {
            typeIter.setVisible(false);
            button2.setVisible(false);
            maximumIterations = Double.parseDouble(typeIter.getText());
            MandelbrotSet();
        });
        m3i2.setAccelerator(KeyCombination.keyCombination("CTRL + V"));
    }


    /* ========================================TextFields================================================= */

    public void textFieldsImg(Group group, TextField typeHeight, TextField typeWidth, Button button) {

        button.setLayoutX(120);
        button.setLayoutY(33);
        button.setPrefWidth(50);

        typeHeight.setLayoutX(7);
        typeHeight.setLayoutY(33);
        typeHeight.setPrefWidth(50);
        typeWidth.setLayoutX(63);
        typeWidth.setLayoutY(33);
        typeWidth.setPrefWidth(50);

        onlyNumbers(typeHeight);
        onlyNumbers(typeWidth);

        group.getChildren().add(typeHeight);
        group.getChildren().add(typeWidth);
        group.getChildren().add(button);
        typeHeight.setVisible(false);
        typeWidth.setVisible(false);
        button.setVisible(false);
    }

    public void textFieldIter(Group group, TextField typeIter, Button button) {
        typeIter.setLayoutY(200);
        button.setLayoutX(92);
        button.setLayoutY(33);
        button.setPrefWidth(78);

        typeIter.setLayoutX(7);
        typeIter.setLayoutY(33);
        typeIter.setPrefWidth(78);

        onlyNumbers(typeIter);
        typeIter.setVisible(false);
        button.setVisible(false);

        group.getChildren().add(typeIter);
        group.getChildren().add(button);
    }

    public void onlyNumbers(TextField text) {
        text.textProperty().addListener((observable, oldNum, newNum) -> {
            if (!newNum.matches("\\d*")) text.setText(newNum.replaceAll("[^\\d]", ""));
        });
    }

    /* ==========================================CalculateRunTime============================================ */

    public void runTime(Group group, Stage stage, long result) {
        Label label = new Label((" Time to compile: " + (result / 1000.0) + " sec.   (" + (int) maximumIterations + "  iterations)  " + (int) width + " x " + (int) height));
        label.layoutXProperty().bind(stage.widthProperty().subtract(stage.getWidth()));   //Should align label to horizontal center, but it is off
        label.layoutYProperty().bind(stage.heightProperty().subtract(label.getHeight() + 45));
        group.getChildren().add(label);
    }

    /* ==========================================CalculateRunTime============================================ */

    private static final Executor executor = Executors.newSingleThreadExecutor();

    /* ============================================ParallelMode============================================== */

}

The Kotlin Code

class MyMandelbrot : Application() {
   private val width = 800.0
   private val height = 600.0
   private var maximumIterations = 50.0
   private var canvas = Canvas(width, height)
   private val zoom: DoubleProperty = SimpleDoubleProperty(250.0)
   private var xPos = -470.0
   private var yPos = 30.0
   private var hue = 264.0
   private var saturation = maximumIterations
   private var brightness = 0.9
   private var red = 60
   private var green = 0
   private var blue = 60
   private val setProgress: DoubleProperty = SimpleDoubleProperty(0.0)
   private val zoomIn: () -> Unit = { mandelbrotSet { zoom.value /= 0.4 } }
   private val zoomOut: () -> Unit = { mandelbrotSet { zoom.value *= 0.4 } }
   private val up: (Int) -> Unit = { mandelbrotSet { yPos -= height / zoom.value * it } }
   private val down: (Int) -> Unit = { mandelbrotSet { yPos += height / zoom.value * it } }
   private val left: (Int) -> Unit = { mandelbrotSet { xPos -= width / zoom.value * it } }
   private val right: (Int) -> Unit = { mandelbrotSet { xPos += width / zoom.value * it } }
   private val reset: () -> Unit = {
      mandelbrotSet {
         zoom.value = 250.0
         xPos = -470.0
         yPos = 30.0
      }
   }

   override fun start(stage: Stage) {
      stage.scene = Scene(createContent(), width, height)
      stage.title = "Mandelbrot Set"
      stage.show()
   }

   private fun createContent() = BorderPane().apply {
      top = mainMenuBar()
      center = canvas
      bottom = createBottom()
      minWidth = this@MyMandelbrot.width
      minHeight = this@MyMandelbrot.height
      addEventFilter(KeyEvent.KEY_PRESSED) {
         if (processKeystroke(it.code, it.isShiftDown)) {
            it.consume()
         }
      }
      with(canvas) {
         widthProperty().also {
            it.bind(this@apply.widthProperty())
            it.addListener(InvalidationListener {
               mandelbrotSet()
            })
         }
         heightProperty().also {
            it.bind(this@apply.heightProperty().add(-60.0))
            it.addListener(InvalidationListener {
               mandelbrotSet()
            })
         }
         onMouseClicked = EventHandler {
            when (it.button) {
               MouseButton.PRIMARY -> zoomIn()
               MouseButton.SECONDARY -> zoomOut()
               else -> {}
            }
         }
      }
   }

   private fun processKeystroke(keyCode: KeyCode, isShiftDown: Boolean): Boolean {
      var didSomething = true
      when (keyCode) {
         KeyCode.W, KeyCode.UP -> up(if (isShiftDown) 10 else 100)
         KeyCode.A, KeyCode.LEFT -> left(if (isShiftDown) 10 else 100)
         KeyCode.S, KeyCode.DOWN -> down(if (isShiftDown) 10 else 100)
         KeyCode.D, KeyCode.RIGHT -> right(if (isShiftDown) 10 else 100)
         KeyCode.EQUALS -> zoomIn()
         KeyCode.MINUS -> zoomOut()
         KeyCode.SPACE -> reset()
         KeyCode.ESCAPE -> Platform.exit()
         else -> {
            didSomething = false
         }
      }
      return didSomething
   }

   private fun createBottom() = HBox(6.0).apply {
      children += listOf(Label("Iterations: "),
                         iterationsBox(),
                         ProgressBar().apply {
                            progressProperty().bind(setProgress)
                            prefWidth = 200.0
                         },
                         Label("      Pending: "),
                         boundLabel(Bindings.createStringBinding({ pendingSets.value.toString() }, pendingSets)),
                         Label("    Zoom:"),
                         boundLabel(Bindings.createStringBinding({ zoom.value.roundToInt().toString() }, zoom)))
      padding = Insets(6.0)
   }

   private fun iterationsBox() = HBox(6.0).apply {
      val iterationsTextField = TextField()
      val textFormatter: TextFormatter<Int> = TextFormatter(IntegerStringConverter())
      iterationsTextField.textFormatter = textFormatter
      val refreshButton = Button("Refresh").apply {
         onAction = EventHandler {
            canvas.requestFocus()
            isDisable = true
            mandelbrotSet { maximumIterations = textFormatter.value?.toDouble() ?: 50.0 }
            isDisable = false
         }
         defaultButtonProperty().bind(iterationsTextField.focusedProperty())
      }
      children += listOf(iterationsTextField, refreshButton)
   }

   private fun boundLabel(contents: ObservableStringValue) = Label().apply { textProperty().bind(contents) }

   private fun iterationChecker(cr: Double, ci: Double): Int {
      var iterationsOfZ = 0
      var zr = 0.0
      var zi = 0.0
      while (iterationsOfZ < maximumIterations && ((zr.pow(2) + zi.pow(2)) < 4)) {
         val oldZr = zr
         zr = zr * zr - zi * zi + cr
         zi = 2 * (oldZr * zi) + ci
         iterationsOfZ++
      }
      return iterationsOfZ
   }

   private var taskRunning = false
   private val preProcesses = mutableListOf<() -> Unit>()
   private val pendingSets: IntegerProperty = SimpleIntegerProperty(0)

   private fun mandelbrotSet(preProcessor: () -> Unit = {}) {
      if ((canvas.width > 0) && (canvas.height > 0)) {
         preProcesses += preProcessor
         pendingSets.value = preProcesses.size
         println("In mandelbrot ${preProcesses.size} - already running?  $taskRunning")
         if (!taskRunning) {
            val startTime = System.currentTimeMillis()
            taskRunning = true
            preProcesses.forEach { it.invoke() }
            preProcesses.clear()
            pendingSets.value = 0
            val task = object : Task<WritableImage>() {
               override fun call(): WritableImage = performMandelbrot { workDone, maxWork -> updateProgress(workDone, maxWork) }
            }
            task.setOnSucceeded {
               canvas.graphicsContext2D.drawImage(task.value, 0.0, 0.0)
               taskRunning = false
               if (preProcesses.isNotEmpty()) {
                  mandelbrotSet()
               }
            }
            task.setOnFailed {
               println("Failed! ${task.exception.stackTraceToString()}")
               taskRunning = false
            }
            setProgress.unbind()
            setProgress.bind(task.progressProperty())
            Thread(task).start()
         }
      }
   }

   private fun performMandelbrot(progressConsumer: (workDone: Long, maxWork: Long) -> Unit) = WritableImage(canvas.width.toInt(), canvas.height.toInt()).apply {
      println("Starting mandelbrot")
      val centerY = width / 2.0
      val centerX = height / 2.0
      val counter = AtomicLong(0)
      (0 until width.roundToInt()).toList().parallelStream().forEach { x ->
         progressConsumer.invoke(counter.incrementAndGet(), width.roundToLong())
         for (y: Int in 0 until height.roundToInt()) {
            val cr = xPos / width + (x - centerY) / zoom.value
            val ci = yPos / height + (y - centerX) / zoom.value
            pixelWriter.setColor(x, y, determineColour(iterationChecker(cr, ci)))
         }
      }
      println("Done")
   }

   private fun determineColour(iterations: Int): Color {
      if (iterations.toDouble() == maximumIterations) {  //inside the set
         return Color.rgb(red, green, blue)
      }
      if (brightness == 0.9) {  //white background
         return Color.hsb(hue, iterations / maximumIterations, brightness)
      }
      if (hue == 300.0) {  //colorful background
         return Color.hsb(hue * iterations / maximumIterations, saturation, brightness)
      }
      if (hue == 0.0 && saturation == 0.0 && brightness == 1.0) {
         return Color.hsb(hue, saturation, brightness)
      }
      return Color.hsb(hue, saturation, iterations / brightness)
   }

   private fun mainMenuBar() = MenuBar().apply {
      prefWidth = 176.0
      menus.addAll(menu1(), menu2())
   }

   @Suppress("UNCHECKED_CAST")
   private fun menu1() = Menu("Color").also {
      ToggleGroup().apply {
         toggles += listOf(makeColourMenuItem("Light", "1") { mandelbrotSet { newSettings(246.0, maximumIterations, 0.9, 60, 0, 60) } },
                           makeColourMenuItem("Dark", "2") { mandelbrotSet { newSettings(0.0, 0.0, maximumIterations, 15, 15, 15) } },
                           makeColourMenuItem("Colourful", "3") { mandelbrotSet { newSettings(300.0, 1.0, 1.0, 35, 0, 35) } },
                           makeColourMenuItem("Solid White", "4") { mandelbrotSet { newSettings(0.0, 0.0, 1.0, 0, 0, 0) } })
         it.items += toggles as List<MenuItem>
      }
   }

   private fun makeColourMenuItem(name: String, accel: String, func: () -> Unit) = RadioMenuItem(name).apply {
      selectedProperty().addListener { ob: Observable -> if ((ob as ObservableBooleanValue).value) func.invoke() }
      accelerator = KeyCombination.keyCombination(accel)
   }

   private fun menu2() = Menu("View").apply {
      items += listOf(makeMenuItem("Zoom In", "Plus") { zoomIn() },
                      makeMenuItem("Zoom Out", "Minus") { zoomOut() },
                      makeMenuItem("Reset", "Space") { reset() },
                      makeMenuItem("Move Up", "UP") { up(100) },
                      makeMenuItem("Move Down", "DOWN") { down(100) },
                      makeMenuItem("Move Left", "LEFT") { left(100) },
                      makeMenuItem("Move Right", "RIGHT") { right(100) })
   }

   private fun makeMenuItem(name: String, accel: String, eventHandler: EventHandler<ActionEvent>) = MenuItem(name).apply {
      accelerator = KeyCombination.keyCombination(accel)
      onAction = eventHandler
   }

   private fun newSettings(newHue: Double, newSat: Double, newBright: Double, newRed: Int, newGreen: Int, newBlue: Int) {
      hue = newHue
      saturation = newSat
      brightness = newBright
      red = newRed
      green = newGreen
      blue = newBlue
   }
}

fun main() {
   Application.launch(MyMandelbrot::class.java)
}