use rayon::prelude::*;
use image::RgbImage;
#[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;
}
}
pub fn radial_gradient_mirror(
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, geometry[0], geometry[1]);
let mut buf: Vec<_> = (0..(height / 2))
.into_par_iter()
.flat_map(|y| {
let mut row: Vec<[u8; 3]> = Vec::with_capacity(width as usize);
for x in 0..(width / 2) {
row.push(color_calculator.calculate(x, y))
}
row.extend(row.clone().iter().rev());
row
})
.collect();
buf.extend(buf.clone().iter().rev());
let buf = buf.into_iter().flatten().collect();
RgbImage::from_raw(width, height, buf).unwrap()
}
I have an algorithm to generate a radial gradient. The original version would calculate the color of each pixel but because there is a horizontal and vertical line of symmetry I can calculate the colors for the top left corner and use some vector manipulation to mirror. I managed to do this by cloning and reversing the iterator buf.clone().iter().rev()
although this is slow. How can I avoid this and are there any other optimizations I could use?
CodePudding user response:
I believe your core question is how to optimize this:
buf.extend(buf.clone().iter().rev());
Vec
has extend_from_within
which can be used to extend a Vec
from its own contents... however you want the results in reverse order. So I don't think there is a neat way using iterators to express this, since we're mutating and iterating over the same Vec
at the same time. However doing this using a loop isn't too bad:
fn mirror_vec<T: Clone>(v: &mut Vec<T>) {
let n = v.len();
v.reserve(n);
for i in (0..n).rev() {
v.push(v[i].clone());
}
}
CodePudding user response:
You do not really need to populate the buffer as later you override it, you can use direct chained iterators for doing so (copied
is used to to have owned values), original buff will be droped at the end of the scope:
let i1 = buf.iter().copied();
let i2 = buf.iter().copied().rev();
let buf = i1.chain(i2).flatten().collect();
Full method:
pub fn radial_gradient_mirror(
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, geometry[0], geometry[1]);
let mut buf: Vec<_> = (0..(height / 2))
.into_par_iter()
.flat_map(|y| {
let mut row: Vec<[u8; 3]> = Vec::with_capacity(width as usize);
for x in 0..(width / 2) {
row.push(color_calculator.calculate(x, y))
}
row.extend(row.clone().iter().rev());
row
})
.collect();
let i1 = buf.iter().copied();
let i2 = buf.iter().copied().rev();
let buf = i1.chain(i2).flatten().collect();
RgbImage::from_raw(width, height, buf).unwrap()
}