1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/skia/trunk/src/core/SkEdgeClipper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,531 @@ 1.4 + 1.5 +/* 1.6 + * Copyright 2009 The Android Open Source Project 1.7 + * 1.8 + * Use of this source code is governed by a BSD-style license that can be 1.9 + * found in the LICENSE file. 1.10 + */ 1.11 + 1.12 + 1.13 +#include "SkEdgeClipper.h" 1.14 +#include "SkGeometry.h" 1.15 + 1.16 +static bool quick_reject(const SkRect& bounds, const SkRect& clip) { 1.17 + return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop; 1.18 +} 1.19 + 1.20 +static inline void clamp_le(SkScalar& value, SkScalar max) { 1.21 + if (value > max) { 1.22 + value = max; 1.23 + } 1.24 +} 1.25 + 1.26 +static inline void clamp_ge(SkScalar& value, SkScalar min) { 1.27 + if (value < min) { 1.28 + value = min; 1.29 + } 1.30 +} 1.31 + 1.32 +/* src[] must be monotonic in Y. This routine copies src into dst, and sorts 1.33 + it to be increasing in Y. If it had to reverse the order of the points, 1.34 + it returns true, otherwise it returns false 1.35 + */ 1.36 +static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) { 1.37 + // we need the data to be monotonically increasing in Y 1.38 + if (src[0].fY > src[count - 1].fY) { 1.39 + for (int i = 0; i < count; i++) { 1.40 + dst[i] = src[count - i - 1]; 1.41 + } 1.42 + return true; 1.43 + } else { 1.44 + memcpy(dst, src, count * sizeof(SkPoint)); 1.45 + return false; 1.46 + } 1.47 +} 1.48 + 1.49 +/////////////////////////////////////////////////////////////////////////////// 1.50 + 1.51 +static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2, 1.52 + SkScalar target, SkScalar* t) { 1.53 + /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2 1.54 + * We solve for t, using quadratic equation, hence we have to rearrange 1.55 + * our cooefficents to look like At^2 + Bt + C 1.56 + */ 1.57 + SkScalar A = c0 - c1 - c1 + c2; 1.58 + SkScalar B = 2*(c1 - c0); 1.59 + SkScalar C = c0 - target; 1.60 + 1.61 + SkScalar roots[2]; // we only expect one, but make room for 2 for safety 1.62 + int count = SkFindUnitQuadRoots(A, B, C, roots); 1.63 + if (count) { 1.64 + *t = roots[0]; 1.65 + return true; 1.66 + } 1.67 + return false; 1.68 +} 1.69 + 1.70 +static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) { 1.71 + return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t); 1.72 +} 1.73 + 1.74 +static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) { 1.75 + return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t); 1.76 +} 1.77 + 1.78 +// Modify pts[] in place so that it is clipped in Y to the clip rect 1.79 +static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) { 1.80 + SkScalar t; 1.81 + SkPoint tmp[5]; // for SkChopQuadAt 1.82 + 1.83 + // are we partially above 1.84 + if (pts[0].fY < clip.fTop) { 1.85 + if (chopMonoQuadAtY(pts, clip.fTop, &t)) { 1.86 + // take the 2nd chopped quad 1.87 + SkChopQuadAt(pts, tmp, t); 1.88 + // clamp to clean up imprecise numerics in the chop 1.89 + tmp[2].fY = clip.fTop; 1.90 + clamp_ge(tmp[3].fY, clip.fTop); 1.91 + 1.92 + pts[0] = tmp[2]; 1.93 + pts[1] = tmp[3]; 1.94 + } else { 1.95 + // if chopMonoQuadAtY failed, then we may have hit inexact numerics 1.96 + // so we just clamp against the top 1.97 + for (int i = 0; i < 3; i++) { 1.98 + if (pts[i].fY < clip.fTop) { 1.99 + pts[i].fY = clip.fTop; 1.100 + } 1.101 + } 1.102 + } 1.103 + } 1.104 + 1.105 + // are we partially below 1.106 + if (pts[2].fY > clip.fBottom) { 1.107 + if (chopMonoQuadAtY(pts, clip.fBottom, &t)) { 1.108 + SkChopQuadAt(pts, tmp, t); 1.109 + // clamp to clean up imprecise numerics in the chop 1.110 + clamp_le(tmp[1].fY, clip.fBottom); 1.111 + tmp[2].fY = clip.fBottom; 1.112 + 1.113 + pts[1] = tmp[1]; 1.114 + pts[2] = tmp[2]; 1.115 + } else { 1.116 + // if chopMonoQuadAtY failed, then we may have hit inexact numerics 1.117 + // so we just clamp against the bottom 1.118 + for (int i = 0; i < 3; i++) { 1.119 + if (pts[i].fY > clip.fBottom) { 1.120 + pts[i].fY = clip.fBottom; 1.121 + } 1.122 + } 1.123 + } 1.124 + } 1.125 +} 1.126 + 1.127 +// srcPts[] must be monotonic in X and Y 1.128 +void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) { 1.129 + SkPoint pts[3]; 1.130 + bool reverse = sort_increasing_Y(pts, srcPts, 3); 1.131 + 1.132 + // are we completely above or below 1.133 + if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { 1.134 + return; 1.135 + } 1.136 + 1.137 + // Now chop so that pts is contained within clip in Y 1.138 + chop_quad_in_Y(pts, clip); 1.139 + 1.140 + if (pts[0].fX > pts[2].fX) { 1.141 + SkTSwap<SkPoint>(pts[0], pts[2]); 1.142 + reverse = !reverse; 1.143 + } 1.144 + SkASSERT(pts[0].fX <= pts[1].fX); 1.145 + SkASSERT(pts[1].fX <= pts[2].fX); 1.146 + 1.147 + // Now chop in X has needed, and record the segments 1.148 + 1.149 + if (pts[2].fX <= clip.fLeft) { // wholly to the left 1.150 + this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); 1.151 + return; 1.152 + } 1.153 + if (pts[0].fX >= clip.fRight) { // wholly to the right 1.154 + this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); 1.155 + return; 1.156 + } 1.157 + 1.158 + SkScalar t; 1.159 + SkPoint tmp[5]; // for SkChopQuadAt 1.160 + 1.161 + // are we partially to the left 1.162 + if (pts[0].fX < clip.fLeft) { 1.163 + if (chopMonoQuadAtX(pts, clip.fLeft, &t)) { 1.164 + SkChopQuadAt(pts, tmp, t); 1.165 + this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse); 1.166 + // clamp to clean up imprecise numerics in the chop 1.167 + tmp[2].fX = clip.fLeft; 1.168 + clamp_ge(tmp[3].fX, clip.fLeft); 1.169 + 1.170 + pts[0] = tmp[2]; 1.171 + pts[1] = tmp[3]; 1.172 + } else { 1.173 + // if chopMonoQuadAtY failed, then we may have hit inexact numerics 1.174 + // so we just clamp against the left 1.175 + this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); 1.176 + return; 1.177 + } 1.178 + } 1.179 + 1.180 + // are we partially to the right 1.181 + if (pts[2].fX > clip.fRight) { 1.182 + if (chopMonoQuadAtX(pts, clip.fRight, &t)) { 1.183 + SkChopQuadAt(pts, tmp, t); 1.184 + // clamp to clean up imprecise numerics in the chop 1.185 + clamp_le(tmp[1].fX, clip.fRight); 1.186 + tmp[2].fX = clip.fRight; 1.187 + 1.188 + this->appendQuad(tmp, reverse); 1.189 + this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse); 1.190 + } else { 1.191 + // if chopMonoQuadAtY failed, then we may have hit inexact numerics 1.192 + // so we just clamp against the right 1.193 + this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); 1.194 + } 1.195 + } else { // wholly inside the clip 1.196 + this->appendQuad(pts, reverse); 1.197 + } 1.198 +} 1.199 + 1.200 +bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) { 1.201 + fCurrPoint = fPoints; 1.202 + fCurrVerb = fVerbs; 1.203 + 1.204 + SkRect bounds; 1.205 + bounds.set(srcPts, 3); 1.206 + 1.207 + if (!quick_reject(bounds, clip)) { 1.208 + SkPoint monoY[5]; 1.209 + int countY = SkChopQuadAtYExtrema(srcPts, monoY); 1.210 + for (int y = 0; y <= countY; y++) { 1.211 + SkPoint monoX[5]; 1.212 + int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX); 1.213 + for (int x = 0; x <= countX; x++) { 1.214 + this->clipMonoQuad(&monoX[x * 2], clip); 1.215 + SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); 1.216 + SkASSERT(fCurrPoint - fPoints <= kMaxPoints); 1.217 + } 1.218 + } 1.219 + } 1.220 + 1.221 + *fCurrVerb = SkPath::kDone_Verb; 1.222 + fCurrPoint = fPoints; 1.223 + fCurrVerb = fVerbs; 1.224 + return SkPath::kDone_Verb != fVerbs[0]; 1.225 +} 1.226 + 1.227 +/////////////////////////////////////////////////////////////////////////////// 1.228 + 1.229 +static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C, 1.230 + SkScalar D, SkScalar t) { 1.231 + return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D); 1.232 +} 1.233 + 1.234 +/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the 1.235 + t value such that cubic(t) = target 1.236 + */ 1.237 +static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3, 1.238 + SkScalar target, SkScalar* t) { 1.239 + // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3); 1.240 + SkASSERT(c0 < target && target < c3); 1.241 + 1.242 + SkScalar D = c0 - target; 1.243 + SkScalar A = c3 + 3*(c1 - c2) - c0; 1.244 + SkScalar B = 3*(c2 - c1 - c1 + c0); 1.245 + SkScalar C = 3*(c1 - c0); 1.246 + 1.247 + const SkScalar TOLERANCE = SK_Scalar1 / 4096; 1.248 + SkScalar minT = 0; 1.249 + SkScalar maxT = SK_Scalar1; 1.250 + SkScalar mid; 1.251 + 1.252 + // This is a lot of iterations. Is there a faster way? 1.253 + for (int i = 0; i < 24; i++) { 1.254 + mid = SkScalarAve(minT, maxT); 1.255 + SkScalar delta = eval_cubic_coeff(A, B, C, D, mid); 1.256 + if (delta < 0) { 1.257 + minT = mid; 1.258 + delta = -delta; 1.259 + } else { 1.260 + maxT = mid; 1.261 + } 1.262 + if (delta < TOLERANCE) { 1.263 + break; 1.264 + } 1.265 + } 1.266 + *t = mid; 1.267 +// SkDebugf("-- evalCubicAt %d delta %g\n", i, eval_cubic_coeff(A, B, C, D, *t)); 1.268 + return true; 1.269 +} 1.270 + 1.271 +static bool chopMonoCubicAtY(SkPoint pts[4], SkScalar y, SkScalar* t) { 1.272 + return chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, t); 1.273 +} 1.274 + 1.275 +static bool chopMonoCubicAtX(SkPoint pts[4], SkScalar x, SkScalar* t) { 1.276 + return chopMonoCubicAt(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, x, t); 1.277 +} 1.278 + 1.279 +// Modify pts[] in place so that it is clipped in Y to the clip rect 1.280 +static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) { 1.281 + 1.282 + // are we partially above 1.283 + if (pts[0].fY < clip.fTop) { 1.284 + SkScalar t; 1.285 + if (chopMonoCubicAtY(pts, clip.fTop, &t)) { 1.286 + SkPoint tmp[7]; 1.287 + SkChopCubicAt(pts, tmp, t); 1.288 + 1.289 + // tmp[3, 4, 5].fY should all be to the below clip.fTop. 1.290 + // Since we can't trust the numerics of 1.291 + // the chopper, we force those conditions now 1.292 + tmp[3].fY = clip.fTop; 1.293 + clamp_ge(tmp[4].fY, clip.fTop); 1.294 + clamp_ge(tmp[5].fY, clip.fTop); 1.295 + 1.296 + pts[0] = tmp[3]; 1.297 + pts[1] = tmp[4]; 1.298 + pts[2] = tmp[5]; 1.299 + } else { 1.300 + // if chopMonoCubicAtY failed, then we may have hit inexact numerics 1.301 + // so we just clamp against the top 1.302 + for (int i = 0; i < 4; i++) { 1.303 + clamp_ge(pts[i].fY, clip.fTop); 1.304 + } 1.305 + } 1.306 + } 1.307 + 1.308 + // are we partially below 1.309 + if (pts[3].fY > clip.fBottom) { 1.310 + SkScalar t; 1.311 + if (chopMonoCubicAtY(pts, clip.fBottom, &t)) { 1.312 + SkPoint tmp[7]; 1.313 + SkChopCubicAt(pts, tmp, t); 1.314 + tmp[3].fY = clip.fBottom; 1.315 + clamp_le(tmp[2].fY, clip.fBottom); 1.316 + 1.317 + pts[1] = tmp[1]; 1.318 + pts[2] = tmp[2]; 1.319 + pts[3] = tmp[3]; 1.320 + } else { 1.321 + // if chopMonoCubicAtY failed, then we may have hit inexact numerics 1.322 + // so we just clamp against the bottom 1.323 + for (int i = 0; i < 4; i++) { 1.324 + clamp_le(pts[i].fY, clip.fBottom); 1.325 + } 1.326 + } 1.327 + } 1.328 +} 1.329 + 1.330 +// srcPts[] must be monotonic in X and Y 1.331 +void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) { 1.332 + SkPoint pts[4]; 1.333 + bool reverse = sort_increasing_Y(pts, src, 4); 1.334 + 1.335 + // are we completely above or below 1.336 + if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { 1.337 + return; 1.338 + } 1.339 + 1.340 + // Now chop so that pts is contained within clip in Y 1.341 + chop_cubic_in_Y(pts, clip); 1.342 + 1.343 + if (pts[0].fX > pts[3].fX) { 1.344 + SkTSwap<SkPoint>(pts[0], pts[3]); 1.345 + SkTSwap<SkPoint>(pts[1], pts[2]); 1.346 + reverse = !reverse; 1.347 + } 1.348 + 1.349 + // Now chop in X has needed, and record the segments 1.350 + 1.351 + if (pts[3].fX <= clip.fLeft) { // wholly to the left 1.352 + this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse); 1.353 + return; 1.354 + } 1.355 + if (pts[0].fX >= clip.fRight) { // wholly to the right 1.356 + this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse); 1.357 + return; 1.358 + } 1.359 + 1.360 + // are we partially to the left 1.361 + if (pts[0].fX < clip.fLeft) { 1.362 + SkScalar t; 1.363 + if (chopMonoCubicAtX(pts, clip.fLeft, &t)) { 1.364 + SkPoint tmp[7]; 1.365 + SkChopCubicAt(pts, tmp, t); 1.366 + this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse); 1.367 + 1.368 + // tmp[3, 4, 5].fX should all be to the right of clip.fLeft. 1.369 + // Since we can't trust the numerics of 1.370 + // the chopper, we force those conditions now 1.371 + tmp[3].fX = clip.fLeft; 1.372 + clamp_ge(tmp[4].fX, clip.fLeft); 1.373 + clamp_ge(tmp[5].fX, clip.fLeft); 1.374 + 1.375 + pts[0] = tmp[3]; 1.376 + pts[1] = tmp[4]; 1.377 + pts[2] = tmp[5]; 1.378 + } else { 1.379 + // if chopMonocubicAtY failed, then we may have hit inexact numerics 1.380 + // so we just clamp against the left 1.381 + this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse); 1.382 + return; 1.383 + } 1.384 + } 1.385 + 1.386 + // are we partially to the right 1.387 + if (pts[3].fX > clip.fRight) { 1.388 + SkScalar t; 1.389 + if (chopMonoCubicAtX(pts, clip.fRight, &t)) { 1.390 + SkPoint tmp[7]; 1.391 + SkChopCubicAt(pts, tmp, t); 1.392 + tmp[3].fX = clip.fRight; 1.393 + clamp_le(tmp[2].fX, clip.fRight); 1.394 + clamp_le(tmp[1].fX, clip.fRight); 1.395 + 1.396 + this->appendCubic(tmp, reverse); 1.397 + this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse); 1.398 + } else { 1.399 + // if chopMonoCubicAtX failed, then we may have hit inexact numerics 1.400 + // so we just clamp against the right 1.401 + this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse); 1.402 + } 1.403 + } else { // wholly inside the clip 1.404 + this->appendCubic(pts, reverse); 1.405 + } 1.406 +} 1.407 + 1.408 +bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) { 1.409 + fCurrPoint = fPoints; 1.410 + fCurrVerb = fVerbs; 1.411 + 1.412 + SkRect bounds; 1.413 + bounds.set(srcPts, 4); 1.414 + 1.415 + if (!quick_reject(bounds, clip)) { 1.416 + SkPoint monoY[10]; 1.417 + int countY = SkChopCubicAtYExtrema(srcPts, monoY); 1.418 + for (int y = 0; y <= countY; y++) { 1.419 + SkPoint monoX[10]; 1.420 + int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX); 1.421 + for (int x = 0; x <= countX; x++) { 1.422 + this->clipMonoCubic(&monoX[x * 3], clip); 1.423 + SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); 1.424 + SkASSERT(fCurrPoint - fPoints <= kMaxPoints); 1.425 + } 1.426 + } 1.427 + } 1.428 + 1.429 + *fCurrVerb = SkPath::kDone_Verb; 1.430 + fCurrPoint = fPoints; 1.431 + fCurrVerb = fVerbs; 1.432 + return SkPath::kDone_Verb != fVerbs[0]; 1.433 +} 1.434 + 1.435 +/////////////////////////////////////////////////////////////////////////////// 1.436 + 1.437 +void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1, 1.438 + bool reverse) { 1.439 + *fCurrVerb++ = SkPath::kLine_Verb; 1.440 + 1.441 + if (reverse) { 1.442 + SkTSwap<SkScalar>(y0, y1); 1.443 + } 1.444 + fCurrPoint[0].set(x, y0); 1.445 + fCurrPoint[1].set(x, y1); 1.446 + fCurrPoint += 2; 1.447 +} 1.448 + 1.449 +void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) { 1.450 + *fCurrVerb++ = SkPath::kQuad_Verb; 1.451 + 1.452 + if (reverse) { 1.453 + fCurrPoint[0] = pts[2]; 1.454 + fCurrPoint[2] = pts[0]; 1.455 + } else { 1.456 + fCurrPoint[0] = pts[0]; 1.457 + fCurrPoint[2] = pts[2]; 1.458 + } 1.459 + fCurrPoint[1] = pts[1]; 1.460 + fCurrPoint += 3; 1.461 +} 1.462 + 1.463 +void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) { 1.464 + *fCurrVerb++ = SkPath::kCubic_Verb; 1.465 + 1.466 + if (reverse) { 1.467 + for (int i = 0; i < 4; i++) { 1.468 + fCurrPoint[i] = pts[3 - i]; 1.469 + } 1.470 + } else { 1.471 + memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint)); 1.472 + } 1.473 + fCurrPoint += 4; 1.474 +} 1.475 + 1.476 +SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) { 1.477 + SkPath::Verb verb = *fCurrVerb; 1.478 + 1.479 + switch (verb) { 1.480 + case SkPath::kLine_Verb: 1.481 + memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint)); 1.482 + fCurrPoint += 2; 1.483 + fCurrVerb += 1; 1.484 + break; 1.485 + case SkPath::kQuad_Verb: 1.486 + memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint)); 1.487 + fCurrPoint += 3; 1.488 + fCurrVerb += 1; 1.489 + break; 1.490 + case SkPath::kCubic_Verb: 1.491 + memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint)); 1.492 + fCurrPoint += 4; 1.493 + fCurrVerb += 1; 1.494 + break; 1.495 + case SkPath::kDone_Verb: 1.496 + break; 1.497 + default: 1.498 + SkDEBUGFAIL("unexpected verb in quadclippper2 iter"); 1.499 + break; 1.500 + } 1.501 + return verb; 1.502 +} 1.503 + 1.504 +/////////////////////////////////////////////////////////////////////////////// 1.505 + 1.506 +#ifdef SK_DEBUG 1.507 +static void assert_monotonic(const SkScalar coord[], int count) { 1.508 + if (coord[0] > coord[(count - 1) * 2]) { 1.509 + for (int i = 1; i < count; i++) { 1.510 + SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]); 1.511 + } 1.512 + } else if (coord[0] < coord[(count - 1) * 2]) { 1.513 + for (int i = 1; i < count; i++) { 1.514 + SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]); 1.515 + } 1.516 + } else { 1.517 + for (int i = 1; i < count; i++) { 1.518 + SkASSERT(coord[2 * (i - 1)] == coord[i * 2]); 1.519 + } 1.520 + } 1.521 +} 1.522 + 1.523 +void sk_assert_monotonic_y(const SkPoint pts[], int count) { 1.524 + if (count > 1) { 1.525 + assert_monotonic(&pts[0].fY, count); 1.526 + } 1.527 +} 1.528 + 1.529 +void sk_assert_monotonic_x(const SkPoint pts[], int count) { 1.530 + if (count > 1) { 1.531 + assert_monotonic(&pts[0].fX, count); 1.532 + } 1.533 +} 1.534 +#endif