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 = &current;
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