// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
use std::{
f32::consts::{FRAC_1_SQRT_2, PI, SQRT_2},
iter::{self, zip},
ops,
};
impl ops::Add<Point> for Point { type Output = Point; fn add(self, rhs: Point) -> Point {
Point {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl ops::Sub<Point> for Point { type Output = Point; fn sub(self, rhs: Point) -> Point {
Point {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl ops::Mul<f32> for Point { type Output = Point; fn mul(self, rhs: f32) -> Point {
Point {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl ops::Div<f32> for Point { type Output = Point; fn div(self, rhs: f32) -> Point { let inv = 1.0 / rhs;
Point {
x: self.x * inv,
y: self.y * inv,
}
}
}
#[derive(Default, Debug)] pubstruct Spline {
control_points: Vec<Point>, // X, Y, B.
color_dct: [Dct32; 3], // Splines are drawn by normalized Gaussian splatting. This controls the // Gaussian's parameter along the spline.
sigma_dct: Dct32, // The estimated area in pixels covered by the spline.
estimated_area_reached: u64,
}
fn validate_spline_point_pos<T: num_traits::ToPrimitive>(x: T, y: T) -> Result<()> { let xi = x.to_i32().unwrap(); let yi = y.to_i32().unwrap(); let ok_range = -(1i32 << 23)..(1i32 << 23); if !ok_range.contains(&xi) { return Err(Error::SplinesPointOutOfRange(
Point {
x: xi as f32,
y: yi as f32,
},
xi,
ok_range,
));
} if !ok_range.contains(&yi) { return Err(Error::SplinesPointOutOfRange(
Point {
x: xi as f32,
y: yi as f32,
},
yi,
ok_range,
));
}
Ok(())
}
for &(dx, dy) in &self.control_points {
current_delta_x += dx as i32;
current_delta_y += dy as i32;
validate_spline_point_pos(current_delta_x, current_delta_y)?;
manhattan_distance +=
current_delta_x.unsigned_abs() as u64 + current_delta_y.unsigned_abs() as u64;
result
.control_points
.push(Point::new(current_x as f32, current_y as f32));
}
let inv_quant = inv_adjusted_quant(quantization_adjustment);
for (c, weight) in CHANNEL_WEIGHT.iter().enumerate().take(3) { for i in0..32 { let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
result.color_dct[c].0[i] = self.color_dct[c][i] as f32 * inv_dct_factor * weight * inv_quant;
}
}
for i in0..32 {
result.color_dct[0].0[i] += y_to_x * result.color_dct[1].0[i];
result.color_dct[2].0[i] += y_to_b * result.color_dct[1].0[i];
}
letmut width_estimate = 0; letmut color = [0u64; 3];
for (c, color_val) in color.iter_mut().enumerate() { for i in0..32 {
*color_val += (inv_quant * self.color_dct[c][i].abs() as f32).ceil() as u64;
}
}
color[0] += y_to_x.abs().ceil() as u64 * color[1];
color[2] += y_to_b.abs().ceil() as u64 * color[1];
let max_color = color[0].max(color[1]).max(color[2]); let logcolor = 1u64.max((1u64 + max_color).ceil_log2());
let weight_limit =
(((area_limit as f32 / logcolor as f32) / manhattan_distance.max(1) as f32).sqrt())
.ceil();
for i in0..32 { let inv_dct_factor = if i == 0 { FRAC_1_SQRT_2 } else { 1.0 };
result.sigma_dct.0[i] = self.sigma_dct[i] as f32 * inv_dct_factor * CHANNEL_WEIGHT[3] * inv_quant;
let weight_f = (inv_quant * self.sigma_dct[i].abs() as f32).ceil(); let weight = weight_limit.min(weight_f.max(1.0)) as u64;
width_estimate += weight * weight * logcolor;
}
fn draw_centripetal_catmull_rom_spline(points: &[Point]) -> Result<Vec<Point>> { if points.is_empty() { return Ok(vec![]);
} if points.len() == 1 { return Ok(vec![points[0]]);
} const NUM_POINTS: usize = 16; // Create a view of points with one prepended and one appended point. let extended_points = iter::once(points[0] + (points[0] - points[1]))
.chain(points.iter().cloned())
.chain(iter::once(
points[points.len() - 1] + (points[points.len() - 1] - points[points.len() - 2]),
)); // Pair each point with the sqrt of the distance to the next point. let points_and_deltas = extended_points
.chain(iter::once(Point::default()))
.scan(Point::default(), |previous, p| { let result = Some((*previous, (p - *previous).abs().sqrt()));
*previous = p;
result
})
.skip(1); // Window the points with a [Point; 4] window. let windowed_points = points_and_deltas
.scan([(Point::default(), 0.0); 4], |window, p| {
(window[0], window[1], window[2], window[3]) =
(window[1], window[2], window[3], (p.0, p.1));
Some([window[0], window[1], window[2], window[3]])
})
.skip(3); // Create the points necessary per window, and flatten the result. let result = windowed_points
.flat_map(|p| { letmut window_result = [Point::default(); NUM_POINTS];
window_result[0] = p[1].0; letmut t = [0.0; 4]; for k in0..3 { // TODO(from libjxl): Restrict d[k] with reasonable limit and spec it.
t[k + 1] = t[k] + p[k].1;
} for (i, window_point) in window_result.iter_mut().enumerate().skip(1) { let tt = p[0].1 + ((i as f32) / (NUM_POINTS as f32)) * p[1].1; letmut a = [Point::default(); 3]; for k in0..3 { // TODO(from libjxl): Reciprocal multiplication would be faster.
a[k] = p[k].0 + (p[k + 1].0 - p[k].0) * ((tt - t[k]) / p[k].1);
} letmut b = [Point::default(); 2]; for k in0..2 {
b[k] = a[k] + (a[k + 1] - a[k]) * ((tt - t[k]) / (p[k].1 + p[k + 1].1));
}
*window_point = b[0] + (b[1] - b[0]) * ((tt - t[1]) / p[1].1);
}
window_result
})
.chain(iter::once(points[points.len() - 1]))
.collect();
Ok(result)
}
fn for_each_equally_spaced_point<F: FnMut(Point, f32)>(
points: &[Point],
desired_distance: f32, mut f: F,
) { if points.is_empty() { return;
} letmut accumulated_distance = 0.0;
f(points[0], desired_distance); if points.len() == 1 { return;
} for index in0..(points.len() - 1) { letmut current = points[index]; let next = points[index + 1]; let segment = next - current; let segment_length = segment.abs(); let unit_step = segment / segment_length; if accumulated_distance + segment_length >= desired_distance {
current = current + unit_step * (desired_distance - accumulated_distance);
f(current, desired_distance);
accumulated_distance -= desired_distance;
}
accumulated_distance += segment_length; while accumulated_distance >= desired_distance {
current = current + unit_step * desired_distance;
f(current, desired_distance);
accumulated_distance -= desired_distance;
}
}
f(points[points.len() - 1], accumulated_distance);
}
/// Precomputed multipliers for DCT: PI / 32.0 * i for i in 0..32 const DCT_MULTIPLIERS: [f32; 32] = [
PI / 32.0 * 0.0,
PI / 32.0 * 1.0,
PI / 32.0 * 2.0,
PI / 32.0 * 3.0,
PI / 32.0 * 4.0,
PI / 32.0 * 5.0,
PI / 32.0 * 6.0,
PI / 32.0 * 7.0,
PI / 32.0 * 8.0,
PI / 32.0 * 9.0,
PI / 32.0 * 10.0,
PI / 32.0 * 11.0,
PI / 32.0 * 12.0,
PI / 32.0 * 13.0,
PI / 32.0 * 14.0,
PI / 32.0 * 15.0,
PI / 32.0 * 16.0,
PI / 32.0 * 17.0,
PI / 32.0 * 18.0,
PI / 32.0 * 19.0,
PI / 32.0 * 20.0,
PI / 32.0 * 21.0,
PI / 32.0 * 22.0,
PI / 32.0 * 23.0,
PI / 32.0 * 24.0,
PI / 32.0 * 25.0,
PI / 32.0 * 26.0,
PI / 32.0 * 27.0,
PI / 32.0 * 28.0,
PI / 32.0 * 29.0,
PI / 32.0 * 30.0,
PI / 32.0 * 31.0,
];
/// Precomputed cosine values for DCT at a given t value. /// Computed once and reused for all 4 DCT evaluations (3 color channels + sigma). struct PrecomputedCosines([f32; 32]);
impl PrecomputedCosines { /// Precompute cosines for a given t value. /// Call this once per point, then use with continuous_idct_fast for each DCT. #[inline] fn new(t: f32) -> Self { let tandhalf = t + 0.5;
PrecomputedCosines(core::array::from_fn(|i| {
fast_cos(DCT_MULTIPLIERS[i] * tandhalf)
}))
}
}
impl Dct32 { /// Fast continuous IDCT using precomputed cosines. /// This avoids recomputing 32 cosines for each of the 4 DCT calls per point. #[inline] fn continuous_idct_fast(&self, precomputed: &PrecomputedCosines) -> f32 { // Compute dot product of coeffs and precomputed cosines // Using iterator for auto-vectorization
zip(self.0, precomputed.0)
.map(|(coeff, cos)| coeff * cos)
.sum::<f32>()
* SQRT_2
}
}
#[inline(always)] fn draw_segment_inner<D: SimdDescriptor>(
d: D,
row: &mut [&mut [f32]],
row_pos: (usize, usize),
x_range: (usize, usize),
segment: &SplineSegment,
) -> usize { let (x_start, x_end) = x_range; let (row_x0, y) = row_pos; let len = D::F32Vec::LEN; if x_start + len > x_end { return x_start;
}
let inv_sigma = D::F32Vec::splat(d, segment.inv_sigma); let half = D::F32Vec::splat(d, 0.5); let one_over_2s2 = D::F32Vec::splat(d, 0.353_553_38); let sigma_over_4_times_intensity = D::F32Vec::splat(d, segment.sigma_over_4_times_intensity); let center_x = D::F32Vec::splat(d, segment.center_x); let center_y = D::F32Vec::splat(d, segment.center_y); let dy = D::F32Vec::splat(d, y as f32) - center_y; let dy2 = dy * dy;
letmut x_base_arr = [0.0f32; 16]; for (i, val) in x_base_arr.iter_mut().enumerate() {
*val = i as f32;
} let vx_base = D::F32Vec::load(d, &x_base_arr);
let start_offset = x_start - row_x0; let end_offset = x_end - row_x0;
let cm0 = D::F32Vec::splat(d, segment.color[0]); let cm1 = D::F32Vec::splat(d, segment.color[1]); let cm2 = D::F32Vec::splat(d, segment.color[2]);
let num_chunks = (end_offset - start_offset) / len; letmut x = x_start; for _ in0..num_chunks { let vx = D::F32Vec::splat(d, x as f32) + vx_base; let dx = vx - center_x; let sqd = dx.mul_add(dx, dy2); let distance = sqd.sqrt();
let arg1 = distance.mul_add(half, one_over_2s2) * inv_sigma; let arg2 = distance.mul_add(half, D::F32Vec::splat(d, -0.353_553_38)) * inv_sigma; let one_dimensional_factor = fast_erff_simd(d, arg1) - fast_erff_simd(d, arg2); let local_intensity =
sigma_over_4_times_intensity * one_dimensional_factor * one_dimensional_factor;
let c0 = it0.next().unwrap();
cm0.mul_add(local_intensity, D::F32Vec::load(d, c0))
.store(c0); let c1 = it1.next().unwrap();
cm1.mul_add(local_intensity, D::F32Vec::load(d, c1))
.store(c1); let c2 = it2.next().unwrap();
cm2.mul_add(local_intensity, D::F32Vec::load(d, c2))
.store(c2);
x += len;
}
x
}
simd_function!(
draw_segment_dispatch,
d: D, fn draw_segment_simd(
row: &mut [&mut [f32]],
row_pos: (usize, usize),
xsize: usize,
segment: &SplineSegment,
) { let (x0, y) = row_pos; let x1 = x0 + xsize; let clamped_x0 = x0.max((segment.center_x - segment.maximum_distance).round() as usize); let clamped_x1 = x1.min((segment.center_x + segment.maximum_distance).round() as usize + 1);
if clamped_x1 <= clamped_x0 { return;
}
let x = clamped_x0; let x = draw_segment_inner(d, row, (x0, y), (x, clamped_x1), segment); let d = d.maybe_downgrade_256bit(); let x = draw_segment_inner(d, row, (x0, y), (x, clamped_x1), segment); let d = d.maybe_downgrade_128bit(); let x = draw_segment_inner(d, row, (x0, y), (x, clamped_x1), segment);
draw_segment_inner(ScalarDescriptor, row, (x0, y), (x, clamped_x1), segment);
}
);
fn add_segment(
&mutself,
center: &Point,
intensity: f32,
color: [f32; 3],
sigma: f32,
high_precision: bool,
segments_by_y: &mut Vec<(u64, usize)>,
) { if sigma.is_infinite()
|| sigma == 0.0
|| (1.0 / sigma).is_infinite()
|| intensity.is_infinite()
{ return;
} let distance_exp: f32 = if high_precision { 5.0 } else { 3.0 }; let max_color = [0.01, color[0], color[1], color[2]]
.iter()
.map(|chan| (chan * intensity).abs())
.max_by(|a, b| a.total_cmp(b))
.unwrap(); let max_distance =
(-2.0 * sigma * sigma * (0.1f32.ln() * distance_exp - max_color.ln())).sqrt(); let segment = SplineSegment {
center_x: center.x,
center_y: center.y,
color,
inv_sigma: 1.0 / sigma,
sigma_over_4_times_intensity: 0.25 * sigma * intensity,
maximum_distance: max_distance,
}; let y0 = (center.y - max_distance).round() as i64; let y1 = (center.y + max_distance).round() as i64 + 1; for y in0.max(y0)..y1 {
segments_by_y.push((y as u64, self.segments.len()));
} self.segments.push(segment);
}
fn add_segments_from_points(
&mutself,
spline: &Spline,
points_to_draw: &[(Point, f32)],
length: f32,
desired_distance: f32,
high_precision: bool,
segments_by_y: &mut Vec<(u64, usize)>,
) { let inv_length = 1.0 / length; for (point_index, (point, multiplier)) in points_to_draw.iter().enumerate() { let progress = (point_index as f32 * desired_distance * inv_length).min(1.0); let t = (32.0 - 1.0) * progress;
// Precompute cosines once for this point (saves 3x cosine computations) let precomputed = PrecomputedCosines::new(t);
// Use precomputed cosines for all 4 DCT evaluations letmut color = [0.0; 3]; for (index, coeffs) in spline.color_dct.iter().enumerate() {
color[index] = coeffs.continuous_idct_fast(&precomputed);
} let sigma = spline.sigma_dct.continuous_idct_fast(&precomputed);
self.add_segment(
point,
*multiplier,
color,
sigma,
high_precision,
segments_by_y,
);
}
}
pubfn initialize_draw_cache(
&mutself,
image_xsize: u64,
image_ysize: u64,
color_correlation_params: &ColorCorrelationParams,
high_precision: bool,
) -> Result<()> { letmut total_estimated_area_reached = 0u64; letmut splines = Vec::new(); // Use saturating_mul to prevent overflow with malicious image dimensions let image_area = image_xsize.saturating_mul(image_ysize); let area_limit = area_limit(image_area); for (index, qspline) inself.splines.iter().enumerate() { let spline = qspline.dequantize(
&self.starting_points[index], self.quantization_adjustment,
color_correlation_params.y_to_x_lf(),
color_correlation_params.y_to_b_lf(),
image_area,
)?;
total_estimated_area_reached += spline.estimated_area_reached; if total_estimated_area_reached > area_limit { return Err(Error::SplinesAreaTooLarge(
total_estimated_area_reached,
area_limit,
));
}
spline.validate_adjacent_point_coincidence()?;
splines.push(spline);
}
self.segment_y_start.clear(); self.segment_y_start.try_reserve(image_ysize as usize + 1)?; self.segment_y_start.resize(image_ysize as usize + 1, 0);
for (i, segment) in segments_by_y.iter().enumerate() { self.segment_indices[i] = segment.1; let y = segment.0; if y < image_ysize { self.segment_y_start[y as usize + 1] += 1;
}
} for y in0..image_ysize { self.segment_y_start[y as usize + 1] += self.segment_y_start[y as usize];
}
Ok(())
}
letmut starting_points = Vec::new(); letmut last_x = 0; letmut last_y = 0; for i in0..num_splines { let unsigned_x =
splines_reader.read_unsigned(&splines_histograms, br, STARTING_POSITION_CONTEXT); let unsigned_y =
splines_reader.read_unsigned(&splines_histograms, br, STARTING_POSITION_CONTEXT);
let (x, y) = if i != 0 {
(
unpack_signed(unsigned_x) as isize + last_x,
unpack_signed(unsigned_y) as isize + last_y,
)
} else {
(unsigned_x as isize, unsigned_y as isize)
}; // It is not in spec, but reasonable limit to avoid overflows. let max_coordinate = x.abs().max(y.abs()); if max_coordinate >= SPLINE_POS_LIMIT { return Err(Error::SplinesCoordinatesLimit(
max_coordinate,
SPLINE_POS_LIMIT,
));
}
starting_points.push(Point {
x: x as f32,
y: y as f32,
});
last_x = x;
last_y = y;
}
let quantization_adjustment =
splines_reader.read_signed(&splines_histograms, br, QUANTIZATION_ADJUSTMENT_CONTEXT);
#[test] fn dct32() -> Result<(), Error> { letmut dct = Dct32::default(); for (i, coeff) in dct.0.iter_mut().enumerate() {
*coeff = 0.05f32 * i as f32;
} // Golden numbers come from libjxl. let want_out = [ 16.7353153229,
-18.6041717529, 7.9931735992,
-7.1250801086, 4.6699867249,
-4.3367614746, 3.2450540066,
-3.0694460869, 2.4446771145,
-2.3350939751, 1.9243829250,
-1.8484034538, 1.5531382561,
-1.4964176416, 1.2701368332,
-1.2254891396, 1.0434474945,
-1.0067725182, 0.8544843197,
-0.8232427835, 0.6916543841,
-0.6642799377, 0.5473306179,
-0.5226536393, 0.4161090851,
-0.3933961987, 0.2940555215,
-0.2726306915, 0.1781132221,
-0.1574717760, 0.0656886101,
-0.0454511642,
]; for (t, want) in want_out.iter().enumerate() { let got_out = dct.continuous_idct(t as f32);
assert_almost_abs_eq(got_out, *want, 1e-4);
}
Ok(())
}
#[test] fn dct32_fast_matches_original() { // Verify that continuous_idct_fast produces the same results as continuous_idct letmut dct = Dct32::default(); for (i, coeff) in dct.0.iter_mut().enumerate() {
*coeff = 0.05f32 * i as f32;
}
for t in0..32 { let t_val = t as f32; let original = dct.continuous_idct(t_val); let precomputed = PrecomputedCosines::new(t_val); let fast = dct.continuous_idct_fast(&precomputed);
assert_almost_abs_eq(fast, original, 1e-5);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.