{"id":378490,"date":"2024-06-09T21:00:22","date_gmt":"2024-06-09T21:00:22","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=378490"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=378490","title":{"rendered":"<span>\u0420\u0438\u0441\u0443\u0435\u043c \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u044b \u043d\u0430 Rust \u0438 CUDA<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/046\/5be\/61a\/0465be61aaccd02fcf942a4cfd797e2b.png\" alt=\"\" title=\"\" width=\"1280\" height=\"640\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/046\/5be\/61a\/0465be61aaccd02fcf942a4cfd797e2b.png\"\/><\/figure>\n<p>\u0424\u0440\u0430\u043a\u0442\u0430\u043b\u044b\u00a0\u2014 \u044d\u0442\u043e \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 \u0441\u0430\u043c\u043e\u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0435 \u0444\u0438\u0433\u0443\u0440\u044b. \u041e\u043d\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u0444\u043e\u0440\u043c\u0443\u043b\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u0443\u0434\u0438\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u043a\u0440\u0430\u0441\u043e\u0442\u0443!<\/p>\n<p>\u0412\u00a0\u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437\u00a0\u0441\u0430\u043c\u044b\u0445 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u043e\u0432 \u043d\u0430\u00a0\u044f\u0437\u044b\u043a\u0435 Rust \u0441\u00a0\u0430\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u044b\u043c \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435\u043c NVIDIA, \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c, \u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u0435\u043c \u0438 \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c\u044e.<\/p>\n<h3>\u041c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u041c\u0430\u043d\u0434\u0435\u043b\u044c\u0431\u0440\u043e\u0442\u0430<\/h3>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0441\u0430\u043c\u044b\u0439 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0444\u0440\u0430\u043a\u0442\u0430\u043b, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0432 1905\u00a0\u0433\u043e\u0434\u0443 \u041f\u044c\u0435\u0440\u043e\u043c \u0424\u0430\u0442\u0443. \u041c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u0434\u0430\u0451\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0444\u043e\u0440\u043c\u0443\u043b\u043e\u0439, \u0433\u0434\u0435 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"z\" alt=\"z\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/853\/c5e\/606\/853c5e6060147caa342d82da7f549910.svg\" width=\"9\" height=\"12\"\/> \u0438 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"C\" alt=\"C\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e6b\/713\/507\/e6b713507a4b85aabb64b7a4dd7657f7.svg\" width=\"15\" height=\"17\"\/>\u00a0\u2014 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0435 \u0447\u0438\u0441\u043b\u0430:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"formula\" source=\"f(z) = z^2 + C\" alt=\"f(z) = z^2 + C\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7fb\/b9a\/a65\/7fbb9aa65495cc54d3f38db77f5bcd0a.svg\" width=\"119\" height=\"25\"\/><\/p>\n<p>\u0424\u0440\u0430\u043a\u0442\u0430\u043b \u043c\u043e\u0436\u043d\u043e \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"f(z)\" alt=\"f(z)\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2e5\/7e9\/8f9\/2e57e98f92436ba75396e09988656e4d.svg\" width=\"35\" height=\"22\"\/>, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"z_0\" alt=\"z_0\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fff\/011\/5a1\/fff0115a1c58808e5cf7804da08ee699.svg\" width=\"18\" height=\"15\"\/>. \u0414\u043e\u043a\u0430\u0437\u0430\u043d\u043e, \u0447\u0442\u043e\u00a0\u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u00a0\u0440\u0430\u0434\u0438\u0443\u0441\u043e\u043c <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"2\" alt=\"2\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1f4\/bd8\/97e\/1f4bd897e647ccf8e0847e16b4d60168.svg\" width=\"10\" height=\"17\"\/>. \u0415\u0441\u043b\u0438 \u0437\u0430 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"n\" alt=\"n\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9e5\/ede\/cf1\/9e5edecf189ba177826fa4802b678854.svg\" width=\"12\" height=\"12\"\/> \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0442\u043e\u0447\u043a\u0430 <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"z\" alt=\"z\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5e1\/737\/fe3\/5e1737fe358df89252f55b14e5d033d6.svg\" width=\"9\" height=\"12\"\/> \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 (<img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"|z| &lt; 2\" alt=\"|z| &lt; 2\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/645\/b98\/f9a\/645b98f9ab93e4d39228ae212e2b0147.svg\" width=\"56\" height=\"22\"\/>), \u0442\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c, \u0447\u0442\u043e <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"z\" alt=\"z\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7e8\/d72\/a44\/7e8d72a4474e06e9fa8704ad54932621.svg\" width=\"9\" height=\"12\"\/> \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443 \u0438 \u0437\u0430\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0435\u0451 \u0432\u00a0\u0447\u0451\u0440\u043d\u044b\u0439 \u0446\u0432\u0435\u0442.<\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430 CPU<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443 \u0434\u043b\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430\u0431\u043e\u0440\u0430 \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f NVIDIA CUDA. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0443\u0441\u0442\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>cargo init<\/code>:<\/p>\n<pre><code class=\"powershell\">$ cargo init mandelbrot_set &amp; cd mandelbrot_set $ tree \u251c\u2500\u2500 Cargo.lock \u251c\u2500\u2500 Cargo.toml \u251c\u2500\u2500 README.md \u2514\u2500\u2500 src     \u2514\u2500\u2500 main.rs<\/code><\/pre>\n<p>\u0412 <code>Cargo.toml<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<pre><code class=\"diff\">[package] name = \"mandelbrot_set\" version = \"0.1.0\" edition = \"2021\"  [dependencies] +num = \"0.4.3\" +image = \"0.25.1\"<\/code><\/pre>\n<ul>\n<li>\n<p><code>image<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><code>num<\/code> \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0445 \u0447\u0438\u0441\u0435\u043b<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u044d\u0442\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex;<\/code><\/pre>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"f(z) = z^2 + C\" alt=\"f(z) = z^2 + C\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b74\/8c6\/44a\/b748c644a17a48055d7a93f05dbc2bc0.svg\" width=\"119\" height=\"25\"\/>:<\/p>\n<pre><code class=\"rust\">fn num_iters(cx: f64, cy: f64, max_iters: u32) -> u32 {     let mut z = Complex::new(0.0, 0.0);     let c = Complex::new(cx, cy);      for i in 0..=max_iters {         if z.norm() > 2.0 {             return i;         }         z = z * z + c;     }      max_iters }<\/code><\/pre>\n<p>\u041e\u0431\u043e\u0437\u043d\u0430\u0447\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>generate_set<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0432 \u0444\u0430\u0439\u043b:<\/p>\n<pre><code class=\"rust\">fn generate_set(     file_name: String,     max_iters: u32,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) { }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043f\u0443\u0441\u0442\u043e\u0435 RGB \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"rust\">let mut buffer = RgbImage::new(resolution, resolution); buffer.save(&amp;file_name).unwrap();<\/code><\/pre>\n<p>\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0438\u0437 <code>num_iters<\/code> \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"rust\">for x in 0..resolution {     for y in 0..resolution {         let x_percent = x as f64 \/ resolution as f64;         let y_percent = y as f64 \/ resolution as f64;         let cx = x_min + (x_max - x_min) * x_percent;         let cy = y_min + (y_max - y_min) * y_percent;         let iters = num_iters(cx, cy, max_iters);     } }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u0435\u0434\u0435\u043b \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0434\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442, \u043c\u044b \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0438\u043a\u0441\u0435\u043b\u044c \u043f\u043e\u00a0\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0447\u0435\u0440\u043d\u044b\u043c. \u0412\u00a0\u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0437\u0430\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u0431\u0435\u043b\u044b\u043c \u0446\u0432\u0435\u0442\u043e\u043c:<\/p>\n<pre><code class=\"rust\">let pixel = buffer.get_pixel_mut(x, y); if iters &lt; max_iters {     *pixel = Rgb([255, 255, 255]); }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 generate_set<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">fn generate_set(     file_name: String,     max_iters: u32,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             let iters = num_iters(cx, cy, max_iters);             let pixel = buffer.get_pixel_mut(x, y);              if iters &lt; max_iters {                 *pixel = Rgb([255, 255, 255]);             }         }     }      buffer.save(&amp;file_name).unwrap(); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412\u044b\u0437\u043e\u0432\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438\u0437 <code>main<\/code>:<\/p>\n<pre><code class=\"rust\">fn main() {     let resolution = 1024;;     let max_iters = 100;      generate_set(         \"fractal.png\".to_string(),         max_iters,         -1.5,         -1.0,         0.5,         1.0,         resolution,     ); }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 main.rs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex;   fn generate_set(     file_name: String,     max_iters: u32,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             let iters = num_iters(cx, cy, max_iters);             let pixel = buffer.get_pixel_mut(x, y);              if iters &lt; max_iters {                 *pixel = Rgb([255, 255, 255]);             }         }     }      buffer.save(&amp;file_name).unwrap(); }  fn num_iters(cx: f64, cy: f64, max_iters: u32) -> u32 {     let mut z = Complex::new(0.0, 0.0);     let c = Complex::new(cx, cy);      for i in 0..=max_iters {         if z.norm() > 2.0 {             return i;         }         z = z * z + c;     }      max_iters }  fn main() {     let resolution = 1024;;     let max_iters = 100;      generate_set(         \"fractal.png\".to_string(),         max_iters,         -1.5,         -1.0,         0.5,         1.0,         resolution,     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443\u00a0\u2014 <code>cargo run --release<\/code>. \u041f\u0440\u0438\u00a0\u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u043e\u0436\u0434\u0430\u0442\u044c, \u043f\u043e\u043a\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4d5\/50a\/1b5\/4d550a1b538717db010f74a625592ef2.png\" alt=\"fractal.png\" title=\"fractal.png\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4d5\/50a\/1b5\/4d550a1b538717db010f74a625592ef2.png\"\/><\/p>\n<div><figcaption>fractal.png<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u00a0\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u043e\u0439 \u0444\u0440\u0430\u043a\u0442\u0430\u043b. \u0421\u0442\u0440\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u00a0\u2014 \u0442\u043e\u0447\u043a\u0430\u00a0\u043b\u0438\u0431\u043e \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443 (\u0447\u0435\u0440\u043d\u044b\u0439 \u0446\u0432\u0435\u0442),\u00a0\u043b\u0438\u0431\u043e \u043d\u0435\u0442 (\u0431\u0435\u043b\u044b\u0439 \u0446\u0432\u0435\u0442), \u043d\u043e\u00a0\u043d\u0438\u043a\u0442\u043e \u043d\u0435\u00a0\u043c\u0435\u0448\u0430\u0435\u0442 \u043d\u0430\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430! \u041e\u0434\u0438\u043d \u0438\u0437\u00a0\u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u0440\u0430\u0441\u043a\u0440\u0430\u0441\u0438\u0442\u044c \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u00a0\u2014 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043a\u0430\u043a\u00a0\u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e\u00a0\u0442\u043e\u0447\u043a\u0430 \u043d\u0435\u00a0\u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443.<\/p>\n<h4>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0446\u0432\u0435\u0442\u0430<\/h4>\n<p>\u041f\u0443\u0441\u0442\u044c \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u043c\u0430\u0441\u0441\u0438\u0432 <code>gradient<\/code> \u0441 <code>max_iters<\/code> \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0446\u0432\u0435\u0442\u0430. \u0415\u0441\u043b\u0438 <code>iters &lt; max_iters<\/code>, \u0442\u043e \u043c\u044b \u0431\u0435\u0440\u0451\u043c <code>gradient[iters]<\/code> \u0438 \u043e\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u043f\u0438\u043a\u0441\u0435\u043b\u044c \u0432 \u044d\u0442\u043e\u0442 \u0446\u0432\u0435\u0442. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u043e\u0447\u043a\u0443 \u0447\u0435\u0440\u043d\u043e\u0439:<\/p>\n<pre><code class=\"rust\">fn generate_set(     ...     colors: Vec&lt;&amp;str>,     ... ) {   ...   let gradient = get_gradient(colors, max_iters);   ...   for x in 0..resolution {       for y in 0..resolution {           ...           let pixel = buffer.get_pixel_mut(x, y);           let color = gradient.get(iters as usize).unwrap_or(&amp;[0, 0, 0]);           *pixel = Rgb(*color);       }   } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b <a href=\"https:\/\/dev.to\/ndesmic\/linear-color-gradients-from-scratch-1a0e\" rel=\"noopener noreferrer nofollow\">\u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/a> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0433\u043e \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u0430 \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0446\u0432\u0435\u0442\u043e\u0432 HEX:<\/p>\n<details class=\"spoiler\">\n<summary>hex2rgb, lerp_color \u0438 get_gradient<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">fn hex2rgb(hex: &amp;str) -> Result&lt;Vec&lt;u8>, String> {     let hex = hex.trim_start_matches('#');     if hex.len() != 6 {         return Err(\"Invalid HEX color length\".to_string());     }      let r = u8::from_str_radix(&amp;hex[0..2], 16).map_err(|_| \"Invalid HEX color\")?;     let g = u8::from_str_radix(&amp;hex[2..4], 16).map_err(|_| \"Invalid HEX color\")?;     let b = u8::from_str_radix(&amp;hex[4..6], 16).map_err(|_| \"Invalid HEX color\")?;      Ok(vec![r, g, b]) }  fn lerp_color(color1: &amp;[u8; 3], color2: &amp;[u8; 3], value: f64) -> [u8; 3] {     [         (color1[0] as f64 + (color2[0] as f64 - color1[0] as f64) * value) as u8,         (color1[1] as f64 + (color2[1] as f64 - color1[1] as f64) * value) as u8,         (color1[2] as f64 + (color2[2] as f64 - color1[2] as f64) * value) as u8,     ] }  fn get_gradient(gradient_colors: Vec&lt;&amp;str>, max_iters: u32) -> Vec&lt;[u8; 3]> {     let mut colors = vec![];     let mut gradient_colors_rgb = vec![];     for color in &amp;gradient_colors {         let rgb = hex2rgb(color).unwrap();         gradient_colors_rgb.push([rgb[0], rgb[1], rgb[2]]);     }      for i in 0..max_iters {         let color_index = (i as usize * (gradient_colors.len() - 1)) \/ max_iters as usize;         let color_value = (i as f64 * (gradient_colors.len() as f64 - 1.0)) \/ max_iters as f64;         let value = color_value % 1.0;         colors.push(lerp_color(             &amp;gradient_colors_rgb[color_index],             &amp;gradient_colors_rgb[color_index + 1],             value,         ));     }      colors }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0446\u0432\u0435\u0442\u043e\u0432\u044b\u0435 \u043f\u0430\u043b\u0438\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u0441\u0430\u0439\u0442\u0435 &#8212; <a href=\"https:\/\/color.adobe.com\/\" rel=\"noopener noreferrer nofollow\">https:\/\/color.adobe.com\/<\/a><\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 main.rs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex;  fn hex2rgb(hex: &amp;str) -> Result&lt;Vec&lt;u8>, String> {     let hex = hex.trim_start_matches('#');     if hex.len() != 6 {         return Err(\"Invalid HEX color length\".to_string());     }      let r = u8::from_str_radix(&amp;hex[0..2], 16).map_err(|_| \"Invalid HEX color\")?;     let g = u8::from_str_radix(&amp;hex[2..4], 16).map_err(|_| \"Invalid HEX color\")?;     let b = u8::from_str_radix(&amp;hex[4..6], 16).map_err(|_| \"Invalid HEX color\")?;      Ok(vec![r, g, b]) }  fn lerp_color(color1: &amp;[u8; 3], color2: &amp;[u8; 3], value: f64) -> [u8; 3] {     [         (color1[0] as f64 + (color2[0] as f64 - color1[0] as f64) * value) as u8,         (color1[1] as f64 + (color2[1] as f64 - color1[1] as f64) * value) as u8,         (color1[2] as f64 + (color2[2] as f64 - color1[2] as f64) * value) as u8,     ] }  fn get_gradient(gradient_colors: Vec&lt;&amp;str>, max_iters: u32) -> Vec&lt;[u8; 3]> {     let mut colors = vec![];     let mut gradient_colors_rgb = vec![];     for color in &amp;gradient_colors {         let rgb = hex2rgb(color).unwrap();         gradient_colors_rgb.push([rgb[0], rgb[1], rgb[2]]);     }      for i in 0..max_iters {         let color_index = (i as usize * (gradient_colors.len() - 1)) \/ max_iters as usize;         let color_value = (i as f64 * (gradient_colors.len() as f64 - 1.0)) \/ max_iters as f64;         let value = color_value % 1.0;         colors.push(lerp_color(             &amp;gradient_colors_rgb[color_index],             &amp;gradient_colors_rgb[color_index + 1],             value,         ));     }      colors }  fn generate_set(     file_name: String,     max_iters: u32,     colors: Vec&lt;&amp;str>,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);     let gradient = get_gradient(colors, max_iters);      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             let iters = num_iters(cx, cy, max_iters);             let pixel = buffer.get_pixel_mut(x, y);             let color = gradient.get(iters as usize).unwrap_or(&amp;[0, 0, 0]);             *pixel = Rgb(*color);         }     }      buffer.save(&amp;file_name).unwrap(); }  fn num_iters(cx: f64, cy: f64, max_iters: u32) -> u32 {     let mut z = Complex::new(0.0, 0.0);     let c = Complex::new(cx, cy);      for i in 0..=max_iters {         if z.norm() > 2.0 {             return i;         }         z = z * z + c;     }      max_iters }  fn main() {     let resolution = 1024;     let max_iters = 100;      generate_set(         \"fractal.png\".to_string(),         max_iters,         vec![\"#1C448E\", \"#6F8695\", \"#CEC288\", \"#FFE381\", \"#DBFE87\"],         -1.5,         -1.0,         0.5,         1.0,         resolution,     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/393\/b15\/6b9\/393b156b9db81ec6c77e183860a3c6ab.png\" alt=\"fractal.png\" title=\"fractal.png\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/393\/b15\/6b9\/393b156b9db81ec6c77e183860a3c6ab.png\"\/><\/p>\n<div><figcaption>fractal.png<\/figcaption><\/div>\n<\/figure>\n<h2>\u041f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u043c \u043a\u043e\u0434 \u043d\u0430 NVIDIA CUDA<\/h2>\n<p>\u0421\u0432\u044f\u0437\u0430\u0442\u044c C++ \u0441 Rust \u043c\u043e\u0436\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 libc \u0438 cc (<a href=\"https:\/\/habr.com\/ru\/articles\/447968\/\" rel=\"noopener noreferrer nofollow\">\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a<\/a>). \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u0445 \u0432 <code>Cargo.toml<\/code>:<\/p>\n<pre><code class=\"diff\">[package] name = \"mandelbrot_set\" version = \"0.1.0\" edition = \"2021\"  [dependencies] num = \"0.4.3\" image = \"0.25.1\" +libc = \"0.2.155\"  +[build-dependencies] +cc = \"1.0.98\"<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043f\u0430\u043f\u043a\u0435 <code>src<\/code> \u0444\u0430\u0439\u043b\u044b <code>mandelbrot_gpu.h, mandelbrot.cpp<\/code><strong> <\/strong>\u0438 <code>mandelbrot_gpu.cu<\/code>. \u0412 \u043d\u043e\u0432\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 CUDA \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <code>thrust\/complex.h<\/code> \u0434\u043b\u044f \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0445 \u0447\u0438\u0441\u0435\u043b. \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>num_iters<\/code> \u043d\u0430 CUDA:<\/p>\n<pre><code class=\"cpp\">#include &lt;thrust\/complex.h>  _device__ unsigned int num_iters(double cx, double cy, unsigned int max_iters) {     thrust::complex&lt;double> z(0.0, 0.0);     thrust::complex&lt;double> c(cx, cy);      for (unsigned int i = 0; i &lt;= max_iters; ++i) {         if (thrust::abs(z) > 2.0) {             return i;         }         z = z * z + c;     }      return max_iters; }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>gpu_mandelbrot<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432 \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043f\u0440\u043e\u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"cpp\">__global__ void mandelbrot_kernel(unsigned int* results, double* cx, double* cy, unsigned int max_iters, int num_points) {     int idx = blockDim.x * blockIdx.x + threadIdx.x;     if (idx &lt; num_points) {         results[idx] = num_iters(cx[idx], cy[idx], max_iters);     } }  unsigned int* gpu_mandelbrot(double* cx, double* cy, size_t num_points, unsigned int max_iters) {     unsigned int *dev_results, *results;     double *dev_cx, *dev_cy;      results = new unsigned int[num_points];      cudaMalloc((void**)&amp;dev_cx, num_points * sizeof(double));     cudaMalloc((void**)&amp;dev_cy, num_points * sizeof(double));     cudaMalloc((void**)&amp;dev_results, num_points * sizeof(unsigned int));      cudaMemcpy(dev_cx, cx, num_points * sizeof(double), cudaMemcpyHostToDevice);     cudaMemcpy(dev_cy, cy, num_points * sizeof(double), cudaMemcpyHostToDevice);      int threads_per_block = 256;     int blocks_per_grid = (num_points + threads_per_block - 1) \/ threads_per_block;      mandelbrot_kernel&lt;&lt;&lt;blocks_per_grid, threads_per_block>>>(dev_results, dev_cx, dev_cy, max_iters, num_points);      cudaMemcpy(results, dev_results, num_points * sizeof(unsigned int), cudaMemcpyDeviceToHost);      cudaFree(dev_cx);     cudaFree(dev_cy);     cudaFree(dev_results);      return results; }<\/code><\/pre>\n<p>\u0412 \u0444\u0430\u0439\u043b\u0435 <code>mandelbrot.cpp<\/code> \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u0438\u0437 Rust:<\/p>\n<pre><code class=\"cpp\">extern \"C\" {     void calculate_mandelbrot(const double* cx, const double* cy, int num_points, unsigned int max_iters, unsigned int* output) {         double* non_const_cx = const_cast&lt;double*>(cx);         double* non_const_cy = const_cast&lt;double*>(cy);          unsigned int* gpu_results = gpu_mandelbrot(non_const_cx, non_const_cy, num_points, max_iters);          for (int i = 0; i &lt; num_points; ++i) {             output[i] = gpu_results[i];         }          delete[] gpu_results;     } }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<p>mandelbrot_gpu.h<\/p>\n<pre><code class=\"cpp\">#ifndef MANDELBROT_GPU_H #define MANDELBROT_GPU_H  unsigned int* gpu_mandelbrot(double* cx, double* cy, size_t num_points, unsigned int max_iters);  #endif \/\/ MANDELBROT_GPU_H<\/code><\/pre>\n<p>mandelbrot_gpu.cu<\/p>\n<pre><code class=\"cpp\">#include &lt;thrust\/complex.h> #include &lt;cuda_runtime.h> #include \"mandelbrot_gpu.h\"  __device__ unsigned int num_iters(double cx, double cy, unsigned int max_iters) {     thrust::complex&lt;double> z(0.0, 0.0);     thrust::complex&lt;double> c(cx, cy);      for (unsigned int i = 0; i &lt;= max_iters; ++i) {         if (thrust::abs(z) > 2.0) {             return i;         }         z = z * z + c;     }      return max_iters; }  __global__ void mandelbrot_kernel(unsigned int* results, double* cx, double* cy, unsigned int max_iters, int num_points) {     int idx = blockDim.x * blockIdx.x + threadIdx.x;     if (idx &lt; num_points) {         results[idx] = num_iters(cx[idx], cy[idx], max_iters);     } }  unsigned int* gpu_mandelbrot(double* cx, double* cy, size_t num_points, unsigned int max_iters) {     unsigned int *dev_results, *results;     double *dev_cx, *dev_cy;      results = new unsigned int[num_points];      cudaMalloc((void**)&amp;dev_cx, num_points * sizeof(double));     cudaMalloc((void**)&amp;dev_cy, num_points * sizeof(double));     cudaMalloc((void**)&amp;dev_results, num_points * sizeof(unsigned int));      cudaMemcpy(dev_cx, cx, num_points * sizeof(double), cudaMemcpyHostToDevice);     cudaMemcpy(dev_cy, cy, num_points * sizeof(double), cudaMemcpyHostToDevice);      int threads_per_block = 256;     int blocks_per_grid = (num_points + threads_per_block - 1) \/ threads_per_block;      mandelbrot_kernel&lt;&lt;&lt;blocks_per_grid, threads_per_block>>>(dev_results, dev_cx, dev_cy, max_iters, num_points);      cudaMemcpy(results, dev_results, num_points * sizeof(unsigned int), cudaMemcpyDeviceToHost);      cudaFree(dev_cx);     cudaFree(dev_cy);     cudaFree(dev_results);      return results; }<\/code><\/pre>\n<p>mandelbrot.cpp<\/p>\n<pre><code class=\"cpp\">#include &lt;iostream> #include \"mandelbrot_gpu.h\"  extern \"C\" {     void calculate_mandelbrot(const double* cx, const double* cy, int num_points, unsigned int max_iters, unsigned int* output) {         double* non_const_cx = const_cast&lt;double*>(cx);         double* non_const_cy = const_cast&lt;double*>(cy);          unsigned int* gpu_results = gpu_mandelbrot(non_const_cx, non_const_cy, num_points, max_iters);          for (int i = 0; i &lt; num_points; ++i) {             output[i] = gpu_results[i];         }          delete[] gpu_results;     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>build.rs<\/code>\u00a0\u2014 \u0444\u0430\u0439\u043b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0431\u0435\u0440\u0451\u0442 \u0438 \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442 CUDA\u2011\u0447\u0430\u0441\u0442\u044c:<\/p>\n<pre><code class=\"rust\">use cc;  fn main() {     cc::Build::new()         .cuda(true)         .cpp(true)         .flag(\"-cudart=shared\")         .flag(\"-ccbin=gcc\")         .files(&amp;[\".\/src\/mandelbrot.cpp\", \".\/src\/mandelbrot_gpu.cu\"])         .compile(\"mandelbrot.a\"); }<\/code><\/pre>\n<p>\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b \u0438\u043c\u0435\u0435\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439<a href=\"https:\/\/docs.nvidia.com\/cuda\/cuda-compiler-driver-nvcc\/\" rel=\"noopener noreferrer nofollow\"> \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 CUDA<\/a>. \u0412\u044b\u043f\u043e\u043b\u043d\u0438\u043c <code>cargo build<\/code> \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<pre><code class=\"powershell\">$ cargo build     Compiling mandelbrot_set v0.1.0 (\/home\/danil\/home\/danil\/RustroverProjects\/mandelbrot_set) warning: mandelbrot_set@0.1.0: nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used warning: mandelbrot_set@0.1.0: nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used warning: mandelbrot_set@0.1.0: nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used warning: mandelbrot_set@0.1.0: nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used warning: mandelbrot_set@0.1.0: nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used warning: mandelbrot_set@0.1.0: nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used     Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.87s<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u0438\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u043e\u0448\u0438\u0431\u043a\u0438, \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u0435 <code>gcc<\/code> \u0432 <code>build.rs<\/code> \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"rust\">...         .flag(\"-ccbin=clang\") ...<\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>calculate_mandelbrot<\/code> \u0432 <code>main.rs<\/code>:<\/p>\n<pre><code class=\"rust\">... use libc::{c_double, c_int, c_uint};  extern \"C\" {     fn calculate_mandelbrot(         cx: *mut c_double,         cy: *mut c_double,         num_points: c_int,         max_iters: c_uint,         output: *mut c_uint,     ); } ...<\/code><\/pre>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u043c <code>generate_set<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 C++:<\/p>\n<pre><code class=\"rust\">fn generate_set(     ... ) {     ...     let mut h_cx = vec![];     let mut h_cy = vec![];     let mut output = vec![0; (resolution * resolution) as usize];      for x in 0..resolution {         for y in 0..resolution {             ...             h_cx.push(cx);             h_cy.push(cy);         }     }      unsafe {         calculate_mandelbrot(             h_cx.as_mut_ptr(),             h_cy.as_mut_ptr(),             (resolution * resolution) as c_int,             max_iters,             output.as_mut_ptr(),         );     }      for (x, row) in output.chunks(resolution as usize).enumerate() {         for (y, column) in row.iter().enumerate() {             let pixel = buffer.get_pixel_mut(x as u32, y as u32);             let color = gradient.get(*column as usize).unwrap_or(&amp;[0, 0, 0]);             *pixel = Rgb(*color);         }     }      buffer.save(&amp;file_name).unwrap(); }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 main.rs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex; use libc::{c_double, c_int, c_uint};  extern \"C\" {     fn calculate_mandelbrot(         cx: *mut c_double,         cy: *mut c_double,         num_points: c_int,         max_iters: c_uint,         output: *mut c_uint,     ); }  fn hex2rgb(hex: &amp;str) -> Result&lt;Vec&lt;u8>, String> {     let hex = hex.trim_start_matches('#');     if hex.len() != 6 {         return Err(\"Invalid HEX color length\".to_string());     }      let r = u8::from_str_radix(&amp;hex[0..2], 16).map_err(|_| \"Invalid HEX color\")?;     let g = u8::from_str_radix(&amp;hex[2..4], 16).map_err(|_| \"Invalid HEX color\")?;     let b = u8::from_str_radix(&amp;hex[4..6], 16).map_err(|_| \"Invalid HEX color\")?;      Ok(vec![r, g, b]) }  fn lerp_color(color1: &amp;[u8; 3], color2: &amp;[u8; 3], value: f64) -> [u8; 3] {     [         (color1[0] as f64 + (color2[0] as f64 - color1[0] as f64) * value) as u8,         (color1[1] as f64 + (color2[1] as f64 - color1[1] as f64) * value) as u8,         (color1[2] as f64 + (color2[2] as f64 - color1[2] as f64) * value) as u8,     ] }  fn get_gradient(gradient_colors: Vec&lt;&amp;str>, max_iters: u32) -> Vec&lt;[u8; 3]> {     let mut colors = vec![];     let mut gradient_colors_rgb = vec![];     for color in &amp;gradient_colors {         let rgb = hex2rgb(color).unwrap();         gradient_colors_rgb.push([rgb[0], rgb[1], rgb[2]]);     }      for i in 0..max_iters {         let color_index = (i as usize * (gradient_colors.len() - 1)) \/ max_iters as usize;         let color_value = (i as f64 * (gradient_colors.len() as f64 - 1.0)) \/ max_iters as f64;         let value = color_value % 1.0;         colors.push(lerp_color(             &amp;gradient_colors_rgb[color_index],             &amp;gradient_colors_rgb[color_index + 1],             value,         ));     }      colors }  fn generate_set(     file_name: String,     max_iters: u32,     colors: Vec&lt;&amp;str>,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);     let gradient = get_gradient(colors, max_iters);     let mut h_cx = vec![];     let mut h_cy = vec![];     let mut output = vec![0; (resolution * resolution) as usize];      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             h_cx.push(cx);             h_cy.push(cy);         }     }      unsafe {         calculate_mandelbrot(             h_cx.as_mut_ptr(),             h_cy.as_mut_ptr(),             (resolution * resolution) as c_int,             max_iters,             output.as_mut_ptr(),         );     }      for (x, row) in output.chunks(resolution as usize).enumerate() {         for (y, column) in row.iter().enumerate() {             let pixel = buffer.get_pixel_mut(x as u32, y as u32);             let color = gradient.get(*column as usize).unwrap_or(&amp;[0, 0, 0]);             *pixel = Rgb(*color);         }     }      buffer.save(&amp;file_name).unwrap(); }  fn num_iters(cx: f64, cy: f64, max_iters: u32) -> u32 {     let mut z = Complex::new(0.0, 0.0);     let c = Complex::new(cx, cy);      for i in 0..=max_iters {         if z.norm() > 2.0 {             return i;         }         z = z * z + c;     }      max_iters }  fn main() {     let resolution = 1024;     let max_iters = 100;      generate_set(         \"fractal.png\".to_string(),         max_iters,         vec![\"#1C448E\", \"#6F8695\", \"#CEC288\", \"#FFE381\", \"#DBFE87\"],         -1.5,         -1.0,         0.5,         1.0,         resolution,     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h4>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h4>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>scale<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u0437\u0430 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u0438\u044f \u043a \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0439 \u0442\u043e\u0447\u043a\u0435 \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u0430:<\/p>\n<pre><code class=\"rust\">fn main() {     let resolution = 1024;     let target_x = -0.6582034218739634;     let target_y = 0.44967917993930356;     let max_iters = 5000;     let scale = 500_000.0;      let x_min = target_x - (1.0 \/ scale);     let x_max = target_x + (1.0 \/ scale);     let y_min = target_y - (1.0 \/ scale);     let y_max = target_y + (1.0 \/ scale);      generate_set(         \"fractal.png\".to_string(),         ...         x_min,         y_min,         x_max,         y_max,         resolution,     ); }<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/293\/b99\/2f3\/293b992f397cb8c4c836c6aac9f70f8c.png\" alt=\"fractal.png\" title=\"fractal.png\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/293\/b99\/2f3\/293b992f397cb8c4c836c6aac9f70f8c.png\"\/><\/p>\n<div><figcaption>fractal.png<\/figcaption><\/div>\n<\/figure>\n<p>\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0438\u0437-\u0437\u0430 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u043e\u0439 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 float64 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043c\u0430\u0441\u0448\u0442\u0430\u0431 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0434\u043e <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"10^{15}\" alt=\"10^{15}\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/842\/76d\/bad\/84276dbadf98047bb0f91526b37fa49c.svg\" width=\"36\" height=\"21\"\/> \u0440\u0430\u0437:<\/p>\n<pre><code class=\"rust\">let resolution = 256; ... let scale = 10f64.powf(15.0); ...<\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/24e\/61d\/02b\/24e61d02bb560361092a26127c1f0406.png\" alt=\"fractal.png\" title=\"fractal.png\" width=\"256\" height=\"256\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/24e\/61d\/02b\/24e61d02bb560361092a26127c1f0406.png\"\/><\/p>\n<div><figcaption>fractal.png<\/figcaption><\/div>\n<\/figure>\n<h2>\u0421\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u043d\u043e \u0438\u0437 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u043d\u0430\u0448\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e &#171;\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a&#187;. \u042d\u0442\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u0448\u0443\u043c.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3c4\/7a4\/d08\/3c47a4d08e38b66ad7b4f2bd329112a7.png\" width=\"1104\" height=\"735\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3c4\/7a4\/d08\/3c47a4d08e38b66ad7b4f2bd329112a7.png\"\/><\/figure>\n<p>\u0420\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u0443 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043f\u0443\u0442\u0451\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u044d\u043c\u043f\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f &#8212; \u044d\u0442\u043e \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439, \u043d\u043e \u043f\u0440\u043e\u0436\u043e\u0440\u043b\u0438\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434. \u0415\u0433\u043e \u0441\u0443\u0442\u044c \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043c\u044b <img loading=\"lazy\" decoding=\"async\" class=\"formula inline\" source=\"n\" alt=\"n\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ff8\/5f3\/ad9\/ff85f3ad90a77c1d1da4ce400387389b.svg\" width=\"12\" height=\"12\"\/> \u0440\u0430\u0437 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u043c \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0443 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438, \u043f\u0440\u043e\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c iters, \u0431\u0435\u0440\u0451\u043c \u0441\u0440\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0438 \u043a\u0440\u0430\u0441\u0438\u043c \u0446\u0435\u043b\u0435\u0432\u0443\u044e \u0442\u043e\u0447\u043a\u0443. \u0412\u043e\u0442, \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442.<\/p>\n<p>\u0414\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443:<\/p>\n<pre><code class=\"diff\">[package] name = \"mandelbrot_set\" version = \"0.1.0\" edition = \"2021\"  [dependencies] num = \"0.4.3\" image = \"0.25.1\" libc = \"0.2.155\" +rand = \"0.9.0-alpha.1\"  [build-dependencies] cc = \"1.0.98\"<\/code><\/pre>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u043c <code>generate_set<\/code>:<\/p>\n<pre><code class=\"rust\">use rand::Rng;   fn generate_set( ...   samples: u32, ... ) {   let mut rng = rand::thread_rng();   ...   for _ in 0..samples {       ...       let x_percent = (x as f64 + rng.gen::&lt;f64>()) \/ resolution as f64;       let y_percent = (y as f64 + rng.gen::&lt;f64>()) \/ resolution as f64;       ...   }   unsafe {     calculate_mandelbrot(       ...       (resolution * resolution * samples) as c_int,       ...     );   }   for (x, row) in output.chunks((resolution * samples) as usize).enumerate() {       for (y, column) in row.chunks(samples as usize).enumerate() {           let mut sum = 0;           for iteration in column {               sum += *iteration as usize;           }           let pixel = buffer.get_pixel_mut(x as u32, y as u32);           let color = gradient.get(sum \/ column.len()).unwrap_or(&amp;[0, 0, 0]);           *pixel = Rgb(*color);       }   } }<\/code><\/pre>\n<pre><code class=\"rust\">fn main() {     ...     let samples = 16;     ...     generate_set(         ...         samples     ); }<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2fa\/c4c\/a16\/2fac4ca16f6d908960cd1c62fa5d8623.png\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2fa\/c4c\/a16\/2fac4ca16f6d908960cd1c62fa5d8623.png\"\/><\/figure>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2aa\/f21\/3a2\/2aaf213a294701e33fe79eab1078fdd8.png\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2aa\/f21\/3a2\/2aaf213a294701e33fe79eab1078fdd8.png\"\/><\/figure>\n<p>\u0412\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043b\u0443\u0447\u0448\u0435!<\/p>\n<h2>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0448\u0442\u0440\u0438\u0445\u0438<\/h2>\n<p>\u0412\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u044d\u043a\u0441\u043f\u043e\u043d\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u043c \u0440\u043e\u0441\u0442\u043e\u043c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430, \u0447\u0442\u043e\u0431\u044b \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u0430:<\/p>\n<pre><code class=\"rust\">let scale = 10.0_f64.powf((i as f64 \/ frames as f64) * args.max_scale.ilog10() as f64);<\/code><\/pre>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/docs.rs\/rayon\/latest\/rayon\/\" rel=\"noopener noreferrer nofollow\">rayon<\/a> \u0434\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0430 \u0432 \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u0438 <a href=\"https:\/\/docs.rs\/clap\/latest\/clap\/\" rel=\"noopener noreferrer nofollow\">clap<\/a> \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438. \u0418\u0442\u043e\u0433\u043e\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043b\u0435\u0436\u0438\u0442 \u043d\u0430 <a href=\"https:\/\/github.com\/0x7o\/mandelbrot_set\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>:<\/p>\n<pre><code class=\"powershell\">$ git clone https:\/\/github.com\/0x7o\/mandelbrot_set $ cd mandelbrot_set $ cargo build --release<\/code><\/pre>\n<pre><code class=\"powershell\">$ .\/target\/release\/mandelbrot_set \\     --resolution 1024 \\     --colors \"#00A3BC, #8B00BD, #81BD00, #BD5400\" \\     --x -0.6582034218739634 \\     --y 0.44967917993930356 \\     --iters 3000 \\     --max-scale 1000000000000000 \\     --fps 24 \\     --seconds 60 \\     --n-samples 4 \\     --output .\/output<\/code><\/pre>\n<pre><code class=\"powershell\">$ ffmpeg -framerate 24 -i output\/frame_%09d.png -c:v libx264 -pix_fmt yuv420p -crf 18 -y video.mp4 <\/code><\/pre>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/66657e86ee339fd48b1b6ad1\" data-style=\"\" id=\"66657e86ee339fd48b1b6ad1\" width=\"\"><\/div>\n<ul>\n<li>\n<p><a href=\"https:\/\/drive.google.com\/drive\/folders\/1VW4DcF5nYx50fnx8Vg2LZ-40N7PXifdq?usp=sharing\" rel=\"noopener noreferrer nofollow\">\u0413\u0430\u043b\u0435\u0440\u0435\u044f \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u043e\u0432<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/0x7o\/mandelbrot_set\" rel=\"noopener noreferrer nofollow\">GitHub \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/t.me\/hikonon\" rel=\"noopener noreferrer nofollow\">Telegram \u0430\u0432\u0442\u043e\u0440\u0430<\/a><\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u043f\u0440\u043e\u0447\u0442\u0435\u043d\u0438\u0435! \u042f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u044e \u0438\u0437\u0443\u0447\u0430\u0442\u044c Rust, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043a\u043e\u0434 \u0434\u0430\u043b\u0435\u043a \u043e\u0442 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0441\u0442\u0432\u0430<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/820489\/\"> https:\/\/habr.com\/ru\/articles\/820489\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><\/figure>\n<p>\u0424\u0440\u0430\u043a\u0442\u0430\u043b\u044b\u00a0\u2014 \u044d\u0442\u043e \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 \u0441\u0430\u043c\u043e\u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0435 \u0444\u0438\u0433\u0443\u0440\u044b. \u041e\u043d\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u0444\u043e\u0440\u043c\u0443\u043b\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u0443\u0434\u0438\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u043a\u0440\u0430\u0441\u043e\u0442\u0443!<\/p>\n<p>\u0412\u00a0\u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437\u00a0\u0441\u0430\u043c\u044b\u0445 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u043e\u0432 \u043d\u0430\u00a0\u044f\u0437\u044b\u043a\u0435 Rust \u0441\u00a0\u0430\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u044b\u043c \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435\u043c NVIDIA, \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c, \u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u0435\u043c \u0438 \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c\u044e.<\/p>\n<h3>\u041c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u041c\u0430\u043d\u0434\u0435\u043b\u044c\u0431\u0440\u043e\u0442\u0430<\/h3>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0441\u0430\u043c\u044b\u0439 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0444\u0440\u0430\u043a\u0442\u0430\u043b, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0432 1905\u00a0\u0433\u043e\u0434\u0443 \u041f\u044c\u0435\u0440\u043e\u043c \u0424\u0430\u0442\u0443. \u041c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u0434\u0430\u0451\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0444\u043e\u0440\u043c\u0443\u043b\u043e\u0439, \u0433\u0434\u0435  \u0438 \u00a0\u2014 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0435 \u0447\u0438\u0441\u043b\u0430:<\/p>\n<p>\u0424\u0440\u0430\u043a\u0442\u0430\u043b \u043c\u043e\u0436\u043d\u043e \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 , \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 . \u0414\u043e\u043a\u0430\u0437\u0430\u043d\u043e, \u0447\u0442\u043e\u00a0\u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043e \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u00a0\u0440\u0430\u0434\u0438\u0443\u0441\u043e\u043c . \u0415\u0441\u043b\u0438 \u0437\u0430  \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0442\u043e\u0447\u043a\u0430  \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u043a\u0440\u0443\u0436\u043d\u043e\u0441\u0442\u0438 (), \u0442\u043e \u0441\u0447\u0438\u0442\u0430\u0435\u043c, \u0447\u0442\u043e  \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443 \u0438 \u0437\u0430\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0435\u0451 \u0432\u00a0\u0447\u0451\u0440\u043d\u044b\u0439 \u0446\u0432\u0435\u0442.<\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0430 CPU<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443 \u0434\u043b\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430\u0431\u043e\u0440\u0430 \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f NVIDIA CUDA. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0443\u0441\u0442\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>cargo init<\/code>:<\/p>\n<pre><code class=\"powershell\">$ cargo init mandelbrot_set &amp; cd mandelbrot_set $ tree \u251c\u2500\u2500 Cargo.lock \u251c\u2500\u2500 Cargo.toml \u251c\u2500\u2500 README.md \u2514\u2500\u2500 src     \u2514\u2500\u2500 main.rs<\/code><\/pre>\n<p>\u0412 <code>Cargo.toml<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<pre><code class=\"diff\">[package] name = \"mandelbrot_set\" version = \"0.1.0\" edition = \"2021\"  [dependencies] +num = \"0.4.3\" +image = \"0.25.1\"<\/code><\/pre>\n<ul>\n<li>\n<p><code>image<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><code>num<\/code> \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0445 \u0447\u0438\u0441\u0435\u043b<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u044d\u0442\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex;<\/code><\/pre>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f :<\/p>\n<pre><code class=\"rust\">fn num_iters(cx: f64, cy: f64, max_iters: u32) -> u32 {     let mut z = Complex::new(0.0, 0.0);     let c = Complex::new(cx, cy);      for i in 0..=max_iters {         if z.norm() > 2.0 {             return i;         }         z = z * z + c;     }      max_iters }<\/code><\/pre>\n<p>\u041e\u0431\u043e\u0437\u043d\u0430\u0447\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>generate_set<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0432 \u0444\u0430\u0439\u043b:<\/p>\n<pre><code class=\"rust\">fn generate_set(     file_name: String,     max_iters: u32,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) { }<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043f\u0443\u0441\u0442\u043e\u0435 RGB \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"rust\">let mut buffer = RgbImage::new(resolution, resolution); buffer.save(&amp;file_name).unwrap();<\/code><\/pre>\n<p>\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0438\u0437 <code>num_iters<\/code> \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"rust\">for x in 0..resolution {     for y in 0..resolution {         let x_percent = x as f64 \/ resolution as f64;         let y_percent = y as f64 \/ resolution as f64;         let cx = x_min + (x_max - x_min) * x_percent;         let cy = y_min + (y_max - y_min) * y_percent;         let iters = num_iters(cx, cy, max_iters);     } }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0440\u0435\u0434\u0435\u043b \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0434\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442, \u043c\u044b \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0438\u043a\u0441\u0435\u043b\u044c \u043f\u043e\u00a0\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0447\u0435\u0440\u043d\u044b\u043c. \u0412\u00a0\u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0437\u0430\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u0431\u0435\u043b\u044b\u043c \u0446\u0432\u0435\u0442\u043e\u043c:<\/p>\n<pre><code class=\"rust\">let pixel = buffer.get_pixel_mut(x, y); if iters &lt; max_iters {     *pixel = Rgb([255, 255, 255]); }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 generate_set<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">fn generate_set(     file_name: String,     max_iters: u32,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             let iters = num_iters(cx, cy, max_iters);             let pixel = buffer.get_pixel_mut(x, y);              if iters &lt; max_iters {                 *pixel = Rgb([255, 255, 255]);             }         }     }      buffer.save(&amp;file_name).unwrap(); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412\u044b\u0437\u043e\u0432\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438\u0437 <code>main<\/code>:<\/p>\n<pre><code class=\"rust\">fn main() {     let resolution = 1024;;     let max_iters = 100;      generate_set(         \"fractal.png\".to_string(),         max_iters,         -1.5,         -1.0,         0.5,         1.0,         resolution,     ); }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 main.rs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex;   fn generate_set(     file_name: String,     max_iters: u32,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             let iters = num_iters(cx, cy, max_iters);             let pixel = buffer.get_pixel_mut(x, y);              if iters &lt; max_iters {                 *pixel = Rgb([255, 255, 255]);             }         }     }      buffer.save(&amp;file_name).unwrap(); }  fn num_iters(cx: f64, cy: f64, max_iters: u32) -> u32 {     let mut z = Complex::new(0.0, 0.0);     let c = Complex::new(cx, cy);      for i in 0..=max_iters {         if z.norm() > 2.0 {             return i;         }         z = z * z + c;     }      max_iters }  fn main() {     let resolution = 1024;;     let max_iters = 100;      generate_set(         \"fractal.png\".to_string(),         max_iters,         -1.5,         -1.0,         0.5,         1.0,         resolution,     ); }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443\u00a0\u2014 <code>cargo run --release<\/code>. \u041f\u0440\u0438\u00a0\u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u043e\u0436\u0434\u0430\u0442\u044c, \u043f\u043e\u043a\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0441\u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>fractal.png<\/figcaption><\/div>\n<\/figure>\n<p>\u0412\u00a0\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u043e\u0439 \u0444\u0440\u0430\u043a\u0442\u0430\u043b. \u0421\u0442\u0440\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u044d\u0442\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f\u00a0\u2014 \u0442\u043e\u0447\u043a\u0430\u00a0\u043b\u0438\u0431\u043e \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443 (\u0447\u0435\u0440\u043d\u044b\u0439 \u0446\u0432\u0435\u0442),\u00a0\u043b\u0438\u0431\u043e \u043d\u0435\u0442 (\u0431\u0435\u043b\u044b\u0439 \u0446\u0432\u0435\u0442), \u043d\u043e\u00a0\u043d\u0438\u043a\u0442\u043e \u043d\u0435\u00a0\u043c\u0435\u0448\u0430\u0435\u0442 \u043d\u0430\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430! \u041e\u0434\u0438\u043d \u0438\u0437\u00a0\u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u0440\u0430\u0441\u043a\u0440\u0430\u0441\u0438\u0442\u044c \u0444\u0440\u0430\u043a\u0442\u0430\u043b\u00a0\u2014 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043a\u0430\u043a\u00a0\u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e\u00a0\u0442\u043e\u0447\u043a\u0430 \u043d\u0435\u00a0\u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0443.<\/p>\n<h4>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0446\u0432\u0435\u0442\u0430<\/h4>\n<p>\u041f\u0443\u0441\u0442\u044c \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u043c\u0430\u0441\u0441\u0438\u0432 <code>gradient<\/code> \u0441 <code>max_iters<\/code> \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0446\u0432\u0435\u0442\u0430. \u0415\u0441\u043b\u0438 <code>iters &lt; max_iters<\/code>, \u0442\u043e \u043c\u044b \u0431\u0435\u0440\u0451\u043c <code>gradient[iters]<\/code> \u0438 \u043e\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u043f\u0438\u043a\u0441\u0435\u043b\u044c \u0432 \u044d\u0442\u043e\u0442 \u0446\u0432\u0435\u0442. \u0412 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u043e\u0447\u043a\u0443 \u0447\u0435\u0440\u043d\u043e\u0439:<\/p>\n<pre><code class=\"rust\">fn generate_set(     ...     colors: Vec&lt;&amp;str>,     ... ) {   ...   let gradient = get_gradient(colors, max_iters);   ...   for x in 0..resolution {       for y in 0..resolution {           ...           let pixel = buffer.get_pixel_mut(x, y);           let color = gradient.get(iters as usize).unwrap_or(&amp;[0, 0, 0]);           *pixel = Rgb(*color);       }   } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b <a href=\"https:\/\/dev.to\/ndesmic\/linear-color-gradients-from-scratch-1a0e\" rel=\"noopener noreferrer nofollow\">\u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/a> \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0433\u043e \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u0430 \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0446\u0432\u0435\u0442\u043e\u0432 HEX:<\/p>\n<details class=\"spoiler\">\n<summary>hex2rgb, lerp_color \u0438 get_gradient<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">fn hex2rgb(hex: &amp;str) -> Result&lt;Vec&lt;u8>, String> {     let hex = hex.trim_start_matches('#');     if hex.len() != 6 {         return Err(\"Invalid HEX color length\".to_string());     }      let r = u8::from_str_radix(&amp;hex[0..2], 16).map_err(|_| \"Invalid HEX color\")?;     let g = u8::from_str_radix(&amp;hex[2..4], 16).map_err(|_| \"Invalid HEX color\")?;     let b = u8::from_str_radix(&amp;hex[4..6], 16).map_err(|_| \"Invalid HEX color\")?;      Ok(vec![r, g, b]) }  fn lerp_color(color1: &amp;[u8; 3], color2: &amp;[u8; 3], value: f64) -> [u8; 3] {     [         (color1[0] as f64 + (color2[0] as f64 - color1[0] as f64) * value) as u8,         (color1[1] as f64 + (color2[1] as f64 - color1[1] as f64) * value) as u8,         (color1[2] as f64 + (color2[2] as f64 - color1[2] as f64) * value) as u8,     ] }  fn get_gradient(gradient_colors: Vec&lt;&amp;str>, max_iters: u32) -> Vec&lt;[u8; 3]> {     let mut colors = vec![];     let mut gradient_colors_rgb = vec![];     for color in &amp;gradient_colors {         let rgb = hex2rgb(color).unwrap();         gradient_colors_rgb.push([rgb[0], rgb[1], rgb[2]]);     }      for i in 0..max_iters {         let color_index = (i as usize * (gradient_colors.len() - 1)) \/ max_iters as usize;         let color_value = (i as f64 * (gradient_colors.len() as f64 - 1.0)) \/ max_iters as f64;         let value = color_value % 1.0;         colors.push(lerp_color(             &amp;gradient_colors_rgb[color_index],             &amp;gradient_colors_rgb[color_index + 1],             value,         ));     }      colors }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0446\u0432\u0435\u0442\u043e\u0432\u044b\u0435 \u043f\u0430\u043b\u0438\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u0441\u0430\u0439\u0442\u0435 &#8212; <a href=\"https:\/\/color.adobe.com\/\" rel=\"noopener noreferrer nofollow\">https:\/\/color.adobe.com\/<\/a><\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 main.rs<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"rust\">use image::{Rgb, RgbImage}; use num::complex::Complex;  fn hex2rgb(hex: &amp;str) -> Result&lt;Vec&lt;u8>, String> {     let hex = hex.trim_start_matches('#');     if hex.len() != 6 {         return Err(\"Invalid HEX color length\".to_string());     }      let r = u8::from_str_radix(&amp;hex[0..2], 16).map_err(|_| \"Invalid HEX color\")?;     let g = u8::from_str_radix(&amp;hex[2..4], 16).map_err(|_| \"Invalid HEX color\")?;     let b = u8::from_str_radix(&amp;hex[4..6], 16).map_err(|_| \"Invalid HEX color\")?;      Ok(vec![r, g, b]) }  fn lerp_color(color1: &amp;[u8; 3], color2: &amp;[u8; 3], value: f64) -> [u8; 3] {     [         (color1[0] as f64 + (color2[0] as f64 - color1[0] as f64) * value) as u8,         (color1[1] as f64 + (color2[1] as f64 - color1[1] as f64) * value) as u8,         (color1[2] as f64 + (color2[2] as f64 - color1[2] as f64) * value) as u8,     ] }  fn get_gradient(gradient_colors: Vec&lt;&amp;str>, max_iters: u32) -> Vec&lt;[u8; 3]> {     let mut colors = vec![];     let mut gradient_colors_rgb = vec![];     for color in &amp;gradient_colors {         let rgb = hex2rgb(color).unwrap();         gradient_colors_rgb.push([rgb[0], rgb[1], rgb[2]]);     }      for i in 0..max_iters {         let color_index = (i as usize * (gradient_colors.len() - 1)) \/ max_iters as usize;         let color_value = (i as f64 * (gradient_colors.len() as f64 - 1.0)) \/ max_iters as f64;         let value = color_value % 1.0;         colors.push(lerp_color(             &amp;gradient_colors_rgb[color_index],             &amp;gradient_colors_rgb[color_index + 1],             value,         ));     }      colors }  fn generate_set(     file_name: String,     max_iters: u32,     colors: Vec&lt;&amp;str>,     x_min: f64,     y_min: f64,     x_max: f64,     y_max: f64,     resolution: u32, ) {     let mut buffer = RgbImage::new(resolution, resolution);     let gradient = get_gradient(colors, max_iters);      for x in 0..resolution {         for y in 0..resolution {             let x_percent = x as f64 \/ resolution as f64;             let y_percent = y as f64 \/ resolution as f64;             let cx = x_min + (x_max - x_min) * x_percent;             let cy = y_min + (y_max - y_min) * y_percent;             let iters = num_iters(cx, cy, max_iters);             let<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-378490","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/378490","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=378490"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/378490\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=378490"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=378490"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=378490"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}