use std::ops::Range;
use crate::{
buffer::Buffer,
color::{Limited, Rgba},
pixel,
processing::Scaler,
util::{clamp, GetClamped},
view,
};
pub struct Super;
impl<PI, CI, PO, CO> Scaler<PI, CI, PO, CO> for Super
where
PI: Into<Rgba>,
PI: pixel::Read<CI>,
CI: pixel::Channel,
PO: From<Rgba> + Into<Rgba> + From<PI>,
PO: pixel::Write<CO> + pixel::Read<CO>,
CO: pixel::Channel,
{
#[inline]
fn scale(input: &view::Read<PI, CI>, width: u32, height: u32) -> Buffer<PO, CO, Vec<CO>> {
let x_factor = width as f32 / input.width() as f32;
let y_factor = height as f32 / input.height() as f32;
let factor = x_factor as u32;
debug_assert!(x_factor != y_factor || x_factor.fract() != 0.0 || (factor & (factor - 1)) != 0);
let mut factor = factor / 2;
let mut output = scale::<PI, CI, PO, CO>(input);
while factor >= 2 {
output = scale::<PO, CO, PO, CO>(&output.readable(Default::default()));
factor /= 2;
}
output
}
}
#[allow(non_snake_case)]
fn scale<PI, CI, PO, CO>(input: &view::Read<PI, CI>) -> Buffer<PO, CO, Vec<CO>>
where
PI: Into<Rgba> + Into<PO>,
PI: pixel::Read<CI>,
CI: pixel::Channel,
PO: From<Rgba> + Into<Rgba>,
PO: pixel::Write<CO> + pixel::Read<CO>,
CO: pixel::Channel,
{
const WEIGHT1: f32 = 0.129633;
const WEIGHT2: f32 = 0.175068;
const W1: f32 = -WEIGHT1;
const W2: f32 = WEIGHT1 + 0.5;
const W3: f32 = -WEIGHT2;
const W4: f32 = WEIGHT2 + 0.5;
let mut output = Buffer::<PO, CO, _>::new(input.width() * 2, input.height() * 2);
for y in 0..input.height() {
for x in 0..input.width() {
let cx = x as i64;
let cy = y as i64;
let x = x * 2;
let y = y * 2;
let (r, g, b, a, Y) = sample(input, 1, -1..3, |sx, sy| (sx + cx, sy + cy));
let (r_min, r_max) = minmax(&r);
let (g_min, g_max) = minmax(&g);
let (b_min, b_max) = minmax(&b);
let (a_min, a_max) = minmax(&a);
let edge = diagonal(&Y, &[2.0, 1.0, -1.0, 4.0, -1.0, 1.0]);
let (r, g, b, a) = pick(edge, W1, W2, &r, &g, &b, &a);
let rgba = Rgba::new(
clamp(r, r_min, r_max),
clamp(g, g_min, g_max),
clamp(b, b_min, b_max),
clamp(a, a_min, a_max),
)
.clamp();
output.set(x, y, &input.get_clamped(cx, cy).into());
output.set(x + 1, y, &input.get_clamped(cx, cy).into());
output.set(x, y + 1, &input.get_clamped(cx, cy).into());
output.set(x + 1, y + 1, &rgba.into());
}
}
for y in 0..input.height() {
for x in 0..input.width() {
let x = x * 2;
let y = y * 2;
let (r, g, b, a, Y) = sample(&output.readable(Default::default()), 1, -1..3, |sx, sy| {
(sx + sy + x as i64, sx - sy + y as i64)
});
let (r_min, r_max) = minmax(&r);
let (g_min, g_max) = minmax(&g);
let (b_min, b_max) = minmax(&b);
let (a_min, a_max) = minmax(&a);
let edge = diagonal(&Y, &[2.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let (r, g, b, a) = pick(edge, W3, W4, &r, &g, &b, &a);
let rgba = Rgba::new(
clamp(r, r_min, r_max),
clamp(g, g_min, g_max),
clamp(b, b_min, b_max),
clamp(a, a_min, a_max),
)
.clamp();
output.set(x + 1, y, &rgba.into());
let (r, g, b, a, Y) = sample(&output.readable(Default::default()), 1, -1..3, |sx, sy| {
(sx + sy - 1 + x as i64, sx - sy + 1 + y as i64)
});
let edge = diagonal(&Y, &[2.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let (r, g, b, a) = pick(edge, W3, W4, &r, &g, &b, &a);
let rgba = Rgba::new(
clamp(r, r_min, r_max),
clamp(g, g_min, g_max),
clamp(b, b_min, b_max),
clamp(a, a_min, a_max),
)
.clamp();
output.set(x, y + 1, &rgba.into());
}
}
for y in (0..input.height() * 2).rev() {
for x in (0..input.width() * 2).rev() {
let (r, g, b, a, Y) = sample(&output.readable(Default::default()), 2, -2..2, |sx, sy| {
(sx + x as i64, sy + y as i64)
});
let (r_min, r_max) = minmax(&r);
let (g_min, g_max) = minmax(&g);
let (b_min, b_max) = minmax(&b);
let (a_min, a_max) = minmax(&a);
let edge = diagonal(&Y, &[2.0, 1.0, -1.0, 4.0, -1.0, 1.0]);
let (r, g, b, a) = pick(edge, W1, W2, &r, &g, &b, &a);
let rgba = Rgba::new(
clamp(r, r_min, r_max),
clamp(g, g_min, g_max),
clamp(b, b_min, b_max),
clamp(a, a_min, a_max),
)
.clamp();
output.set(x, y, &rgba.into());
}
}
output
}
type Matrix = [[f32; 4]; 4];
#[inline]
#[allow(non_snake_case)]
fn sample<PI, CI, F>(
input: &view::Read<PI, CI>,
offset: i64,
range: Range<i64>,
func: F,
) -> (Matrix, Matrix, Matrix, Matrix, Matrix)
where
PI: Into<Rgba>,
PI: pixel::Read<CI>,
CI: pixel::Channel,
F: Fn(i64, i64) -> (i64, i64),
{
let mut r = [[0.0f32; 4]; 4];
let mut g = [[0.0f32; 4]; 4];
let mut b = [[0.0f32; 4]; 4];
let mut a = [[0.0f32; 4]; 4];
let mut Y = [[0.0f32; 4]; 4];
for sx in range.start..range.end {
for sy in range.start..range.end {
let (x, y) = func(sx, sy);
let p = input.get_clamped(x, y).into();
r[(sx + offset) as usize][(sy + offset) as usize] = p.red;
g[(sx + offset) as usize][(sy + offset) as usize] = p.green;
b[(sx + offset) as usize][(sy + offset) as usize] = p.blue;
a[(sx + offset) as usize][(sy + offset) as usize] = p.alpha;
Y[(sx + offset) as usize][(sy + offset) as usize] = (0.2126 * p.red) + (0.7152 * p.green) + (0.0722 * p.blue);
}
}
(r, g, b, a, Y)
}
#[inline]
fn minmax(m: &Matrix) -> (f32, f32) {
(
m[1][1].min(m[2][1]).min(m[1][2]).min(m[2][2]),
m[1][1].max(m[2][1]).max(m[1][2]).max(m[2][2]),
)
}
#[inline]
fn diagonal(m: &[[f32; 4]; 4], w: &[f32; 6]) -> f32 {
#[inline]
fn df(a: f32, b: f32) -> f32 {
(a - b).abs()
}
let first = w[0] * (df(m[0][2], m[1][1]) + df(m[1][1], m[2][0]) + df(m[1][3], m[2][2]) + df(m[2][2], m[3][1]))
+ w[1] * (df(m[0][3], m[1][2]) + df(m[2][1], m[3][0]))
+ w[2] * (df(m[0][3], m[2][1]) + df(m[1][2], m[3][0]))
+ w[3] * (df(m[1][2], m[2][1]))
+ w[4] * (df(m[0][2], m[2][0]) + df(m[1][3], m[3][1]))
+ w[5] * (df(m[0][1], m[1][0]) + df(m[2][3], m[3][2]));
let second = w[0] * (df(m[0][1], m[1][2]) + df(m[1][2], m[2][3]) + df(m[1][0], m[2][1]) + df(m[2][1], m[3][2]))
+ w[1] * (df(m[0][0], m[1][1]) + df(m[2][2], m[3][3]))
+ w[2] * (df(m[0][0], m[2][2]) + df(m[1][1], m[3][3]))
+ w[3] * (df(m[1][1], m[2][2]))
+ w[4] * (df(m[1][0], m[3][2]) + df(m[0][1], m[2][3]))
+ w[5] * (df(m[0][2], m[1][3]) + df(m[2][0], m[3][1]));
first - second
}
#[inline]
#[allow(non_snake_case)]
fn pick(edge: f32, W1: f32, W2: f32, r: &Matrix, g: &Matrix, b: &Matrix, a: &Matrix) -> (f32, f32, f32, f32) {
if edge <= 0.0 {
(
W1 * (r[0][3] + r[3][0]) + W2 * (r[1][2] + r[2][1]),
W1 * (g[0][3] + g[3][0]) + W2 * (g[1][2] + g[2][1]),
W1 * (b[0][3] + b[3][0]) + W2 * (b[1][2] + b[2][1]),
W1 * (a[0][3] + a[3][0]) + W2 * (a[1][2] + a[2][1]),
)
}
else {
(
W1 * (r[0][0] + r[3][3]) + W2 * (r[1][1] + r[2][2]),
W1 * (g[0][0] + g[3][3]) + W2 * (g[1][1] + g[2][2]),
W1 * (b[0][0] + b[3][3]) + W2 * (b[1][1] + b[2][2]),
W1 * (a[0][0] + a[3][3]) + W2 * (a[1][1] + a[2][2]),
)
}
}