use image::{Rgb, RgbImage};
use rayon::prelude::*;
#[inline]
fn lerp(pct: f32, a: f32, b: f32) -> f32 {
pct.mul_add(b - a, a)
}
#[inline]
fn distance(x: i32, y: i32) -> f32 {
((x * x y * y) as f32).sqrt()
}
struct ColorCalculator {
from: [f32; 3],
to: [f32; 3],
center_x: i32,
center_y: i32,
max_dist: f32,
}
impl ColorCalculator {
fn new(from: [u8; 3], to: [u8; 3], width: u32, height: u32) -> Self {
let center_x = width as i32 / 2;
let center_y = height as i32 / 2;
Self {
from: from.map(|channel| channel as f32),
to: to.map(|channel| channel as f32),
center_x,
center_y,
max_dist: distance(center_x, center_y),
}
}
fn calculate(&self, x: u32, y: u32) -> [u8; 3] {
let x_dist = self.center_x - x as i32;
let y_dist = self.center_y - y as i32;
let t = distance(x_dist, y_dist) / self.max_dist;
[
lerp(t, self.from[0], self.to[0]) as u8,
lerp(t, self.from[1], self.to[1]) as u8,
lerp(t, self.from[2], self.to[2]) as u8,
]
}
}
fn radial_gradient(geometry: [u32; 2], inner_color: [u8; 3], outer_color: [u8; 3]) -> RgbImage {
let [width, height] = geometry;
let color_calculator = ColorCalculator::new(inner_color, outer_color, width, height);
let mut background = RgbImage::new(width, height);
(0..height / 2).into_par_iter().for_each(|y| {
for x in 0..width / 2 {
let color = Rgb(color_calculator.calculate(x, y));
background.put_pixel(x, y, color);
background.put_pixel(width - x - 1, y, color);
background.put_pixel(x, height - y - 1, color);
background.put_pixel(width - x - 1, height - y - 1, color);
};
});
background
}
I know that I could just use a mutex here although it is unnecessary since provided my code is correct no pixel should be mutated more than once. So how do I tell rust that doing background.put_pixel(x, y, color)
in multiple threads is actually okay here?
I'm guessing some use of unsafe has to be used here although I am new to rust and am not sure how to use it effectively here.
Here's the error
error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
--> src\lib.rs:212:13
|
212 | background.put_pixel(x, y, color);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
--> src\lib.rs:213:13
|
213 | background.put_pixel(width - x - 1, y, color);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
--> src\lib.rs:214:13
|
214 | background.put_pixel(x, height - y - 1, color);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
error[E0596]: cannot borrow `background` as mutable, as it is a captured variable in a `Fn` closure
--> src\lib.rs:215:13
|
215 | background.put_pixel(width - x - 1, height - y - 1, color);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
CodePudding user response:
You can't. At least not with an RgbImage
.
put_pixel
takes a &mut self
. In Rust, it's undefined behavior to have two &mut
references alias - the optimizer can do some funky stuff to your code if you break this assumption.
You will probably have an easier time creating a Vec<u8>
of pixel data, calculating each pixel's value using Rayon's parallel iterators (which will take special care to not alias the mutable references), then assemble the buffer into an image using from_vec
.
CodePudding user response:
You can use a raw pointer, cast it to unsigned integer, and then unsafely dereference inside the loop:
let background: *mut RgbImage =
&mut RgbImage::new(width,height) as *mut RgbImage;
let pointer = background as usize;
(0..height / 2).into_par_iter().for_each(|y| {
for x in 0..width / 2 {
...
let mut background = unsafe { (pointer as *mut RgbImage).as_mut() }.unwrap();
...
};
});
unsafe { background.as_ref().unwrap() })