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
6 #include "lib/jxl/splines.h"
7
8 #include <algorithm>
9 #include <cmath>
10
11 #include "lib/jxl/ans_params.h"
12 #include "lib/jxl/base/status.h"
13 #include "lib/jxl/chroma_from_luma.h"
14 #include "lib/jxl/common.h"
15 #include "lib/jxl/dct_scales.h"
16 #include "lib/jxl/entropy_coder.h"
17 #include "lib/jxl/opsin_params.h"
18
19 #undef HWY_TARGET_INCLUDE
20 #define HWY_TARGET_INCLUDE "lib/jxl/splines.cc"
21 #include <hwy/foreach_target.h>
22 #include <hwy/highway.h>
23
24 #include "lib/jxl/fast_math-inl.h"
25 HWY_BEFORE_NAMESPACE();
26 namespace jxl {
27 namespace HWY_NAMESPACE {
28 namespace {
29
30 // Given a set of DCT coefficients, this returns the result of performing cosine
31 // interpolation on the original samples.
ContinuousIDCT(const float dct[32],float t)32 float ContinuousIDCT(const float dct[32], float t) {
33 // We compute here the DCT-3 of the `dct` vector, rescaled by a factor of
34 // sqrt(32). This is such that an input vector vector {x, 0, ..., 0} produces
35 // a constant result of x. dct[0] was scaled in Dequantize() to allow uniform
36 // treatment of all the coefficients.
37 constexpr float kMultipliers[32] = {
38 kPi / 32 * 0, kPi / 32 * 1, kPi / 32 * 2, kPi / 32 * 3, kPi / 32 * 4,
39 kPi / 32 * 5, kPi / 32 * 6, kPi / 32 * 7, kPi / 32 * 8, kPi / 32 * 9,
40 kPi / 32 * 10, kPi / 32 * 11, kPi / 32 * 12, kPi / 32 * 13, kPi / 32 * 14,
41 kPi / 32 * 15, kPi / 32 * 16, kPi / 32 * 17, kPi / 32 * 18, kPi / 32 * 19,
42 kPi / 32 * 20, kPi / 32 * 21, kPi / 32 * 22, kPi / 32 * 23, kPi / 32 * 24,
43 kPi / 32 * 25, kPi / 32 * 26, kPi / 32 * 27, kPi / 32 * 28, kPi / 32 * 29,
44 kPi / 32 * 30, kPi / 32 * 31,
45 };
46 HWY_CAPPED(float, 32) df;
47 auto result = Zero(df);
48 const auto tandhalf = Set(df, t + 0.5f);
49 for (int i = 0; i < 32; i += Lanes(df)) {
50 auto cos_arg = LoadU(df, kMultipliers + i) * tandhalf;
51 auto cos = FastCosf(df, cos_arg);
52 auto local_res = LoadU(df, dct + i) * cos;
53 result = MulAdd(Set(df, square_root<2>::value), local_res, result);
54 }
55 return GetLane(SumOfLanes(result));
56 }
57
58 template <typename DF>
DrawSegment(DF df,const SplineSegment & segment,bool add,size_t y,size_t x,float * JXL_RESTRICT rows[3])59 void DrawSegment(DF df, const SplineSegment& segment, bool add, size_t y,
60 size_t x, float* JXL_RESTRICT rows[3]) {
61 Rebind<int32_t, DF> di;
62 const auto inv_sigma = Set(df, segment.inv_sigma);
63 const auto half = Set(df, 0.5f);
64 const auto one_over_2s2 = Set(df, 0.353553391f);
65 const auto sigma_over_4_times_intensity =
66 Set(df, segment.sigma_over_4_times_intensity);
67 const auto dx = ConvertTo(df, Iota(di, x)) - Set(df, segment.center_x);
68 const auto dy = Set(df, y - segment.center_y);
69 const auto sqd = MulAdd(dx, dx, dy * dy);
70 const auto distance = Sqrt(sqd);
71 const auto one_dimensional_factor =
72 FastErff(df, MulAdd(distance, half, one_over_2s2) * inv_sigma) -
73 FastErff(df, MulSub(distance, half, one_over_2s2) * inv_sigma);
74 auto local_intensity = sigma_over_4_times_intensity * one_dimensional_factor *
75 one_dimensional_factor;
76 for (size_t c = 0; c < 3; ++c) {
77 const auto cm = Set(df, add ? segment.color[c] : -segment.color[c]);
78 const auto in = LoadU(df, rows[c] + x);
79 StoreU(MulAdd(cm, local_intensity, in), df, rows[c] + x);
80 }
81 }
82
DrawSegment(const SplineSegment & segment,bool add,size_t y,ssize_t x0,ssize_t x1,float * JXL_RESTRICT rows[3])83 void DrawSegment(const SplineSegment& segment, bool add, size_t y, ssize_t x0,
84 ssize_t x1, float* JXL_RESTRICT rows[3]) {
85 ssize_t x = std::max<ssize_t>(x0, segment.xbegin);
86 x1 = std::min<ssize_t>(x1, segment.xend);
87 HWY_FULL(float) df;
88 for (; x + static_cast<ssize_t>(Lanes(df)) <= x1; x += Lanes(df)) {
89 DrawSegment(df, segment, add, y, x, rows);
90 }
91 for (; x < x1; ++x) {
92 DrawSegment(HWY_CAPPED(float, 1)(), segment, add, y, x, rows);
93 }
94 }
95
ComputeSegments(const Spline::Point & center,const float intensity,const float color[3],const float sigma,std::vector<SplineSegment> & segments,std::vector<std::pair<size_t,size_t>> & segments_by_y)96 void ComputeSegments(const Spline::Point& center, const float intensity,
97 const float color[3], const float sigma,
98 std::vector<SplineSegment>& segments,
99 std::vector<std::pair<size_t, size_t>>& segments_by_y) {
100 // Sanity check sigma, inverse sigma and intensity
101 if (!(std::isfinite(sigma) && sigma != 0.0f && std::isfinite(1.0f / sigma) &&
102 std::isfinite(intensity))) {
103 return;
104 }
105 #if JXL_HIGH_PRECISION
106 constexpr float kDistanceExp = 5;
107 #else
108 // About 30% faster.
109 constexpr float kDistanceExp = 3;
110 #endif
111 // We cap from below colors to at least 0.01.
112 float max_color = 0.01f;
113 for (size_t c = 0; c < 3; c++) {
114 max_color = std::max(max_color, std::abs(color[c] * intensity));
115 }
116 // Distance beyond which max_color*intensity*exp(-d^2 / (2 * sigma^2)) drops
117 // below 10^-kDistanceExp.
118 const float maximum_distance =
119 std::sqrt(-2 * sigma * sigma *
120 (std::log(0.1) * kDistanceExp - std::log(max_color)));
121 SplineSegment segment;
122 segment.center_y = center.y;
123 segment.center_x = center.x;
124 memcpy(segment.color, color, sizeof(segment.color));
125 segment.sigma = sigma;
126 segment.inv_sigma = 1.0f / sigma;
127 segment.sigma_over_4_times_intensity = .25f * sigma * intensity;
128 segment.xbegin = std::max<float>(0, center.x - maximum_distance + 0.5f);
129 segment.xend = center.x + maximum_distance + 1.5f; // one-past-the-end
130 segment.maximum_distance = maximum_distance;
131 ssize_t y0 = center.y - maximum_distance + .5f;
132 ssize_t y1 = center.y + maximum_distance + 1.5f; // one-past-the-end
133 for (ssize_t y = std::max<ssize_t>(y0, 0); y < y1; y++) {
134 segments_by_y.emplace_back(y, segments.size());
135 }
136 segments.push_back(segment);
137 }
138
DrawSegments(Image3F * const opsin,const Rect & opsin_rect,const Rect & image_rect,bool add,const SplineSegment * segments,const size_t * segment_indices,const size_t * segment_y_start)139 void DrawSegments(Image3F* const opsin, const Rect& opsin_rect,
140 const Rect& image_rect, bool add,
141 const SplineSegment* segments, const size_t* segment_indices,
142 const size_t* segment_y_start) {
143 JXL_ASSERT(image_rect.ysize() == 1);
144 float* JXL_RESTRICT rows[3] = {
145 opsin_rect.PlaneRow(opsin, 0, 0) - image_rect.x0(),
146 opsin_rect.PlaneRow(opsin, 1, 0) - image_rect.x0(),
147 opsin_rect.PlaneRow(opsin, 2, 0) - image_rect.x0(),
148 };
149 size_t y = image_rect.y0();
150 for (size_t i = segment_y_start[y]; i < segment_y_start[y + 1]; i++) {
151 DrawSegment(segments[segment_indices[i]], add, y, image_rect.x0(),
152 image_rect.x0() + image_rect.xsize(), rows);
153 }
154 }
155
SegmentsFromPoints(const Spline & spline,const std::vector<std::pair<Spline::Point,float>> & points_to_draw,float arc_length,std::vector<SplineSegment> & segments,std::vector<std::pair<size_t,size_t>> & segments_by_y)156 void SegmentsFromPoints(
157 const Spline& spline,
158 const std::vector<std::pair<Spline::Point, float>>& points_to_draw,
159 float arc_length, std::vector<SplineSegment>& segments,
160 std::vector<std::pair<size_t, size_t>>& segments_by_y) {
161 float inv_arc_length = 1.0f / arc_length;
162 int k = 0;
163 for (const auto& point_to_draw : points_to_draw) {
164 const Spline::Point& point = point_to_draw.first;
165 const float multiplier = point_to_draw.second;
166 const float progress_along_arc =
167 std::min(1.f, (k * kDesiredRenderingDistance) * inv_arc_length);
168 ++k;
169 float color[3];
170 for (size_t c = 0; c < 3; ++c) {
171 color[c] =
172 ContinuousIDCT(spline.color_dct[c], (32 - 1) * progress_along_arc);
173 }
174 const float sigma =
175 ContinuousIDCT(spline.sigma_dct, (32 - 1) * progress_along_arc);
176 ComputeSegments(point, multiplier, color, sigma, segments, segments_by_y);
177 }
178 }
179 } // namespace
180 // NOLINTNEXTLINE(google-readability-namespace-comments)
181 } // namespace HWY_NAMESPACE
182 } // namespace jxl
183 HWY_AFTER_NAMESPACE();
184
185 #if HWY_ONCE
186 namespace jxl {
187 HWY_EXPORT(SegmentsFromPoints);
188 HWY_EXPORT(DrawSegments);
189
190 namespace {
191
192 // Maximum number of spline control points per frame is
193 // std::min(kMaxNumControlPoints, xsize * ysize / 2)
194 constexpr size_t kMaxNumControlPoints = 1u << 20u;
195 constexpr size_t kMaxNumControlPointsPerPixelRatio = 2;
196
197 // X, Y, B, sigma.
ColorQuantizationWeight(const int32_t adjustment,const int channel,const int i)198 float ColorQuantizationWeight(const int32_t adjustment, const int channel,
199 const int i) {
200 const float multiplier = adjustment >= 0 ? 1.f + .125f * adjustment
201 : 1.f / (1.f + .125f * -adjustment);
202
203 static constexpr float kChannelWeight[] = {0.0042f, 0.075f, 0.07f, .3333f};
204
205 return multiplier / kChannelWeight[channel];
206 }
207
DecodeAllStartingPoints(std::vector<Spline::Point> * const points,BitReader * const br,ANSSymbolReader * reader,const std::vector<uint8_t> & context_map,size_t num_splines)208 Status DecodeAllStartingPoints(std::vector<Spline::Point>* const points,
209 BitReader* const br, ANSSymbolReader* reader,
210 const std::vector<uint8_t>& context_map,
211 size_t num_splines) {
212 points->clear();
213 points->reserve(num_splines);
214 int64_t last_x = 0;
215 int64_t last_y = 0;
216 for (size_t i = 0; i < num_splines; i++) {
217 int64_t x =
218 reader->ReadHybridUint(kStartingPositionContext, br, context_map);
219 int64_t y =
220 reader->ReadHybridUint(kStartingPositionContext, br, context_map);
221 if (i != 0) {
222 x = UnpackSigned(x) + last_x;
223 y = UnpackSigned(y) + last_y;
224 }
225 points->emplace_back(static_cast<float>(x), static_cast<float>(y));
226 last_x = x;
227 last_y = y;
228 }
229 return true;
230 }
231
232 struct Vector {
233 float x, y;
operator -jxl::__anone46ec0e70211::Vector234 Vector operator-() const { return {-x, -y}; }
operator +jxl::__anone46ec0e70211::Vector235 Vector operator+(const Vector& other) const {
236 return {x + other.x, y + other.y};
237 }
SquaredNormjxl::__anone46ec0e70211::Vector238 float SquaredNorm() const { return x * x + y * y; }
239 };
operator *(const float k,const Vector & vec)240 Vector operator*(const float k, const Vector& vec) {
241 return {k * vec.x, k * vec.y};
242 }
243
operator +(const Spline::Point & p,const Vector & vec)244 Spline::Point operator+(const Spline::Point& p, const Vector& vec) {
245 return {p.x + vec.x, p.y + vec.y};
246 }
operator -(const Spline::Point & p,const Vector & vec)247 Spline::Point operator-(const Spline::Point& p, const Vector& vec) {
248 return p + -vec;
249 }
operator -(const Spline::Point & a,const Spline::Point & b)250 Vector operator-(const Spline::Point& a, const Spline::Point& b) {
251 return {a.x - b.x, a.y - b.y};
252 }
253
DrawCentripetalCatmullRomSpline(std::vector<Spline::Point> points)254 std::vector<Spline::Point> DrawCentripetalCatmullRomSpline(
255 std::vector<Spline::Point> points) {
256 if (points.size() <= 1) return points;
257 // Number of points to compute between each control point.
258 static constexpr int kNumPoints = 16;
259 std::vector<Spline::Point> result;
260 result.reserve((points.size() - 1) * kNumPoints + 1);
261 points.insert(points.begin(), points[0] + (points[0] - points[1]));
262 points.push_back(points[points.size() - 1] +
263 (points[points.size() - 1] - points[points.size() - 2]));
264 // points has at least 4 elements at this point.
265 for (size_t start = 0; start < points.size() - 3; ++start) {
266 // 4 of them are used, and we draw from p[1] to p[2].
267 const Spline::Point* const p = &points[start];
268 result.push_back(p[1]);
269 float t[4] = {0};
270 for (int k = 1; k < 4; ++k) {
271 t[k] = std::sqrt(hypotf(p[k].x - p[k - 1].x, p[k].y - p[k - 1].y)) +
272 t[k - 1];
273 }
274 for (int i = 1; i < kNumPoints; ++i) {
275 const float tt =
276 t[1] + (static_cast<float>(i) / kNumPoints) * (t[2] - t[1]);
277 Spline::Point a[3];
278 for (int k = 0; k < 3; ++k) {
279 a[k] = p[k] + ((tt - t[k]) / (t[k + 1] - t[k])) * (p[k + 1] - p[k]);
280 }
281 Spline::Point b[2];
282 for (int k = 0; k < 2; ++k) {
283 b[k] = a[k] + ((tt - t[k]) / (t[k + 2] - t[k])) * (a[k + 1] - a[k]);
284 }
285 result.push_back(b[0] + ((tt - t[1]) / (t[2] - t[1])) * (b[1] - b[0]));
286 }
287 }
288 result.push_back(points[points.size() - 2]);
289 return result;
290 }
291
292 // Move along the line segments defined by `points`, `kDesiredRenderingDistance`
293 // pixels at a time, and call `functor` with each point and the actual distance
294 // to the previous point (which will always be kDesiredRenderingDistance except
295 // possibly for the very last point).
296 template <typename Points, typename Functor>
ForEachEquallySpacedPoint(const Points & points,const Functor & functor)297 void ForEachEquallySpacedPoint(const Points& points, const Functor& functor) {
298 JXL_ASSERT(!points.empty());
299 Spline::Point current = points.front();
300 functor(current, kDesiredRenderingDistance);
301 auto next = points.begin();
302 while (next != points.end()) {
303 const Spline::Point* previous = ¤t;
304 float arclength_from_previous = 0.f;
305 for (;;) {
306 if (next == points.end()) {
307 functor(*previous, arclength_from_previous);
308 return;
309 }
310 const float arclength_to_next =
311 std::sqrt((*next - *previous).SquaredNorm());
312 if (arclength_from_previous + arclength_to_next >=
313 kDesiredRenderingDistance) {
314 current =
315 *previous + ((kDesiredRenderingDistance - arclength_from_previous) /
316 arclength_to_next) *
317 (*next - *previous);
318 functor(current, kDesiredRenderingDistance);
319 break;
320 }
321 arclength_from_previous += arclength_to_next;
322 previous = &*next;
323 ++next;
324 }
325 }
326 }
327
328 } // namespace
329
QuantizedSpline(const Spline & original,const int32_t quantization_adjustment,float ytox,float ytob)330 QuantizedSpline::QuantizedSpline(const Spline& original,
331 const int32_t quantization_adjustment,
332 float ytox, float ytob) {
333 JXL_ASSERT(!original.control_points.empty());
334 control_points_.reserve(original.control_points.size() - 1);
335 const Spline::Point& starting_point = original.control_points.front();
336 int previous_x = static_cast<int>(roundf(starting_point.x)),
337 previous_y = static_cast<int>(roundf(starting_point.y));
338 int previous_delta_x = 0, previous_delta_y = 0;
339 for (auto it = original.control_points.begin() + 1;
340 it != original.control_points.end(); ++it) {
341 const int new_x = static_cast<int>(roundf(it->x));
342 const int new_y = static_cast<int>(roundf(it->y));
343 const int new_delta_x = new_x - previous_x;
344 const int new_delta_y = new_y - previous_y;
345 control_points_.emplace_back(new_delta_x - previous_delta_x,
346 new_delta_y - previous_delta_y);
347 previous_delta_x = new_delta_x;
348 previous_delta_y = new_delta_y;
349 previous_x = new_x;
350 previous_y = new_y;
351 }
352
353 for (int c = 0; c < 3; ++c) {
354 float factor = c == 0 ? ytox : c == 1 ? 0 : ytob;
355 for (int i = 0; i < 32; ++i) {
356 const float coefficient =
357 original.color_dct[c][i] -
358 factor * color_dct_[1][i] /
359 ColorQuantizationWeight(quantization_adjustment, 1, i);
360 color_dct_[c][i] = static_cast<int>(
361 roundf(coefficient *
362 ColorQuantizationWeight(quantization_adjustment, c, i)));
363 }
364 }
365 for (int i = 0; i < 32; ++i) {
366 sigma_dct_[i] = static_cast<int>(
367 roundf(original.sigma_dct[i] *
368 ColorQuantizationWeight(quantization_adjustment, 3, i)));
369 }
370 }
371
Dequantize(const Spline::Point & starting_point,const int32_t quantization_adjustment,float ytox,float ytob) const372 Spline QuantizedSpline::Dequantize(const Spline::Point& starting_point,
373 const int32_t quantization_adjustment,
374 float ytox, float ytob) const {
375 Spline result;
376
377 result.control_points.reserve(control_points_.size() + 1);
378 int current_x = static_cast<int>(roundf(starting_point.x)),
379 current_y = static_cast<int>(roundf(starting_point.y));
380 result.control_points.push_back(Spline::Point{static_cast<float>(current_x),
381 static_cast<float>(current_y)});
382 int current_delta_x = 0, current_delta_y = 0;
383 for (const auto& point : control_points_) {
384 current_delta_x += point.first;
385 current_delta_y += point.second;
386 current_x += current_delta_x;
387 current_y += current_delta_y;
388 result.control_points.push_back(Spline::Point{
389 static_cast<float>(current_x), static_cast<float>(current_y)});
390 }
391
392 for (int c = 0; c < 3; ++c) {
393 for (int i = 0; i < 32; ++i) {
394 result.color_dct[c][i] =
395 color_dct_[c][i] * (i == 0 ? 1.0f / square_root<2>::value : 1.0f) /
396 ColorQuantizationWeight(quantization_adjustment, c, i);
397 }
398 }
399 for (int i = 0; i < 32; ++i) {
400 result.color_dct[0][i] += ytox * result.color_dct[1][i];
401 result.color_dct[2][i] += ytob * result.color_dct[1][i];
402 }
403 for (int i = 0; i < 32; ++i) {
404 result.sigma_dct[i] =
405 sigma_dct_[i] * (i == 0 ? 1.0f / square_root<2>::value : 1.0f) /
406 ColorQuantizationWeight(quantization_adjustment, 3, i);
407 }
408
409 return result;
410 }
411
Decode(const std::vector<uint8_t> & context_map,ANSSymbolReader * const decoder,BitReader * const br,size_t max_control_points,size_t * total_num_control_points)412 Status QuantizedSpline::Decode(const std::vector<uint8_t>& context_map,
413 ANSSymbolReader* const decoder,
414 BitReader* const br, size_t max_control_points,
415 size_t* total_num_control_points) {
416 const size_t num_control_points =
417 decoder->ReadHybridUint(kNumControlPointsContext, br, context_map);
418 *total_num_control_points += num_control_points;
419 if (*total_num_control_points > max_control_points) {
420 return JXL_FAILURE("Too many control points: %zu",
421 *total_num_control_points);
422 }
423 control_points_.resize(num_control_points);
424 for (std::pair<int64_t, int64_t>& control_point : control_points_) {
425 control_point.first = UnpackSigned(
426 decoder->ReadHybridUint(kControlPointsContext, br, context_map));
427 control_point.second = UnpackSigned(
428 decoder->ReadHybridUint(kControlPointsContext, br, context_map));
429 }
430
431 const auto decode_dct = [decoder, br, &context_map](int dct[32]) -> Status {
432 for (int i = 0; i < 32; ++i) {
433 dct[i] =
434 UnpackSigned(decoder->ReadHybridUint(kDCTContext, br, context_map));
435 }
436 return true;
437 };
438 for (int c = 0; c < 3; ++c) {
439 JXL_RETURN_IF_ERROR(decode_dct(color_dct_[c]));
440 }
441 JXL_RETURN_IF_ERROR(decode_dct(sigma_dct_));
442 return true;
443 }
444
Clear()445 void Splines::Clear() {
446 quantization_adjustment_ = 0;
447 splines_.clear();
448 starting_points_.clear();
449 segments_.clear();
450 segment_indices_.clear();
451 segment_y_start_.clear();
452 }
453
Decode(jxl::BitReader * br,size_t num_pixels)454 Status Splines::Decode(jxl::BitReader* br, size_t num_pixels) {
455 std::vector<uint8_t> context_map;
456 ANSCode code;
457 JXL_RETURN_IF_ERROR(
458 DecodeHistograms(br, kNumSplineContexts, &code, &context_map));
459 ANSSymbolReader decoder(&code, br);
460 const size_t num_splines =
461 1 + decoder.ReadHybridUint(kNumSplinesContext, br, context_map);
462 size_t max_control_points = std::min(
463 kMaxNumControlPoints, num_pixels / kMaxNumControlPointsPerPixelRatio);
464 if (num_splines > max_control_points) {
465 return JXL_FAILURE("Too many splines: %zu", num_splines);
466 }
467 JXL_RETURN_IF_ERROR(DecodeAllStartingPoints(&starting_points_, br, &decoder,
468 context_map, num_splines));
469
470 quantization_adjustment_ = UnpackSigned(
471 decoder.ReadHybridUint(kQuantizationAdjustmentContext, br, context_map));
472
473 splines_.clear();
474 splines_.reserve(num_splines);
475 size_t num_control_points = num_splines;
476 for (size_t i = 0; i < num_splines; ++i) {
477 QuantizedSpline spline;
478 JXL_RETURN_IF_ERROR(spline.Decode(context_map, &decoder, br,
479 max_control_points, &num_control_points));
480 splines_.push_back(std::move(spline));
481 }
482
483 JXL_RETURN_IF_ERROR(decoder.CheckANSFinalState());
484
485 if (!HasAny()) {
486 return JXL_FAILURE("Decoded splines but got none");
487 }
488
489 return true;
490 }
491
AddTo(Image3F * const opsin,const Rect & opsin_rect,const Rect & image_rect) const492 void Splines::AddTo(Image3F* const opsin, const Rect& opsin_rect,
493 const Rect& image_rect) const {
494 return Apply</*add=*/true>(opsin, opsin_rect, image_rect);
495 }
496
SubtractFrom(Image3F * const opsin) const497 void Splines::SubtractFrom(Image3F* const opsin) const {
498 return Apply</*add=*/false>(opsin, Rect(*opsin), Rect(*opsin));
499 }
500
InitializeDrawCache(size_t image_xsize,size_t image_ysize,const ColorCorrelationMap & cmap)501 Status Splines::InitializeDrawCache(size_t image_xsize, size_t image_ysize,
502 const ColorCorrelationMap& cmap) {
503 // TODO(veluca): avoid storing segments that are entirely outside image
504 // boundaries.
505 segments_.clear();
506 segment_indices_.clear();
507 segment_y_start_.clear();
508 std::vector<std::pair<size_t, size_t>> segments_by_y;
509 for (size_t i = 0; i < splines_.size(); ++i) {
510 const Spline spline =
511 splines_[i].Dequantize(starting_points_[i], quantization_adjustment_,
512 cmap.YtoXRatio(0), cmap.YtoBRatio(0));
513 if (std::adjacent_find(spline.control_points.begin(),
514 spline.control_points.end()) !=
515 spline.control_points.end()) {
516 return JXL_FAILURE("identical successive control points in spline %zu",
517 i);
518 }
519 std::vector<std::pair<Spline::Point, float>> points_to_draw;
520 ForEachEquallySpacedPoint(
521 DrawCentripetalCatmullRomSpline(spline.control_points),
522 [&](const Spline::Point& point, const float multiplier) {
523 points_to_draw.emplace_back(point, multiplier);
524 });
525 const float arc_length =
526 (points_to_draw.size() - 2) * kDesiredRenderingDistance +
527 points_to_draw.back().second;
528 if (arc_length <= 0.f) {
529 // This spline wouldn't have any effect.
530 continue;
531 }
532 HWY_DYNAMIC_DISPATCH(SegmentsFromPoints)
533 (spline, points_to_draw, arc_length, segments_, segments_by_y);
534 }
535 std::sort(segments_by_y.begin(), segments_by_y.end());
536 segment_indices_.resize(segments_by_y.size());
537 segment_y_start_.resize(image_ysize + 1);
538 for (size_t i = 0; i < segments_by_y.size(); i++) {
539 segment_indices_[i] = segments_by_y[i].second;
540 size_t y = segments_by_y[i].first;
541 if (y < image_ysize) {
542 segment_y_start_[y + 1]++;
543 }
544 }
545 for (size_t y = 0; y < image_ysize; y++) {
546 segment_y_start_[y + 1] += segment_y_start_[y];
547 }
548 return true;
549 }
550
551 template <bool add>
Apply(Image3F * const opsin,const Rect & opsin_rect,const Rect & image_rect) const552 void Splines::Apply(Image3F* const opsin, const Rect& opsin_rect,
553 const Rect& image_rect) const {
554 if (segments_.empty()) return;
555 for (size_t iy = 0; iy < image_rect.ysize(); iy++) {
556 HWY_DYNAMIC_DISPATCH(DrawSegments)
557 (opsin, opsin_rect.Line(iy), image_rect.Line(iy), add, segments_.data(),
558 segment_indices_.data(), segment_y_start_.data());
559 }
560 }
561
562 } // namespace jxl
563 #endif // HWY_ONCE
564