tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

splines.cc (28564B)


      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 <jxl/memory_manager.h>
      9 
     10 #include <algorithm>
     11 #include <cinttypes>  // PRIu64
     12 #include <cmath>
     13 #include <limits>
     14 
     15 #include "lib/jxl/base/common.h"
     16 #include "lib/jxl/base/printf_macros.h"
     17 #include "lib/jxl/base/rect.h"
     18 #include "lib/jxl/base/status.h"
     19 #include "lib/jxl/chroma_from_luma.h"
     20 #include "lib/jxl/common.h"  // JXL_HIGH_PRECISION
     21 #include "lib/jxl/dct_scales.h"
     22 #include "lib/jxl/dec_ans.h"
     23 #include "lib/jxl/dec_bit_reader.h"
     24 #include "lib/jxl/pack_signed.h"
     25 
     26 #undef HWY_TARGET_INCLUDE
     27 #define HWY_TARGET_INCLUDE "lib/jxl/splines.cc"
     28 #include <hwy/foreach_target.h>
     29 #include <hwy/highway.h>
     30 
     31 #include "lib/jxl/base/fast_math-inl.h"
     32 HWY_BEFORE_NAMESPACE();
     33 namespace jxl {
     34 namespace HWY_NAMESPACE {
     35 namespace {
     36 
     37 // These templates are not found via ADL.
     38 using hwy::HWY_NAMESPACE::Mul;
     39 using hwy::HWY_NAMESPACE::MulAdd;
     40 using hwy::HWY_NAMESPACE::MulSub;
     41 using hwy::HWY_NAMESPACE::Sqrt;
     42 using hwy::HWY_NAMESPACE::Sub;
     43 
     44 // Given a set of DCT coefficients, this returns the result of performing cosine
     45 // interpolation on the original samples.
     46 float ContinuousIDCT(const Dct32& dct, const float t) {
     47  // We compute here the DCT-3 of the `dct` vector, rescaled by a factor of
     48  // sqrt(32). This is such that an input vector vector {x, 0, ..., 0} produces
     49  // a constant result of x. dct[0] was scaled in Dequantize() to allow uniform
     50  // treatment of all the coefficients.
     51  constexpr float kMultipliers[32] = {
     52      kPi / 32 * 0,  kPi / 32 * 1,  kPi / 32 * 2,  kPi / 32 * 3,  kPi / 32 * 4,
     53      kPi / 32 * 5,  kPi / 32 * 6,  kPi / 32 * 7,  kPi / 32 * 8,  kPi / 32 * 9,
     54      kPi / 32 * 10, kPi / 32 * 11, kPi / 32 * 12, kPi / 32 * 13, kPi / 32 * 14,
     55      kPi / 32 * 15, kPi / 32 * 16, kPi / 32 * 17, kPi / 32 * 18, kPi / 32 * 19,
     56      kPi / 32 * 20, kPi / 32 * 21, kPi / 32 * 22, kPi / 32 * 23, kPi / 32 * 24,
     57      kPi / 32 * 25, kPi / 32 * 26, kPi / 32 * 27, kPi / 32 * 28, kPi / 32 * 29,
     58      kPi / 32 * 30, kPi / 32 * 31,
     59  };
     60  HWY_CAPPED(float, 32) df;
     61  auto result = Zero(df);
     62  const auto tandhalf = Set(df, t + 0.5f);
     63  for (int i = 0; i < 32; i += Lanes(df)) {
     64    auto cos_arg = Mul(LoadU(df, kMultipliers + i), tandhalf);
     65    auto cos = FastCosf(df, cos_arg);
     66    auto local_res = Mul(LoadU(df, dct.data() + i), cos);
     67    result = MulAdd(Set(df, kSqrt2), local_res, result);
     68  }
     69  return GetLane(SumOfLanes(df, result));
     70 }
     71 
     72 template <typename DF>
     73 void DrawSegment(DF df, const SplineSegment& segment, const bool add,
     74                 const size_t y, const size_t x, float* JXL_RESTRICT rows[3]) {
     75  Rebind<int32_t, DF> di;
     76  const auto inv_sigma = Set(df, segment.inv_sigma);
     77  const auto half = Set(df, 0.5f);
     78  const auto one_over_2s2 = Set(df, 0.353553391f);
     79  const auto sigma_over_4_times_intensity =
     80      Set(df, segment.sigma_over_4_times_intensity);
     81  const auto dx = Sub(ConvertTo(df, Iota(di, x)), Set(df, segment.center_x));
     82  const auto dy = Set(df, y - segment.center_y);
     83  const auto sqd = MulAdd(dx, dx, Mul(dy, dy));
     84  const auto distance = Sqrt(sqd);
     85  const auto one_dimensional_factor =
     86      Sub(FastErff(df, Mul(MulAdd(distance, half, one_over_2s2), inv_sigma)),
     87          FastErff(df, Mul(MulSub(distance, half, one_over_2s2), inv_sigma)));
     88  auto local_intensity =
     89      Mul(sigma_over_4_times_intensity,
     90          Mul(one_dimensional_factor, one_dimensional_factor));
     91  for (size_t c = 0; c < 3; ++c) {
     92    const auto cm = Set(df, add ? segment.color[c] : -segment.color[c]);
     93    const auto in = LoadU(df, rows[c] + x);
     94    StoreU(MulAdd(cm, local_intensity, in), df, rows[c] + x);
     95  }
     96 }
     97 
     98 void DrawSegment(const SplineSegment& segment, const bool add, const size_t y,
     99                 const ssize_t x0, ssize_t x1, float* JXL_RESTRICT rows[3]) {
    100  ssize_t x = std::max<ssize_t>(
    101      x0, std::llround(segment.center_x - segment.maximum_distance));
    102  // one-past-the-end
    103  x1 = std::min<ssize_t>(
    104      x1, std::llround(segment.center_x + segment.maximum_distance) + 1);
    105  HWY_FULL(float) df;
    106  for (; x + static_cast<ssize_t>(Lanes(df)) <= x1; x += Lanes(df)) {
    107    DrawSegment(df, segment, add, y, x, rows);
    108  }
    109  for (; x < x1; ++x) {
    110    DrawSegment(HWY_CAPPED(float, 1)(), segment, add, y, x, rows);
    111  }
    112 }
    113 
    114 void ComputeSegments(const Spline::Point& center, const float intensity,
    115                     const float color[3], const float sigma,
    116                     std::vector<SplineSegment>& segments,
    117                     std::vector<std::pair<size_t, size_t>>& segments_by_y) {
    118  // Sanity check sigma, inverse sigma and intensity
    119  if (!(std::isfinite(sigma) && sigma != 0.0f && std::isfinite(1.0f / sigma) &&
    120        std::isfinite(intensity))) {
    121    return;
    122  }
    123 #if JXL_HIGH_PRECISION
    124  constexpr float kDistanceExp = 5;
    125 #else
    126  // About 30% faster.
    127  constexpr float kDistanceExp = 3;
    128 #endif
    129  // We cap from below colors to at least 0.01.
    130  float max_color = 0.01f;
    131  for (size_t c = 0; c < 3; c++) {
    132    max_color = std::max(max_color, std::abs(color[c] * intensity));
    133  }
    134  // Distance beyond which max_color*intensity*exp(-d^2 / (2 * sigma^2)) drops
    135  // below 10^-kDistanceExp.
    136  const float maximum_distance =
    137      std::sqrt(-2 * sigma * sigma *
    138                (std::log(0.1) * kDistanceExp - std::log(max_color)));
    139  SplineSegment segment;
    140  segment.center_y = center.y;
    141  segment.center_x = center.x;
    142  memcpy(segment.color, color, sizeof(segment.color));
    143  segment.inv_sigma = 1.0f / sigma;
    144  segment.sigma_over_4_times_intensity = .25f * sigma * intensity;
    145  segment.maximum_distance = maximum_distance;
    146  ssize_t y0 = std::llround(center.y - maximum_distance);
    147  ssize_t y1 =
    148      std::llround(center.y + maximum_distance) + 1;  // one-past-the-end
    149  for (ssize_t y = std::max<ssize_t>(y0, 0); y < y1; y++) {
    150    segments_by_y.emplace_back(y, segments.size());
    151  }
    152  segments.push_back(segment);
    153 }
    154 
    155 void DrawSegments(float* JXL_RESTRICT row_x, float* JXL_RESTRICT row_y,
    156                  float* JXL_RESTRICT row_b, size_t y, size_t x0, size_t x1,
    157                  const bool add, const SplineSegment* segments,
    158                  const size_t* segment_indices,
    159                  const size_t* segment_y_start) {
    160  float* JXL_RESTRICT rows[3] = {row_x - x0, row_y - x0, row_b - x0};
    161  for (size_t i = segment_y_start[y]; i < segment_y_start[y + 1]; i++) {
    162    DrawSegment(segments[segment_indices[i]], add, y, x0, x1, rows);
    163  }
    164 }
    165 
    166 void SegmentsFromPoints(
    167    const Spline& spline,
    168    const std::vector<std::pair<Spline::Point, float>>& points_to_draw,
    169    const float arc_length, std::vector<SplineSegment>& segments,
    170    std::vector<std::pair<size_t, size_t>>& segments_by_y) {
    171  const float inv_arc_length = 1.0f / arc_length;
    172  int k = 0;
    173  for (const auto& point_to_draw : points_to_draw) {
    174    const Spline::Point& point = point_to_draw.first;
    175    const float multiplier = point_to_draw.second;
    176    const float progress_along_arc =
    177        std::min(1.f, (k * kDesiredRenderingDistance) * inv_arc_length);
    178    ++k;
    179    float color[3];
    180    for (size_t c = 0; c < 3; ++c) {
    181      color[c] =
    182          ContinuousIDCT(spline.color_dct[c], (32 - 1) * progress_along_arc);
    183    }
    184    const float sigma =
    185        ContinuousIDCT(spline.sigma_dct, (32 - 1) * progress_along_arc);
    186    ComputeSegments(point, multiplier, color, sigma, segments, segments_by_y);
    187  }
    188 }
    189 }  // namespace
    190 // NOLINTNEXTLINE(google-readability-namespace-comments)
    191 }  // namespace HWY_NAMESPACE
    192 }  // namespace jxl
    193 HWY_AFTER_NAMESPACE();
    194 
    195 #if HWY_ONCE
    196 namespace jxl {
    197 HWY_EXPORT(SegmentsFromPoints);
    198 HWY_EXPORT(DrawSegments);
    199 
    200 namespace {
    201 
    202 // It is not in spec, but reasonable limit to avoid overflows.
    203 template <typename T>
    204 Status ValidateSplinePointPos(const T& x, const T& y) {
    205  constexpr T kSplinePosLimit = 1u << 23;
    206  if ((x >= kSplinePosLimit) || (x <= -kSplinePosLimit) ||
    207      (y >= kSplinePosLimit) || (y <= -kSplinePosLimit)) {
    208    return JXL_FAILURE("Spline coordinates out of bounds");
    209  }
    210  return true;
    211 }
    212 
    213 // Maximum number of spline control points per frame is
    214 //   std::min(kMaxNumControlPoints, xsize * ysize / 2)
    215 constexpr size_t kMaxNumControlPoints = 1u << 20u;
    216 constexpr size_t kMaxNumControlPointsPerPixelRatio = 2;
    217 
    218 float AdjustedQuant(const int32_t adjustment) {
    219  return (adjustment >= 0) ? (1.f + .125f * adjustment)
    220                           : 1.f / (1.f - .125f * adjustment);
    221 }
    222 
    223 float InvAdjustedQuant(const int32_t adjustment) {
    224  return (adjustment >= 0) ? 1.f / (1.f + .125f * adjustment)
    225                           : (1.f - .125f * adjustment);
    226 }
    227 
    228 // X, Y, B, sigma.
    229 constexpr float kChannelWeight[] = {0.0042f, 0.075f, 0.07f, .3333f};
    230 
    231 Status DecodeAllStartingPoints(std::vector<Spline::Point>* const points,
    232                               BitReader* const br, ANSSymbolReader* reader,
    233                               const std::vector<uint8_t>& context_map,
    234                               const size_t num_splines) {
    235  points->clear();
    236  points->reserve(num_splines);
    237  int64_t last_x = 0;
    238  int64_t last_y = 0;
    239  for (size_t i = 0; i < num_splines; i++) {
    240    int64_t x =
    241        reader->ReadHybridUint(kStartingPositionContext, br, context_map);
    242    int64_t y =
    243        reader->ReadHybridUint(kStartingPositionContext, br, context_map);
    244    if (i != 0) {
    245      x = UnpackSigned(x) + last_x;
    246      y = UnpackSigned(y) + last_y;
    247    }
    248    JXL_RETURN_IF_ERROR(ValidateSplinePointPos(x, y));
    249    points->emplace_back(static_cast<float>(x), static_cast<float>(y));
    250    last_x = x;
    251    last_y = y;
    252  }
    253  return true;
    254 }
    255 
    256 struct Vector {
    257  float x, y;
    258  Vector operator-() const { return {-x, -y}; }
    259  Vector operator+(const Vector& other) const {
    260    return {x + other.x, y + other.y};
    261  }
    262  float SquaredNorm() const { return x * x + y * y; }
    263 };
    264 Vector operator*(const float k, const Vector& vec) {
    265  return {k * vec.x, k * vec.y};
    266 }
    267 
    268 Spline::Point operator+(const Spline::Point& p, const Vector& vec) {
    269  return {p.x + vec.x, p.y + vec.y};
    270 }
    271 Vector operator-(const Spline::Point& a, const Spline::Point& b) {
    272  return {a.x - b.x, a.y - b.y};
    273 }
    274 
    275 // TODO(eustas): avoid making a copy of "points".
    276 void DrawCentripetalCatmullRomSpline(std::vector<Spline::Point> points,
    277                                     std::vector<Spline::Point>& result) {
    278  if (points.empty()) return;
    279  if (points.size() == 1) {
    280    result.push_back(points[0]);
    281    return;
    282  }
    283  // Number of points to compute between each control point.
    284  static constexpr int kNumPoints = 16;
    285  result.reserve((points.size() - 1) * kNumPoints + 1);
    286  points.insert(points.begin(), points[0] + (points[0] - points[1]));
    287  points.push_back(points[points.size() - 1] +
    288                   (points[points.size() - 1] - points[points.size() - 2]));
    289  // points has at least 4 elements at this point.
    290  for (size_t start = 0; start < points.size() - 3; ++start) {
    291    // 4 of them are used, and we draw from p[1] to p[2].
    292    const Spline::Point* const p = &points[start];
    293    result.push_back(p[1]);
    294    float d[3];
    295    float t[4];
    296    t[0] = 0;
    297    for (int k = 0; k < 3; ++k) {
    298      // TODO(eustas): for each segment delta is calculated 3 times...
    299      // TODO(eustas): restrict d[k] with reasonable limit and spec it.
    300      d[k] = std::sqrt(hypotf(p[k + 1].x - p[k].x, p[k + 1].y - p[k].y));
    301      t[k + 1] = t[k] + d[k];
    302    }
    303    for (int i = 1; i < kNumPoints; ++i) {
    304      const float tt = d[0] + (static_cast<float>(i) / kNumPoints) * d[1];
    305      Spline::Point a[3];
    306      for (int k = 0; k < 3; ++k) {
    307        // TODO(eustas): reciprocal multiplication would be faster.
    308        a[k] = p[k] + ((tt - t[k]) / d[k]) * (p[k + 1] - p[k]);
    309      }
    310      Spline::Point b[2];
    311      for (int k = 0; k < 2; ++k) {
    312        b[k] = a[k] + ((tt - t[k]) / (d[k] + d[k + 1])) * (a[k + 1] - a[k]);
    313      }
    314      result.push_back(b[0] + ((tt - t[1]) / d[1]) * (b[1] - b[0]));
    315    }
    316  }
    317  result.push_back(points[points.size() - 2]);
    318 }
    319 
    320 // Move along the line segments defined by `points`, `kDesiredRenderingDistance`
    321 // pixels at a time, and call `functor` with each point and the actual distance
    322 // to the previous point (which will always be kDesiredRenderingDistance except
    323 // possibly for the very last point).
    324 // TODO(eustas): this method always adds the last point, but never the first
    325 //               (unless those are one); I believe both ends matter.
    326 template <typename Points, typename Functor>
    327 Status ForEachEquallySpacedPoint(const Points& points, const Functor& functor) {
    328  JXL_ENSURE(!points.empty());
    329  Spline::Point current = points.front();
    330  functor(current, kDesiredRenderingDistance);
    331  auto next = points.begin();
    332  while (next != points.end()) {
    333    const Spline::Point* previous = &current;
    334    float arclength_from_previous = 0.f;
    335    for (;;) {
    336      if (next == points.end()) {
    337        functor(*previous, arclength_from_previous);
    338        return true;
    339      }
    340      const float arclength_to_next =
    341          std::sqrt((*next - *previous).SquaredNorm());
    342      if (arclength_from_previous + arclength_to_next >=
    343          kDesiredRenderingDistance) {
    344        current =
    345            *previous + ((kDesiredRenderingDistance - arclength_from_previous) /
    346                         arclength_to_next) *
    347                            (*next - *previous);
    348        functor(current, kDesiredRenderingDistance);
    349        break;
    350      }
    351      arclength_from_previous += arclength_to_next;
    352      previous = &*next;
    353      ++next;
    354    }
    355  }
    356  return true;
    357 }
    358 
    359 }  // namespace
    360 
    361 StatusOr<QuantizedSpline> QuantizedSpline::Create(
    362    const Spline& original, const int32_t quantization_adjustment,
    363    const float y_to_x, const float y_to_b) {
    364  JXL_ENSURE(!original.control_points.empty());
    365  QuantizedSpline result;
    366  result.control_points_.reserve(original.control_points.size() - 1);
    367  const Spline::Point& starting_point = original.control_points.front();
    368  int previous_x = static_cast<int>(std::roundf(starting_point.x));
    369  int previous_y = static_cast<int>(std::roundf(starting_point.y));
    370  int previous_delta_x = 0;
    371  int previous_delta_y = 0;
    372  for (auto it = original.control_points.begin() + 1;
    373       it != original.control_points.end(); ++it) {
    374    const int new_x = static_cast<int>(std::roundf(it->x));
    375    const int new_y = static_cast<int>(std::roundf(it->y));
    376    const int new_delta_x = new_x - previous_x;
    377    const int new_delta_y = new_y - previous_y;
    378    result.control_points_.emplace_back(new_delta_x - previous_delta_x,
    379                                        new_delta_y - previous_delta_y);
    380    previous_delta_x = new_delta_x;
    381    previous_delta_y = new_delta_y;
    382    previous_x = new_x;
    383    previous_y = new_y;
    384  }
    385 
    386  const auto to_int = [](float v) -> int {
    387    // Maximal int representable with float.
    388    constexpr float kMax = std::numeric_limits<int>::max() - 127;
    389    constexpr float kMin = -kMax;
    390    return static_cast<int>(std::roundf(Clamp1(v, kMin, kMax)));
    391  };
    392 
    393  const auto quant = AdjustedQuant(quantization_adjustment);
    394  const auto inv_quant = InvAdjustedQuant(quantization_adjustment);
    395  for (int c : {1, 0, 2}) {
    396    float factor = (c == 0) ? y_to_x : (c == 1) ? 0 : y_to_b;
    397    for (int i = 0; i < 32; ++i) {
    398      const float dct_factor = (i == 0) ? kSqrt2 : 1.0f;
    399      const float inv_dct_factor = (i == 0) ? kSqrt0_5 : 1.0f;
    400      auto restored_y = result.color_dct_[1][i] * inv_dct_factor *
    401                        kChannelWeight[1] * inv_quant;
    402      auto decorrelated = original.color_dct[c][i] - factor * restored_y;
    403      result.color_dct_[c][i] =
    404          to_int(decorrelated * dct_factor * quant / kChannelWeight[c]);
    405    }
    406  }
    407  for (int i = 0; i < 32; ++i) {
    408    const float dct_factor = (i == 0) ? kSqrt2 : 1.0f;
    409    result.sigma_dct_[i] =
    410        to_int(original.sigma_dct[i] * dct_factor * quant / kChannelWeight[3]);
    411  }
    412  return result;
    413 }
    414 
    415 Status QuantizedSpline::Dequantize(const Spline::Point& starting_point,
    416                                   const int32_t quantization_adjustment,
    417                                   const float y_to_x, const float y_to_b,
    418                                   const uint64_t image_size,
    419                                   uint64_t* total_estimated_area_reached,
    420                                   Spline& result) const {
    421  constexpr uint64_t kOne = static_cast<uint64_t>(1);
    422  const uint64_t area_limit =
    423      std::min(1024 * image_size + (kOne << 32), kOne << 42);
    424 
    425  result.control_points.clear();
    426  result.control_points.reserve(control_points_.size() + 1);
    427  float px = std::roundf(starting_point.x);
    428  float py = std::roundf(starting_point.y);
    429  JXL_RETURN_IF_ERROR(ValidateSplinePointPos(px, py));
    430  int current_x = static_cast<int>(px);
    431  int current_y = static_cast<int>(py);
    432  result.control_points.emplace_back(static_cast<float>(current_x),
    433                                     static_cast<float>(current_y));
    434  int current_delta_x = 0;
    435  int current_delta_y = 0;
    436  uint64_t manhattan_distance = 0;
    437  for (const auto& point : control_points_) {
    438    current_delta_x += point.first;
    439    current_delta_y += point.second;
    440    manhattan_distance += std::abs(current_delta_x) + std::abs(current_delta_y);
    441    if (manhattan_distance > area_limit) {
    442      return JXL_FAILURE("Too large manhattan_distance reached: %" PRIu64,
    443                         manhattan_distance);
    444    }
    445    JXL_RETURN_IF_ERROR(
    446        ValidateSplinePointPos(current_delta_x, current_delta_y));
    447    current_x += current_delta_x;
    448    current_y += current_delta_y;
    449    JXL_RETURN_IF_ERROR(ValidateSplinePointPos(current_x, current_y));
    450    result.control_points.emplace_back(static_cast<float>(current_x),
    451                                       static_cast<float>(current_y));
    452  }
    453 
    454  const auto inv_quant = InvAdjustedQuant(quantization_adjustment);
    455  for (int c = 0; c < 3; ++c) {
    456    for (int i = 0; i < 32; ++i) {
    457      const float inv_dct_factor = (i == 0) ? kSqrt0_5 : 1.0f;
    458      result.color_dct[c][i] =
    459          color_dct_[c][i] * inv_dct_factor * kChannelWeight[c] * inv_quant;
    460    }
    461  }
    462  for (int i = 0; i < 32; ++i) {
    463    result.color_dct[0][i] += y_to_x * result.color_dct[1][i];
    464    result.color_dct[2][i] += y_to_b * result.color_dct[1][i];
    465  }
    466  uint64_t width_estimate = 0;
    467 
    468  uint64_t color[3] = {};
    469  for (int c = 0; c < 3; ++c) {
    470    for (int i = 0; i < 32; ++i) {
    471      color[c] += static_cast<uint64_t>(
    472          std::ceil(inv_quant * std::abs(color_dct_[c][i])));
    473    }
    474  }
    475  color[0] += static_cast<uint64_t>(std::ceil(std::abs(y_to_x))) * color[1];
    476  color[2] += static_cast<uint64_t>(std::ceil(std::abs(y_to_b))) * color[1];
    477  // This is not taking kChannelWeight into account, but up to constant factors
    478  // it gives an indication of the influence of the color values on the area
    479  // that will need to be rendered.
    480  const uint64_t max_color = std::max({color[1], color[0], color[2]});
    481  uint64_t logcolor =
    482      std::max(kOne, static_cast<uint64_t>(CeilLog2Nonzero(kOne + max_color)));
    483 
    484  const float weight_limit =
    485      std::ceil(std::sqrt((static_cast<float>(area_limit) / logcolor) /
    486                          std::max<size_t>(1, manhattan_distance)));
    487 
    488  for (int i = 0; i < 32; ++i) {
    489    const float inv_dct_factor = (i == 0) ? kSqrt0_5 : 1.0f;
    490    result.sigma_dct[i] =
    491        sigma_dct_[i] * inv_dct_factor * kChannelWeight[3] * inv_quant;
    492    // If we include the factor kChannelWeight[3]=.3333f here, we get a
    493    // realistic area estimate. We leave it out to simplify the calculations,
    494    // and understand that this way we underestimate the area by a factor of
    495    // 1/(0.3333*0.3333). This is taken into account in the limits below.
    496    float weight_f = std::ceil(inv_quant * std::abs(sigma_dct_[i]));
    497    uint64_t weight =
    498        static_cast<uint64_t>(std::min(weight_limit, std::max(1.0f, weight_f)));
    499    width_estimate += weight * weight * logcolor;
    500  }
    501  *total_estimated_area_reached += (width_estimate * manhattan_distance);
    502  if (*total_estimated_area_reached > area_limit) {
    503    return JXL_FAILURE("Too large total_estimated_area eached: %" PRIu64,
    504                       *total_estimated_area_reached);
    505  }
    506 
    507  return true;
    508 }
    509 
    510 Status QuantizedSpline::Decode(const std::vector<uint8_t>& context_map,
    511                               ANSSymbolReader* const decoder,
    512                               BitReader* const br,
    513                               const size_t max_control_points,
    514                               size_t* total_num_control_points) {
    515  const size_t num_control_points =
    516      decoder->ReadHybridUint(kNumControlPointsContext, br, context_map);
    517  if (num_control_points > max_control_points) {
    518    return JXL_FAILURE("Too many control points: %" PRIuS, num_control_points);
    519  }
    520  *total_num_control_points += num_control_points;
    521  if (*total_num_control_points > max_control_points) {
    522    return JXL_FAILURE("Too many control points: %" PRIuS,
    523                       *total_num_control_points);
    524  }
    525  control_points_.resize(num_control_points);
    526  // Maximal image dimension.
    527  constexpr int64_t kDeltaLimit = 1u << 30;
    528  for (std::pair<int64_t, int64_t>& control_point : control_points_) {
    529    control_point.first = UnpackSigned(
    530        decoder->ReadHybridUint(kControlPointsContext, br, context_map));
    531    control_point.second = UnpackSigned(
    532        decoder->ReadHybridUint(kControlPointsContext, br, context_map));
    533    // Check delta-deltas are not outrageous; it is not in spec, but there is
    534    // no reason to allow larger values.
    535    if ((control_point.first >= kDeltaLimit) ||
    536        (control_point.first <= -kDeltaLimit) ||
    537        (control_point.second >= kDeltaLimit) ||
    538        (control_point.second <= -kDeltaLimit)) {
    539      return JXL_FAILURE("Spline delta-delta is out of bounds");
    540    }
    541  }
    542 
    543  const auto decode_dct = [decoder, br, &context_map](int dct[32]) -> Status {
    544    constexpr int kWeirdNumber = std::numeric_limits<int>::min();
    545    for (int i = 0; i < 32; ++i) {
    546      dct[i] =
    547          UnpackSigned(decoder->ReadHybridUint(kDCTContext, br, context_map));
    548      if (dct[i] == kWeirdNumber) {
    549        return JXL_FAILURE("The weird number in spline DCT");
    550      }
    551    }
    552    return true;
    553  };
    554  for (auto& dct : color_dct_) {
    555    JXL_RETURN_IF_ERROR(decode_dct(dct));
    556  }
    557  JXL_RETURN_IF_ERROR(decode_dct(sigma_dct_));
    558  return true;
    559 }
    560 
    561 void Splines::Clear() {
    562  quantization_adjustment_ = 0;
    563  splines_.clear();
    564  starting_points_.clear();
    565  segments_.clear();
    566  segment_indices_.clear();
    567  segment_y_start_.clear();
    568 }
    569 
    570 Status Splines::Decode(JxlMemoryManager* memory_manager, jxl::BitReader* br,
    571                       const size_t num_pixels) {
    572  std::vector<uint8_t> context_map;
    573  ANSCode code;
    574  JXL_RETURN_IF_ERROR(DecodeHistograms(memory_manager, br, kNumSplineContexts,
    575                                       &code, &context_map));
    576  JXL_ASSIGN_OR_RETURN(ANSSymbolReader decoder,
    577                       ANSSymbolReader::Create(&code, br));
    578  size_t num_splines =
    579      decoder.ReadHybridUint(kNumSplinesContext, br, context_map);
    580  size_t max_control_points = std::min(
    581      kMaxNumControlPoints, num_pixels / kMaxNumControlPointsPerPixelRatio);
    582  if (num_splines > max_control_points ||
    583      num_splines + 1 > max_control_points) {
    584    return JXL_FAILURE("Too many splines: %" PRIuS, num_splines);
    585  }
    586  num_splines++;
    587  JXL_RETURN_IF_ERROR(DecodeAllStartingPoints(&starting_points_, br, &decoder,
    588                                              context_map, num_splines));
    589 
    590  quantization_adjustment_ = UnpackSigned(
    591      decoder.ReadHybridUint(kQuantizationAdjustmentContext, br, context_map));
    592 
    593  splines_.clear();
    594  splines_.reserve(num_splines);
    595  size_t num_control_points = num_splines;
    596  for (size_t i = 0; i < num_splines; ++i) {
    597    QuantizedSpline spline;
    598    JXL_RETURN_IF_ERROR(spline.Decode(context_map, &decoder, br,
    599                                      max_control_points, &num_control_points));
    600    splines_.push_back(std::move(spline));
    601  }
    602 
    603  JXL_RETURN_IF_ERROR(decoder.CheckANSFinalState());
    604 
    605  if (!HasAny()) {
    606    return JXL_FAILURE("Decoded splines but got none");
    607  }
    608 
    609  return true;
    610 }
    611 
    612 void Splines::AddTo(Image3F* const opsin, const Rect& opsin_rect) const {
    613  Apply</*add=*/true>(opsin, opsin_rect);
    614 }
    615 void Splines::AddToRow(float* JXL_RESTRICT row_x, float* JXL_RESTRICT row_y,
    616                       float* JXL_RESTRICT row_b, size_t y, size_t x0,
    617                       size_t x1) const {
    618  ApplyToRow</*add=*/true>(row_x, row_y, row_b, y, x0, x1);
    619 }
    620 
    621 void Splines::SubtractFrom(Image3F* const opsin) const {
    622  Apply</*add=*/false>(opsin, Rect(*opsin));
    623 }
    624 
    625 Status Splines::InitializeDrawCache(const size_t image_xsize,
    626                                    const size_t image_ysize,
    627                                    const ColorCorrelation& color_correlation) {
    628  // TODO(veluca): avoid storing segments that are entirely outside image
    629  // boundaries.
    630  segments_.clear();
    631  segment_indices_.clear();
    632  segment_y_start_.clear();
    633  std::vector<std::pair<size_t, size_t>> segments_by_y;
    634  std::vector<Spline::Point> intermediate_points;
    635  uint64_t total_estimated_area_reached = 0;
    636  std::vector<Spline> splines;
    637  for (size_t i = 0; i < splines_.size(); ++i) {
    638    Spline spline;
    639    JXL_RETURN_IF_ERROR(splines_[i].Dequantize(
    640        starting_points_[i], quantization_adjustment_,
    641        color_correlation.YtoXRatio(0), color_correlation.YtoBRatio(0),
    642        image_xsize * image_ysize, &total_estimated_area_reached, spline));
    643    if (std::adjacent_find(spline.control_points.begin(),
    644                           spline.control_points.end()) !=
    645        spline.control_points.end()) {
    646      // Otherwise division by zero might occur. Once control points coincide,
    647      // the direction of curve is undefined...
    648      return JXL_FAILURE(
    649          "identical successive control points in spline %" PRIuS, i);
    650    }
    651    splines.push_back(spline);
    652  }
    653  // TODO(firsching) Change this into a JXL_FAILURE for level 5 codestreams.
    654  if (total_estimated_area_reached >
    655      std::min(
    656          (8 * image_xsize * image_ysize + (static_cast<uint64_t>(1) << 25)),
    657          (static_cast<uint64_t>(1) << 30))) {
    658    JXL_WARNING(
    659        "Large total_estimated_area_reached, expect slower decoding: %" PRIu64,
    660        total_estimated_area_reached);
    661 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    662    return JXL_FAILURE("Total spline area is too large");
    663 #endif
    664  }
    665 
    666  for (Spline& spline : splines) {
    667    std::vector<std::pair<Spline::Point, float>> points_to_draw;
    668    auto add_point = [&](const Spline::Point& point, const float multiplier) {
    669      points_to_draw.emplace_back(point, multiplier);
    670    };
    671    intermediate_points.clear();
    672    DrawCentripetalCatmullRomSpline(spline.control_points, intermediate_points);
    673    JXL_RETURN_IF_ERROR(
    674        ForEachEquallySpacedPoint(intermediate_points, add_point));
    675    const float arc_length =
    676        (points_to_draw.size() - 2) * kDesiredRenderingDistance +
    677        points_to_draw.back().second;
    678    if (arc_length <= 0.f) {
    679      // This spline wouldn't have any effect.
    680      continue;
    681    }
    682    HWY_DYNAMIC_DISPATCH(SegmentsFromPoints)
    683    (spline, points_to_draw, arc_length, segments_, segments_by_y);
    684  }
    685 
    686  // TODO(eustas): consider linear sorting here.
    687  std::sort(segments_by_y.begin(), segments_by_y.end());
    688  segment_indices_.resize(segments_by_y.size());
    689  segment_y_start_.resize(image_ysize + 1);
    690  for (size_t i = 0; i < segments_by_y.size(); i++) {
    691    segment_indices_[i] = segments_by_y[i].second;
    692    size_t y = segments_by_y[i].first;
    693    if (y < image_ysize) {
    694      segment_y_start_[y + 1]++;
    695    }
    696  }
    697  for (size_t y = 0; y < image_ysize; y++) {
    698    segment_y_start_[y + 1] += segment_y_start_[y];
    699  }
    700  return true;
    701 }
    702 
    703 template <bool add>
    704 void Splines::ApplyToRow(float* JXL_RESTRICT row_x, float* JXL_RESTRICT row_y,
    705                         float* JXL_RESTRICT row_b, size_t y, size_t x0,
    706                         size_t x1) const {
    707  if (segments_.empty()) return;
    708  HWY_DYNAMIC_DISPATCH(DrawSegments)
    709  (row_x, row_y, row_b, y, x0, x1, add, segments_.data(),
    710   segment_indices_.data(), segment_y_start_.data());
    711 }
    712 
    713 template <bool add>
    714 void Splines::Apply(Image3F* const opsin, const Rect& opsin_rect) const {
    715  if (segments_.empty()) return;
    716  const size_t y0 = opsin_rect.y0();
    717  const size_t x0 = opsin_rect.x0();
    718  const size_t x1 = opsin_rect.x1();
    719  for (size_t y = 0; y < opsin_rect.ysize(); y++) {
    720    ApplyToRow<add>(opsin->PlaneRow(0, y0 + y) + x0,
    721                    opsin->PlaneRow(1, y0 + y) + x0,
    722                    opsin->PlaneRow(2, y0 + y) + x0, y0 + y, x0, x1);
    723  }
    724 }
    725 
    726 }  // namespace jxl
    727 #endif  // HWY_ONCE