use rand::seq::SliceRandom;
use crate::matrix::{Matrix2d, Matrix3d};
use crate::{QuantError, SpatialQuant};
pub fn compute_max_coarse_level(mut width: usize, mut height: usize) -> usize {
const MAX_PIXELS: usize = 4000;
let mut result = 0;
while width * height > MAX_PIXELS {
width >>= 1;
height >>= 1;
result += 1;
}
result
}
pub fn fill_random(arr: &mut Matrix3d<f64>, rng: &mut impl rand::Rng) {
arr.iter_mut().for_each(|a| {
*a = rng.gen_range(0.0, 1.0);
});
}
pub fn random_permutation_2d(
width: usize,
height: usize,
result: &mut std::collections::VecDeque<(i32, i32)>,
rng: &mut impl rand::Rng,
) {
let mut perm1d = (0..width as i32 * height as i32).collect::<Vec<_>>();
perm1d.shuffle(rng);
while let Some(val) = perm1d.pop() {
result.push_back((val % width as i32, val / width as i32));
}
}
pub fn compute_b_array<T>(
filter_weights: &Matrix2d<T>,
b: &mut Matrix2d<T>,
) -> Result<(), QuantError>
where
T: Clone + Default + Copy + core::ops::AddAssign<T> + SpatialQuant,
{
let radius_width = (filter_weights.width() as isize - 1) / 2;
let radius_height = (filter_weights.height() as isize - 1) / 2;
let offset_x = (b.width() as isize - 1) / 2 - radius_width;
let offset_y = (b.height() as isize - 1) / 2 - radius_height;
for j_y in 0isize..(b.height() as isize) {
for j_x in 0isize..(b.width() as isize) {
for k_y in 0isize..(filter_weights.height() as isize) {
for k_x in 0isize..(filter_weights.width() as isize) {
if k_x + offset_x >= j_x - radius_width
&& k_x + offset_x <= j_x + radius_width
&& k_y + offset_y >= j_y - radius_height
&& k_y + offset_y <= j_y + radius_height
{
*b.get_mut(j_x as usize, j_y as usize).unwrap() += filter_weights
.get(k_x as usize, k_y as usize)
.unwrap()
.direct_product(
filter_weights
.get(
(k_x + offset_x - j_x + radius_width) as usize,
(k_y + offset_y - j_y + radius_height) as usize,
)
.ok_or_else(|| "Could not compute b array")?,
);
}
}
}
}
}
Ok(())
}
pub fn b_value<T>(b: &Matrix2d<T>, i_x: isize, i_y: isize, j_x: isize, j_y: isize) -> T
where
T: Clone + Default + Copy,
{
let radius_width = b.width().saturating_sub(1) / 2;
let radius_height = b.height().saturating_sub(1) / 2;
let k_x = j_x - i_x + radius_width as isize;
let k_y = j_y - i_y + radius_height as isize;
if let Some(val) = b.get(k_x as usize, k_y as usize) {
*val
} else {
T::default()
}
}
pub fn compute_a_image<T>(
image: &Matrix2d<T>,
b: &Matrix2d<T>,
a: &mut Matrix2d<T>,
) -> Result<(), QuantError>
where
T: Clone + Default + Copy + core::ops::AddAssign<T> + core::ops::MulAssign<f64> + SpatialQuant,
{
let radius_width = b.width().saturating_sub(1) / 2;
let radius_height = b.height().saturating_sub(1) / 2;
for i_y in 0..a.height() {
for i_x in 0..a.width() {
for j_y in i_y.saturating_sub(radius_height)..=(i_y + radius_height) {
if j_y >= a.height() {
break;
}
for j_x in (i_x.saturating_sub(radius_width))..=(i_x + radius_width) {
if j_x >= a.width() {
break;
}
if let Some(inner) = image.get(j_x, j_y) {
*a.get_mut(i_x, i_y).unwrap() +=
b_value(b, i_x as isize, i_y as isize, j_x as isize, j_y as isize)
.direct_product(inner);
}
}
}
*a.get_mut(i_x, i_y).unwrap() *= -2.0;
}
}
Ok(())
}
pub fn sum_coarsen<T>(fine: &Matrix2d<T>, coarse: &mut Matrix2d<T>)
where
T: Clone + Default + Copy + core::ops::AddAssign<T> + core::ops::Mul<f64, Output = T>,
{
for y in 0..coarse.height() {
for x in 0..coarse.width() {
let mut val = *fine.get(x * 2, y * 2).unwrap();
if let Some(v) = fine.get(x * 2 + 1, y * 2) {
val += *v;
}
if let Some(v) = fine.get(x * 2, y * 2 + 1) {
val += *v;
}
if let Some(v) = fine.get(x * 2 + 1, y * 2 + 1) {
val += *v;
}
*coarse.get_mut(x, y).unwrap() = val;
}
}
}
pub fn best_match_color<T>(vars: &Matrix3d<f64>, i_x: usize, i_y: usize, palette: &[T]) -> usize {
let mut max_v = 0;
let mut max_weight = *vars.get(i_x, i_y, 0).unwrap();
for v in 1..palette.len() {
if let Some(var) = vars.get(i_x, i_y, v) {
if *var > max_weight {
max_v = v;
max_weight = *var;
}
}
}
max_v
}
pub fn zoom_double(small: &Matrix3d<f64>, big: &mut Matrix3d<f64>) -> Result<(), QuantError> {
for y in 0..(big.height() / 2 * 2) {
for x in 0..(big.width() / 2 * 2) {
let left = ((x as f64 - 0.1) / 2.0).max(0.0);
let right = (small.width() as f64 - 0.001).min((x as f64 + 1.1) / 2.0);
let top = ((y as f64 - 0.1) / 2.0).max(0.0);
let bottom = (small.height() as f64 - 0.001).min((y as f64 + 1.1) / 2.0);
let x_left = left.floor() as usize;
let x_right = right.floor() as usize;
let y_top = top.floor() as usize;
let y_bottom = bottom.floor() as usize;
let area = (right - left) * (bottom - top);
let top_weight = (right - left) * (top.ceil() - top) / area;
let bottom_weight = (right - left) * (bottom - bottom.floor()) / area;
let left_weight = (bottom - top) * (left.ceil() - left) / area;
let right_weight = (bottom - top) * (right - right.floor()) / area;
let top_left_weight = (left.ceil() - left) * (top.ceil() - top) / area;
let top_right_weight = (right - right.floor()) * (top.ceil() - top) / area;
let bottom_left_weight = (left.ceil() - left) * (bottom - bottom.floor()) / area;
let bottom_right_weight = (right - right.floor()) * (bottom - bottom.floor()) / area;
for z in 0..big.depth() {
if x_left == x_right && y_top == y_bottom {
*big.get_mut(x, y, z).unwrap() = *small
.get(x_left, y_top, z)
.ok_or_else(|| "Could not zoom double")?;
} else if x_left == x_right {
*big.get_mut(x, y, z).unwrap() = top_weight
* *small
.get(x_left, y_top, z)
.ok_or_else(|| "Could not zoom double")?
+ bottom_weight
* *small
.get(x_left, y_bottom, z)
.ok_or_else(|| "Could not zoom double")?;
} else if y_top == y_bottom {
*big.get_mut(x, y, z).unwrap() = left_weight
* *small
.get(x_left, y_top, z)
.ok_or_else(|| "Could not zoom double")?
+ right_weight
* *small
.get(x_right, y_top, z)
.ok_or_else(|| "Could not zoom double")?;
} else {
*big.get_mut(x, y, z).unwrap() = top_left_weight
* *small
.get(x_left, y_top, z)
.ok_or_else(|| "Could not zoom double")?
+ top_right_weight
* *small
.get(x_right, y_top, z)
.ok_or_else(|| "Could not zoom double")?
+ bottom_left_weight
* *small
.get(x_left, y_bottom, z)
.ok_or_else(|| "Could not zoom double")?
+ bottom_right_weight
* *small
.get(x_right, y_bottom, z)
.ok_or_else(|| "Could not zoom double")?;
}
}
}
}
Ok(())
}
pub fn compute_initial_s<T>(
s: &mut Matrix2d<T>,
coarse_variables: &Matrix3d<f64>,
b: &Matrix2d<T>,
) -> Result<(), QuantError>
where
T: Clone + Default + Copy + core::ops::AddAssign<T> + core::ops::Mul<f64, Output = T>,
{
let palette_size = s.width();
let coarse_width = coarse_variables.width();
let coarse_height = coarse_variables.height();
let center_x = b.width().saturating_sub(1) / 2;
let center_y = b.height().saturating_sub(1) / 2;
let center_b = b_value(&b, 0, 0, 0, 0);
for v in 0..palette_size {
for alpha in v..palette_size {
*s.get_mut(v, alpha).unwrap() = T::default();
}
}
for i_y in 0..coarse_height {
for i_x in 0..coarse_width {
let max_j_x = coarse_width.min(i_x.saturating_sub(center_x) + b.width());
let max_j_y = coarse_height.min(i_y.saturating_sub(center_y) + b.height());
for j_y in i_y.saturating_sub(center_y)..max_j_y {
for j_x in i_x.saturating_sub(center_x)..max_j_x {
if i_x == j_x && i_y == j_y {
continue;
}
let b_ij = b_value(b, i_x as isize, i_y as isize, j_x as isize, j_y as isize);
for v in 0..palette_size {
for alpha in v..palette_size {
let mult = coarse_variables
.get(i_x as usize, i_y as usize, v)
.ok_or_else(|| "Could not compute initial s")?
* coarse_variables
.get(j_x, j_y, alpha)
.ok_or_else(|| "Could not compute initial s")?;
*s.get_mut(v, alpha).unwrap() += b_ij * mult;
}
}
}
}
for v in 0..palette_size {
*s.get_mut(v, v).unwrap() += center_b
* *coarse_variables
.get(i_x as usize, i_y as usize, v)
.ok_or_else(|| "Could not compute initial s")?;
}
}
}
Ok(())
}
pub fn update_s<T>(
s: &mut Matrix2d<T>,
coarse_variables: &Matrix3d<f64>,
b: &Matrix2d<T>,
j_x: usize,
j_y: usize,
alpha: usize,
delta: f64,
) -> Result<(), QuantError>
where
T: Clone + Default + Copy + core::ops::AddAssign<T> + core::ops::Mul<f64, Output = T>,
{
let palette_size = s.width();
let center_x = b.width().saturating_sub(1) / 2;
let center_y = b.height().saturating_sub(1) / 2;
let max_i_x = coarse_variables.width().min(j_x + center_x + 1);
let max_i_y = coarse_variables.height().min(j_y + center_y + 1);
for i_y in j_y.saturating_sub(center_y)..max_i_y {
for i_x in j_x.saturating_sub(center_x)..max_i_x {
let delta_b_ij =
b_value(b, i_x as isize, i_y as isize, j_x as isize, j_y as isize) * delta;
if i_x == j_x && i_y == j_y {
continue;
}
for v in 0..=alpha {
let mult = *coarse_variables
.get(i_x, i_y, v)
.ok_or_else(|| "Could not update s")?;
*s.get_mut(v, alpha).unwrap() += delta_b_ij * mult;
}
for v in alpha..palette_size {
let mult = *coarse_variables
.get(i_x, i_y, v)
.ok_or_else(|| "Could not update s")?;
*s.get_mut(alpha, v).unwrap() += delta_b_ij * mult;
}
}
}
*s.get_mut(alpha, alpha).unwrap() += b_value(b, 0, 0, 0, 0) * delta;
Ok(())
}
pub fn compute_initial_j_palette_sum<T>(
j_palette_sum: &mut Matrix2d<T>,
coarse_variables: &Matrix3d<f64>,
palette: &[T],
) where
T: Clone + Default + Copy + core::ops::AddAssign<T> + core::ops::Mul<f64, Output = T>,
{
for j_y in 0..coarse_variables.height() {
for j_x in 0..coarse_variables.width() {
let mut palette_sum = T::default();
for (alpha, &item) in palette.iter().enumerate() {
palette_sum += item * *coarse_variables.get(j_x, j_y, alpha).unwrap();
}
*j_palette_sum.get_mut(j_x, j_y).unwrap() = palette_sum;
}
}
}