| 187 |      1 | // Mandelbrot pictures
 | 
| 309 |      2 | //=====================
 | 
| 266 |      3 | //
 | 
| 187 |      4 | //   see https://en.wikipedia.org/wiki/Mandelbrot_set
 | 
| 266 |      5 | // 
 | 
| 309 |      6 | // under scala 2.13.1 needs to be called with
 | 
|  |      7 | // 
 | 
|  |      8 | // scala -cp scala-parallel-collections_2.13-0.2.0.jar mandelbrot.scala
 | 
| 421 |      9 | //
 | 
|  |     10 | // scala-cli run --jar scala-parallel-collections_3-1.0.4.jar mandelbrot3.sc
 | 
|  |     11 | 
 | 
| 187 |     12 | 
 | 
| 124 |     13 | import java.awt.Color
 | 
|  |     14 | import java.awt.Dimension
 | 
|  |     15 | import java.awt.Graphics
 | 
|  |     16 | import java.awt.Graphics2D
 | 
|  |     17 | import java.awt.image.BufferedImage
 | 
|  |     18 | import javax.swing.JFrame
 | 
|  |     19 | import javax.swing.JPanel
 | 
|  |     20 | import javax.swing.WindowConstants
 | 
| 186 |     21 | import scala.language.implicitConversions    
 | 
| 266 |     22 | import scala.collection.parallel.CollectionConverters._
 | 
| 265 |     23 | 
 | 
| 422 |     24 | //object Test {
 | 
| 421 |     25 | 
 | 
| 422 |     26 | //def main() = {
 | 
| 421 |     27 | 
 | 
| 124 |     28 | // complex numbers
 | 
| 186 |     29 | case class Complex(val re: Double, val im: Double) { 
 | 
|  |     30 |   // represents the complex number re + im * i
 | 
|  |     31 |   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)
 | 
|  |     33 |   def *(that: Complex) = Complex(this.re * that.re - this.im * that.im,
 | 
|  |     34 |                                  this.re * that.im + that.re * this.im)
 | 
|  |     35 |   def *(that: Double) = Complex(this.re * that, this.im * that)
 | 
|  |     36 |   def abs() = Math.sqrt(this.re * this.re + this.im * this.im)
 | 
| 124 |     37 | }
 | 
|  |     38 | 
 | 
| 187 |     39 | // to allow the notation n + m * i
 | 
| 186 |     40 | object i extends Complex(0, 1)
 | 
| 421 |     41 | 
 | 
|  |     42 | given Conversion[Double, Complex] = Complex(_, 0)
 | 
| 186 |     43 | 
 | 
|  |     44 | 
 | 
|  |     45 | // some customn colours for the "sliding effect"
 | 
| 124 |     46 | val colours = List(
 | 
| 186 |     47 |   new Color(66, 30, 15),    new Color(25, 7, 26),
 | 
|  |     48 |   new Color(9, 1, 47),      new Color(4, 4, 73),
 | 
|  |     49 |   new Color(0, 7, 100),     new Color(12, 44, 138),
 | 
|  |     50 |   new Color(24, 82, 177),   new Color(57, 125, 209),
 | 
|  |     51 |   new Color(134, 181, 229), new Color(211, 236, 248),
 | 
|  |     52 |   new Color(241, 233, 191), new Color(248, 201, 95),
 | 
|  |     53 |   new Color(255, 170, 0),   new Color(204, 128, 0),
 | 
|  |     54 |   new Color(153, 87, 0),    new Color(106, 52, 3))
 | 
| 124 |     55 | 
 | 
| 266 |     56 | // the viewer panel with an image canvas
 | 
| 124 |     57 | class Viewer(width: Int, height: Int) extends JPanel {
 | 
| 186 |     58 |   val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
 | 
|  |     59 |   
 | 
|  |     60 |   override def paintComponent(g: Graphics) = 
 | 
|  |     61 |     g.asInstanceOf[Graphics2D].drawImage(canvas, null, null)
 | 
|  |     62 |   
 | 
|  |     63 |   override def getPreferredSize() = 
 | 
|  |     64 |     new Dimension(width, height)
 | 
| 124 |     65 | 
 | 
| 186 |     66 |   def clearCanvas(color: Color) = {
 | 
|  |     67 |     for (x <- 0 to width - 1; y <- 0 to height - 1) 
 | 
|  |     68 |       canvas.setRGB(x, y, color.getRGB())
 | 
|  |     69 |     repaint()
 | 
|  |     70 |   }  
 | 
| 124 |     71 | }
 | 
|  |     72 | 
 | 
| 266 |     73 | // initialising the viewer panel
 | 
| 186 |     74 | def openViewer(width: Int, height: Int) : Viewer = {
 | 
|  |     75 |   val frame = new JFrame("XYPlane")
 | 
|  |     76 |   val viewer = new Viewer(width, height)
 | 
|  |     77 |   frame.add(viewer)
 | 
|  |     78 |   frame.pack()
 | 
|  |     79 |   frame.setVisible(true)
 | 
|  |     80 |   frame.setResizable(false)
 | 
|  |     81 |   frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
 | 
|  |     82 |   viewer
 | 
| 124 |     83 | }
 | 
|  |     84 | 
 | 
| 266 |     85 | // some hardcoded parameters
 | 
| 186 |     86 | val W = 900   // width
 | 
|  |     87 | val H = 800   // height
 | 
| 124 |     88 | val black = Color.black
 | 
|  |     89 | val viewer = openViewer(W, H)
 | 
|  |     90 | 
 | 
| 266 |     91 | // draw a pixel on the canvas
 | 
| 124 |     92 | def pixel(x: Int, y: Int, color: Color) = 
 | 
|  |     93 |   viewer.canvas.setRGB(x, y, color.getRGB())
 | 
| 186 |     94 | 
 | 
| 124 |     95 | 
 | 
| 266 |     96 | // calculates the number of iterations using lazy lists (streams)
 | 
| 186 |     97 | //   the iteration goes on for a maximum of max steps,
 | 
|  |     98 | //   but might leave early when the pred is satisfied
 | 
|  |     99 | def iterations(c: Complex, max: Int) : Int = {
 | 
|  |    100 |   def next(z: Complex) = z * z + c    
 | 
| 353 |    101 |   def pred(z: Complex) = z.abs() < 2    // exit condition
 | 
| 266 |    102 |   LazyList.iterate(0.0 * i, max)(next).takeWhile(pred).size
 | 
| 186 |    103 | }
 | 
|  |    104 | 
 | 
|  |    105 | // main function 
 | 
| 266 |    106 | //    start and end are the upper-left and lower-right corners, 
 | 
| 187 |    107 | //    max is the number of maximum iterations
 | 
| 186 |    108 | def mandelbrot(start: Complex, end: Complex, max: Int) : Unit = {
 | 
| 124 |    109 |   viewer.clearCanvas(black)
 | 
| 186 |    110 |   
 | 
|  |    111 |   // deltas for each grid step 
 | 
|  |    112 |   val d_x = (end.re - start.re) / W
 | 
|  |    113 |   val d_y = (end.im - start.im) / H
 | 
| 124 |    114 |    
 | 
| 415 |    115 |   for (y <- (0 until H).par) {
 | 
|  |    116 |     for (x <- (0 until W).par) {
 | 
| 124 |    117 |     
 | 
| 186 |    118 |      val c = start + 
 | 
|  |    119 |       (x * d_x + y * d_y * i)
 | 
|  |    120 |      val iters = iterations(c, max) 
 | 
|  |    121 |      val col = 
 | 
|  |    122 |        if (iters == max) black 
 | 
|  |    123 |        else colours(iters % 16)
 | 
| 124 |    124 | 
 | 
| 186 |    125 |      pixel(x, y, col)
 | 
| 143 |    126 |     }
 | 
|  |    127 |     viewer.updateUI()
 | 
| 186 |    128 |   }   
 | 
| 124 |    129 | }
 | 
|  |    130 | 
 | 
| 187 |    131 | 
 | 
| 124 |    132 | // Examples
 | 
|  |    133 | //==========
 | 
|  |    134 | 
 | 
|  |    135 | //for measuring time
 | 
|  |    136 | def time_needed[T](code: => T) = {
 | 
|  |    137 |   val start = System.nanoTime()
 | 
|  |    138 |   code
 | 
|  |    139 |   val end = System.nanoTime()
 | 
|  |    140 |   (end - start) / 1.0e9
 | 
|  |    141 | }
 | 
|  |    142 | 
 | 
|  |    143 | 
 | 
| 353 |    144 | 
 | 
| 124 |    145 | // example 1
 | 
| 186 |    146 | val exa1 = -2.0 + -1.5 * i
 | 
|  |    147 | val exa2 =  1.0 +  1.5 * i
 | 
| 124 |    148 | 
 | 
| 309 |    149 | println(s"${time_needed(mandelbrot(exa1, exa2, 1000))} secs")
 | 
| 124 |    150 | 
 | 
| 136 |    151 | // example 2
 | 
| 186 |    152 | val exb1 = -0.37465401 + 0.659227668 * i
 | 
|  |    153 | val exb2 = -0.37332410 + 0.66020767 * i
 | 
| 124 |    154 | 
 | 
| 186 |    155 | //time_needed(mandelbrot(exb1, exb2, 1000))
 | 
| 124 |    156 | 
 | 
|  |    157 | // example 3
 | 
| 186 |    158 | val exc1 = 0.435396403 + 0.367981352 * i
 | 
|  |    159 | val exc2 = 0.451687191 + 0.380210061 * i
 | 
| 124 |    160 | 
 | 
| 166 |    161 | //time_needed(mandelbrot(exc1, exc2, 1000))
 | 
| 124 |    162 | 
 | 
| 266 |    163 | 
 | 
|  |    164 | 
 | 
| 124 |    165 | // some more computations with example 3
 | 
| 186 |    166 | 
 | 
| 124 |    167 | val delta = (exc2 - exc1) * 0.0333
 | 
|  |    168 | 
 | 
| 421 |    169 | println(s"${time_needed(
 | 
|  |    170 |   for (n <- (0 to 12)) 
 | 
|  |    171 |       mandelbrot(exc1 + delta * n, 
 | 
|  |    172 |                 exc2 - delta * n, 100))} secs") 
 | 
| 266 |    173 | 
 | 
| 124 |    174 | 
 | 
|  |    175 | 
 | 
| 191 |    176 | // Larry Paulson's example
 | 
|  |    177 | val exl1 = -0.74364990 + 0.13188170 * i
 | 
|  |    178 | val exl2 = -0.74291189 + 0.13261971 * i
 | 
| 189 |    179 | 
 | 
| 309 |    180 | //println(s"${time_needed(mandelbrot(exl1, exl2, 1000))} secs")
 | 
| 189 |    181 | 
 | 
| 191 |    182 | 
 | 
|  |    183 | // example by Jorgen Villadsen
 | 
|  |    184 | val exj1 = 0.10284 - 0.63275 * i
 | 
|  |    185 | val exj2 = 0.11084 - 0.64075 * i
 | 
|  |    186 | 
 | 
| 195 |    187 | //time_needed(mandelbrot(exj1, exj2, 1000))
 | 
| 394 |    188 | 
 | 
|  |    189 | 
 | 
|  |    190 | // another example
 | 
|  |    191 | val exA = 0.3439274 + 0.6516478 * i
 | 
|  |    192 | val exB = 0.3654477 + 0.6301795 * i
 | 
|  |    193 | 
 | 
|  |    194 | //time_needed(mandelbrot(exA, exB, 1000))
 | 
| 421 |    195 | 
 | 
|  |    196 | 
 | 
| 422 |    197 | //}
 | 
| 421 |    198 | 
 | 
| 422 |    199 | //}
 | 
| 421 |    200 | 
 | 
| 422 |    201 | //Test.main()
 |