├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | target 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" 8 | 9 | [[package]] 10 | name = "arrayvec" 11 | version = "0.4.11" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" 14 | dependencies = [ 15 | "nodrop", 16 | ] 17 | 18 | [[package]] 19 | name = "autocfg" 20 | version = "1.0.1" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 23 | 24 | [[package]] 25 | name = "bitflags" 26 | version = "0.7.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 29 | 30 | [[package]] 31 | name = "byteorder" 32 | version = "1.3.2" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 35 | 36 | [[package]] 37 | name = "cfg-if" 38 | version = "0.1.9" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 41 | 42 | [[package]] 43 | name = "color_quant" 44 | version = "1.0.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 47 | 48 | [[package]] 49 | name = "crossbeam-deque" 50 | version = "0.7.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" 53 | dependencies = [ 54 | "crossbeam-epoch", 55 | "crossbeam-utils", 56 | ] 57 | 58 | [[package]] 59 | name = "crossbeam-epoch" 60 | version = "0.7.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" 63 | dependencies = [ 64 | "arrayvec", 65 | "cfg-if", 66 | "crossbeam-utils", 67 | "lazy_static", 68 | "memoffset", 69 | "scopeguard", 70 | ] 71 | 72 | [[package]] 73 | name = "crossbeam-queue" 74 | version = "0.1.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" 77 | dependencies = [ 78 | "crossbeam-utils", 79 | ] 80 | 81 | [[package]] 82 | name = "crossbeam-utils" 83 | version = "0.6.6" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 86 | dependencies = [ 87 | "cfg-if", 88 | "lazy_static", 89 | ] 90 | 91 | [[package]] 92 | name = "deflate" 93 | version = "0.7.20" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" 96 | dependencies = [ 97 | "adler32", 98 | "byteorder", 99 | ] 100 | 101 | [[package]] 102 | name = "either" 103 | version = "1.5.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" 106 | 107 | [[package]] 108 | name = "enum_primitive" 109 | version = "0.1.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 112 | dependencies = [ 113 | "num-traits 0.1.43", 114 | ] 115 | 116 | [[package]] 117 | name = "gif" 118 | version = "0.9.2" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f" 121 | dependencies = [ 122 | "color_quant", 123 | "lzw", 124 | ] 125 | 126 | [[package]] 127 | name = "image" 128 | version = "0.13.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "1c3f4f5ea213ed9899eca760a8a14091d4b82d33e27cf8ced336ff730e9f6da8" 131 | dependencies = [ 132 | "byteorder", 133 | "enum_primitive", 134 | "gif", 135 | "jpeg-decoder", 136 | "num-iter", 137 | "num-rational 0.1.42", 138 | "num-traits 0.1.43", 139 | "png", 140 | "scoped_threadpool", 141 | ] 142 | 143 | [[package]] 144 | name = "inflate" 145 | version = "0.2.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "d1238524675af3938a7c74980899535854b88ba07907bb1c944abe5b8fc437e5" 148 | 149 | [[package]] 150 | name = "jpeg-decoder" 151 | version = "0.1.16" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "c1aae18ffeeae409c6622c3b6a7ee49792a7e5a062eea1b135fbb74e301792ba" 154 | dependencies = [ 155 | "byteorder", 156 | "rayon", 157 | ] 158 | 159 | [[package]] 160 | name = "lazy_static" 161 | version = "1.4.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 164 | 165 | [[package]] 166 | name = "libc" 167 | version = "0.2.62" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 170 | 171 | [[package]] 172 | name = "lzw" 173 | version = "0.10.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 176 | 177 | [[package]] 178 | name = "mandelbrot" 179 | version = "0.2.0" 180 | dependencies = [ 181 | "image", 182 | "num", 183 | "rayon", 184 | ] 185 | 186 | [[package]] 187 | name = "memoffset" 188 | version = "0.5.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" 191 | dependencies = [ 192 | "rustc_version", 193 | ] 194 | 195 | [[package]] 196 | name = "nodrop" 197 | version = "0.1.13" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" 200 | 201 | [[package]] 202 | name = "num" 203 | version = "0.4.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" 206 | dependencies = [ 207 | "num-bigint", 208 | "num-complex", 209 | "num-integer", 210 | "num-iter", 211 | "num-rational 0.4.0", 212 | "num-traits 0.2.14", 213 | ] 214 | 215 | [[package]] 216 | name = "num-bigint" 217 | version = "0.4.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" 220 | dependencies = [ 221 | "autocfg", 222 | "num-integer", 223 | "num-traits 0.2.14", 224 | ] 225 | 226 | [[package]] 227 | name = "num-complex" 228 | version = "0.4.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" 231 | dependencies = [ 232 | "num-traits 0.2.14", 233 | ] 234 | 235 | [[package]] 236 | name = "num-integer" 237 | version = "0.1.44" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 240 | dependencies = [ 241 | "autocfg", 242 | "num-traits 0.2.14", 243 | ] 244 | 245 | [[package]] 246 | name = "num-iter" 247 | version = "0.1.42" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 250 | dependencies = [ 251 | "autocfg", 252 | "num-integer", 253 | "num-traits 0.2.14", 254 | ] 255 | 256 | [[package]] 257 | name = "num-rational" 258 | version = "0.1.42" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" 261 | dependencies = [ 262 | "num-integer", 263 | "num-traits 0.2.14", 264 | ] 265 | 266 | [[package]] 267 | name = "num-rational" 268 | version = "0.4.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" 271 | dependencies = [ 272 | "autocfg", 273 | "num-bigint", 274 | "num-integer", 275 | "num-traits 0.2.14", 276 | ] 277 | 278 | [[package]] 279 | name = "num-traits" 280 | version = "0.1.43" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 283 | dependencies = [ 284 | "num-traits 0.2.14", 285 | ] 286 | 287 | [[package]] 288 | name = "num-traits" 289 | version = "0.2.14" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 292 | dependencies = [ 293 | "autocfg", 294 | ] 295 | 296 | [[package]] 297 | name = "num_cpus" 298 | version = "1.10.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 301 | dependencies = [ 302 | "libc", 303 | ] 304 | 305 | [[package]] 306 | name = "png" 307 | version = "0.7.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "48f397b84083c2753ba53c7b56ad023edb94512b2885ffe227c66ff7edb61868" 310 | dependencies = [ 311 | "bitflags", 312 | "deflate", 313 | "inflate", 314 | "num-iter", 315 | ] 316 | 317 | [[package]] 318 | name = "rayon" 319 | version = "1.2.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "83a27732a533a1be0a0035a111fe76db89ad312f6f0347004c220c57f209a123" 322 | dependencies = [ 323 | "crossbeam-deque", 324 | "either", 325 | "rayon-core", 326 | ] 327 | 328 | [[package]] 329 | name = "rayon-core" 330 | version = "1.6.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "98dcf634205083b17d0861252431eb2acbfb698ab7478a2d20de07954f47ec7b" 333 | dependencies = [ 334 | "crossbeam-deque", 335 | "crossbeam-queue", 336 | "crossbeam-utils", 337 | "lazy_static", 338 | "num_cpus", 339 | ] 340 | 341 | [[package]] 342 | name = "rustc_version" 343 | version = "0.2.3" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 346 | dependencies = [ 347 | "semver", 348 | ] 349 | 350 | [[package]] 351 | name = "scoped_threadpool" 352 | version = "0.1.9" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 355 | 356 | [[package]] 357 | name = "scopeguard" 358 | version = "1.0.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 361 | 362 | [[package]] 363 | name = "semver" 364 | version = "0.9.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 367 | dependencies = [ 368 | "semver-parser", 369 | ] 370 | 371 | [[package]] 372 | name = "semver-parser" 373 | version = "0.7.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 376 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mandelbrot" 3 | version = "0.2.0" 4 | authors = ["Jim Blandy "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | num = "0.4" 9 | image = "0.13.0" 10 | rayon = "1" 11 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Parallel Mandelbrot Set Plotter 2 | 3 | This program plots the Mandelbrot set and writes it out as a PNG file. It uses Rust's concurrency primitives to distribute the work across eight threads. 4 | 5 | The book shows several different versions of this program: single- and multi-threaded versions in the "Tour of Rust" chapter, and a final version based on the Rayon crate in the "Concurrency" chapter, which makes more effective use of parallelism. 6 | 7 | Each of these versions appears on a different branch in this repository: 8 | 9 | * [Branch `single-threaded`](https://github.com/ProgrammingRust/mandelbrot/tree/single-threaded) 10 | is the base version of the program. It does all the work on the main 11 | thread. This is the first version shown in the "Tour of Rust" chapter. 12 | 13 | * [Branch `bands`](https://github.com/ProgrammingRust/mandelbrot/tree/bands) 14 | splits the plotting area up into eight bands, and assigns one thread to 15 | each. This often makes poor use of the threads, because some bands take 16 | significantly longer than others to complete: once a fast thread completes 17 | its band, its CPU goes idle while its less fortunate brethren are still hard 18 | at work. This is the final version shown in the Tour. 19 | 20 | * [Branch `task-queue`](https://github.com/ProgrammingRust/mandelbrot/tree/task-queue) 21 | gets an almost perfect linear speedup from its threads. It splits 22 | the plotting area up into many more bands, and then has threads draw 23 | bands from a common pool until the pool is empty. When a thread 24 | finishes one band, it goes back for more work. Since the bands still 25 | take different amounts of time to render, the problem cited above 26 | still occurs, but on a much smaller scale. This version is not shown in the book. 27 | 28 | * [Branch `lockfree`](https://github.com/ProgrammingRust/mandelbrot/tree/lockfree) 29 | uses Rust's atomic types to implement a lock-free iterator type, and 30 | uses that to dole out bands from the pool instead of a 31 | mutex-protected count. On Linux, this is no faster than the 32 | mutex-based version, which isn't too surprising: on Linux, locking 33 | and unlocking an uncontended mutex *is* simply a pair of atomic 34 | operations. This version is also not shown in the book. 35 | 36 | * [Branch `rayon`](https://github.com/ProgrammingRust/mandelbrot/tree/rayon) 37 | uses the Rayon library instead of Crossbeam. Rayon provides a *parallel 38 | iterator* API that makes our code much simpler. It looks a lot like Rust 39 | code that uses plain old iterators. This is the final version shown in the 40 | Concurrency chapter. 41 | 42 | ## License 43 | 44 | The example code in this directory and its subdirectories is licensed under the 45 | terms of the MIT license. See [LICENSE-MIT](LICENSE-MIT) for details. 46 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | #![allow(elided_lifetimes_in_paths)] 3 | 4 | use num::Complex; 5 | use rayon::prelude::*; 6 | 7 | /// Try to determine if `c` is in the Mandelbrot set, using at most `limit` 8 | /// iterations to decide. 9 | /// 10 | /// If `c` is not a member, return `Some(i)`, where `i` is the number of 11 | /// iterations it took for `c` to leave the circle of radius two centered on the 12 | /// origin. If `c` seems to be a member (more precisely, if we reached the 13 | /// iteration limit without being able to prove that `c` is not a member), 14 | /// return `None`. 15 | fn escape_time(c: Complex, limit: usize) -> Option { 16 | let mut z = Complex { re: 0.0, im: 0.0 }; 17 | for i in 0..limit { 18 | if z.norm_sqr() > 4.0 { 19 | return Some(i); 20 | } 21 | z = z * z + c; 22 | } 23 | 24 | None 25 | } 26 | 27 | use std::str::FromStr; 28 | 29 | /// Parse the string `s` as a coordinate pair, like `"400x600"` or `"1.0,0.5"`. 30 | /// 31 | /// Specifically, `s` should have the form , where is 32 | /// the character given by the `separator` argument, and and are both 33 | /// strings that can be parsed by `T::from_str`. 34 | /// 35 | /// If `s` has the proper form, return `Some<(x, y)>`. If it doesn't parse 36 | /// correctly, return `None`. 37 | fn parse_pair(s: &str, separator: char) -> Option<(T, T)> { 38 | match s.find(separator) { 39 | None => None, 40 | Some(index) => { 41 | match (T::from_str(&s[..index]), T::from_str(&s[index + 1..])) { 42 | (Ok(l), Ok(r)) => Some((l, r)), 43 | _ => None 44 | } 45 | } 46 | } 47 | } 48 | 49 | #[test] 50 | fn test_parse_pair() { 51 | assert_eq!(parse_pair::("", ','), None); 52 | assert_eq!(parse_pair::("10,", ','), None); 53 | assert_eq!(parse_pair::(",10", ','), None); 54 | assert_eq!(parse_pair::("10,20", ','), Some((10, 20))); 55 | assert_eq!(parse_pair::("10,20xy", ','), None); 56 | assert_eq!(parse_pair::("0.5x", 'x'), None); 57 | assert_eq!(parse_pair::("0.5x1.5", 'x'), Some((0.5, 1.5))); 58 | } 59 | 60 | /// Parse a pair of floating-point numbers separated by a comma as a complex 61 | /// number. 62 | fn parse_complex(s: &str) -> Option> { 63 | match parse_pair(s, ',') { 64 | Some((re, im)) => Some(Complex { re, im }), 65 | None => None 66 | } 67 | } 68 | 69 | #[test] 70 | fn test_parse_complex() { 71 | assert_eq!(parse_complex("1.25,-0.0625"), 72 | Some(Complex { re: 1.25, im: -0.0625 })); 73 | assert_eq!(parse_complex(",-0.0625"), None); 74 | } 75 | 76 | /// Given the row and column of a pixel in the output image, return the 77 | /// corresponding point on the complex plane. 78 | /// 79 | /// `bounds` is a pair giving the width and height of the image in pixels. 80 | /// `pixel` is a (column, row) pair indicating a particular pixel in that image. 81 | /// The `upper_left` and `lower_right` parameters are points on the complex 82 | /// plane designating the area our image covers. 83 | fn pixel_to_point(bounds: (usize, usize), 84 | pixel: (usize, usize), 85 | upper_left: Complex, 86 | lower_right: Complex) 87 | -> Complex 88 | { 89 | let (width, height) = (lower_right.re - upper_left.re, 90 | upper_left.im - lower_right.im); 91 | Complex { 92 | re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64, 93 | im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64 94 | // Why subtraction here? pixel.1 increases as we go down, 95 | // but the imaginary component increases as we go up. 96 | } 97 | } 98 | 99 | #[test] 100 | fn test_pixel_to_point() { 101 | assert_eq!(pixel_to_point((100, 200), (25, 175), 102 | Complex { re: -1.0, im: 1.0 }, 103 | Complex { re: 1.0, im: -1.0 }), 104 | Complex { re: -0.5, im: -0.75 }); 105 | } 106 | 107 | /// Render a rectangle of the Mandelbrot set into a buffer of pixels. 108 | /// 109 | /// The `bounds` argument gives the width and height of the buffer `pixels`, 110 | /// which holds one grayscale pixel per byte. The `upper_left` and `lower_right` 111 | /// arguments specify points on the complex plane corresponding to the upper- 112 | /// left and lower-right corners of the pixel buffer. 113 | fn render(pixels: &mut [u8], 114 | bounds: (usize, usize), 115 | upper_left: Complex, 116 | lower_right: Complex) 117 | { 118 | assert!(pixels.len() == bounds.0 * bounds.1); 119 | 120 | for row in 0..bounds.1 { 121 | for column in 0..bounds.0 { 122 | let point = pixel_to_point(bounds, (column, row), 123 | upper_left, lower_right); 124 | pixels[row * bounds.0 + column] = 125 | match escape_time(point, 255) { 126 | None => 0, 127 | Some(count) => 255 - count as u8 128 | }; 129 | } 130 | } 131 | } 132 | 133 | use image::ColorType; 134 | use image::png::PNGEncoder; 135 | use std::fs::File; 136 | 137 | /// Write the buffer `pixels`, whose dimensions are given by `bounds`, to the 138 | /// file named `filename`. 139 | fn write_image(filename: &str, pixels: &[u8], bounds: (usize, usize)) 140 | -> Result<(), std::io::Error> 141 | { 142 | let output = File::create(filename)?; 143 | 144 | let encoder = PNGEncoder::new(output); 145 | encoder.encode(&pixels, 146 | bounds.0 as u32, bounds.1 as u32, 147 | ColorType::Gray(8))?; 148 | 149 | Ok(()) 150 | } 151 | 152 | use std::env; 153 | 154 | fn main() { 155 | let args: Vec = env::args().collect(); 156 | 157 | if args.len() != 5 { 158 | eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", 159 | args[0]); 160 | eprintln!("Example: {} mandel.png 1000x750 -1.20,0.35 -1,0.20", 161 | args[0]); 162 | std::process::exit(1); 163 | } 164 | 165 | let bounds = parse_pair(&args[2], 'x') 166 | .expect("error parsing image dimensions"); 167 | let upper_left = parse_complex(&args[3]) 168 | .expect("error parsing upper left corner point"); 169 | let lower_right = parse_complex(&args[4]) 170 | .expect("error parsing lower right corner point"); 171 | 172 | let mut pixels = vec![0; bounds.0 * bounds.1]; 173 | 174 | // Scope of slicing up `pixels` into horizontal bands. 175 | { 176 | let bands: Vec<(usize, &mut [u8])> = pixels 177 | .chunks_mut(bounds.0) 178 | .enumerate() 179 | .collect(); 180 | 181 | bands.into_par_iter() 182 | .for_each(|(i, band)| { 183 | let top = i; 184 | let band_bounds = (bounds.0, 1); 185 | let band_upper_left = pixel_to_point(bounds, (0, top), 186 | upper_left, lower_right); 187 | let band_lower_right = pixel_to_point(bounds, (bounds.0, top + 1), 188 | upper_left, lower_right); 189 | render(band, band_bounds, band_upper_left, band_lower_right); 190 | }); 191 | } 192 | 193 | write_image(&args[1], &pixels, bounds) 194 | .expect("error writing PNG file"); 195 | } 196 | --------------------------------------------------------------------------------