Skip to main content

zenjxl_decoder/features/
spline.rs

1// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6use std::{
7    f32::consts::{FRAC_1_SQRT_2, PI, SQRT_2},
8    iter::{self, zip},
9    ops,
10};
11
12use crate::{
13    bit_reader::BitReader,
14    entropy_coding::decode::{Histograms, SymbolReader, unpack_signed},
15    error::{Error, Result},
16    frame::color_correlation_map::ColorCorrelationParams,
17    util::{
18        CeilLog2, MemoryTracker, NewWithCapacity, fast_cos, fast_erff_simd, tracing_wrappers::*,
19    },
20};
21use jxl_simd::{F32SimdVec, ScalarDescriptor, SimdDescriptor, simd_function};
22const MAX_NUM_CONTROL_POINTS: u32 = 1 << 20;
23const MAX_NUM_CONTROL_POINTS_PER_PIXEL_RATIO: u32 = 2;
24const DELTA_LIMIT: i64 = 1 << 30;
25const SPLINE_POS_LIMIT: isize = 1 << 23;
26
27const QUANTIZATION_ADJUSTMENT_CONTEXT: usize = 0;
28const STARTING_POSITION_CONTEXT: usize = 1;
29const NUM_SPLINES_CONTEXT: usize = 2;
30const NUM_CONTROL_POINTS_CONTEXT: usize = 3;
31const CONTROL_POINTS_CONTEXT: usize = 4;
32const DCT_CONTEXT: usize = 5;
33const NUM_SPLINE_CONTEXTS: usize = 6;
34const DESIRED_RENDERING_DISTANCE: f32 = 1.0;
35
36#[derive(Debug, Clone, Copy, Default)]
37pub struct Point {
38    pub x: f32,
39    pub y: f32,
40}
41
42impl Point {
43    fn new(x: f32, y: f32) -> Self {
44        Point { x, y }
45    }
46    fn abs(&self) -> f32 {
47        self.x.hypot(self.y)
48    }
49}
50
51impl PartialEq for Point {
52    fn eq(&self, other: &Self) -> bool {
53        (self.x - other.x).abs() < 1e-3 && (self.y - other.y).abs() < 1e-3
54    }
55}
56
57impl ops::Add<Point> for Point {
58    type Output = Point;
59    fn add(self, rhs: Point) -> Point {
60        Point {
61            x: self.x + rhs.x,
62            y: self.y + rhs.y,
63        }
64    }
65}
66
67impl ops::Sub<Point> for Point {
68    type Output = Point;
69    fn sub(self, rhs: Point) -> Point {
70        Point {
71            x: self.x - rhs.x,
72            y: self.y - rhs.y,
73        }
74    }
75}
76
77impl ops::Mul<f32> for Point {
78    type Output = Point;
79    fn mul(self, rhs: f32) -> Point {
80        Point {
81            x: self.x * rhs,
82            y: self.y * rhs,
83        }
84    }
85}
86
87impl ops::Div<f32> for Point {
88    type Output = Point;
89    fn div(self, rhs: f32) -> Point {
90        let inv = 1.0 / rhs;
91        Point {
92            x: self.x * inv,
93            y: self.y * inv,
94        }
95    }
96}
97
98#[derive(Default, Debug)]
99pub struct Spline {
100    control_points: Vec<Point>,
101    // X, Y, B.
102    color_dct: [Dct32; 3],
103    // Splines are drawn by normalized Gaussian splatting. This controls the
104    // Gaussian's parameter along the spline.
105    sigma_dct: Dct32,
106    // The estimated area in pixels covered by the spline.
107    estimated_area_reached: u64,
108}
109
110impl Spline {
111    pub fn validate_adjacent_point_coincidence(&self) -> Result<()> {
112        if let Some(((index, p0), p1)) = zip(
113            self.control_points
114                .iter()
115                .take(self.control_points.len() - 1)
116                .enumerate(),
117            self.control_points.iter().skip(1),
118        )
119        .find(|((_, p0), p1)| **p0 == **p1)
120        {
121            return Err(Error::SplineAdjacentCoincidingControlPoints(
122                index,
123                *p0,
124                index + 1,
125                *p1,
126            ));
127        }
128        Ok(())
129    }
130}
131
132#[derive(Debug, Default, Clone)]
133pub struct QuantizedSpline {
134    // Double delta-encoded.
135    pub control_points: Vec<(i64, i64)>,
136    pub color_dct: [[i32; 32]; 3],
137    pub sigma_dct: [i32; 32],
138}
139
140fn inv_adjusted_quant(adjustment: i32) -> f32 {
141    if adjustment >= 0 {
142        1.0 / (1.0 + 0.125 * adjustment as f32)
143    } else {
144        1.0 - 0.125 * adjustment as f32
145    }
146}
147
148fn validate_spline_point_pos<T: num_traits::ToPrimitive>(x: T, y: T) -> Result<()> {
149    let xi = x.to_i32().unwrap();
150    let yi = y.to_i32().unwrap();
151    let ok_range = -(1i32 << 23)..(1i32 << 23);
152    if !ok_range.contains(&xi) {
153        return Err(Error::SplinesPointOutOfRange(
154            Point {
155                x: xi as f32,
156                y: yi as f32,
157            },
158            xi,
159            ok_range,
160        ));
161    }
162    if !ok_range.contains(&yi) {
163        return Err(Error::SplinesPointOutOfRange(
164            Point {
165                x: xi as f32,
166                y: yi as f32,
167            },
168            yi,
169            ok_range,
170        ));
171    }
172    Ok(())
173}
174
175const CHANNEL_WEIGHT: [f32; 4] = [0.0042, 0.075, 0.07, 0.3333];
176
177fn area_limit(image_size: u64) -> u64 {
178    // Use saturating arithmetic to prevent overflow
179    1024u64
180        .saturating_mul(image_size)
181        .saturating_add(1u64 << 32)
182        .min(1u64 << 42)
183}
184
185impl QuantizedSpline {
186    #[instrument(level = "debug", skip(br), ret, err)]
187    pub fn read(
188        br: &mut BitReader,
189        splines_histograms: &Histograms,
190        splines_reader: &mut SymbolReader,
191        max_control_points: u32,
192        total_num_control_points: &mut u32,
193    ) -> Result<QuantizedSpline> {
194        let num_control_points =
195            splines_reader.read_unsigned(splines_histograms, br, NUM_CONTROL_POINTS_CONTEXT);
196        *total_num_control_points += num_control_points;
197        if *total_num_control_points > max_control_points {
198            return Err(Error::SplinesTooManyControlPoints(
199                *total_num_control_points,
200                max_control_points,
201            ));
202        }
203        let mut control_points = Vec::new_with_capacity(num_control_points as usize)?;
204        for _ in 0..num_control_points {
205            let x =
206                splines_reader.read_signed(splines_histograms, br, CONTROL_POINTS_CONTEXT) as i64;
207            let y =
208                splines_reader.read_signed(splines_histograms, br, CONTROL_POINTS_CONTEXT) as i64;
209            control_points.push((x, y));
210            // Add check that double deltas are not outrageous (not in spec).
211            let max_delta_delta = x.abs().max(y.abs());
212            if max_delta_delta >= DELTA_LIMIT {
213                return Err(Error::SplinesDeltaLimit(max_delta_delta, DELTA_LIMIT));
214            }
215        }
216        // Decode DCTs and populate the QuantizedSpline struct
217        let mut color_dct = [[0; 32]; 3];
218        let mut sigma_dct = [0; 32];
219
220        let mut decode_dct = |dct: &mut [i32; 32]| -> Result<()> {
221            for value in dct.iter_mut() {
222                *value = splines_reader.read_signed(splines_histograms, br, DCT_CONTEXT);
223            }
224            Ok(())
225        };
226
227        for channel in &mut color_dct {
228            decode_dct(channel)?;
229        }
230        decode_dct(&mut sigma_dct)?;
231
232        Ok(QuantizedSpline {
233            control_points,
234            color_dct,
235            sigma_dct,
236        })
237    }
238
239    pub fn dequantize(
240        &self,
241        starting_point: &Point,
242        quantization_adjustment: i32,
243        y_to_x: f32,
244        y_to_b: f32,
245        image_size: u64,
246    ) -> Result<Spline> {
247        let area_limit = area_limit(image_size);
248
249        let mut result = Spline {
250            control_points: Vec::new_with_capacity(self.control_points.len() + 1)?,
251            ..Default::default()
252        };
253
254        let px = starting_point.x.round();
255        let py = starting_point.y.round();
256        validate_spline_point_pos(px, py)?;
257
258        let mut current_x = px as i32;
259        let mut current_y = py as i32;
260        result
261            .control_points
262            .push(Point::new(current_x as f32, current_y as f32));
263
264        let mut current_delta_x = 0i32;
265        let mut current_delta_y = 0i32;
266        let mut manhattan_distance = 0u64;
267
268        for &(dx, dy) in &self.control_points {
269            current_delta_x += dx as i32;
270            current_delta_y += dy as i32;
271            validate_spline_point_pos(current_delta_x, current_delta_y)?;
272
273            manhattan_distance +=
274                current_delta_x.unsigned_abs() as u64 + current_delta_y.unsigned_abs() as u64;
275
276            if manhattan_distance > area_limit {
277                return Err(Error::SplinesDistanceTooLarge(
278                    manhattan_distance,
279                    area_limit,
280                ));
281            }
282
283            current_x += current_delta_x;
284            current_y += current_delta_y;
285            validate_spline_point_pos(current_x, current_y)?;
286
287            result
288                .control_points
289                .push(Point::new(current_x as f32, current_y as f32));
290        }
291
292        let inv_quant = inv_adjusted_quant(quantization_adjustment);
293
294        for (c, weight) in CHANNEL_WEIGHT.iter().enumerate().take(3) {
295            for i in 0..32 {
296                let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
297                result.color_dct[c].0[i] =
298                    self.color_dct[c][i] as f32 * inv_dct_factor * weight * inv_quant;
299            }
300        }
301
302        for i in 0..32 {
303            result.color_dct[0].0[i] += y_to_x * result.color_dct[1].0[i];
304            result.color_dct[2].0[i] += y_to_b * result.color_dct[1].0[i];
305        }
306
307        let mut width_estimate = 0;
308        let mut color = [0u64; 3];
309
310        for (c, color_val) in color.iter_mut().enumerate() {
311            for i in 0..32 {
312                *color_val += (inv_quant * self.color_dct[c][i].abs() as f32).ceil() as u64;
313            }
314        }
315
316        color[0] += y_to_x.abs().ceil() as u64 * color[1];
317        color[2] += y_to_b.abs().ceil() as u64 * color[1];
318
319        let max_color = color[0].max(color[1]).max(color[2]);
320        let logcolor = 1u64.max((1u64 + max_color).ceil_log2());
321
322        let weight_limit =
323            (((area_limit as f32 / logcolor as f32) / manhattan_distance.max(1) as f32).sqrt())
324                .ceil();
325
326        for i in 0..32 {
327            let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
328            result.sigma_dct.0[i] =
329                self.sigma_dct[i] as f32 * inv_dct_factor * CHANNEL_WEIGHT[3] * inv_quant;
330
331            let weight_f = (inv_quant * self.sigma_dct[i].abs() as f32).ceil();
332            let weight = weight_limit.min(weight_f.max(1.0)) as u64;
333            width_estimate += weight * weight * logcolor;
334        }
335
336        result.estimated_area_reached = width_estimate * manhattan_distance;
337
338        Ok(result)
339    }
340}
341
342#[derive(Debug, Clone, Copy, Default)]
343struct SplineSegment {
344    center_x: f32,
345    center_y: f32,
346    maximum_distance: f32,
347    inv_sigma: f32,
348    sigma_over_4_times_intensity: f32,
349    color: [f32; 3],
350}
351
352#[derive(Debug, Default, Clone)]
353pub struct Splines {
354    pub quantization_adjustment: i32,
355    pub splines: Vec<QuantizedSpline>,
356    pub starting_points: Vec<Point>,
357    segments: Vec<SplineSegment>,
358    segment_indices: Vec<usize>,
359    segment_y_start: Vec<u64>,
360}
361
362fn draw_centripetal_catmull_rom_spline(points: &[Point]) -> Result<Vec<Point>> {
363    if points.is_empty() {
364        return Ok(vec![]);
365    }
366    if points.len() == 1 {
367        return Ok(vec![points[0]]);
368    }
369    const NUM_POINTS: usize = 16;
370    // Create a view of points with one prepended and one appended point.
371    let extended_points = iter::once(points[0] + (points[0] - points[1]))
372        .chain(points.iter().cloned())
373        .chain(iter::once(
374            points[points.len() - 1] + (points[points.len() - 1] - points[points.len() - 2]),
375        ));
376    // Pair each point with the sqrt of the distance to the next point.
377    let points_and_deltas = extended_points
378        .chain(iter::once(Point::default()))
379        .scan(Point::default(), |previous, p| {
380            let result = Some((*previous, (p - *previous).abs().sqrt()));
381            *previous = p;
382            result
383        })
384        .skip(1);
385    // Window the points with a [Point; 4] window.
386    let windowed_points = points_and_deltas
387        .scan([(Point::default(), 0.0); 4], |window, p| {
388            (window[0], window[1], window[2], window[3]) =
389                (window[1], window[2], window[3], (p.0, p.1));
390            Some([window[0], window[1], window[2], window[3]])
391        })
392        .skip(3);
393    // Create the points necessary per window, and flatten the result.
394    let result = windowed_points
395        .flat_map(|p| {
396            let mut window_result = [Point::default(); NUM_POINTS];
397            window_result[0] = p[1].0;
398            let mut t = [0.0; 4];
399            for k in 0..3 {
400                // TODO(from libjxl): Restrict d[k] with reasonable limit and spec it.
401                t[k + 1] = t[k] + p[k].1;
402            }
403            for (i, window_point) in window_result.iter_mut().enumerate().skip(1) {
404                let tt = p[0].1 + ((i as f32) / (NUM_POINTS as f32)) * p[1].1;
405                let mut a = [Point::default(); 3];
406                for k in 0..3 {
407                    // TODO(from libjxl): Reciprocal multiplication would be faster.
408                    a[k] = p[k].0 + (p[k + 1].0 - p[k].0) * ((tt - t[k]) / p[k].1);
409                }
410                let mut b = [Point::default(); 2];
411                for k in 0..2 {
412                    b[k] = a[k] + (a[k + 1] - a[k]) * ((tt - t[k]) / (p[k].1 + p[k + 1].1));
413                }
414                *window_point = b[0] + (b[1] - b[0]) * ((tt - t[1]) / p[1].1);
415            }
416            window_result
417        })
418        .chain(iter::once(points[points.len() - 1]))
419        .collect();
420    Ok(result)
421}
422
423fn for_each_equally_spaced_point<F: FnMut(Point, f32)>(
424    points: &[Point],
425    desired_distance: f32,
426    mut f: F,
427) {
428    if points.is_empty() {
429        return;
430    }
431    let mut accumulated_distance = 0.0;
432    f(points[0], desired_distance);
433    if points.len() == 1 {
434        return;
435    }
436    for index in 0..(points.len() - 1) {
437        let mut current = points[index];
438        let next = points[index + 1];
439        let segment = next - current;
440        let segment_length = segment.abs();
441        let unit_step = segment / segment_length;
442        if accumulated_distance + segment_length >= desired_distance {
443            current = current + unit_step * (desired_distance - accumulated_distance);
444            f(current, desired_distance);
445            accumulated_distance -= desired_distance;
446        }
447        accumulated_distance += segment_length;
448        while accumulated_distance >= desired_distance {
449            current = current + unit_step * desired_distance;
450            f(current, desired_distance);
451            accumulated_distance -= desired_distance;
452        }
453    }
454    f(points[points.len() - 1], accumulated_distance);
455}
456
457/// Precomputed multipliers for DCT: PI / 32.0 * i for i in 0..32
458const DCT_MULTIPLIERS: [f32; 32] = [
459    PI / 32.0 * 0.0,
460    PI / 32.0 * 1.0,
461    PI / 32.0 * 2.0,
462    PI / 32.0 * 3.0,
463    PI / 32.0 * 4.0,
464    PI / 32.0 * 5.0,
465    PI / 32.0 * 6.0,
466    PI / 32.0 * 7.0,
467    PI / 32.0 * 8.0,
468    PI / 32.0 * 9.0,
469    PI / 32.0 * 10.0,
470    PI / 32.0 * 11.0,
471    PI / 32.0 * 12.0,
472    PI / 32.0 * 13.0,
473    PI / 32.0 * 14.0,
474    PI / 32.0 * 15.0,
475    PI / 32.0 * 16.0,
476    PI / 32.0 * 17.0,
477    PI / 32.0 * 18.0,
478    PI / 32.0 * 19.0,
479    PI / 32.0 * 20.0,
480    PI / 32.0 * 21.0,
481    PI / 32.0 * 22.0,
482    PI / 32.0 * 23.0,
483    PI / 32.0 * 24.0,
484    PI / 32.0 * 25.0,
485    PI / 32.0 * 26.0,
486    PI / 32.0 * 27.0,
487    PI / 32.0 * 28.0,
488    PI / 32.0 * 29.0,
489    PI / 32.0 * 30.0,
490    PI / 32.0 * 31.0,
491];
492
493/// Precomputed cosine values for DCT at a given t value.
494/// Computed once and reused for all 4 DCT evaluations (3 color channels + sigma).
495struct PrecomputedCosines([f32; 32]);
496
497impl PrecomputedCosines {
498    /// Precompute cosines for a given t value.
499    /// Call this once per point, then use with continuous_idct_fast for each DCT.
500    #[inline]
501    fn new(t: f32) -> Self {
502        let tandhalf = t + 0.5;
503        PrecomputedCosines(core::array::from_fn(|i| {
504            fast_cos(DCT_MULTIPLIERS[i] * tandhalf)
505        }))
506    }
507}
508
509#[derive(Default, Clone, Copy, Debug)]
510struct Dct32([f32; 32]);
511
512impl Dct32 {
513    /// Fast continuous IDCT using precomputed cosines.
514    /// This avoids recomputing 32 cosines for each of the 4 DCT calls per point.
515    #[inline]
516    fn continuous_idct_fast(&self, precomputed: &PrecomputedCosines) -> f32 {
517        // Compute dot product of coeffs and precomputed cosines
518        // Using iterator for auto-vectorization
519        zip(self.0, precomputed.0)
520            .map(|(coeff, cos)| coeff * cos)
521            .sum::<f32>()
522            * SQRT_2
523    }
524}
525
526#[inline(always)]
527fn draw_segment_inner<D: SimdDescriptor>(
528    d: D,
529    row: &mut [&mut [f32]],
530    row_pos: (usize, usize),
531    x_range: (usize, usize),
532    segment: &SplineSegment,
533) -> usize {
534    let (x_start, x_end) = x_range;
535    let (row_x0, y) = row_pos;
536    let len = D::F32Vec::LEN;
537    if x_start + len > x_end {
538        return x_start;
539    }
540
541    let inv_sigma = D::F32Vec::splat(d, segment.inv_sigma);
542    let half = D::F32Vec::splat(d, 0.5);
543    let one_over_2s2 = D::F32Vec::splat(d, 0.353_553_38);
544    let sigma_over_4_times_intensity = D::F32Vec::splat(d, segment.sigma_over_4_times_intensity);
545    let center_x = D::F32Vec::splat(d, segment.center_x);
546    let center_y = D::F32Vec::splat(d, segment.center_y);
547    let dy = D::F32Vec::splat(d, y as f32) - center_y;
548    let dy2 = dy * dy;
549
550    let mut x_base_arr = [0.0f32; 16];
551    for (i, val) in x_base_arr.iter_mut().enumerate() {
552        *val = i as f32;
553    }
554    let vx_base = D::F32Vec::load(d, &x_base_arr);
555
556    let start_offset = x_start - row_x0;
557    let end_offset = x_end - row_x0;
558
559    let [r0, r1, r2] = row else { unreachable!() };
560
561    let mut it0 = r0[start_offset..end_offset].chunks_exact_mut(len);
562    let mut it1 = r1[start_offset..end_offset].chunks_exact_mut(len);
563    let mut it2 = r2[start_offset..end_offset].chunks_exact_mut(len);
564
565    let cm0 = D::F32Vec::splat(d, segment.color[0]);
566    let cm1 = D::F32Vec::splat(d, segment.color[1]);
567    let cm2 = D::F32Vec::splat(d, segment.color[2]);
568
569    let num_chunks = (end_offset - start_offset) / len;
570    let mut x = x_start;
571    for _ in 0..num_chunks {
572        let vx = D::F32Vec::splat(d, x as f32) + vx_base;
573        let dx = vx - center_x;
574        let sqd = dx.mul_add(dx, dy2);
575        let distance = sqd.sqrt();
576
577        let arg1 = distance.mul_add(half, one_over_2s2) * inv_sigma;
578        let arg2 = distance.mul_add(half, D::F32Vec::splat(d, -0.353_553_38)) * inv_sigma;
579        let one_dimensional_factor = fast_erff_simd(d, arg1) - fast_erff_simd(d, arg2);
580        let local_intensity =
581            sigma_over_4_times_intensity * one_dimensional_factor * one_dimensional_factor;
582
583        let c0 = it0.next().unwrap();
584        cm0.mul_add(local_intensity, D::F32Vec::load(d, c0))
585            .store(c0);
586        let c1 = it1.next().unwrap();
587        cm1.mul_add(local_intensity, D::F32Vec::load(d, c1))
588            .store(c1);
589        let c2 = it2.next().unwrap();
590        cm2.mul_add(local_intensity, D::F32Vec::load(d, c2))
591            .store(c2);
592
593        x += len;
594    }
595    x
596}
597
598simd_function!(
599    draw_segment_dispatch,
600    d: D,
601    fn draw_segment_simd(
602        row: &mut [&mut [f32]],
603        row_pos: (usize, usize),
604        xsize: usize,
605        segment: &SplineSegment,
606    ) {
607        let (x0, y) = row_pos;
608        let x1 = x0 + xsize;
609        let clamped_x0 = x0.max((segment.center_x - segment.maximum_distance).round() as usize);
610        let clamped_x1 = x1.min((segment.center_x + segment.maximum_distance).round() as usize + 1);
611
612        if clamped_x1 <= clamped_x0 {
613            return;
614        }
615
616        let x = clamped_x0;
617        let x = draw_segment_inner(d, row, (x0, y), (x, clamped_x1), segment);
618        let d = d.maybe_downgrade_256bit();
619        let x = draw_segment_inner(d, row, (x0, y), (x, clamped_x1), segment);
620        let d = d.maybe_downgrade_128bit();
621        let x = draw_segment_inner(d, row, (x0, y), (x, clamped_x1), segment);
622        draw_segment_inner(ScalarDescriptor, row, (x0, y), (x, clamped_x1), segment);
623    }
624);
625
626impl Splines {
627    #[cfg(test)]
628    pub fn create(
629        quantization_adjustment: i32,
630        splines: Vec<QuantizedSpline>,
631        starting_points: Vec<Point>,
632    ) -> Splines {
633        Splines {
634            quantization_adjustment,
635            splines,
636            starting_points,
637            segments: vec![],
638            segment_indices: vec![],
639            segment_y_start: vec![],
640        }
641    }
642    pub fn draw_segments(&self, row: &mut [&mut [f32]], row_pos: (usize, usize), xsize: usize) {
643        let first_segment_index_pos = self.segment_y_start[row_pos.1];
644        let last_segment_index_pos = self.segment_y_start[row_pos.1 + 1];
645        for segment_index_pos in first_segment_index_pos..last_segment_index_pos {
646            draw_segment_dispatch(
647                row,
648                row_pos,
649                xsize,
650                &self.segments[self.segment_indices[segment_index_pos as usize]],
651            );
652        }
653    }
654
655    fn add_segment(
656        &mut self,
657        center: &Point,
658        intensity: f32,
659        color: [f32; 3],
660        sigma: f32,
661        high_precision: bool,
662        segments_by_y: &mut Vec<(u64, usize)>,
663    ) {
664        if sigma.is_infinite()
665            || sigma == 0.0
666            || (1.0 / sigma).is_infinite()
667            || intensity.is_infinite()
668        {
669            return;
670        }
671        let distance_exp: f32 = if high_precision { 5.0 } else { 3.0 };
672        let max_color = [0.01, color[0], color[1], color[2]]
673            .iter()
674            .map(|chan| (chan * intensity).abs())
675            .max_by(|a, b| a.total_cmp(b))
676            .unwrap();
677        let max_distance =
678            (-2.0 * sigma * sigma * (0.1f32.ln() * distance_exp - max_color.ln())).sqrt();
679        let segment = SplineSegment {
680            center_x: center.x,
681            center_y: center.y,
682            color,
683            inv_sigma: 1.0 / sigma,
684            sigma_over_4_times_intensity: 0.25 * sigma * intensity,
685            maximum_distance: max_distance,
686        };
687        let y0 = (center.y - max_distance).round() as i64;
688        let y1 = (center.y + max_distance).round() as i64 + 1;
689        for y in 0.max(y0)..y1 {
690            segments_by_y.push((y as u64, self.segments.len()));
691        }
692        self.segments.push(segment);
693    }
694
695    fn add_segments_from_points(
696        &mut self,
697        spline: &Spline,
698        points_to_draw: &[(Point, f32)],
699        length: f32,
700        desired_distance: f32,
701        high_precision: bool,
702        segments_by_y: &mut Vec<(u64, usize)>,
703    ) {
704        let inv_length = 1.0 / length;
705        for (point_index, (point, multiplier)) in points_to_draw.iter().enumerate() {
706            let progress = (point_index as f32 * desired_distance * inv_length).min(1.0);
707            let t = (32.0 - 1.0) * progress;
708
709            // Precompute cosines once for this point (saves 3x cosine computations)
710            let precomputed = PrecomputedCosines::new(t);
711
712            // Use precomputed cosines for all 4 DCT evaluations
713            let mut color = [0.0; 3];
714            for (index, coeffs) in spline.color_dct.iter().enumerate() {
715                color[index] = coeffs.continuous_idct_fast(&precomputed);
716            }
717            let sigma = spline.sigma_dct.continuous_idct_fast(&precomputed);
718
719            self.add_segment(
720                point,
721                *multiplier,
722                color,
723                sigma,
724                high_precision,
725                segments_by_y,
726            );
727        }
728    }
729
730    pub fn initialize_draw_cache(
731        &mut self,
732        image_xsize: u64,
733        image_ysize: u64,
734        color_correlation_params: &ColorCorrelationParams,
735        high_precision: bool,
736    ) -> Result<()> {
737        let mut total_estimated_area_reached = 0u64;
738        let mut splines = Vec::new();
739        // Use saturating_mul to prevent overflow with malicious image dimensions
740        let image_area = image_xsize.saturating_mul(image_ysize);
741        let area_limit = area_limit(image_area);
742        for (index, qspline) in self.splines.iter().enumerate() {
743            let spline = qspline.dequantize(
744                &self.starting_points[index],
745                self.quantization_adjustment,
746                color_correlation_params.y_to_x_lf(),
747                color_correlation_params.y_to_b_lf(),
748                image_area,
749            )?;
750            total_estimated_area_reached += spline.estimated_area_reached;
751            if total_estimated_area_reached > area_limit {
752                return Err(Error::SplinesAreaTooLarge(
753                    total_estimated_area_reached,
754                    area_limit,
755                ));
756            }
757            spline.validate_adjacent_point_coincidence()?;
758            splines.push(spline);
759        }
760
761        if total_estimated_area_reached
762            > (8 * image_xsize * image_ysize + (1u64 << 25)).min(1u64 << 30)
763        {
764            warn!(
765                "Large total_estimated_area_reached, expect slower decoding:{}",
766                total_estimated_area_reached
767            );
768        }
769
770        let mut segments_by_y = Vec::new();
771
772        self.segments.clear();
773        for spline in splines {
774            let mut points_to_draw = Vec::<(Point, f32)>::new();
775            let intermediate_points = draw_centripetal_catmull_rom_spline(&spline.control_points)?;
776            for_each_equally_spaced_point(
777                &intermediate_points,
778                DESIRED_RENDERING_DISTANCE,
779                |p, d| points_to_draw.push((p, d)),
780            );
781            let length = (points_to_draw.len() as isize - 2) as f32 * DESIRED_RENDERING_DISTANCE
782                + points_to_draw[points_to_draw.len() - 1].1;
783            if length <= 0.0 {
784                continue;
785            }
786            self.add_segments_from_points(
787                &spline,
788                &points_to_draw,
789                length,
790                DESIRED_RENDERING_DISTANCE,
791                high_precision,
792                &mut segments_by_y,
793            );
794        }
795
796        // TODO(from libjxl): Consider linear sorting here.
797        segments_by_y.sort_by_key(|segment| segment.0);
798
799        self.segment_indices.clear();
800        self.segment_indices.try_reserve(segments_by_y.len())?;
801        self.segment_indices.resize(segments_by_y.len(), 0);
802
803        self.segment_y_start.clear();
804        self.segment_y_start.try_reserve(image_ysize as usize + 1)?;
805        self.segment_y_start.resize(image_ysize as usize + 1, 0);
806
807        for (i, segment) in segments_by_y.iter().enumerate() {
808            self.segment_indices[i] = segment.1;
809            let y = segment.0;
810            if y < image_ysize {
811                self.segment_y_start[y as usize + 1] += 1;
812            }
813        }
814        for y in 0..image_ysize {
815            self.segment_y_start[y as usize + 1] += self.segment_y_start[y as usize];
816        }
817        Ok(())
818    }
819
820    #[instrument(level = "debug", skip(br, memory_tracker), ret, err)]
821    pub fn read(
822        br: &mut BitReader,
823        num_pixels: u32,
824        max_spline_points: Option<u32>,
825        memory_tracker: &MemoryTracker,
826    ) -> Result<Splines> {
827        trace!(pos = br.total_bits_read());
828        let splines_histograms = Histograms::decode(NUM_SPLINE_CONTEXTS, br, true)?;
829        let mut splines_reader = SymbolReader::new(&splines_histograms, br, None)?;
830        let num_splines = splines_reader
831            .read_unsigned(&splines_histograms, br, NUM_SPLINES_CONTEXT)
832            .saturating_add(1);
833        // Use configured limit if set, otherwise use default 2^20
834        let hard_limit = max_spline_points.unwrap_or(MAX_NUM_CONTROL_POINTS);
835        let max_control_points =
836            hard_limit.min(num_pixels / MAX_NUM_CONTROL_POINTS_PER_PIXEL_RATIO);
837        if num_splines > max_control_points {
838            return Err(Error::SplinesTooMany(num_splines, max_control_points));
839        }
840
841        let mut starting_points = Vec::new();
842        let mut last_x = 0;
843        let mut last_y = 0;
844        for i in 0..num_splines {
845            let unsigned_x =
846                splines_reader.read_unsigned(&splines_histograms, br, STARTING_POSITION_CONTEXT);
847            let unsigned_y =
848                splines_reader.read_unsigned(&splines_histograms, br, STARTING_POSITION_CONTEXT);
849
850            let (x, y) = if i != 0 {
851                (
852                    unpack_signed(unsigned_x) as isize + last_x,
853                    unpack_signed(unsigned_y) as isize + last_y,
854                )
855            } else {
856                (unsigned_x as isize, unsigned_y as isize)
857            };
858            // It is not in spec, but reasonable limit to avoid overflows.
859            let max_coordinate = x.abs().max(y.abs());
860            if max_coordinate >= SPLINE_POS_LIMIT {
861                return Err(Error::SplinesCoordinatesLimit(
862                    max_coordinate,
863                    SPLINE_POS_LIMIT,
864                ));
865            }
866
867            starting_points.push(Point {
868                x: x as f32,
869                y: y as f32,
870            });
871
872            last_x = x;
873            last_y = y;
874        }
875
876        let quantization_adjustment =
877            splines_reader.read_signed(&splines_histograms, br, QUANTIZATION_ADJUSTMENT_CONTEXT);
878
879        // Check memory budget for worst-case control point allocations
880        memory_tracker
881            .check_alloc(max_control_points as u64 * std::mem::size_of::<Point>() as u64 * 2)?;
882        let mut splines = Vec::new();
883        let mut num_control_points = 0u32;
884        for _ in 0..num_splines {
885            splines.push(QuantizedSpline::read(
886                br,
887                &splines_histograms,
888                &mut splines_reader,
889                max_control_points,
890                &mut num_control_points,
891            )?);
892        }
893        splines_reader.check_final_state(&splines_histograms, br)?;
894        Ok(Splines {
895            quantization_adjustment,
896            splines,
897            starting_points,
898            ..Splines::default()
899        })
900    }
901}
902
903#[cfg(test)]
904#[allow(clippy::excessive_precision)]
905mod test_splines {
906    use std::{f32::consts::SQRT_2, iter::zip};
907    use test_log::test;
908
909    use crate::{
910        error::{Error, Result},
911        features::spline::SplineSegment,
912        frame::color_correlation_map::ColorCorrelationParams,
913        util::test::{assert_all_almost_abs_eq, assert_almost_abs_eq, assert_almost_eq},
914    };
915
916    use super::{
917        DCT_MULTIPLIERS, DESIRED_RENDERING_DISTANCE, Dct32, Point, PrecomputedCosines,
918        QuantizedSpline, Spline, Splines, draw_centripetal_catmull_rom_spline,
919        for_each_equally_spaced_point,
920    };
921    use crate::util::fast_cos;
922
923    impl Dct32 {
924        /// Original continuous_idct for testing - validates correctness against golden values.
925        fn continuous_idct(&self, t: f32) -> f32 {
926            let tandhalf = t + 0.5;
927            zip(DCT_MULTIPLIERS, self.0)
928                .map(|(multiplier, coeff)| SQRT_2 * coeff * fast_cos(multiplier * tandhalf))
929                .sum()
930        }
931    }
932
933    #[test]
934    fn dequantize() -> Result<(), Error> {
935        // Golden data generated by libjxl.
936        let quantized_and_dequantized = [
937            (
938                QuantizedSpline {
939                    control_points: vec![
940                        (109, 105),
941                        (-247, -261),
942                        (168, 427),
943                        (-46, -360),
944                        (-61, 181),
945                    ],
946                    color_dct: [
947                        [
948                            12223, 9452, 5524, 16071, 1048, 17024, 14833, 7690, 21952, 2405, 2571,
949                            2190, 1452, 2500, 18833, 1667, 5857, 21619, 1310, 20000, 10429, 11667,
950                            7976, 18786, 12976, 18548, 14786, 12238, 8667, 3405, 19929, 8429,
951                        ],
952                        [
953                            177, 712, 127, 999, 969, 356, 105, 12, 1132, 309, 353, 415, 1213, 156,
954                            988, 524, 316, 1100, 64, 36, 816, 1285, 183, 889, 839, 1099, 79, 1316,
955                            287, 105, 689, 841,
956                        ],
957                        [
958                            780, -201, -38, -695, -563, -293, -88, 1400, -357, 520, 979, 431, -118,
959                            590, -971, -127, 157, 206, 1266, 204, -320, -223, 704, -687, -276,
960                            -716, 787, -1121, 40, 292, 249, -10,
961                        ],
962                    ],
963                    sigma_dct: [
964                        139, 65, 133, 5, 137, 272, 88, 178, 71, 256, 254, 82, 126, 252, 152, 53,
965                        281, 15, 8, 209, 285, 156, 73, 56, 36, 287, 86, 244, 270, 94, 224, 156,
966                    ],
967                },
968                Spline {
969                    control_points: vec![
970                        Point { x: 109.0, y: 54.0 },
971                        Point { x: 218.0, y: 159.0 },
972                        Point { x: 80.0, y: 3.0 },
973                        Point { x: 110.0, y: 274.0 },
974                        Point { x: 94.0, y: 185.0 },
975                        Point { x: 17.0, y: 277.0 },
976                    ],
977                    color_dct: [
978                        Dct32([
979                            36.300457,
980                            39.69839859,
981                            23.20079994,
982                            67.49819946,
983                            4.401599884,
984                            71.50080109,
985                            62.29859924,
986                            32.29800034,
987                            92.19839478,
988                            10.10099983,
989                            10.79819965,
990                            9.197999954,
991                            6.098399639,
992                            10.5,
993                            79.09859467,
994                            7.001399517,
995                            24.59939957,
996                            90.79979706,
997                            5.501999855,
998                            84.0,
999                            43.80179977,
1000                            49.00139999,
1001                            33.49919891,
1002                            78.90119934,
1003                            54.49919891,
1004                            77.90159607,
1005                            62.10119629,
1006                            51.39959717,
1007                            36.40139771,
1008                            14.30099964,
1009                            83.70179749,
1010                            35.40179825,
1011                        ]),
1012                        Dct32([
1013                            9.386842728,
1014                            53.40000153,
1015                            9.525000572,
1016                            74.92500305,
1017                            72.67500305,
1018                            26.70000076,
1019                            7.875000477,
1020                            0.9000000358,
1021                            84.90000153,
1022                            23.17500114,
1023                            26.47500038,
1024                            31.12500191,
1025                            90.9750061,
1026                            11.70000076,
1027                            74.1000061,
1028                            39.30000305,
1029                            23.70000076,
1030                            82.5,
1031                            4.800000191,
1032                            2.700000048,
1033                            61.20000076,
1034                            96.37500763,
1035                            13.72500038,
1036                            66.67500305,
1037                            62.92500305,
1038                            82.42500305,
1039                            5.925000191,
1040                            98.70000458,
1041                            21.52500153,
1042                            7.875000477,
1043                            51.67500305,
1044                            63.07500076,
1045                        ]),
1046                        Dct32([
1047                            47.99487305,
1048                            39.33000183,
1049                            6.865000725,
1050                            26.27500153,
1051                            33.2650032,
1052                            6.190000534,
1053                            1.715000629,
1054                            98.90000153,
1055                            59.91000366,
1056                            59.57500458,
1057                            95.00499725,
1058                            61.29500198,
1059                            82.71500397,
1060                            53.0,
1061                            6.130004883,
1062                            30.41000366,
1063                            34.69000244,
1064                            96.91999817,
1065                            93.4200058,
1066                            16.97999954,
1067                            38.80000305,
1068                            80.76500702,
1069                            63.00499725,
1070                            18.5850029,
1071                            43.60500336,
1072                            32.30500412,
1073                            61.01499939,
1074                            20.23000336,
1075                            24.32500076,
1076                            28.31500053,
1077                            69.10500336,
1078                            62.375,
1079                        ]),
1080                    ],
1081                    sigma_dct: Dct32([
1082                        32.75933838,
1083                        21.66449928,
1084                        44.32889938,
1085                        1.666499972,
1086                        45.66209793,
1087                        90.6576004,
1088                        29.33039856,
1089                        59.32740021,
1090                        23.66429901,
1091                        85.32479858,
1092                        84.6581955,
1093                        27.33059883,
1094                        41.99580002,
1095                        83.99160004,
1096                        50.66159821,
1097                        17.66489983,
1098                        93.65729523,
1099                        4.999499798,
1100                        2.666399956,
1101                        69.65969849,
1102                        94.9905014,
1103                        51.99480057,
1104                        24.33090019,
1105                        18.66479874,
1106                        11.99880028,
1107                        95.65709686,
1108                        28.66379929,
1109                        81.32519531,
1110                        89.99099731,
1111                        31.3302002,
1112                        74.65919495,
1113                        51.99480057,
1114                    ]),
1115                    estimated_area_reached: 19843491681,
1116                },
1117            ),
1118            (
1119                QuantizedSpline {
1120                    control_points: vec![
1121                        (24, -32),
1122                        (-178, -7),
1123                        (226, 151),
1124                        (121, -172),
1125                        (-184, 39),
1126                        (-201, -182),
1127                        (301, 404),
1128                    ],
1129                    color_dct: [
1130                        [
1131                            5051, 6881, 5238, 1571, 9952, 19762, 2048, 13524, 16405, 2310, 1286,
1132                            4714, 16857, 21429, 12500, 15524, 1857, 5595, 6286, 17190, 15405,
1133                            20738, 310, 16071, 10952, 16286, 15571, 8452, 6929, 3095, 9905, 5690,
1134                        ],
1135                        [
1136                            899, 1059, 836, 388, 1291, 247, 235, 203, 1073, 747, 1283, 799, 356,
1137                            1281, 1231, 561, 477, 720, 309, 733, 1013, 477, 779, 1183, 32, 1041,
1138                            1275, 367, 88, 1047, 321, 931,
1139                        ],
1140                        [
1141                            -78, 244, -883, 943, -682, 752, 107, 262, -75, 557, -202, -575, -231,
1142                            -731, -605, 732, 682, 650, 592, -14, -1035, 913, -188, -95, 286, -574,
1143                            -509, 67, 86, -1056, 592, 380,
1144                        ],
1145                    ],
1146                    sigma_dct: [
1147                        308, 8, 125, 7, 119, 237, 209, 60, 277, 215, 126, 186, 90, 148, 211, 136,
1148                        188, 142, 140, 124, 272, 140, 274, 165, 24, 209, 76, 254, 185, 83, 11, 141,
1149                    ],
1150                },
1151                Spline {
1152                    control_points: vec![
1153                        Point { x: 172.0, y: 309.0 },
1154                        Point { x: 196.0, y: 277.0 },
1155                        Point { x: 42.0, y: 238.0 },
1156                        Point { x: 114.0, y: 350.0 },
1157                        Point { x: 307.0, y: 290.0 },
1158                        Point { x: 316.0, y: 269.0 },
1159                        Point { x: 124.0, y: 66.0 },
1160                        Point { x: 233.0, y: 267.0 },
1161                    ],
1162                    color_dct: [
1163                        Dct32([
1164                            15.00070381,
1165                            28.90019989,
1166                            21.99959946,
1167                            6.598199844,
1168                            41.79839706,
1169                            83.00039673,
1170                            8.601599693,
1171                            56.80079651,
1172                            68.90100098,
1173                            9.701999664,
1174                            5.401199818,
1175                            19.79879951,
1176                            70.79940033,
1177                            90.00180054,
1178                            52.5,
1179                            65.20079803,
1180                            7.799399853,
1181                            23.49899864,
1182                            26.40119934,
1183                            72.19799805,
1184                            64.7009964,
1185                            87.09959412,
1186                            1.301999927,
1187                            67.49819946,
1188                            45.99839783,
1189                            68.40119934,
1190                            65.39820099,
1191                            35.49839783,
1192                            29.10179901,
1193                            12.9989996,
1194                            41.60099792,
1195                            23.89799881,
1196                        ]),
1197                        Dct32([
1198                            47.67667389,
1199                            79.42500305,
1200                            62.70000076,
1201                            29.10000038,
1202                            96.82500458,
1203                            18.52500153,
1204                            17.625,
1205                            15.22500038,
1206                            80.4750061,
1207                            56.02500153,
1208                            96.2250061,
1209                            59.92500305,
1210                            26.70000076,
1211                            96.07500458,
1212                            92.32500458,
1213                            42.07500076,
1214                            35.77500153,
1215                            54.00000381,
1216                            23.17500114,
1217                            54.97500229,
1218                            75.9750061,
1219                            35.77500153,
1220                            58.42500305,
1221                            88.7250061,
1222                            2.400000095,
1223                            78.07500458,
1224                            95.625,
1225                            27.52500153,
1226                            6.600000381,
1227                            78.52500153,
1228                            24.07500076,
1229                            69.82500458,
1230                        ]),
1231                        Dct32([
1232                            43.81587219,
1233                            96.50500488,
1234                            0.8899993896,
1235                            95.11000061,
1236                            49.0850029,
1237                            71.16500092,
1238                            25.11499977,
1239                            33.56500244,
1240                            75.2250061,
1241                            95.01499939,
1242                            82.08500671,
1243                            19.67500305,
1244                            10.53000069,
1245                            44.90500259,
1246                            49.9750061,
1247                            93.31500244,
1248                            83.51499939,
1249                            99.5,
1250                            64.61499786,
1251                            53.99500275,
1252                            3.525009155,
1253                            99.68499756,
1254                            45.2650032,
1255                            82.07500458,
1256                            22.42000008,
1257                            37.89500427,
1258                            59.99499893,
1259                            32.21500015,
1260                            12.62000084,
1261                            4.605003357,
1262                            65.51499939,
1263                            96.42500305,
1264                        ]),
1265                    ],
1266                    sigma_dct: Dct32([
1267                        72.58903503,
1268                        2.666399956,
1269                        41.66249847,
1270                        2.333099842,
1271                        39.66270065,
1272                        78.99209595,
1273                        69.65969849,
1274                        19.99799919,
1275                        92.32409668,
1276                        71.65950012,
1277                        41.99580002,
1278                        61.9937973,
1279                        29.99699974,
1280                        49.32839966,
1281                        70.32630157,
1282                        45.3288002,
1283                        62.66040039,
1284                        47.32859802,
1285                        46.66199875,
1286                        41.32920074,
1287                        90.6576004,
1288                        46.66199875,
1289                        91.32419586,
1290                        54.99449921,
1291                        7.999199867,
1292                        69.65969849,
1293                        25.3307991,
1294                        84.6581955,
1295                        61.66049957,
1296                        27.66390038,
1297                        3.66629982,
1298                        46.99530029,
1299                    ]),
1300                    estimated_area_reached: 25829781306,
1301                },
1302            ),
1303            (
1304                QuantizedSpline {
1305                    control_points: vec![
1306                        (157, -89),
1307                        (-244, 41),
1308                        (-58, 168),
1309                        (429, -185),
1310                        (-361, 198),
1311                        (230, -269),
1312                        (-416, 203),
1313                        (167, 65),
1314                        (460, -344),
1315                    ],
1316                    color_dct: [
1317                        [
1318                            5691, 15429, 1000, 2524, 5595, 4048, 18881, 1357, 14381, 3952, 22595,
1319                            15167, 20857, 2500, 905, 14548, 5452, 19500, 19143, 9643, 10929, 6048,
1320                            9476, 7143, 11952, 21524, 6643, 22310, 15500, 11476, 5310, 10452,
1321                        ],
1322                        [
1323                            470, 880, 47, 1203, 1295, 211, 475, 8, 907, 528, 325, 1145, 769, 1035,
1324                            633, 905, 57, 72, 1216, 780, 1, 696, 47, 637, 843, 580, 1144, 477, 669,
1325                            479, 256, 643,
1326                        ],
1327                        [
1328                            1169, -301, 1041, -725, -43, -22, 774, 134, -822, 499, 456, -287, -713,
1329                            -776, 76, 449, 750, 580, -207, -643, 956, -426, 377, -64, 101, -250,
1330                            -164, 259, 169, -240, 430, -22,
1331                        ],
1332                    ],
1333                    sigma_dct: [
1334                        354, 5, 75, 56, 140, 226, 84, 187, 151, 70, 257, 288, 137, 99, 100, 159,
1335                        79, 176, 59, 210, 278, 68, 171, 65, 230, 263, 69, 199, 107, 107, 170, 202,
1336                    ],
1337                },
1338                Spline {
1339                    control_points: vec![
1340                        Point { x: 100.0, y: 186.0 },
1341                        Point { x: 257.0, y: 97.0 },
1342                        Point { x: 170.0, y: 49.0 },
1343                        Point { x: 25.0, y: 169.0 },
1344                        Point { x: 309.0, y: 104.0 },
1345                        Point { x: 232.0, y: 237.0 },
1346                        Point { x: 385.0, y: 101.0 },
1347                        Point { x: 122.0, y: 168.0 },
1348                        Point { x: 26.0, y: 300.0 },
1349                        Point { x: 390.0, y: 88.0 },
1350                    ],
1351                    color_dct: [
1352                        Dct32([
1353                            16.90140724,
1354                            64.80179596,
1355                            4.199999809,
1356                            10.60079956,
1357                            23.49899864,
1358                            17.00160027,
1359                            79.30019379,
1360                            5.699399948,
1361                            60.40019608,
1362                            16.59840012,
1363                            94.89899445,
1364                            63.70139694,
1365                            87.59939575,
1366                            10.5,
1367                            3.80099988,
1368                            61.10159683,
1369                            22.89839935,
1370                            81.8999939,
1371                            80.40059662,
1372                            40.50059891,
1373                            45.90179825,
1374                            25.40159988,
1375                            39.79919815,
1376                            30.00059891,
1377                            50.19839859,
1378                            90.40079498,
1379                            27.90059853,
1380                            93.70199585,
1381                            65.09999847,
1382                            48.19919968,
1383                            22.30200005,
1384                            43.89839935,
1385                        ]),
1386                        Dct32([
1387                            24.92551422,
1388                            66.0,
1389                            3.525000095,
1390                            90.2250061,
1391                            97.12500763,
1392                            15.82500076,
1393                            35.625,
1394                            0.6000000238,
1395                            68.02500153,
1396                            39.60000229,
1397                            24.37500191,
1398                            85.875,
1399                            57.67500305,
1400                            77.625,
1401                            47.47500229,
1402                            67.875,
1403                            4.275000095,
1404                            5.400000095,
1405                            91.20000458,
1406                            58.50000381,
1407                            0.07500000298,
1408                            52.20000076,
1409                            3.525000095,
1410                            47.77500153,
1411                            63.22500229,
1412                            43.5,
1413                            85.80000305,
1414                            35.77500153,
1415                            50.17500305,
1416                            35.92500305,
1417                            19.20000076,
1418                            48.22500229,
1419                        ]),
1420                        Dct32([
1421                            82.78805542,
1422                            44.93000031,
1423                            76.39500427,
1424                            39.4750061,
1425                            94.11500549,
1426                            14.2850008,
1427                            89.80500031,
1428                            9.980000496,
1429                            10.48500061,
1430                            74.52999878,
1431                            56.29500198,
1432                            65.78500366,
1433                            7.765003204,
1434                            23.30500031,
1435                            52.79500198,
1436                            99.30500031,
1437                            56.77500153,
1438                            46.0,
1439                            76.71000671,
1440                            13.49000549,
1441                            66.99499512,
1442                            22.38000107,
1443                            29.91499901,
1444                            43.29500198,
1445                            70.2950058,
1446                            26.0,
1447                            74.31999969,
1448                            53.90499878,
1449                            62.00500488,
1450                            19.12500381,
1451                            49.30000305,
1452                            46.68500137,
1453                        ]),
1454                    ],
1455                    sigma_dct: Dct32([
1456                        83.43025208,
1457                        1.666499972,
1458                        24.99749947,
1459                        18.66479874,
1460                        46.66199875,
1461                        75.32579803,
1462                        27.99720001,
1463                        62.32709885,
1464                        50.32830048,
1465                        23.33099937,
1466                        85.65809631,
1467                        95.99040222,
1468                        45.66209793,
1469                        32.99670029,
1470                        33.32999802,
1471                        52.99469757,
1472                        26.33069992,
1473                        58.66079712,
1474                        19.66469955,
1475                        69.99299622,
1476                        92.65740204,
1477                        22.6644001,
1478                        56.99430084,
1479                        21.66449928,
1480                        76.65899658,
1481                        87.65789795,
1482                        22.99769974,
1483                        66.3266983,
1484                        35.6631012,
1485                        35.6631012,
1486                        56.6609993,
1487                        67.32659912,
1488                    ]),
1489                    estimated_area_reached: 47263284396,
1490                },
1491            ),
1492        ];
1493        for (quantized, want_dequantized) in quantized_and_dequantized {
1494            let got_dequantized = quantized.dequantize(
1495                &want_dequantized.control_points[0],
1496                0,
1497                0.0,
1498                1.0,
1499                2u64 << 30,
1500            )?;
1501            assert_eq!(
1502                got_dequantized.control_points.len(),
1503                want_dequantized.control_points.len()
1504            );
1505            assert_all_almost_abs_eq(
1506                got_dequantized
1507                    .control_points
1508                    .iter()
1509                    .map(|p| p.x)
1510                    .collect::<Vec<f32>>(),
1511                want_dequantized
1512                    .control_points
1513                    .iter()
1514                    .map(|p| p.x)
1515                    .collect::<Vec<f32>>(),
1516                1e-6,
1517            );
1518            assert_all_almost_abs_eq(
1519                got_dequantized
1520                    .control_points
1521                    .iter()
1522                    .map(|p| p.y)
1523                    .collect::<Vec<f32>>(),
1524                want_dequantized
1525                    .control_points
1526                    .iter()
1527                    .map(|p| p.y)
1528                    .collect::<Vec<f32>>(),
1529                1e-6,
1530            );
1531            for index in 0..got_dequantized.color_dct.len() {
1532                assert_all_almost_abs_eq(
1533                    got_dequantized.color_dct[index].0,
1534                    want_dequantized.color_dct[index].0,
1535                    1e-4,
1536                );
1537            }
1538            assert_all_almost_abs_eq(
1539                got_dequantized.sigma_dct.0,
1540                want_dequantized.sigma_dct.0,
1541                1e-4,
1542            );
1543            assert_eq!(
1544                got_dequantized.estimated_area_reached,
1545                want_dequantized.estimated_area_reached,
1546            );
1547        }
1548        Ok(())
1549    }
1550
1551    #[test]
1552    fn centripetal_catmull_rom_spline() -> Result<(), Error> {
1553        let control_points = vec![Point { x: 1.0, y: 2.0 }, Point { x: 4.0, y: 3.0 }];
1554        let want_result = [
1555            Point { x: 1.0, y: 2.0 },
1556            Point {
1557                x: 1.187500119,
1558                y: 2.0625,
1559            },
1560            Point { x: 1.375, y: 2.125 },
1561            Point {
1562                x: 1.562499881,
1563                y: 2.1875,
1564            },
1565            Point {
1566                x: 1.750000119,
1567                y: 2.25,
1568            },
1569            Point {
1570                x: 1.9375,
1571                y: 2.3125,
1572            },
1573            Point { x: 2.125, y: 2.375 },
1574            Point {
1575                x: 2.312500238,
1576                y: 2.4375,
1577            },
1578            Point {
1579                x: 2.500000238,
1580                y: 2.5,
1581            },
1582            Point {
1583                x: 2.6875,
1584                y: 2.5625,
1585            },
1586            Point {
1587                x: 2.875000477,
1588                y: 2.625,
1589            },
1590            Point {
1591                x: 3.062499762,
1592                y: 2.6875,
1593            },
1594            Point { x: 3.25, y: 2.75 },
1595            Point {
1596                x: 3.4375,
1597                y: 2.8125,
1598            },
1599            Point {
1600                x: 3.624999762,
1601                y: 2.875,
1602            },
1603            Point {
1604                x: 3.812500238,
1605                y: 2.9375,
1606            },
1607            Point { x: 4.0, y: 3.0 },
1608        ];
1609        let got_result = draw_centripetal_catmull_rom_spline(&control_points)?;
1610        assert_all_almost_abs_eq(
1611            got_result.iter().map(|p| p.x).collect::<Vec<f32>>(),
1612            want_result.iter().map(|p| p.x).collect::<Vec<f32>>(),
1613            1e-10,
1614        );
1615        Ok(())
1616    }
1617
1618    #[test]
1619    fn equally_spaced_points() -> Result<(), Error> {
1620        let desired_rendering_distance = 10.0f32;
1621        let segments = [
1622            Point { x: 0.0, y: 0.0 },
1623            Point { x: 5.0, y: 0.0 },
1624            Point { x: 35.0, y: 0.0 },
1625            Point { x: 35.0, y: 10.0 },
1626        ];
1627        let want_results = [
1628            (Point { x: 0.0, y: 0.0 }, desired_rendering_distance),
1629            (Point { x: 10.0, y: 0.0 }, desired_rendering_distance),
1630            (Point { x: 20.0, y: 0.0 }, desired_rendering_distance),
1631            (Point { x: 30.0, y: 0.0 }, desired_rendering_distance),
1632            (Point { x: 35.0, y: 5.0 }, desired_rendering_distance),
1633            (Point { x: 35.0, y: 10.0 }, 5.0f32),
1634        ];
1635        let mut got_results = Vec::<(Point, f32)>::new();
1636        for_each_equally_spaced_point(&segments, desired_rendering_distance, |p, d| {
1637            got_results.push((p, d))
1638        });
1639        assert_all_almost_abs_eq(
1640            got_results.iter().map(|(p, _)| p.x).collect::<Vec<f32>>(),
1641            want_results.iter().map(|(p, _)| p.x).collect::<Vec<f32>>(),
1642            1e-9,
1643        );
1644        assert_all_almost_abs_eq(
1645            got_results.iter().map(|(p, _)| p.y).collect::<Vec<f32>>(),
1646            want_results.iter().map(|(p, _)| p.y).collect::<Vec<f32>>(),
1647            1e-9,
1648        );
1649        assert_all_almost_abs_eq(
1650            got_results.iter().map(|(_, d)| *d).collect::<Vec<f32>>(),
1651            want_results.iter().map(|(_, d)| *d).collect::<Vec<f32>>(),
1652            1e-9,
1653        );
1654        Ok(())
1655    }
1656
1657    #[test]
1658    fn dct32() -> Result<(), Error> {
1659        let mut dct = Dct32::default();
1660        for (i, coeff) in dct.0.iter_mut().enumerate() {
1661            *coeff = 0.05f32 * i as f32;
1662        }
1663        // Golden numbers come from libjxl.
1664        let want_out = [
1665            16.7353153229,
1666            -18.6041717529,
1667            7.9931735992,
1668            -7.1250801086,
1669            4.6699867249,
1670            -4.3367614746,
1671            3.2450540066,
1672            -3.0694460869,
1673            2.4446771145,
1674            -2.3350939751,
1675            1.9243829250,
1676            -1.8484034538,
1677            1.5531382561,
1678            -1.4964176416,
1679            1.2701368332,
1680            -1.2254891396,
1681            1.0434474945,
1682            -1.0067725182,
1683            0.8544843197,
1684            -0.8232427835,
1685            0.6916543841,
1686            -0.6642799377,
1687            0.5473306179,
1688            -0.5226536393,
1689            0.4161090851,
1690            -0.3933961987,
1691            0.2940555215,
1692            -0.2726306915,
1693            0.1781132221,
1694            -0.1574717760,
1695            0.0656886101,
1696            -0.0454511642,
1697        ];
1698        for (t, want) in want_out.iter().enumerate() {
1699            let got_out = dct.continuous_idct(t as f32);
1700            assert_almost_abs_eq(got_out, *want, 1e-4);
1701        }
1702        Ok(())
1703    }
1704
1705    #[test]
1706    fn dct32_fast_matches_original() {
1707        // Verify that continuous_idct_fast produces the same results as continuous_idct
1708        let mut dct = Dct32::default();
1709        for (i, coeff) in dct.0.iter_mut().enumerate() {
1710            *coeff = 0.05f32 * i as f32;
1711        }
1712
1713        for t in 0..32 {
1714            let t_val = t as f32;
1715            let original = dct.continuous_idct(t_val);
1716            let precomputed = PrecomputedCosines::new(t_val);
1717            let fast = dct.continuous_idct_fast(&precomputed);
1718            assert_almost_abs_eq(fast, original, 1e-5);
1719        }
1720    }
1721
1722    fn verify_segment_almost_equal(seg1: &SplineSegment, seg2: &SplineSegment) {
1723        assert_almost_eq(seg1.center_x, seg2.center_x, 1e-2, 1e-4);
1724        assert_almost_eq(seg1.center_y, seg2.center_y, 1e-2, 1e-4);
1725        for (got, want) in zip(seg1.color.iter(), seg2.color.iter()) {
1726            assert_almost_eq(*got, *want, 1e-2, 1e-4);
1727        }
1728        assert_almost_eq(seg1.inv_sigma, seg2.inv_sigma, 1e-2, 1e-4);
1729        assert_almost_eq(seg1.maximum_distance, seg2.maximum_distance, 1e-2, 1e-4);
1730        assert_almost_eq(
1731            seg1.sigma_over_4_times_intensity,
1732            seg2.sigma_over_4_times_intensity,
1733            1e-2,
1734            1e-4,
1735        );
1736    }
1737
1738    #[test]
1739    fn spline_segments_add_segment() -> Result<(), Error> {
1740        let mut splines = Splines::default();
1741        let mut segments_by_y = Vec::<(u64, usize)>::new();
1742
1743        splines.add_segment(
1744            &Point { x: 10.0, y: 20.0 },
1745            0.5,
1746            [0.5, 0.6, 0.7],
1747            0.8,
1748            true,
1749            &mut segments_by_y,
1750        );
1751        // Golden numbers come from libjxl.
1752        let want_segment = SplineSegment {
1753            center_x: 10.0,
1754            center_y: 20.0,
1755            color: [0.5, 0.6, 0.7],
1756            inv_sigma: 1.25,
1757            maximum_distance: 3.65961,
1758            sigma_over_4_times_intensity: 0.1,
1759        };
1760        assert_eq!(splines.segments.len(), 1);
1761        verify_segment_almost_equal(&splines.segments[0], &want_segment);
1762        let want_segments_by_y = [
1763            (16, 0),
1764            (17, 0),
1765            (18, 0),
1766            (19, 0),
1767            (20, 0),
1768            (21, 0),
1769            (22, 0),
1770            (23, 0),
1771            (24, 0),
1772        ];
1773        for (got, want) in zip(segments_by_y.iter(), want_segments_by_y.iter()) {
1774            assert_eq!(got.0, want.0);
1775            assert_eq!(got.1, want.1);
1776        }
1777        Ok(())
1778    }
1779
1780    #[test]
1781    fn spline_segments_add_segments_from_points() -> Result<(), Error> {
1782        let mut splines = Splines::default();
1783        let mut segments_by_y = Vec::<(u64, usize)>::new();
1784        let mut color_dct = [Dct32::default(); 3];
1785        for (channel_index, channel_dct) in color_dct.iter_mut().enumerate() {
1786            for (coeff_index, coeff) in channel_dct.0.iter_mut().enumerate() {
1787                *coeff = 0.1 * channel_index as f32 + 0.05 * coeff_index as f32;
1788            }
1789        }
1790        let mut sigma_dct = Dct32::default();
1791        for (coeff_index, coeff) in sigma_dct.0.iter_mut().enumerate() {
1792            *coeff = 0.06 * coeff_index as f32;
1793        }
1794        let spline = Spline {
1795            control_points: vec![],
1796            color_dct,
1797            sigma_dct,
1798            estimated_area_reached: 0,
1799        };
1800        let points_to_draw = vec![
1801            (Point { x: 10.0, y: 20.0 }, 1.0),
1802            (Point { x: 11.0, y: 21.0 }, 1.0),
1803            (Point { x: 12.0, y: 21.0 }, 1.0),
1804        ];
1805        splines.add_segments_from_points(
1806            &spline,
1807            &points_to_draw,
1808            SQRT_2 + 1.0,
1809            DESIRED_RENDERING_DISTANCE,
1810            true,
1811            &mut segments_by_y,
1812        );
1813        // Golden numbers come from libjxl.
1814        let want_segments = [
1815            SplineSegment {
1816                center_x: 10.0,
1817                center_y: 20.0,
1818                color: [16.73531532, 19.68646049, 22.63760757],
1819                inv_sigma: 0.04979490861,
1820                maximum_distance: 108.6400299,
1821                sigma_over_4_times_intensity: 5.020593643,
1822            },
1823            SplineSegment {
1824                center_x: 11.0,
1825                center_y: 21.0,
1826                color: [-0.8199231625, -0.7960500717, -0.7721766233],
1827                inv_sigma: -1.016355753,
1828                maximum_distance: 4.680418015,
1829                sigma_over_4_times_intensity: -0.2459768653,
1830            },
1831            SplineSegment {
1832                center_x: 12.0,
1833                center_y: 21.0,
1834                color: [-0.7767754197, -0.7544237971, -0.7320720553],
1835                inv_sigma: -1.072811365,
1836                maximum_distance: 4.423510075,
1837                sigma_over_4_times_intensity: -0.2330325693,
1838            },
1839        ];
1840        assert_eq!(splines.segments.len(), want_segments.len());
1841        for (got, want) in zip(splines.segments.iter(), want_segments.iter()) {
1842            verify_segment_almost_equal(got, want);
1843        }
1844        let want_segments_by_y: Vec<(u64, usize)> = (0..=129)
1845            .map(|c| (c, 0))
1846            .chain((16..=26).map(|c| (c, 1)))
1847            .chain((17..=25).map(|c| (c, 2)))
1848            .collect();
1849        for (got, want) in zip(segments_by_y.iter(), want_segments_by_y.iter()) {
1850            assert_eq!(got.0, want.0);
1851            assert_eq!(got.1, want.1);
1852        }
1853        Ok(())
1854    }
1855
1856    #[test]
1857    fn init_draw_cache() -> Result<(), Error> {
1858        let mut splines = Splines {
1859            splines: vec![
1860                QuantizedSpline {
1861                    control_points: vec![
1862                        (109, 105),
1863                        (-247, -261),
1864                        (168, 427),
1865                        (-46, -360),
1866                        (-61, 181),
1867                    ],
1868                    color_dct: [
1869                        [
1870                            12223, 9452, 5524, 16071, 1048, 17024, 14833, 7690, 21952, 2405, 2571,
1871                            2190, 1452, 2500, 18833, 1667, 5857, 21619, 1310, 20000, 10429, 11667,
1872                            7976, 18786, 12976, 18548, 14786, 12238, 8667, 3405, 19929, 8429,
1873                        ],
1874                        [
1875                            177, 712, 127, 999, 969, 356, 105, 12, 1132, 309, 353, 415, 1213, 156,
1876                            988, 524, 316, 1100, 64, 36, 816, 1285, 183, 889, 839, 1099, 79, 1316,
1877                            287, 105, 689, 841,
1878                        ],
1879                        [
1880                            780, -201, -38, -695, -563, -293, -88, 1400, -357, 520, 979, 431, -118,
1881                            590, -971, -127, 157, 206, 1266, 204, -320, -223, 704, -687, -276,
1882                            -716, 787, -1121, 40, 292, 249, -10,
1883                        ],
1884                    ],
1885                    sigma_dct: [
1886                        139, 65, 133, 5, 137, 272, 88, 178, 71, 256, 254, 82, 126, 252, 152, 53,
1887                        281, 15, 8, 209, 285, 156, 73, 56, 36, 287, 86, 244, 270, 94, 224, 156,
1888                    ],
1889                },
1890                QuantizedSpline {
1891                    control_points: vec![
1892                        (24, -32),
1893                        (-178, -7),
1894                        (226, 151),
1895                        (121, -172),
1896                        (-184, 39),
1897                        (-201, -182),
1898                        (301, 404),
1899                    ],
1900                    color_dct: [
1901                        [
1902                            5051, 6881, 5238, 1571, 9952, 19762, 2048, 13524, 16405, 2310, 1286,
1903                            4714, 16857, 21429, 12500, 15524, 1857, 5595, 6286, 17190, 15405,
1904                            20738, 310, 16071, 10952, 16286, 15571, 8452, 6929, 3095, 9905, 5690,
1905                        ],
1906                        [
1907                            899, 1059, 836, 388, 1291, 247, 235, 203, 1073, 747, 1283, 799, 356,
1908                            1281, 1231, 561, 477, 720, 309, 733, 1013, 477, 779, 1183, 32, 1041,
1909                            1275, 367, 88, 1047, 321, 931,
1910                        ],
1911                        [
1912                            -78, 244, -883, 943, -682, 752, 107, 262, -75, 557, -202, -575, -231,
1913                            -731, -605, 732, 682, 650, 592, -14, -1035, 913, -188, -95, 286, -574,
1914                            -509, 67, 86, -1056, 592, 380,
1915                        ],
1916                    ],
1917                    sigma_dct: [
1918                        308, 8, 125, 7, 119, 237, 209, 60, 277, 215, 126, 186, 90, 148, 211, 136,
1919                        188, 142, 140, 124, 272, 140, 274, 165, 24, 209, 76, 254, 185, 83, 11, 141,
1920                    ],
1921                },
1922            ],
1923            starting_points: vec![Point { x: 10.0, y: 20.0 }, Point { x: 5.0, y: 40.0 }],
1924            ..Default::default()
1925        };
1926        splines.initialize_draw_cache(
1927            1 << 15,
1928            1 << 15,
1929            &ColorCorrelationParams {
1930                color_factor: 1,
1931                base_correlation_x: 0.0,
1932                base_correlation_b: 0.0,
1933                ytox_lf: 0,
1934                ytob_lf: 0,
1935            },
1936            true,
1937        )?;
1938        assert_eq!(splines.segments.len(), 1940);
1939        let want_segments_sample = [
1940            (
1941                22,
1942                SplineSegment {
1943                    center_x: 25.77652359,
1944                    center_y: 35.33295059,
1945                    color: [-524.996582, -509.9048462, 43.3883667],
1946                    inv_sigma: -0.00197347207,
1947                    maximum_distance: 3021.377197,
1948                    sigma_over_4_times_intensity: -126.6802902,
1949                },
1950            ),
1951            (
1952                474,
1953                SplineSegment {
1954                    center_x: -16.45600891,
1955                    center_y: 78.81845856,
1956                    color: [-117.6707535, -133.5515594, 343.5632629],
1957                    inv_sigma: -0.002631845651,
1958                    maximum_distance: 2238.376221,
1959                    sigma_over_4_times_intensity: -94.9903717,
1960                },
1961            ),
1962            (
1963                835,
1964                SplineSegment {
1965                    center_x: -71.93701172,
1966                    center_y: 230.0635529,
1967                    color: [44.79507446, 298.9411621, -395.3574524],
1968                    inv_sigma: 0.01869126037,
1969                    maximum_distance: 316.4499207,
1970                    sigma_over_4_times_intensity: 13.3752346,
1971                },
1972            ),
1973            (
1974                1066,
1975                SplineSegment {
1976                    center_x: -126.2593002,
1977                    center_y: -22.97857094,
1978                    color: [-136.4196625, 194.757019, -98.18778992],
1979                    inv_sigma: 0.007531851064,
1980                    maximum_distance: 769.2540283,
1981                    sigma_over_4_times_intensity: 33.19237137,
1982                },
1983            ),
1984            (
1985                1328,
1986                SplineSegment {
1987                    center_x: 73.70871735,
1988                    center_y: 56.31413269,
1989                    color: [-13.44394779, 162.6139221, 93.78419495],
1990                    inv_sigma: 0.003664178308,
1991                    maximum_distance: 1572.710327,
1992                    sigma_over_4_times_intensity: 68.2281189,
1993                },
1994            ),
1995            (
1996                1545,
1997                SplineSegment {
1998                    center_x: 77.48892975,
1999                    center_y: -92.33877563,
2000                    color: [-220.6807556, 66.13040924, -32.26184082],
2001                    inv_sigma: 0.03166157752,
2002                    maximum_distance: 183.6748352,
2003                    sigma_over_4_times_intensity: 7.89600563,
2004                },
2005            ),
2006            (
2007                1774,
2008                SplineSegment {
2009                    center_x: -16.43594933,
2010                    center_y: -144.8626556,
2011                    color: [57.31535339, -46.36843109, 92.14952087],
2012                    inv_sigma: -0.01524505392,
2013                    maximum_distance: 371.4827271,
2014                    sigma_over_4_times_intensity: -16.39876175,
2015                },
2016            ),
2017            (
2018                1929,
2019                SplineSegment {
2020                    center_x: 61.19338608,
2021                    center_y: -10.70717049,
2022                    color: [-69.78807068, 300.6082458, -476.5135803],
2023                    inv_sigma: 0.003229281865,
2024                    maximum_distance: 1841.37854,
2025                    sigma_over_4_times_intensity: 77.41659546,
2026                },
2027            ),
2028        ];
2029        for (index, segment) in want_segments_sample {
2030            verify_segment_almost_equal(&segment, &splines.segments[index]);
2031        }
2032        Ok(())
2033    }
2034}