use crate::dequant::{CoefficientCanvas, TileComponentCanvas};
use crate::error::{JpxError, Result};
use crate::geometry::{ceil_div, Rect};
const ALPHA: f64 = -1.586134342059924;
const BETA: f64 = -0.052980118572961;
const GAMMA: f64 = 0.882911075530934;
const DELTA: f64 = 0.443506852043971;
const KAPPA: f64 = 1.230174104914001;
pub(crate) fn inverse(canvas: &mut TileComponentCanvas) -> Result<()> {
let extent = canvas.rect;
let area = u64::from(extent.width()) * u64::from(extent.height());
let held = match &canvas.samples {
CoefficientCanvas::Reversible(samples) => samples.len() as u64,
CoefficientCanvas::Irreversible(samples) => samples.len() as u64,
};
if held != area {
return Err(JpxError::Malformed(format!(
"tile-component canvas holds {held} samples for a {}x{} extent",
extent.width(),
extent.height()
)));
}
if canvas.levels == 0 || extent.is_empty() {
return Ok(());
}
match &mut canvas.samples {
CoefficientCanvas::Reversible(samples) => run_levels::<i64>(extent, canvas.levels, samples),
CoefficientCanvas::Irreversible(samples) => {
run_levels::<f32>(extent, canvas.levels, samples);
}
}
Ok(())
}
trait LiftScalar: Copy {
type Stored: Copy;
const MARGIN: i64;
fn load(stored: Self::Stored) -> Self;
fn store(self) -> Self::Stored;
fn halve(self) -> Self;
fn filter(ext: &[Self], x: &mut [Self], base: i64, i0: i64, i1: i64);
}
impl LiftScalar for i64 {
type Stored = i32;
const MARGIN: i64 = 2;
fn load(stored: i32) -> i64 {
i64::from(stored)
}
fn store(self) -> i32 {
self.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
}
fn halve(self) -> i64 {
self.div_euclid(2)
}
fn filter(ext: &[i64], x: &mut [i64], base: i64, i0: i64, i1: i64) {
let at = |i: i64| (i - base) as usize;
let nlo = i0.div_euclid(2);
let nhi = i1.div_euclid(2);
for n in nlo..=nhi {
let k = 2 * n;
x[at(k)] = ext[at(k)] - (ext[at(k - 1)] + ext[at(k + 1)] + 2).div_euclid(4);
}
for n in nlo..nhi {
let k = 2 * n + 1;
x[at(k)] = ext[at(k)] + (x[at(k - 1)] + x[at(k + 1)]).div_euclid(2);
}
}
}
impl LiftScalar for f32 {
type Stored = f32;
const MARGIN: i64 = 4;
fn load(stored: f32) -> f32 {
stored
}
fn store(self) -> f32 {
self
}
fn halve(self) -> f32 {
self / 2.0
}
fn filter(ext: &[f32], x: &mut [f32], base: i64, i0: i64, i1: i64) {
let at = |i: i64| (i - base) as usize;
let nlo = i0.div_euclid(2);
let nhi = i1.div_euclid(2);
let scale_even = KAPPA as f32;
let scale_odd = (1.0 / KAPPA) as f32;
let delta = DELTA as f32;
let gamma = GAMMA as f32;
let beta = BETA as f32;
let alpha = ALPHA as f32;
for n in (nlo - 1)..(nhi + 2) {
let k = 2 * n;
x[at(k)] = scale_even * ext[at(k)];
}
for n in (nlo - 2)..(nhi + 2) {
let k = 2 * n + 1;
x[at(k)] = scale_odd * ext[at(k)];
}
for n in (nlo - 1)..(nhi + 2) {
let k = 2 * n;
x[at(k)] -= delta * (x[at(k - 1)] + x[at(k + 1)]);
}
for n in (nlo - 1)..(nhi + 1) {
let k = 2 * n + 1;
x[at(k)] -= gamma * (x[at(k - 1)] + x[at(k + 1)]);
}
for n in nlo..(nhi + 1) {
let k = 2 * n;
x[at(k)] -= beta * (x[at(k - 1)] + x[at(k + 1)]);
}
for n in nlo..nhi {
let k = 2 * n + 1;
x[at(k)] -= alpha * (x[at(k - 1)] + x[at(k + 1)]);
}
}
}
fn pseo(i: i64, i0: i64, i1: i64) -> i64 {
let period = 2 * (i1 - i0 - 1);
let m = (i - i0).rem_euclid(period);
i0 + m.min(period - m)
}
fn sr_lane<S: LiftScalar>(lane: &mut [S], i0: i64, i1: i64, ext: &mut Vec<S>, x: &mut Vec<S>) {
if i1 - i0 == 1 {
if i0.rem_euclid(2) == 1 {
lane[0] = lane[0].halve();
}
return;
}
let base = i0 - S::MARGIN;
ext.clear();
ext.extend((base..i1 + S::MARGIN).map(|i| lane[(pseo(i, i0, i1) - i0) as usize]));
x.clear();
x.extend_from_slice(ext);
S::filter(ext, x, base, i0, i1);
let offset = S::MARGIN as usize;
for (k, slot) in lane.iter_mut().enumerate() {
*slot = x[offset + k];
}
}
fn run_levels<S: LiftScalar>(extent: Rect, levels: u8, data: &mut [S::Stored]) {
let width = u64::from(extent.width());
let x0 = u64::from(extent.x0);
let y0 = u64::from(extent.y0);
let longest = extent.width().max(extent.height()) as usize;
let mut lane: Vec<S> = Vec::with_capacity(longest);
let mut ext: Vec<S> = Vec::with_capacity(longest + 2 * S::MARGIN as usize);
let mut x: Vec<S> = Vec::with_capacity(longest + 2 * S::MARGIN as usize);
for lev in (1..=levels).rev() {
let stride = 1u64 << u32::from(lev - 1).min(32);
let u0 = ceil_div(x0, stride);
let u1 = ceil_div(u64::from(extent.x1), stride);
let v0 = ceil_div(y0, stride);
let v1 = ceil_div(u64::from(extent.y1), stride);
if u0 >= u1 || v0 >= v1 {
continue;
}
let pos = |u: u64, v: u64| ((v * stride - y0) * width + (u * stride - x0)) as usize;
for v in v0..v1 {
lane.clear();
lane.extend((u0..u1).map(|u| S::load(data[pos(u, v)])));
sr_lane(&mut lane, u0 as i64, u1 as i64, &mut ext, &mut x);
for (u, value) in (u0..u1).zip(&lane) {
data[pos(u, v)] = value.store();
}
}
for u in u0..u1 {
lane.clear();
lane.extend((v0..v1).map(|v| S::load(data[pos(u, v)])));
sr_lane(&mut lane, v0 as i64, v1 as i64, &mut ext, &mut x);
for (v, value) in (v0..v1).zip(&lane) {
data[pos(u, v)] = value.store();
}
}
}
}
#[cfg(test)]
mod tests {
use super::inverse;
use crate::dequant::{CoefficientCanvas, TileComponentCanvas};
use crate::error::JpxError;
use crate::geometry::{ceil_div, Rect};
fn rect(x0: u32, y0: u32, x1: u32, y1: u32) -> Rect {
Rect { x0, y0, x1, y1 }
}
fn reversible(extent: Rect, levels: u8, samples: Vec<i32>) -> TileComponentCanvas {
TileComponentCanvas {
rect: extent,
levels,
samples: CoefficientCanvas::Reversible(samples),
}
}
fn irreversible(extent: Rect, levels: u8, samples: Vec<f32>) -> TileComponentCanvas {
TileComponentCanvas {
rect: extent,
levels,
samples: CoefficientCanvas::Irreversible(samples),
}
}
fn int_samples(canvas: &TileComponentCanvas) -> &[i32] {
match &canvas.samples {
CoefficientCanvas::Reversible(samples) => samples,
CoefficientCanvas::Irreversible(other) => {
panic!(
"expected a reversible canvas, found {} f32 samples",
other.len()
)
}
}
}
fn float_samples(canvas: &TileComponentCanvas) -> &[f32] {
match &canvas.samples {
CoefficientCanvas::Irreversible(samples) => samples,
CoefficientCanvas::Reversible(other) => {
panic!(
"expected an irreversible canvas, found {} i32 samples",
other.len()
)
}
}
}
#[test]
fn levels_zero_leaves_the_canvas_untouched() {
let mut canvas = reversible(rect(3, 5, 7, 7), 0, vec![9, -2, 4, 0, 1, 2, 3, 4]);
inverse(&mut canvas).unwrap();
assert_eq!(int_samples(&canvas), &[9, -2, 4, 0, 1, 2, 3, 4]);
}
#[test]
fn empty_rect_is_a_no_op() {
let mut canvas = reversible(rect(5, 5, 5, 9), 2, Vec::new());
inverse(&mut canvas).unwrap();
assert!(int_samples(&canvas).is_empty());
}
#[test]
fn mismatched_sample_count_is_malformed() {
let mut canvas = reversible(rect(0, 0, 2, 1), 1, vec![1, 2, 3]);
assert!(matches!(
inverse(&mut canvas),
Err(JpxError::Malformed(detail)) if detail.contains('3')
));
}
#[test]
fn hand_computed_5_3_row_with_even_origin() {
let mut canvas = reversible(rect(2, 0, 7, 1), 1, vec![1, 2, 3, 4, 5]);
inverse(&mut canvas).unwrap();
assert_eq!(int_samples(&canvas), &[0, 2, 1, 6, 3]);
}
#[test]
fn hand_computed_5_3_row_with_odd_origin() {
let mut canvas = reversible(rect(3, 0, 8, 1), 1, vec![1, 2, 3, 4, 5]);
inverse(&mut canvas).unwrap();
assert_eq!(int_samples(&canvas), &[2, 1, 4, 2, 7]);
}
#[test]
fn length_one_lanes_follow_the_parity_rule() {
let mut odd = reversible(rect(1, 1, 2, 2), 1, vec![8]);
inverse(&mut odd).unwrap();
assert_eq!(int_samples(&odd), &[2]);
let mut even = reversible(rect(2, 2, 3, 3), 1, vec![7]);
inverse(&mut even).unwrap();
assert_eq!(int_samples(&even), &[7]);
let mut lossy = irreversible(rect(1, 1, 2, 2), 1, vec![3.0]);
inverse(&mut lossy).unwrap();
assert_eq!(float_samples(&lossy), &[0.75]);
}
#[test]
fn hand_computed_two_level_2d_with_odd_origin() {
let mut canvas = reversible(rect(3, 1, 7, 3), 2, vec![1, -2, 3, 0, 2, 10, -1, 4]);
inverse(&mut canvas).unwrap();
assert_eq!(int_samples(&canvas), &[5, 2, 4, 5, 7, 5, 4, 7]);
}
#[test]
fn constant_9_7_canvas_reconstructs_the_lifted_constants() {
let mut canvas = irreversible(rect(0, 0, 8, 1), 1, vec![1.0; 8]);
inverse(&mut canvas).unwrap();
for (k, value) in float_samples(&canvas).iter().enumerate() {
let expected = if k % 2 == 0 { 0.5 } else { 1.5 };
assert!(
(value - expected).abs() < 1e-4,
"sample {k}: {value} vs {expected}"
);
}
}
fn pseo(i: i64, i0: i64, i1: i64) -> i64 {
let period = 2 * (i1 - i0 - 1);
let m = (i - i0).rem_euclid(period);
i0 + m.min(period - m)
}
fn fwd_lane_53(lane: &mut [i64], i0: i64, i1: i64) {
if i1 - i0 == 1 {
if i0.rem_euclid(2) == 1 {
lane[0] *= 2;
}
return;
}
let base = i0 - 2;
let ext: Vec<i64> = (base..i1 + 2)
.map(|i| lane[(pseo(i, i0, i1) - i0) as usize])
.collect();
let mut y = ext.clone();
let at = |i: i64| (i - base) as usize;
let clo = (i0 + 1).div_euclid(2); let chi = (i1 + 1).div_euclid(2); for n in (clo - 1)..chi {
let k = 2 * n + 1;
y[at(k)] = ext[at(k)] - (ext[at(k - 1)] + ext[at(k + 1)]).div_euclid(2);
}
for n in clo..chi {
let k = 2 * n;
y[at(k)] = ext[at(k)] + (y[at(k - 1)] + y[at(k + 1)] + 2).div_euclid(4);
}
for (slot, k) in lane.iter_mut().zip(i0..i1) {
*slot = y[at(k)];
}
}
const ALPHA: f64 = -1.586134342059924;
const BETA: f64 = -0.052980118572961;
const GAMMA: f64 = 0.882911075530934;
const DELTA: f64 = 0.443506852043971;
const KAPPA: f64 = 1.230174104914001;
fn lift_forward(y: &mut [f64], base: i64, lo: i64, hi: i64, parity: i64, weight: f64) {
let mut k = lo + (parity - lo).rem_euclid(2);
while k < hi {
y[(k - base) as usize] +=
weight * (y[(k - 1 - base) as usize] + y[(k + 1 - base) as usize]);
k += 2;
}
}
fn fwd_lane_97(lane: &mut [f64], i0: i64, i1: i64) {
if i1 - i0 == 1 {
if i0.rem_euclid(2) == 1 {
lane[0] *= 2.0;
}
return;
}
let base = i0 - 4;
let top = i1 + 4;
let mut y: Vec<f64> = (base..top)
.map(|i| lane[(pseo(i, i0, i1) - i0) as usize])
.collect();
lift_forward(&mut y, base, base + 1, top - 1, 1, ALPHA); lift_forward(&mut y, base, base + 2, top - 2, 0, BETA); lift_forward(&mut y, base, base + 3, top - 3, 1, GAMMA); lift_forward(&mut y, base, i0, i1, 0, DELTA); for (slot, k) in lane.iter_mut().zip(i0..i1) {
let value = y[(k - base) as usize];
*slot = if k.rem_euclid(2) == 1 {
KAPPA * value
} else {
value / KAPPA
};
}
}
fn level_bounds(extent: Rect, lev: u8) -> (u64, u64, u64, u64, u64) {
let stride = 1u64 << u32::from(lev - 1).min(32);
(
stride,
ceil_div(u64::from(extent.x0), stride),
ceil_div(u64::from(extent.x1), stride),
ceil_div(u64::from(extent.y0), stride),
ceil_div(u64::from(extent.y1), stride),
)
}
fn forward_53(extent: Rect, levels: u8, data: &mut [i32]) {
let width = u64::from(extent.x1 - extent.x0);
let x0 = u64::from(extent.x0);
let y0 = u64::from(extent.y0);
for lev in 1..=levels {
let (stride, u0, u1, v0, v1) = level_bounds(extent, lev);
if u0 >= u1 || v0 >= v1 {
continue;
}
let pos = |u: u64, v: u64| ((v * stride - y0) * width + (u * stride - x0)) as usize;
for u in u0..u1 {
let mut lane: Vec<i64> = (v0..v1).map(|v| i64::from(data[pos(u, v)])).collect();
fwd_lane_53(&mut lane, v0 as i64, v1 as i64);
for (v, value) in (v0..v1).zip(&lane) {
data[pos(u, v)] = *value as i32;
}
}
for v in v0..v1 {
let mut lane: Vec<i64> = (u0..u1).map(|u| i64::from(data[pos(u, v)])).collect();
fwd_lane_53(&mut lane, u0 as i64, u1 as i64);
for (u, value) in (u0..u1).zip(&lane) {
data[pos(u, v)] = *value as i32;
}
}
}
}
fn forward_97(extent: Rect, levels: u8, data: &mut [f32]) {
let width = u64::from(extent.x1 - extent.x0);
let x0 = u64::from(extent.x0);
let y0 = u64::from(extent.y0);
for lev in 1..=levels {
let (stride, u0, u1, v0, v1) = level_bounds(extent, lev);
if u0 >= u1 || v0 >= v1 {
continue;
}
let pos = |u: u64, v: u64| ((v * stride - y0) * width + (u * stride - x0)) as usize;
for u in u0..u1 {
let mut lane: Vec<f64> = (v0..v1).map(|v| f64::from(data[pos(u, v)])).collect();
fwd_lane_97(&mut lane, v0 as i64, v1 as i64);
for (v, value) in (v0..v1).zip(&lane) {
data[pos(u, v)] = *value as f32;
}
}
for v in v0..v1 {
let mut lane: Vec<f64> = (u0..u1).map(|u| f64::from(data[pos(u, v)])).collect();
fwd_lane_97(&mut lane, u0 as i64, u1 as i64);
for (u, value) in (u0..u1).zip(&lane) {
data[pos(u, v)] = *value as f32;
}
}
}
}
struct Lcg(u64);
impl Lcg {
fn step(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
fn int(&mut self, span: i64) -> i64 {
let raw = (self.step() >> 33) as i64;
raw.rem_euclid(2 * span + 1) - span
}
}
#[test]
fn forward_then_inverse_5_3_is_bit_exact() {
let mut seed = 1u64;
for x0 in [4u32, 5] {
for y0 in [6u32, 7] {
for width in 1..17u32 {
for height in 1..17u32 {
for levels in 1..4u8 {
seed += 1;
let extent = rect(x0, y0, x0 + width, y0 + height);
let mut lcg = Lcg(seed);
let original: Vec<i32> =
(0..width * height).map(|_| lcg.int(100) as i32).collect();
let mut data = original.clone();
forward_53(extent, levels, &mut data);
let mut canvas = reversible(extent, levels, data);
inverse(&mut canvas).unwrap();
assert_eq!(
int_samples(&canvas),
&original[..],
"x0={x0} y0={y0} {width}x{height} levels={levels}"
);
}
}
}
}
}
}
#[test]
fn forward_then_inverse_9_7_stays_within_tolerance() {
let mut seed = 99u64;
for x0 in [4u32, 5] {
for y0 in [6u32, 7] {
for width in 1..17u32 {
for height in 1..17u32 {
for levels in 1..4u8 {
seed += 1;
let extent = rect(x0, y0, x0 + width, y0 + height);
let mut lcg = Lcg(seed);
let original: Vec<f32> =
(0..width * height).map(|_| lcg.int(8) as f32).collect();
let mut data = original.clone();
forward_97(extent, levels, &mut data);
let mut canvas = irreversible(extent, levels, data);
inverse(&mut canvas).unwrap();
for (got, want) in float_samples(&canvas).iter().zip(&original) {
assert!(
(got - want).abs() <= 1e-4,
"x0={x0} y0={y0} {width}x{height} levels={levels}: \
{got} vs {want}"
);
}
}
}
}
}
}
}
}