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 //========== |