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 = ¤t; 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