use core::f32::consts::{FRAC_1_SQRT_2, PI, SQRT_2};
use super::common::pack_signed;
use crate::bit_writer::BitWriter;
use crate::entropy_coding::encode::{
build_entropy_code_ans_with_options, write_entropy_code_ans, write_tokens_ans,
};
use crate::entropy_coding::token::Token;
use crate::error::Result;
#[derive(Clone, Copy, Debug, Default)]
pub struct SplinePoint {
pub x: f32,
pub y: f32,
}
impl SplinePoint {
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
fn abs(&self) -> f32 {
self.x.hypot(self.y)
}
}
impl core::ops::Add for SplinePoint {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl core::ops::Sub for SplinePoint {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl core::ops::Mul<f32> for SplinePoint {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl core::ops::Div<f32> for SplinePoint {
type Output = Self;
fn div(self, rhs: f32) -> Self {
let inv = 1.0 / rhs;
Self {
x: self.x * inv,
y: self.y * inv,
}
}
}
#[derive(Clone, Debug)]
pub struct Spline {
pub control_points: Vec<SplinePoint>,
pub color_dct: [[f32; 32]; 3],
pub sigma_dct: [f32; 32],
}
struct QuantizedSpline {
control_points: Vec<(i64, i64)>,
color_dct: [[i32; 32]; 3],
sigma_dct: [i32; 32],
}
#[derive(Clone, Copy, Debug, Default)]
struct SplineSegment {
center_x: f32,
center_y: f32,
maximum_distance: f32,
inv_sigma: f32,
sigma_over_4_times_intensity: f32,
color: [f32; 3],
}
pub(crate) struct SplinesData {
quantization_adjustment: i32,
splines: Vec<Spline>,
quantized: Vec<QuantizedSpline>,
segments: Vec<SplineSegment>,
segment_indices: Vec<usize>,
segment_y_start: Vec<usize>,
}
const CHANNEL_WEIGHT: [f32; 4] = [0.0042, 0.075, 0.07, 0.3333];
const NUM_SPLINE_CONTEXTS: usize = 6;
const DESIRED_RENDERING_DISTANCE: f32 = 1.0;
const ONE_OVER_2S2: f32 = 0.353_553_38;
const DISTANCE_EXP: f32 = 3.0;
const NUM_POINTS_PER_SEGMENT: usize = 16;
#[allow(clippy::excessive_precision)]
#[inline]
fn fast_erf(x: f32) -> f32 {
let absx = x.abs();
let d1 = absx * 7.77394369e-02 + 2.05260015e-04;
let d2 = d1 * absx + 2.32120216e-01;
let d3 = d2 * absx + 2.77820801e-01;
let d4 = d3 * absx + 1.0;
let d5 = d4 * d4;
let inv = 1.0 / d5;
(-inv * inv + 1.0).copysign(x)
}
#[allow(clippy::excessive_precision)]
#[inline]
fn fast_cos(x: f32) -> f32 {
let pi2 = PI * 2.0;
let pi2_inv = 0.5 / PI;
let npi2 = (x * pi2_inv).floor() * pi2;
let xmodpi2 = x - npi2;
let x_pi = xmodpi2.min(pi2 - xmodpi2);
let above_pihalf = x_pi >= PI / 2.0;
let x_pihalf = if above_pihalf { PI - x_pi } else { x_pi };
let xs = x_pihalf * 0.25;
let x2 = xs * xs;
let x4 = x2 * x2;
let cosx_prescaling = x4 * 0.06960438 + (x2 * -0.84087373 + 1.68179268);
let cosx_scale1 = cosx_prescaling * cosx_prescaling - SQRT_2;
let cosx_scale2 = cosx_scale1 * cosx_scale1 - 1.0;
if above_pihalf {
-cosx_scale2
} else {
cosx_scale2
}
}
struct PrecomputedCosines([f32; 32]);
impl PrecomputedCosines {
#[inline]
fn new(t: f32) -> Self {
let tandhalf = t + 0.5;
Self(core::array::from_fn(|i| {
fast_cos(PI / 32.0 * i as f32 * tandhalf)
}))
}
}
#[inline]
fn continuous_idct(dct: &[f32; 32], precomputed: &PrecomputedCosines) -> f32 {
dct.iter()
.zip(precomputed.0.iter())
.map(|(&c, &cos)| c * cos)
.sum::<f32>()
* SQRT_2
}
fn draw_centripetal_catmull_rom(points: &[SplinePoint]) -> Vec<SplinePoint> {
if points.is_empty() {
return vec![];
}
if points.len() == 1 {
return vec![points[0]];
}
let first_extra = points[0] + (points[0] - points[1]);
let last_extra =
points[points.len() - 1] + (points[points.len() - 1] - points[points.len() - 2]);
let extended: Vec<SplinePoint> = core::iter::once(first_extra)
.chain(points.iter().copied())
.chain(core::iter::once(last_extra))
.collect();
let mut dists = Vec::with_capacity(extended.len());
for i in 0..extended.len() - 1 {
dists.push((extended[i + 1] - extended[i]).abs().sqrt());
}
let num_windows = extended.len() - 3; let mut result = Vec::with_capacity(num_windows * NUM_POINTS_PER_SEGMENT + 1);
for w in 0..num_windows {
let p = [
extended[w],
extended[w + 1],
extended[w + 2],
extended[w + 3],
];
let d = [dists[w], dists[w + 1], dists[w + 2]];
let mut t = [0.0f32; 4];
t[1] = t[0] + d[0];
t[2] = t[1] + d[1];
t[3] = t[2] + d[2];
result.push(p[1]);
for i in 1..NUM_POINTS_PER_SEGMENT {
let tt = d[0] + (i as f32 / NUM_POINTS_PER_SEGMENT as f32) * d[1];
let mut a = [SplinePoint::default(); 3];
for k in 0..3 {
a[k] = p[k] + (p[k + 1] - p[k]) * ((tt - t[k]) / d[k]);
}
let mut b = [SplinePoint::default(); 2];
for k in 0..2 {
b[k] = a[k] + (a[k + 1] - a[k]) * ((tt - t[k]) / (d[k] + d[k + 1]));
}
let point = b[0] + (b[1] - b[0]) * ((tt - t[1]) / d[1]);
result.push(point);
}
}
result.push(points[points.len() - 1]);
result
}
fn for_each_equally_spaced_point(
points: &[SplinePoint],
desired_distance: f32,
) -> Vec<(SplinePoint, f32)> {
if points.is_empty() {
return vec![];
}
let mut result = Vec::new();
result.push((points[0], desired_distance));
if points.len() == 1 {
return result;
}
let mut accumulated_distance = 0.0f32;
for index in 0..points.len() - 1 {
let mut current = points[index];
let next = points[index + 1];
let segment = next - current;
let segment_length = segment.abs();
if segment_length < 1e-10 {
continue;
}
let unit_step = segment / segment_length;
if accumulated_distance + segment_length >= desired_distance {
current = current + unit_step * (desired_distance - accumulated_distance);
result.push((current, desired_distance));
accumulated_distance -= desired_distance;
}
accumulated_distance += segment_length;
while accumulated_distance >= desired_distance {
current = current + unit_step * desired_distance;
result.push((current, desired_distance));
accumulated_distance -= desired_distance;
}
}
result.push((points[points.len() - 1], accumulated_distance));
result
}
fn inv_adjusted_quant(adjustment: i32) -> f32 {
if adjustment >= 0 {
1.0 / (1.0 + 0.125 * adjustment as f32)
} else {
1.0 - 0.125 * adjustment as f32
}
}
fn adjusted_quant(adjustment: i32) -> f32 {
if adjustment >= 0 {
1.0 + 0.125 * adjustment as f32
} else {
1.0 / (1.0 - 0.125 * adjustment as f32)
}
}
impl QuantizedSpline {
fn from_spline(
spline: &Spline,
quantization_adjustment: i32,
y_to_x: f32,
y_to_b: f32,
) -> Self {
let quant = adjusted_quant(quantization_adjustment);
let mut control_points = Vec::new();
if spline.control_points.len() > 1 {
let pts = &spline.control_points;
let mut prev_delta_x = 0i64;
let mut prev_delta_y = 0i64;
let mut prev_x = pts[0].x.round() as i64;
let mut prev_y = pts[0].y.round() as i64;
for p in pts.iter().skip(1) {
let cur_x = p.x.round() as i64;
let cur_y = p.y.round() as i64;
let delta_x = cur_x - prev_x;
let delta_y = cur_y - prev_y;
let dd_x = delta_x - prev_delta_x;
let dd_y = delta_y - prev_delta_y;
control_points.push((dd_x, dd_y));
prev_delta_x = delta_x;
prev_delta_y = delta_y;
prev_x = cur_x;
prev_y = cur_y;
}
}
let mut quantized_color = [[0i32; 32]; 3];
for (i, qc) in quantized_color[1].iter_mut().enumerate() {
let dct_factor = if i == 0 { SQRT_2 } else { 1.0 };
*qc = (spline.color_dct[1][i] * dct_factor * quant / CHANNEL_WEIGHT[1]).round() as i32;
}
let inv_quant = inv_adjusted_quant(quantization_adjustment);
let mut restored_y = [0.0f32; 32];
for (i, ry) in restored_y.iter_mut().enumerate() {
let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
*ry = quantized_color[1][i] as f32 * inv_dct_factor * CHANNEL_WEIGHT[1] * inv_quant;
}
for c in [0, 2] {
let cfl_factor = if c == 0 { y_to_x } else { y_to_b };
for (i, qc) in quantized_color[c].iter_mut().enumerate() {
let dct_factor = if i == 0 { SQRT_2 } else { 1.0 };
let decorrelated = spline.color_dct[c][i] - cfl_factor * restored_y[i];
*qc = (decorrelated * dct_factor * quant / CHANNEL_WEIGHT[c]).round() as i32;
}
}
let mut quantized_sigma = [0i32; 32];
for (i, qs) in quantized_sigma.iter_mut().enumerate() {
let dct_factor = if i == 0 { SQRT_2 } else { 1.0 };
*qs = (spline.sigma_dct[i] * dct_factor * quant / CHANNEL_WEIGHT[3]).round() as i32;
}
Self {
control_points,
color_dct: quantized_color,
sigma_dct: quantized_sigma,
}
}
fn dequantize(
&self,
starting_point: SplinePoint,
quantization_adjustment: i32,
y_to_x: f32,
y_to_b: f32,
) -> DequantizedSpline {
let inv_quant = inv_adjusted_quant(quantization_adjustment);
let mut control_points = Vec::with_capacity(self.control_points.len() + 1);
let sp_x = starting_point.x.round() as i64;
let sp_y = starting_point.y.round() as i64;
control_points.push(SplinePoint::new(sp_x as f32, sp_y as f32));
let mut cur_x = sp_x;
let mut cur_y = sp_y;
let mut delta_x = 0i64;
let mut delta_y = 0i64;
for &(dd_x, dd_y) in &self.control_points {
delta_x += dd_x;
delta_y += dd_y;
cur_x += delta_x;
cur_y += delta_y;
control_points.push(SplinePoint::new(cur_x as f32, cur_y as f32));
}
let mut color_dct = [[0.0f32; 32]; 3];
for (c, (out_ch, in_ch)) in color_dct.iter_mut().zip(self.color_dct.iter()).enumerate() {
for (i, (out, &inp)) in out_ch.iter_mut().zip(in_ch.iter()).enumerate() {
let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
*out = inp as f32 * inv_dct_factor * CHANNEL_WEIGHT[c] * inv_quant;
}
}
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
color_dct[0][i] += y_to_x * color_dct[1][i];
color_dct[2][i] += y_to_b * color_dct[1][i];
}
let mut sigma_dct = [0.0f32; 32];
for (i, (out, &inp)) in sigma_dct.iter_mut().zip(self.sigma_dct.iter()).enumerate() {
let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
*out = inp as f32 * inv_dct_factor * CHANNEL_WEIGHT[3] * inv_quant;
}
DequantizedSpline {
control_points,
color_dct,
sigma_dct,
}
}
}
struct DequantizedSpline {
control_points: Vec<SplinePoint>,
color_dct: [[f32; 32]; 3],
sigma_dct: [f32; 32],
}
fn make_segment(
center: &SplinePoint,
intensity: f32,
color: [f32; 3],
sigma: f32,
) -> Option<SplineSegment> {
if sigma.is_infinite() || sigma == 0.0 || (1.0 / sigma).is_infinite() || intensity.is_infinite()
{
return None;
}
let max_color = [0.01, color[0].abs(), color[1].abs(), color[2].abs()]
.iter()
.copied()
.map(|c| (c * intensity).abs())
.max_by(|a, b| a.total_cmp(b))
.unwrap();
let max_distance =
(-2.0 * sigma * sigma * (0.1f32.ln() * DISTANCE_EXP - max_color.ln())).sqrt();
if max_distance.is_nan() || max_distance <= 0.0 {
return None;
}
Some(SplineSegment {
center_x: center.x,
center_y: center.y,
color,
inv_sigma: 1.0 / sigma,
sigma_over_4_times_intensity: 0.25 * sigma * intensity,
maximum_distance: max_distance,
})
}
fn generate_segments(spline: &DequantizedSpline) -> Vec<SplineSegment> {
let intermediate = draw_centripetal_catmull_rom(&spline.control_points);
let points_to_draw = for_each_equally_spaced_point(&intermediate, DESIRED_RENDERING_DISTANCE);
if points_to_draw.len() < 2 {
return vec![];
}
let length = (points_to_draw.len() as isize - 2) as f32 * DESIRED_RENDERING_DISTANCE
+ points_to_draw[points_to_draw.len() - 1].1;
if length <= 0.0 {
return vec![];
}
let inv_length = 1.0 / length;
let mut segments = Vec::new();
for (point_index, (point, multiplier)) in points_to_draw.iter().enumerate() {
let progress = (point_index as f32 * DESIRED_RENDERING_DISTANCE * inv_length).min(1.0);
let t = 31.0 * progress;
let precomputed = PrecomputedCosines::new(t);
let mut color = [0.0f32; 3];
for (c, coeffs) in spline.color_dct.iter().enumerate() {
color[c] = continuous_idct(coeffs, &precomputed);
}
let sigma = continuous_idct(&spline.sigma_dct, &precomputed);
if let Some(seg) = make_segment(point, *multiplier, color, sigma) {
segments.push(seg);
}
}
segments
}
#[inline]
fn apply_segment_at(
planes: &mut [Vec<f32>; 3],
stride: usize,
x: usize,
y: usize,
segment: &SplineSegment,
add: bool,
) {
let dx = x as f32 - segment.center_x;
let dy = y as f32 - segment.center_y;
let distance = (dx * dx + dy * dy).sqrt();
let one_dim = fast_erf((distance * 0.5 + ONE_OVER_2S2) * segment.inv_sigma)
- fast_erf((distance * 0.5 - ONE_OVER_2S2) * segment.inv_sigma);
let local_intensity = segment.sigma_over_4_times_intensity * one_dim * one_dim;
let idx = y * stride + x;
let sign = if add { 1.0 } else { -1.0 };
for (plane, &color) in planes.iter_mut().zip(segment.color.iter()) {
plane[idx] += sign * color * local_intensity;
}
}
fn apply_splines(
planes: &mut [Vec<f32>; 3],
stride: usize,
width: usize,
height: usize,
data: &SplinesData,
add: bool,
) {
for y in 0..height {
let first = data.segment_y_start[y];
let last = data.segment_y_start[y + 1];
for seg_idx_pos in first..last {
let segment = &data.segments[data.segment_indices[seg_idx_pos]];
let x0 = (segment.center_x - segment.maximum_distance)
.round()
.max(0.0) as usize;
let x1 = width.min((segment.center_x + segment.maximum_distance).round() as usize + 1);
for x in x0..x1 {
apply_segment_at(planes, stride, x, y, segment, add);
}
}
}
}
pub(crate) fn subtract_splines(
planes: &mut [Vec<f32>; 3],
stride: usize,
width: usize,
height: usize,
data: &SplinesData,
) {
apply_splines(planes, stride, width, height, data, false);
}
#[allow(dead_code)]
pub(crate) fn add_splines(
planes: &mut [Vec<f32>; 3],
stride: usize,
width: usize,
height: usize,
data: &SplinesData,
) {
apply_splines(planes, stride, width, height, data, true);
}
impl SplinesData {
pub(crate) fn from_splines(
splines: Vec<Spline>,
quantization_adjustment: i32,
y_to_x: f32,
y_to_b: f32,
_image_width: usize,
image_height: usize,
) -> Self {
let mut quantized = Vec::with_capacity(splines.len());
let mut all_segments: Vec<SplineSegment> = Vec::new();
let mut segments_by_y: Vec<(usize, usize)> = Vec::new();
for spline in &splines {
let qs = QuantizedSpline::from_spline(spline, quantization_adjustment, y_to_x, y_to_b);
let starting_point = spline.control_points[0];
let dqs = qs.dequantize(starting_point, quantization_adjustment, y_to_x, y_to_b);
let segs = generate_segments(&dqs);
let base_idx = all_segments.len();
for (i, seg) in segs.iter().enumerate() {
let seg_idx = base_idx + i;
let y0 = 0i64.max((seg.center_y - seg.maximum_distance).round() as i64);
let y1 = (image_height as i64)
.min((seg.center_y + seg.maximum_distance).round() as i64 + 1);
for y in y0..y1 {
segments_by_y.push((y as usize, seg_idx));
}
}
all_segments.extend(segs);
quantized.push(qs);
}
segments_by_y.sort_by_key(|&(y, _)| y);
let mut segment_indices = Vec::with_capacity(segments_by_y.len());
let mut segment_y_start = vec![0usize; image_height + 1];
for &(y, idx) in &segments_by_y {
segment_indices.push(idx);
if y < image_height {
segment_y_start[y + 1] += 1;
}
}
for y in 0..image_height {
segment_y_start[y + 1] += segment_y_start[y];
}
Self {
quantization_adjustment,
splines,
quantized,
segments: all_segments,
segment_indices,
segment_y_start,
}
}
}
pub(crate) fn encode_splines_section(data: &SplinesData, writer: &mut BitWriter) -> Result<()> {
let mut tokens = Vec::new();
let num_splines = data.splines.len();
tokens.push(Token::new(2, (num_splines - 1) as u32));
let mut last_x = 0i64;
let mut last_y = 0i64;
for (i, spline) in data.splines.iter().enumerate() {
let sp = spline.control_points[0];
let x = sp.x.round() as i64;
let y = sp.y.round() as i64;
if i == 0 {
tokens.push(Token::new(1, x as u32));
tokens.push(Token::new(1, y as u32));
} else {
let dx = x - last_x;
let dy = y - last_y;
tokens.push(Token::new(1, pack_signed(dx as i32)));
tokens.push(Token::new(1, pack_signed(dy as i32)));
}
last_x = x;
last_y = y;
}
tokens.push(Token::new(0, pack_signed(data.quantization_adjustment)));
for qs in &data.quantized {
tokens.push(Token::new(3, qs.control_points.len() as u32));
for &(dd_x, dd_y) in &qs.control_points {
tokens.push(Token::new(4, pack_signed(dd_x as i32)));
tokens.push(Token::new(4, pack_signed(dd_y as i32)));
}
for channel in &qs.color_dct {
for &coeff in channel {
tokens.push(Token::new(5, pack_signed(coeff)));
}
}
for &coeff in &qs.sigma_dct {
tokens.push(Token::new(5, pack_signed(coeff)));
}
}
writer.write(1, 0)?;
let code =
build_entropy_code_ans_with_options(&tokens, NUM_SPLINE_CONTEXTS, false, true, None, None);
write_entropy_code_ans(&code, writer)?;
write_tokens_ans(&tokens, &code, None, writer)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fast_erf_accuracy() {
let golden = [
(0.0, 0.0),
(0.1, 0.112_462_92),
(0.2, 0.222_702_6),
(0.5, 0.520_499_9),
(1.0, 0.842_700_8),
(1.5, 0.966_105_16),
(2.0, 0.995_322_3),
(2.5, 0.999_593),
(3.0, 0.999_977_9),
];
for (x, expected) in golden {
let got = fast_erf(x);
assert!(
(got - expected).abs() < 6e-4,
"fast_erf({x}) = {got}, expected {expected}"
);
let got_neg = fast_erf(-x);
assert!(
(got_neg - (-expected)).abs() < 6e-4,
"fast_erf(-{x}) = {got_neg}, expected {}",
-expected
);
}
}
#[test]
fn test_fast_cos_accuracy() {
for i in 0..100 {
let x = i as f32 / 100.0 * (5.0 * PI) - (2.5 * PI);
let got = fast_cos(x);
let expected = x.cos();
assert!(
(got - expected).abs() < 1e-4,
"fast_cos({x}) = {got}, expected {expected}"
);
}
}
#[test]
fn test_continuous_idct_values() {
let mut dct = [0.0f32; 32];
dct[0] = 1.0;
for t_idx in 0..32 {
let t = t_idx as f32;
let pc = PrecomputedCosines::new(t);
let val = continuous_idct(&dct, &pc);
assert!(
(val - SQRT_2).abs() < 0.01,
"DC-only IDCT at t={t} = {val}, expected ~{SQRT_2}"
);
}
}
#[test]
fn test_catmull_rom_basic() {
let points = vec![SplinePoint::new(0.0, 0.0), SplinePoint::new(10.0, 0.0)];
let interpolated = draw_centripetal_catmull_rom(&points);
assert!(interpolated.len() > 2, "should produce intermediate points");
assert!((interpolated[0].x - 0.0).abs() < 0.01);
assert!((interpolated[0].y - 0.0).abs() < 0.01);
let last = interpolated[interpolated.len() - 1];
assert!((last.x - 10.0).abs() < 0.01);
assert!((last.y - 0.0).abs() < 0.01);
}
#[test]
fn test_quantize_roundtrip() {
let spline = Spline {
control_points: vec![SplinePoint::new(10.0, 10.0), SplinePoint::new(50.0, 50.0)],
color_dct: {
let mut dct = [[0.0f32; 32]; 3];
dct[1][0] = 0.5; dct[0][0] = 0.1; dct[2][0] = 0.2; dct
},
sigma_dct: {
let mut s = [0.0f32; 32];
s[0] = 2.0;
s
},
};
let adj = 0;
let y_to_x = 0.0;
let y_to_b = 1.13;
let qs = QuantizedSpline::from_spline(&spline, adj, y_to_x, y_to_b);
let dqs = qs.dequantize(spline.control_points[0], adj, y_to_x, y_to_b);
assert_eq!(dqs.control_points.len(), 2);
assert!((dqs.control_points[0].x - 10.0).abs() < 1.0);
assert!((dqs.control_points[1].x - 50.0).abs() < 1.0);
assert!(
(dqs.sigma_dct[0] - spline.sigma_dct[0]).abs() < 0.5,
"sigma DC roundtrip: got {}, expected {}",
dqs.sigma_dct[0],
spline.sigma_dct[0]
);
}
#[test]
fn test_double_delta_encoding() {
let spline = Spline {
control_points: vec![
SplinePoint::new(0.0, 0.0),
SplinePoint::new(10.0, 0.0),
SplinePoint::new(20.0, 5.0),
SplinePoint::new(30.0, 15.0),
],
color_dct: [[0.0; 32]; 3],
sigma_dct: {
let mut s = [0.0; 32];
s[0] = 1.0;
s
},
};
let qs = QuantizedSpline::from_spline(&spline, 0, 0.0, 0.0);
assert_eq!(qs.control_points.len(), 3); assert_eq!(qs.control_points[0], (10, 0));
assert_eq!(qs.control_points[1], (0, 5));
assert_eq!(qs.control_points[2], (0, 5));
}
#[test]
fn test_splines_data_construction() {
let spline = Spline {
control_points: vec![SplinePoint::new(10.0, 10.0), SplinePoint::new(50.0, 50.0)],
color_dct: {
let mut dct = [[0.0f32; 32]; 3];
dct[1][0] = 0.5;
dct
},
sigma_dct: {
let mut s = [0.0f32; 32];
s[0] = 3.0;
s
},
};
let data = SplinesData::from_splines(vec![spline], 0, 0.0, 1.13, 64, 64);
assert_eq!(data.splines.len(), 1);
assert_eq!(data.quantized.len(), 1);
assert!(!data.segments.is_empty(), "should have rendered segments");
assert_eq!(
data.segment_y_start.len(),
65,
"y_start should have height+1 entries"
);
}
}