1 /* 2 * Copyright 2014 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkPatchUtils.h" 9 10 #include "SkColorPriv.h" 11 #include "SkColorSpace_Base.h" 12 #include "SkGeometry.h" 13 #include "SkPM4f.h" 14 15 namespace { 16 enum CubicCtrlPts { 17 kTopP0_CubicCtrlPts = 0, 18 kTopP1_CubicCtrlPts = 1, 19 kTopP2_CubicCtrlPts = 2, 20 kTopP3_CubicCtrlPts = 3, 21 22 kRightP0_CubicCtrlPts = 3, 23 kRightP1_CubicCtrlPts = 4, 24 kRightP2_CubicCtrlPts = 5, 25 kRightP3_CubicCtrlPts = 6, 26 27 kBottomP0_CubicCtrlPts = 9, 28 kBottomP1_CubicCtrlPts = 8, 29 kBottomP2_CubicCtrlPts = 7, 30 kBottomP3_CubicCtrlPts = 6, 31 32 kLeftP0_CubicCtrlPts = 0, 33 kLeftP1_CubicCtrlPts = 11, 34 kLeftP2_CubicCtrlPts = 10, 35 kLeftP3_CubicCtrlPts = 9, 36 }; 37 38 // Enum for corner also clockwise. 39 enum Corner { 40 kTopLeft_Corner = 0, 41 kTopRight_Corner, 42 kBottomRight_Corner, 43 kBottomLeft_Corner 44 }; 45 } 46 47 /** 48 * Evaluator to sample the values of a cubic bezier using forward differences. 49 * Forward differences is a method for evaluating a nth degree polynomial at a uniform step by only 50 * adding precalculated values. 51 * For a linear example we have the function f(t) = m*t+b, then the value of that function at t+h 52 * would be f(t+h) = m*(t+h)+b. If we want to know the uniform step that we must add to the first 53 * evaluation f(t) then we need to substract f(t+h) - f(t) = m*t + m*h + b - m*t + b = mh. After 54 * obtaining this value (mh) we could just add this constant step to our first sampled point 55 * to compute the next one. 56 * 57 * For the cubic case the first difference gives as a result a quadratic polynomial to which we can 58 * apply again forward differences and get linear function to which we can apply again forward 59 * differences to get a constant difference. This is why we keep an array of size 4, the 0th 60 * position keeps the sampled value while the next ones keep the quadratic, linear and constant 61 * difference values. 62 */ 63 64 class FwDCubicEvaluator { 65 66 public: 67 68 /** 69 * Receives the 4 control points of the cubic bezier. 70 */ 71 72 explicit FwDCubicEvaluator(const SkPoint points[4]) 73 : fCoefs(points) { 74 memcpy(fPoints, points, 4 * sizeof(SkPoint)); 75 76 this->restart(1); 77 } 78 79 /** 80 * Restarts the forward differences evaluator to the first value of t = 0. 81 */ 82 void restart(int divisions) { 83 fDivisions = divisions; 84 fCurrent = 0; 85 fMax = fDivisions + 1; 86 Sk2s h = Sk2s(1.f / fDivisions); 87 Sk2s h2 = h * h; 88 Sk2s h3 = h2 * h; 89 Sk2s fwDiff3 = Sk2s(6) * fCoefs.fA * h3; 90 fFwDiff[3] = to_point(fwDiff3); 91 fFwDiff[2] = to_point(fwDiff3 + times_2(fCoefs.fB) * h2); 92 fFwDiff[1] = to_point(fCoefs.fA * h3 + fCoefs.fB * h2 + fCoefs.fC * h); 93 fFwDiff[0] = to_point(fCoefs.fD); 94 } 95 96 /** 97 * Check if the evaluator is still within the range of 0<=t<=1 98 */ 99 bool done() const { 100 return fCurrent > fMax; 101 } 102 103 /** 104 * Call next to obtain the SkPoint sampled and move to the next one. 105 */ 106 SkPoint next() { 107 SkPoint point = fFwDiff[0]; 108 fFwDiff[0] += fFwDiff[1]; 109 fFwDiff[1] += fFwDiff[2]; 110 fFwDiff[2] += fFwDiff[3]; 111 fCurrent++; 112 return point; 113 } 114 115 const SkPoint* getCtrlPoints() const { 116 return fPoints; 117 } 118 119 private: 120 SkCubicCoeff fCoefs; 121 int fMax, fCurrent, fDivisions; 122 SkPoint fFwDiff[4], fPoints[4]; 123 }; 124 125 //////////////////////////////////////////////////////////////////////////////// 126 127 // size in pixels of each partition per axis, adjust this knob 128 static const int kPartitionSize = 10; 129 130 /** 131 * Calculate the approximate arc length given a bezier curve's control points. 132 */ 133 static SkScalar approx_arc_length(SkPoint* points, int count) { 134 if (count < 2) { 135 return 0; 136 } 137 SkScalar arcLength = 0; 138 for (int i = 0; i < count - 1; i++) { 139 arcLength += SkPoint::Distance(points[i], points[i + 1]); 140 } 141 return arcLength; 142 } 143 144 static SkScalar bilerp(SkScalar tx, SkScalar ty, SkScalar c00, SkScalar c10, SkScalar c01, 145 SkScalar c11) { 146 SkScalar a = c00 * (1.f - tx) + c10 * tx; 147 SkScalar b = c01 * (1.f - tx) + c11 * tx; 148 return a * (1.f - ty) + b * ty; 149 } 150 151 static Sk4f bilerp(SkScalar tx, SkScalar ty, 152 const Sk4f& c00, const Sk4f& c10, const Sk4f& c01, const Sk4f& c11) { 153 Sk4f a = c00 * (1.f - tx) + c10 * tx; 154 Sk4f b = c01 * (1.f - tx) + c11 * tx; 155 return a * (1.f - ty) + b * ty; 156 } 157 158 SkISize SkPatchUtils::GetLevelOfDetail(const SkPoint cubics[12], const SkMatrix* matrix) { 159 160 // Approximate length of each cubic. 161 SkPoint pts[kNumPtsCubic]; 162 SkPatchUtils::GetTopCubic(cubics, pts); 163 matrix->mapPoints(pts, kNumPtsCubic); 164 SkScalar topLength = approx_arc_length(pts, kNumPtsCubic); 165 166 SkPatchUtils::GetBottomCubic(cubics, pts); 167 matrix->mapPoints(pts, kNumPtsCubic); 168 SkScalar bottomLength = approx_arc_length(pts, kNumPtsCubic); 169 170 SkPatchUtils::GetLeftCubic(cubics, pts); 171 matrix->mapPoints(pts, kNumPtsCubic); 172 SkScalar leftLength = approx_arc_length(pts, kNumPtsCubic); 173 174 SkPatchUtils::GetRightCubic(cubics, pts); 175 matrix->mapPoints(pts, kNumPtsCubic); 176 SkScalar rightLength = approx_arc_length(pts, kNumPtsCubic); 177 178 // Level of detail per axis, based on the larger side between top and bottom or left and right 179 int lodX = static_cast<int>(SkMaxScalar(topLength, bottomLength) / kPartitionSize); 180 int lodY = static_cast<int>(SkMaxScalar(leftLength, rightLength) / kPartitionSize); 181 182 return SkISize::Make(SkMax32(8, lodX), SkMax32(8, lodY)); 183 } 184 185 void SkPatchUtils::GetTopCubic(const SkPoint cubics[12], SkPoint points[4]) { 186 points[0] = cubics[kTopP0_CubicCtrlPts]; 187 points[1] = cubics[kTopP1_CubicCtrlPts]; 188 points[2] = cubics[kTopP2_CubicCtrlPts]; 189 points[3] = cubics[kTopP3_CubicCtrlPts]; 190 } 191 192 void SkPatchUtils::GetBottomCubic(const SkPoint cubics[12], SkPoint points[4]) { 193 points[0] = cubics[kBottomP0_CubicCtrlPts]; 194 points[1] = cubics[kBottomP1_CubicCtrlPts]; 195 points[2] = cubics[kBottomP2_CubicCtrlPts]; 196 points[3] = cubics[kBottomP3_CubicCtrlPts]; 197 } 198 199 void SkPatchUtils::GetLeftCubic(const SkPoint cubics[12], SkPoint points[4]) { 200 points[0] = cubics[kLeftP0_CubicCtrlPts]; 201 points[1] = cubics[kLeftP1_CubicCtrlPts]; 202 points[2] = cubics[kLeftP2_CubicCtrlPts]; 203 points[3] = cubics[kLeftP3_CubicCtrlPts]; 204 } 205 206 void SkPatchUtils::GetRightCubic(const SkPoint cubics[12], SkPoint points[4]) { 207 points[0] = cubics[kRightP0_CubicCtrlPts]; 208 points[1] = cubics[kRightP1_CubicCtrlPts]; 209 points[2] = cubics[kRightP2_CubicCtrlPts]; 210 points[3] = cubics[kRightP3_CubicCtrlPts]; 211 } 212 213 #include "SkPM4fPriv.h" 214 #include "SkColorSpace_Base.h" 215 #include "SkColorSpaceXform.h" 216 217 struct SkRGBAf { 218 float fVec[4]; 219 220 static SkRGBAf From4f(const Sk4f& x) { 221 SkRGBAf c; 222 x.store(c.fVec); 223 return c; 224 } 225 226 static SkRGBAf FromBGRA32(SkColor c) { 227 return From4f(swizzle_rb(SkNx_cast<float>(Sk4b::Load(&c)) * (1/255.0f))); 228 } 229 230 Sk4f to4f() const { 231 return Sk4f::Load(fVec); 232 } 233 234 SkColor toBGRA32() const { 235 SkColor color; 236 SkNx_cast<uint8_t>(swizzle_rb(this->to4f()) * Sk4f(255) + Sk4f(0.5f)).store(&color); 237 return color; 238 } 239 240 SkRGBAf premul() const { 241 float a = fVec[3]; 242 return From4f(this->to4f() * Sk4f(a, a, a, 1)); 243 } 244 245 SkRGBAf unpremul() const { 246 float a = fVec[3]; 247 float inv = a ? 1/a : 0; 248 return From4f(this->to4f() * Sk4f(inv, inv, inv, 1)); 249 } 250 }; 251 252 static void skcolor_to_linear(SkRGBAf dst[], const SkColor src[], int count, SkColorSpace* cs, 253 bool doPremul) { 254 if (cs) { 255 auto srcCS = SkColorSpace::MakeSRGB(); 256 auto dstCS = as_CSB(cs)->makeLinearGamma(); 257 auto op = doPremul ? SkColorSpaceXform::kPremul_AlphaOp 258 : SkColorSpaceXform::kPreserve_AlphaOp; 259 SkColorSpaceXform::Apply(dstCS.get(), SkColorSpaceXform::kRGBA_F32_ColorFormat, dst, 260 srcCS.get(), SkColorSpaceXform::kBGRA_8888_ColorFormat, src, 261 count, op); 262 } else { 263 for (int i = 0; i < count; ++i) { 264 dst[i] = SkRGBAf::FromBGRA32(src[i]); 265 if (doPremul) { 266 dst[i] = dst[i].premul(); 267 } 268 } 269 } 270 } 271 272 static void linear_to_skcolor(SkColor dst[], const SkRGBAf src[], int count, SkColorSpace* cs) { 273 if (cs) { 274 auto srcCS = as_CSB(cs)->makeLinearGamma(); 275 auto dstCS = SkColorSpace::MakeSRGB(); 276 SkColorSpaceXform::Apply(dstCS.get(), SkColorSpaceXform::kBGRA_8888_ColorFormat, dst, 277 srcCS.get(), SkColorSpaceXform::kRGBA_F32_ColorFormat, src, 278 count, SkColorSpaceXform::kPreserve_AlphaOp); 279 } else { 280 for (int i = 0; i < count; ++i) { 281 dst[i] = src[i].toBGRA32(); 282 } 283 } 284 } 285 286 static void unpremul(SkRGBAf array[], int count) { 287 for (int i = 0; i < count; ++i) { 288 array[i] = array[i].unpremul(); 289 } 290 } 291 292 sk_sp<SkVertices> SkPatchUtils::MakeVertices(const SkPoint cubics[12], const SkColor srcColors[4], 293 const SkPoint srcTexCoords[4], int lodX, int lodY, 294 bool interpColorsLinearly) { 295 if (lodX < 1 || lodY < 1 || nullptr == cubics) { 296 return nullptr; 297 } 298 299 // check for overflow in multiplication 300 const int64_t lodX64 = (lodX + 1), 301 lodY64 = (lodY + 1), 302 mult64 = lodX64 * lodY64; 303 if (mult64 > SK_MaxS32) { 304 return nullptr; 305 } 306 307 int vertexCount = SkToS32(mult64); 308 // it is recommended to generate draw calls of no more than 65536 indices, so we never generate 309 // more than 60000 indices. To accomplish that we resize the LOD and vertex count 310 if (vertexCount > 10000 || lodX > 200 || lodY > 200) { 311 float weightX = static_cast<float>(lodX) / (lodX + lodY); 312 float weightY = static_cast<float>(lodY) / (lodX + lodY); 313 314 // 200 comes from the 100 * 2 which is the max value of vertices because of the limit of 315 // 60000 indices ( sqrt(60000 / 6) that comes from data->fIndexCount = lodX * lodY * 6) 316 lodX = static_cast<int>(weightX * 200); 317 lodY = static_cast<int>(weightY * 200); 318 vertexCount = (lodX + 1) * (lodY + 1); 319 } 320 const int indexCount = lodX * lodY * 6; 321 uint32_t flags = 0; 322 if (srcTexCoords) { 323 flags |= SkVertices::kHasTexCoords_BuilderFlag; 324 } 325 if (srcColors) { 326 flags |= SkVertices::kHasColors_BuilderFlag; 327 } 328 329 SkSTArenaAlloc<2048> alloc; 330 SkRGBAf* cornerColors = srcColors ? alloc.makeArray<SkRGBAf>(4) : nullptr; 331 SkRGBAf* tmpColors = srcColors ? alloc.makeArray<SkRGBAf>(vertexCount) : nullptr; 332 auto convertCS = interpColorsLinearly ? SkColorSpace::MakeSRGB() : nullptr; 333 334 SkVertices::Builder builder(SkVertices::kTriangles_VertexMode, vertexCount, indexCount, flags); 335 SkPoint* pos = builder.positions(); 336 SkPoint* texs = builder.texCoords(); 337 uint16_t* indices = builder.indices(); 338 bool is_opaque = false; 339 340 /* 341 * 1. Should we offer this as a runtime choice, as we do in gradients? 342 * 2. Since drawing the vertices wants premul, shoudl we extend SkVertices to store 343 * premul colors (as floats, w/ a colorspace)? 344 */ 345 bool doPremul = true; 346 if (cornerColors) { 347 SkColor c = ~0; 348 for (int i = 0; i < kNumCorners; i++) { 349 c &= srcColors[i]; 350 } 351 is_opaque = (SkColorGetA(c) == 0xFF); 352 if (is_opaque) { 353 doPremul = false; // no need 354 } 355 356 skcolor_to_linear(cornerColors, srcColors, kNumCorners, convertCS.get(), doPremul); 357 } 358 359 SkPoint pts[kNumPtsCubic]; 360 SkPatchUtils::GetBottomCubic(cubics, pts); 361 FwDCubicEvaluator fBottom(pts); 362 SkPatchUtils::GetTopCubic(cubics, pts); 363 FwDCubicEvaluator fTop(pts); 364 SkPatchUtils::GetLeftCubic(cubics, pts); 365 FwDCubicEvaluator fLeft(pts); 366 SkPatchUtils::GetRightCubic(cubics, pts); 367 FwDCubicEvaluator fRight(pts); 368 369 fBottom.restart(lodX); 370 fTop.restart(lodX); 371 372 SkScalar u = 0.0f; 373 int stride = lodY + 1; 374 for (int x = 0; x <= lodX; x++) { 375 SkPoint bottom = fBottom.next(), top = fTop.next(); 376 fLeft.restart(lodY); 377 fRight.restart(lodY); 378 SkScalar v = 0.f; 379 for (int y = 0; y <= lodY; y++) { 380 int dataIndex = x * (lodY + 1) + y; 381 382 SkPoint left = fLeft.next(), right = fRight.next(); 383 384 SkPoint s0 = SkPoint::Make((1.0f - v) * top.x() + v * bottom.x(), 385 (1.0f - v) * top.y() + v * bottom.y()); 386 SkPoint s1 = SkPoint::Make((1.0f - u) * left.x() + u * right.x(), 387 (1.0f - u) * left.y() + u * right.y()); 388 SkPoint s2 = SkPoint::Make( 389 (1.0f - v) * ((1.0f - u) * fTop.getCtrlPoints()[0].x() 390 + u * fTop.getCtrlPoints()[3].x()) 391 + v * ((1.0f - u) * fBottom.getCtrlPoints()[0].x() 392 + u * fBottom.getCtrlPoints()[3].x()), 393 (1.0f - v) * ((1.0f - u) * fTop.getCtrlPoints()[0].y() 394 + u * fTop.getCtrlPoints()[3].y()) 395 + v * ((1.0f - u) * fBottom.getCtrlPoints()[0].y() 396 + u * fBottom.getCtrlPoints()[3].y())); 397 pos[dataIndex] = s0 + s1 - s2; 398 399 if (cornerColors) { 400 bilerp(u, v, cornerColors[kTopLeft_Corner].to4f(), 401 cornerColors[kTopRight_Corner].to4f(), 402 cornerColors[kBottomLeft_Corner].to4f(), 403 cornerColors[kBottomRight_Corner].to4f()).store(tmpColors[dataIndex].fVec); 404 if (is_opaque) { 405 tmpColors[dataIndex].fVec[3] = 1; 406 } 407 } 408 409 if (texs) { 410 texs[dataIndex] = SkPoint::Make(bilerp(u, v, srcTexCoords[kTopLeft_Corner].x(), 411 srcTexCoords[kTopRight_Corner].x(), 412 srcTexCoords[kBottomLeft_Corner].x(), 413 srcTexCoords[kBottomRight_Corner].x()), 414 bilerp(u, v, srcTexCoords[kTopLeft_Corner].y(), 415 srcTexCoords[kTopRight_Corner].y(), 416 srcTexCoords[kBottomLeft_Corner].y(), 417 srcTexCoords[kBottomRight_Corner].y())); 418 419 } 420 421 if(x < lodX && y < lodY) { 422 int i = 6 * (x * lodY + y); 423 indices[i] = x * stride + y; 424 indices[i + 1] = x * stride + 1 + y; 425 indices[i + 2] = (x + 1) * stride + 1 + y; 426 indices[i + 3] = indices[i]; 427 indices[i + 4] = indices[i + 2]; 428 indices[i + 5] = (x + 1) * stride + y; 429 } 430 v = SkScalarClampMax(v + 1.f / lodY, 1); 431 } 432 u = SkScalarClampMax(u + 1.f / lodX, 1); 433 } 434 435 if (tmpColors) { 436 if (doPremul) { 437 unpremul(tmpColors, vertexCount); 438 } 439 linear_to_skcolor(builder.colors(), tmpColors, vertexCount, convertCS.get()); 440 } 441 return builder.detach(); 442 } 443