cubek-interpolate 0.3.0

CubeK: Interpolation
use cubecl::zspace::Shape;
use cubek_test_utils::{HostData, HostDataVec, Progress};

use super::super::{contiguous_strides, for_each_output_coord};

fn cubic_convolution_1(x: f32, a: f32) -> f32 {
    ((a + 2.0) * x - (a + 3.0)) * x * x + 1.0
}

fn cubic_convolution_2(x: f32, a: f32) -> f32 {
    ((a * x - 5.0 * a) * x + 8.0 * a) * x - 4.0 * a
}

fn cubic_interp_1d(x0: f32, x1: f32, x2: f32, x3: f32, t: f32) -> f32 {
    let a = -0.75;
    let coeffs0 = cubic_convolution_2(t + 1.0, a);
    let coeffs1 = cubic_convolution_1(t, a);
    let coeffs2 = cubic_convolution_1(1.0 - t, a);
    let coeffs3 = cubic_convolution_2(2.0 - t, a);

    x0 * coeffs0 + x1 * coeffs1 + x2 * coeffs2 + x3 * coeffs3
}

pub fn reference_bicubic(
    input: &HostData,
    output_shape: &[usize],
    align_corners: bool,
    progress: Option<&Progress>,
) -> HostData {
    let mut data = vec![0.0f32; output_shape.iter().product()];
    let input_height_f = (input.shape[1] - 1) as f32;
    let input_width_f = (input.shape[2] - 1) as f32;

    for_each_output_coord(output_shape, |linear, out_coord| {
        let b = out_coord[0];
        let y_out = out_coord[1];
        let x_out = out_coord[2];
        let c = out_coord[3];

        let frac_y = if align_corners {
            let output_height = (output_shape[1] - 1).max(1) as f32;
            (y_out as f32 * input_height_f) / output_height
        } else {
            let in_size = input.shape[1] as f32;
            let out_size = output_shape[1] as f32;
            (y_out as f32 + 0.5) * (in_size / out_size) - 0.5
        };

        let y_in_f = frac_y.floor();
        let yw = frac_y - y_in_f;

        let y0 = (y_in_f - 1.0).clamp(0.0, input_height_f) as usize;
        let y1 = (y_in_f).clamp(0.0, input_height_f) as usize;
        let y2 = (y_in_f + 1.0).clamp(0.0, input_height_f) as usize;
        let y3 = (y_in_f + 2.0).clamp(0.0, input_height_f) as usize;

        let frac_x = if align_corners {
            let output_width = (output_shape[2] - 1).max(1) as f32;
            (x_out as f32 * input_width_f) / output_width
        } else {
            let in_size = input.shape[2] as f32;
            let out_size = output_shape[2] as f32;
            (x_out as f32 + 0.5) * (in_size / out_size) - 0.5
        };

        let x_in_f = frac_x.floor();
        let xw = frac_x - x_in_f;

        let x0 = (x_in_f - 1.0).clamp(0.0, input_width_f) as usize;
        let x1 = (x_in_f).clamp(0.0, input_width_f) as usize;
        let x2 = (x_in_f + 1.0).clamp(0.0, input_width_f) as usize;
        let x3 = (x_in_f + 2.0).clamp(0.0, input_width_f) as usize;

        let mut row_coeffs = [0.0f32; 4];
        let y_coords = [y0, y1, y2, y3];
        let x_coords = [x0, x1, x2, x3];

        for i in 0..4 {
            let v0 = input.get_f32(&[b, y_coords[i], x_coords[0], c]);
            let v1 = input.get_f32(&[b, y_coords[i], x_coords[1], c]);
            let v2 = input.get_f32(&[b, y_coords[i], x_coords[2], c]);
            let v3 = input.get_f32(&[b, y_coords[i], x_coords[3], c]);
            row_coeffs[i] = cubic_interp_1d(v0, v1, v2, v3, xw);
        }

        let value = cubic_interp_1d(
            row_coeffs[0],
            row_coeffs[1],
            row_coeffs[2],
            row_coeffs[3],
            yw,
        );

        data[linear] = value;

        if let Some(p) = progress {
            p.bump();
        }
    });

    HostData {
        data: HostDataVec::F32(data),
        shape: Shape::from(output_shape.to_vec()),
        strides: contiguous_strides(output_shape),
    }
}