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