matrix-image-editor/src/img_processor.rs
2024-05-22 12:10:00 +02:00

151 lines
5.7 KiB
Rust

use std::{
sync::{mpsc, Arc},
thread,
};
use eframe::egui::{self, TextureOptions};
use image::{DynamicImage, Pixel, Rgb, Rgb32FImage};
use nalgebra::{ComplexField, Const, Matrix, OMatrix, SMatrix, Vector3, Vector4};
use rayon::prelude::*;
use image::EncodableLayout;
struct ProcessorInstruction {
img: Arc<Rgb32FImage>,
color_matrix: SMatrix<f32, 3, 4>,
pos_matrix: SMatrix<f32, 3, 3>,
ret: oneshot::Sender<(egui::TextureHandle, (f32, f32))>,
ctx: egui::Context,
handle: Option<egui::TextureHandle>,
realloc: bool,
}
pub struct Processor {
channel: mpsc::Sender<ProcessorInstruction>,
}
impl Processor {
pub fn init() -> Self {
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
let mut working_img = DynamicImage::new_rgb32f(1, 1);
for ProcessorInstruction {
img,
color_matrix,
pos_matrix,
ret,
ctx,
handle,
realloc,
} in rx
{
let (width, height) = img.dimensions();
if realloc {
working_img = DynamicImage::new_rgb32f(width, height);
}
let do_pos_trans =
!pos_matrix.is_identity(f32::EPSILON) && pos_matrix.is_invertible();
let do_color_trans = !color_matrix.is_identity(f32::EPSILON);
let mut inv_pos_mat = OMatrix::<f32, Const<3>, Const<3>>::identity();
if do_pos_trans {
inv_pos_mat = pos_matrix.try_inverse().unwrap();
let mut cols_mut = inv_pos_mat.column_part_mut(2, 2);
*(cols_mut.get_mut(0).unwrap()) *= width as f32;
*(cols_mut.get_mut(1).unwrap()) *= height as f32;
}
{
let working_img = working_img.as_mut_rgb32f().unwrap();
match (do_pos_trans, do_color_trans) {
(true, true) => {
working_img
.par_enumerate_pixels_mut()
.for_each(|(x, y, px)| {
let org_pos =
inv_pos_mat * Vector3::new(x as f32, y as f32, 1.0);
let Rgb([r, g, b]) = *img
.get_pixel_checked(
org_pos.x.abs() as u32 % width,
org_pos.y.abs() as u32 % height,
)
.unwrap_or(&Rgb([0., 0., 0.]));
*px = Rgb::from(Into::<[f32; 3]>::into(
color_matrix * Vector4::new(r, g, b, 1.0),
));
});
}
(true, false) => {
working_img
.par_enumerate_pixels_mut()
.for_each(|(x, y, px)| {
let r = inv_pos_mat * Vector3::new(x as f32, y as f32, 1.0);
*px = *img
.get_pixel_checked(
r.x.abs() as u32 % width,
r.y.abs() as u32 % height,
)
.unwrap_or(&Rgb([0., 0., 0.]))
});
}
(false, true) => {
working_img
.par_enumerate_pixels_mut()
.for_each(|(x, y, px)| {
let Rgb([r, g, b]) = *img.get_pixel(x, y);
*px = Rgb::from(Into::<[f32; 3]>::into(
color_matrix * Vector4::new(r, g, b, 1.0),
));
});
}
(false, false) => {
*working_img = (*img).clone();
}
}
}
let color_image = egui::ColorImage::from_rgb(
[working_img.width() as usize, working_img.height() as usize],
working_img.to_rgb8().as_bytes(),
);
let handle = if let Some(mut handle) = handle {
handle.set(color_image, TextureOptions::default());
handle
} else {
ctx.load_texture("res_tex", color_image, TextureOptions::default())
};
ret.send((handle, (width as f32, height as f32))).unwrap();
// (color_matrix.is_identity(f32::EPSILON), pos_matrix.is_identity(f32::EPSILON))
}
});
Self { channel: tx }
}
pub fn exec(
&self,
img: Arc<Rgb32FImage>,
color_matrix: SMatrix<f32, 3, 4>,
pos_matrix: SMatrix<f32, 3, 3>,
ctx: egui::Context,
handle: Option<egui::TextureHandle>,
realloc: bool,
) -> oneshot::Receiver<(egui::TextureHandle, (f32, f32))> {
let (tx, rx) = oneshot::channel();
let r = self.channel.send(ProcessorInstruction {
img,
color_matrix,
pos_matrix,
ret: tx,
ctx,
handle,
realloc,
});
dbg!(r).unwrap();
rx
}
}