|      1 // Mandelbrot pictures |      1 // Mandelbrot pictures | 
|      2 //===================== |      2 //===================== | 
|      3 // |      3 // | 
|      4 //   see https://en.wikipedia.org/wiki/Mandelbrot_set |      4 //   see https://en.wikipedia.org/wiki/Mandelbrot_set | 
|      5 //  |      5 // | 
|      6 // needs to be called with |      6 // needs to be called with | 
|      7 //  |         | 
|      8 // scala-cli --extra-jars scala-parallel-collections_3-1.0.4.jar |         | 
|      9 // |      7 // | 
|     10 // the library is also uploaded to KEATS  |      8 //   scala-cli --extra-jars scala-parallel-collections_3-1.0.4.jar | 
|     11 // |      9 // | 
|     12 // !! UPDATE: On my faster Mac-M1 machine the times |     10 // the jar-file is uploaded to KEATS | 
|     13 // !! are ca. 4 secs for the sequential version and |     11 // | 
|     14 // !! around 0.7 secs for the par-version. |     12 // | 
|         |     13 // !! UPDATE ON TIMING: On my faster Mac-M1 machine  | 
|         |     14 // !! the times for the first example are ca. 4 secs for  | 
|         |     15 // !! the sequential version and around 0.7 secs for the  | 
|         |     16 // !! par-version. | 
|     15  |     17  | 
|     16  |     18  | 
|     17 import java.awt.Color |     19 import javax.swing.{JFrame, JPanel, WindowConstants} | 
|     18 import java.awt.Dimension |     20 import java.awt.{Color, Dimension, Graphics, Graphics2D} | 
|     19 import java.awt.Graphics |         | 
|     20 import java.awt.Graphics2D |         | 
|     21 import java.awt.image.BufferedImage |     21 import java.awt.image.BufferedImage | 
|     22 import javax.swing.JFrame |     22  | 
|     23 import javax.swing.JPanel |     23 import scala.language.implicitConversions | 
|     24 import javax.swing.WindowConstants |     24 import scala.collection.parallel.CollectionConverters.* | 
|     25 import scala.language.implicitConversions     |         | 
|     26 import scala.collection.parallel.CollectionConverters._ |         | 
|     27  |     25  | 
|     28 // complex numbers |     26 // complex numbers | 
|     29 case class Complex(val re: Double, val im: Double) {  |     27 // represents the complex number re + im * i | 
|     30   // represents the complex number re + im * i |     28 case class Complex(val re: Double, val im: Double) { | 
|         |     29    | 
|     31   def +(that: Complex) = Complex(this.re + that.re, this.im + that.im) |     30   def +(that: Complex) = Complex(this.re + that.re, this.im + that.im) | 
|     32   def -(that: Complex) = Complex(this.re - that.re, this.im - that.im) |     31   def -(that: Complex) = Complex(this.re - that.re, this.im - that.im) | 
|     33   def *(that: Complex) = Complex(this.re * that.re - this.im * that.im, |     32   def *(that: Complex) = Complex(this.re * that.re - this.im * that.im, | 
|     34                                  this.re * that.im + that.re * this.im) |     33                                  this.re * that.im + that.re * this.im) | 
|     35   def *(that: Double) = Complex(this.re * that, this.im * that) |     34   def *(that: Double) = Complex(this.re * that, this.im * that) | 
|     36   def abs() = Math.sqrt(this.re * this.re + this.im * this.im) |     35   def abs() = Math.sqrt(this.re * this.re + this.im * this.im) | 
|     37 } |     36 } | 
|     38  |     37  | 
|     39 // to allow the notation n + m * i |     38 // to allow the usual mathmo notation n + m * i | 
|     40 object i extends Complex(0, 1) |     39 object i extends Complex(0, 1) | 
|     41  |     40  | 
|     42 // implicit conversion from Doubles to Complex |     41 // implicit conversion from Doubles to Complex | 
|     43 given Conversion[Double, Complex] = Complex(_, 0) |     42 given Conversion[Double, Complex] = Complex(_, 0) | 
|     44  |     43  | 
|     45  |         | 
|     46 // some customn colours for the "sliding effect" |     44 // some customn colours for the "sliding effect" | 
|     47 val colours = List( |     45 val colours = List( | 
|     48   new Color(66, 30, 15),    new Color(25, 7, 26), |     46   Color(66, 30, 15),    Color(25, 7, 26), | 
|     49   new Color(9, 1, 47),      new Color(4, 4, 73), |     47   Color(9, 1, 47),      Color(4, 4, 73), | 
|     50   new Color(0, 7, 100),     new Color(12, 44, 138), |     48   Color(0, 7, 100),     Color(12, 44, 138), | 
|     51   new Color(24, 82, 177),   new Color(57, 125, 209), |     49   Color(24, 82, 177),   Color(57, 125, 209), | 
|     52   new Color(134, 181, 229), new Color(211, 236, 248), |     50   Color(134, 181, 229), Color(211, 236, 248), | 
|     53   new Color(241, 233, 191), new Color(248, 201, 95), |     51   Color(241, 233, 191), Color(248, 201, 95), | 
|     54   new Color(255, 170, 0),   new Color(204, 128, 0), |     52   Color(255, 170, 0),   Color(204, 128, 0), | 
|     55   new Color(153, 87, 0),    new Color(106, 52, 3)) |     53   Color(153, 87, 0),    Color(106, 52, 3)) | 
|     56  |     54  | 
|     57 // the viewer panel with an image canvas |     55 // the viewer panel with an image canvas | 
|     58 class Viewer(width: Int, height: Int) extends JPanel { |     56 class Viewer(width: Int, height: Int) extends JPanel { | 
|     59   val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) |     57   val canvas = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) | 
|     60    |     58  | 
|     61   override def paintComponent(g: Graphics) =  |     59   override def paintComponent(g: Graphics) = | 
|     62     g.asInstanceOf[Graphics2D].drawImage(canvas, null, null) |     60     g.asInstanceOf[Graphics2D].drawImage(canvas, null, null) | 
|     63    |     61  | 
|     64   override def getPreferredSize() =  |     62   override def getPreferredSize() = | 
|     65     new Dimension(width, height) |     63     Dimension(width, height) | 
|     66  |     64  | 
|     67   def clearCanvas(color: Color) = { |     65   def clearCanvas(color: Color) = { | 
|     68     for (x <- 0 to width - 1; y <- 0 to height - 1)  |     66     for (x <- 0 to width - 1; y <- 0 to height - 1) | 
|     69       canvas.setRGB(x, y, color.getRGB()) |     67       canvas.setRGB(x, y, color.getRGB()) | 
|     70     repaint() |     68     repaint() | 
|     71   }   |     69   } | 
|     72 } |     70 } | 
|     73  |     71  | 
|     74 // initialising the viewer panel |     72 // initialising the viewer panel | 
|     75 def openViewer(width: Int, height: Int) : Viewer = { |     73 def openViewer(width: Int, height: Int) : Viewer = { | 
|     76   val frame = new JFrame("XYPlane") |     74   val frame = JFrame("XYPlane") | 
|     77   val viewer = new Viewer(width, height) |     75   val viewer = Viewer(width, height) | 
|     78   frame.add(viewer) |     76   frame.add(viewer) | 
|     79   frame.pack() |     77   frame.pack() | 
|     80   frame.setVisible(true) |     78   frame.setVisible(true) | 
|     81   frame.setResizable(false) |     79   frame.setResizable(false) | 
|     82   frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) |     80   frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) | 
|     88 val H = 800   // height |     86 val H = 800   // height | 
|     89 val black = Color.black |     87 val black = Color.black | 
|     90 val viewer = openViewer(W, H) |     88 val viewer = openViewer(W, H) | 
|     91  |     89  | 
|     92 // draw a pixel on the canvas |     90 // draw a pixel on the canvas | 
|     93 def pixel(x: Int, y: Int, color: Color) =  |     91 def pixel(x: Int, y: Int, color: Color) = | 
|     94   viewer.canvas.setRGB(x, y, color.getRGB()) |     92   viewer.canvas.setRGB(x, y, color.getRGB()) | 
|     95  |     93  | 
|     96  |     94  | 
|     97 // calculates the number of iterations using lazy lists  |     95 // calculates the number of iterations using lazy lists (streams) | 
|     98 // (streams) |         | 
|     99 //   the iteration goes on for a maximum of max steps, |     96 //   the iteration goes on for a maximum of max steps, | 
|    100 //   but might leave early when the pred is satisfied |     97 //   but might leave early when the pred is satisfied | 
|    101 def iterations(c: Complex, max: Int) : Int = { |     98 def iterations(c: Complex, max: Int) : Int = { | 
|    102   def next(z: Complex) = z * z + c     |     99   def next(z: Complex) = z * z + c | 
|    103   def pred(z: Complex) = z.abs() < 2    // exit condition |    100   def pred(z: Complex) = z.abs() < 2    // exit condition | 
|    104   LazyList.iterate(0.0 * i, max)(next).takeWhile(pred).size |    101   LazyList.iterate(0.0 * i, max)(next).takeWhile(pred).size | 
|    105 } |    102 } | 
|    106  |    103  | 
|    107 // main function  |    104 // main function | 
|    108 //    start and end are the upper-left and lower-right corners,  |    105 //    start and end are the upper-left and lower-right corners, | 
|    109 //    max is the number of maximum iterations |    106 //    max is the number of maximum iterations | 
|    110 def mandelbrot(start: Complex, end: Complex, max: Int) : Unit = { |    107 def mandelbrot(start: Complex, end: Complex, max: Int) : Unit = { | 
|    111   viewer.clearCanvas(black) |    108   viewer.clearCanvas(black) | 
|    112    |    109  | 
|    113   // deltas for each grid step  |    110   // deltas for each grid step | 
|    114   val d_x = (end.re - start.re) / W |    111   val d_x = (end.re - start.re) / W | 
|    115   val d_y = (end.im - start.im) / H |    112   val d_y = (end.im - start.im) / H | 
|    116     |    113  | 
|    117   for (y <- (0 until H).par) { |    114   for (y <- (0 until H).par) { | 
|    118     for (x <- (0 until W).par) { |    115     for (x <- (0 until W).par) { | 
|    119      |    116  | 
|    120      val c = start +  |    117      val c = start + x * d_x + y * d_y * i | 
|    121       (x * d_x + y * d_y * i) |    118      val iters = iterations(c, max) | 
|    122      val iters = iterations(c, max)  |         | 
|    123      val colour =  |    119      val colour =  | 
|    124        if (iters == max) black  |    120         if (iters == max) black | 
|    125        else colours(iters % 16) |    121         else colours(iters % 16) | 
|    126  |    122  | 
|    127      pixel(x, y, colour) |    123      pixel(x, y, colour) | 
|    128     } |    124     } | 
|    129     viewer.updateUI() |    125     viewer.updateUI() | 
|    130   }    |    126   } | 
|    131 } |    127 } | 
|    132  |    128  | 
|    133  |    129  | 
|    134 // Examples |    130 // Examples | 
|    135 //========== |    131 //========== |