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