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