SkPDFGradientShader.cpp (44437B)
1 /* 2 * Copyright 2017 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 "src/pdf/SkPDFGradientShader.h" 9 10 #include "include/core/SkAlphaType.h" 11 #include "include/core/SkPaint.h" 12 #include "include/core/SkPathTypes.h" 13 #include "include/core/SkSpan.h" 14 #include "include/core/SkStream.h" 15 #include "include/core/SkTileMode.h" 16 #include "include/effects/SkGradientShader.h" 17 #include "include/private/base/SkTemplates.h" 18 #include "include/private/base/SkTo.h" 19 #include "src/core/SkChecksum.h" 20 #include "src/core/SkTHash.h" 21 #include "src/pdf/SkPDFDocumentPriv.h" 22 #include "src/pdf/SkPDFFormXObject.h" 23 #include "src/pdf/SkPDFGraphicState.h" 24 #include "src/pdf/SkPDFResourceDict.h" 25 #include "src/pdf/SkPDFTypes.h" 26 #include "src/pdf/SkPDFUtils.h" 27 28 #include <cmath> 29 #include <cstddef> 30 #include <utility> 31 #include <vector> 32 33 using namespace skia_private; 34 35 static uint32_t hash(const SkShaderBase::GradientInfo& v) { 36 uint32_t buffer[] = { 37 (uint32_t)v.fColorCount, 38 SkChecksum::Hash32(v.fColors, v.fColorCount * sizeof(SkColor)), 39 SkChecksum::Hash32(v.fColorOffsets, v.fColorCount * sizeof(SkScalar)), 40 SkChecksum::Hash32(v.fPoint, 2 * sizeof(SkPoint)), 41 SkChecksum::Hash32(v.fRadius, 2 * sizeof(SkScalar)), 42 (uint32_t)v.fTileMode, 43 v.fGradientFlags, 44 }; 45 return SkChecksum::Hash32(buffer, sizeof(buffer)); 46 } 47 48 static uint32_t hash(const SkPDFGradientShader::Key& k) { 49 uint32_t buffer[] = { 50 (uint32_t)k.fType, 51 hash(k.fInfo), 52 SkChecksum::Hash32(&k.fCanvasTransform, sizeof(SkMatrix)), 53 SkChecksum::Hash32(&k.fShaderTransform, sizeof(SkMatrix)), 54 SkChecksum::Hash32(&k.fBBox, sizeof(SkIRect)) 55 }; 56 return SkChecksum::Hash32(buffer, sizeof(buffer)); 57 } 58 59 static void unit_to_points_matrix(const SkPoint pts[2], SkMatrix* matrix) { 60 SkVector vec = pts[1] - pts[0]; 61 SkScalar mag = vec.length(); 62 SkScalar inv = mag ? SkScalarInvert(mag) : 0; 63 64 vec.scale(inv); 65 matrix->setSinCos(vec.fY, vec.fX); 66 matrix->preScale(mag, mag); 67 matrix->postTranslate(pts[0].fX, pts[0].fY); 68 } 69 70 static bool is_premul(const SkShaderBase::GradientInfo& info) { 71 return SkToBool(info.fGradientFlags & SkGradientShader::kInterpolateColorsInPremul_Flag); 72 } 73 74 enum NumComponents { 75 Three = 3, 76 Four = 4, 77 Max = Four, 78 }; 79 80 /* Assumes t - startOffset is on the stack and does a linear interpolation on t 81 between startOffset and endOffset from prevColor to curColor (for each color 82 component), leaving the result in component order on the stack. 83 @param range endOffset - startOffset 84 @param beginColor The previous color. 85 @param endColor The current color. 86 @param numComponents The number of components (3 or 4 if alpha is needed). 87 @param result The result ps function. 88 */ 89 static void interpolate_color_code(SkScalar range, NumComponents numComponents, 90 SkColor4f prevColor, SkColor4f curColor, 91 SkDynamicMemoryWStream* result) { 92 SkASSERT(range != SkIntToScalar(0)); 93 94 /* Linearly interpolate from the previous color to the current. 95 Take the components 0..1 and determine the multipliers for interpolation. 96 C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. 97 */ 98 99 // Figure out how to scale each color component. 100 SkScalar multiplier[NumComponents::Max]; 101 for (int i = 0; i < numComponents; i++) { 102 multiplier[i] = (curColor[i] - prevColor[i]) / range; 103 } 104 105 // Calculate when we no longer need to keep a copy of the input parameter t. 106 // If the last component to use t is i, then dupInput[0..i - 1] = true 107 // and dupInput[i .. components] = false. 108 bool dupInput[NumComponents::Max]; 109 dupInput[numComponents - 1] = false; 110 for (int i = numComponents - 2; i >= 0; i--) { 111 dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0; 112 } 113 114 if (!dupInput[0] && multiplier[0] == 0) { 115 result->writeText("pop "); 116 } 117 118 for (int i = 0; i < numComponents; i++) { 119 // If the next components needs t and this component will consume a 120 // copy, make another copy. 121 if (dupInput[i] && multiplier[i] != 0) { 122 result->writeText("dup "); 123 } 124 125 if (multiplier[i] == 0) { 126 SkPDFUtils::AppendColorComponentF(prevColor[i], result); 127 result->writeText(" "); 128 } else { 129 if (multiplier[i] != 1) { 130 SkPDFUtils::AppendScalar(multiplier[i], result); 131 result->writeText(" mul "); 132 } 133 if (prevColor[i] != 0) { 134 SkPDFUtils::AppendColorComponentF(prevColor[i], result); 135 result->writeText(" add "); 136 } 137 } 138 139 if (dupInput[i]) { 140 result->writeText("exch "); 141 } 142 } 143 } 144 145 // Convert { r, g, b, a } to a == 0 ? {0 0 0} : { r/a, g/a, b/a } 146 static void unpremul(const SkShaderBase::GradientInfo& info, SkDynamicMemoryWStream* function) { 147 // Preview Version 11.0 (1069.7.1) aborts the function if the predicate is like 148 // "dup 0 eq" or any other use of "eq" with "a". 149 function->writeText("dup abs 0.00001 lt" 150 "{ pop pop pop pop 0 0 0 }" 151 "{" 152 " dup" // r g b a a 153 " 3 1 roll" // r g a b a 154 " div" // r g a b/a 155 " 4 1 roll" // b/a r g a 156 " dup" // b/a r g a a 157 " 3 1 roll" // b/a r a g a 158 " div" // b/a r a g/a 159 " 4 1 roll" // g/a b/a r a 160 " div" // g/a b/a r/a 161 " 3 1 roll" // r/a g/a b/a 162 "} ifelse\n"); 163 } 164 165 static void write_gradient_ranges(const SkShaderBase::GradientInfo& info, SkSpan<size_t> rangeEnds, 166 NumComponents numComponents, 167 bool top, bool first, SkDynamicMemoryWStream* result) { 168 SkASSERT(!rangeEnds.empty()); 169 170 size_t rangeEndIndex = rangeEnds[rangeEnds.size() - 1]; 171 SkScalar rangeEnd = info.fColorOffsets[rangeEndIndex]; 172 173 // Each range check tests 0 < t <= end. 174 if (top) { 175 SkASSERT(first); 176 // t may have been set to 0 to signal that the answer has already been found. 177 result->writeText("dup dup 0 gt exch "); // In Preview 11.0 (1033.3) `0. 0 ne` is true. 178 SkPDFUtils::AppendScalar(rangeEnd, result); 179 result->writeText(" le and {\n"); 180 } else if (first) { 181 // After the top level check, only t <= end needs to be tested on if (lo) side. 182 result->writeText("dup "); 183 SkPDFUtils::AppendScalar(rangeEnd, result); 184 result->writeText(" le {\n"); 185 } else { 186 // The else (hi) side. 187 result->writeText("{\n"); 188 } 189 190 if (rangeEnds.size() == 1) { 191 // Set the stack to [r g b]. 192 size_t rangeBeginIndex = rangeEndIndex - 1; 193 SkScalar rangeBegin = info.fColorOffsets[rangeBeginIndex]; 194 SkPDFUtils::AppendScalar(rangeBegin, result); 195 result->writeText(" sub "); // consume t, put t - startOffset on the stack. 196 interpolate_color_code(rangeEnd - rangeBegin, numComponents, 197 info.fColors[rangeBeginIndex], info.fColors[rangeEndIndex], result); 198 result->writeText("\n"); 199 } else { 200 size_t loCount = rangeEnds.size() / 2; 201 SkSpan<size_t> loSpan = rangeEnds.subspan(0, loCount); 202 write_gradient_ranges(info, loSpan, numComponents, false, true, result); 203 204 SkSpan<size_t> hiSpan = rangeEnds.subspan(loCount, rangeEnds.size() - loCount); 205 write_gradient_ranges(info, hiSpan, numComponents, false, false, result); 206 } 207 208 if (top) { 209 // Put 0 on the stack for t once here instead of after every call to interpolate_color_code. 210 result->writeText("0} if\n"); 211 } else if (first) { 212 result->writeText("}"); // The else (hi) side will come next. 213 } else { 214 result->writeText("} ifelse\n"); 215 } 216 } 217 218 /* Generate Type 4 function code to map t to the passed gradient, clamping at the ends. 219 The types integer, real, and boolean are available. 220 There are no string, array, procedure, variable, or name types available. 221 222 The generated code will be of the following form with all values hard coded. 223 224 if (t <= 0) { 225 ret = color[0]; 226 t = 0; 227 } 228 if (t > 0 && t <= stop[4]) { 229 if (t <= stop[2]) { 230 if (t <= stop[1]) { 231 ret = interp(t - stop[0], stop[1] - stop[0], color[0], color[1]); 232 } else { 233 ret = interp(t - stop[1], stop[2] - stop[1], color[1], color[2]); 234 } 235 } else { 236 if (t <= stop[3] { 237 ret = interp(t - stop[2], stop[3] - stop[2], color[2], color[3]); 238 } else { 239 ret = interp(t - stop[3], stop[4] - stop[3], color[3], color[4]); 240 } 241 } 242 t = 0; 243 } 244 if (t > 0) { 245 ret = color[4]; 246 } 247 248 which in PDF will be represented like 249 250 dup 0 le {pop 0 0 0 0} if 251 dup dup 0 gt exch 1 le and { 252 dup .5 le { 253 dup .25 le { 254 0 sub 2 mul 0 0 255 }{ 256 .25 sub .5 exch 2 mul 0 257 } ifelse 258 }{ 259 dup .75 le { 260 .5 sub .5 exch .5 exch 2 mul 261 }{ 262 .75 sub dup 2 mul .5 add exch dup 2 mul .5 add exch 2 mul .5 add 263 } ifelse 264 } ifelse 265 0} if 266 0 gt {1 1 1} if 267 */ 268 static void gradient_function_code(const SkShaderBase::GradientInfo& info, 269 SkDynamicMemoryWStream* result) { 270 // While looking for a hit the stack is [t]. 271 // After finding a hit the stack is [r g b 0]. 272 // The 0 is consumed just before returning. 273 274 const bool premul = is_premul(info); 275 NumComponents numComponents = premul ? NumComponents::Four : NumComponents::Three; 276 277 // The initial range has no previous and contains a solid color. 278 // Any t <= 0 will be handled by this initial range, so later t == 0 indicates a hit was found. 279 result->writeText("dup 0 le {pop "); 280 SkPDFUtils::AppendColorComponentF(info.fColors[0].fR, result); 281 result->writeText(" "); 282 SkPDFUtils::AppendColorComponentF(info.fColors[0].fG, result); 283 result->writeText(" "); 284 SkPDFUtils::AppendColorComponentF(info.fColors[0].fB, result); 285 if (numComponents == NumComponents::Four) { 286 result->writeText(" "); 287 SkPDFUtils::AppendColorComponentF(info.fColors[0].fA, result); 288 } 289 result->writeText(" 0} if\n"); 290 291 // Optimize out ranges which don't make any visual difference. 292 AutoSTMalloc<4, size_t> rangeEnds(info.fColorCount); 293 size_t rangeEndsCount = 0; 294 for (int i = 1; i < info.fColorCount; ++i) { 295 // Ignoring the alpha, is this range the same solid color as the next range? 296 // This optimizes gradients where sometimes only the color or only the alpha is changing. 297 auto eqIgnoringAlpha = [&](SkColor4f a, SkColor4f b) { 298 if (premul) { 299 return a == b; 300 } else { 301 return a.makeOpaque() == b.makeOpaque(); 302 } 303 }; 304 bool constantColorBothSides = 305 eqIgnoringAlpha(info.fColors[i-1], info.fColors[i]) &&// This range is a solid color. 306 i != info.fColorCount-1 && // This is not the last range. 307 eqIgnoringAlpha(info.fColors[i], info.fColors[i+1]); // Next range is same solid color. 308 309 // Does this range have zero size? 310 bool degenerateRange = info.fColorOffsets[i-1] == info.fColorOffsets[i]; 311 312 if (!degenerateRange && !constantColorBothSides) { 313 rangeEnds[rangeEndsCount] = i; 314 ++rangeEndsCount; 315 } 316 } 317 318 // If a cap on depth is needed, loop here. 319 write_gradient_ranges(info, SkSpan(rangeEnds.get(), rangeEndsCount), 320 numComponents, true, true, result); 321 322 // Clamp the final color. 323 result->writeText("0 gt {"); 324 SkPDFUtils::AppendColorComponentF(info.fColors[info.fColorCount - 1].fR, result); 325 result->writeText(" "); 326 SkPDFUtils::AppendColorComponentF(info.fColors[info.fColorCount - 1].fG, result); 327 result->writeText(" "); 328 SkPDFUtils::AppendColorComponentF(info.fColors[info.fColorCount - 1].fB, result); 329 if (numComponents == NumComponents::Four) { 330 result->writeText(" "); 331 SkPDFUtils::AppendColorComponentF(info.fColors[info.fColorCount - 1].fA, result); 332 } 333 result->writeText("} if\n"); 334 335 if (premul) { 336 unpremul(info, result); 337 } 338 } 339 340 static std::unique_ptr<SkPDFDict> createInterpolationFunction(const SkColor4f& color1, 341 const SkColor4f& color2) { 342 auto retval = SkPDFMakeDict(); 343 344 auto c0 = SkPDFMakeArray(); 345 c0->appendColorComponentF(color1.fR); 346 c0->appendColorComponentF(color1.fG); 347 c0->appendColorComponentF(color1.fB); 348 retval->insertObject("C0", std::move(c0)); 349 350 auto c1 = SkPDFMakeArray(); 351 c1->appendColorComponentF(color2.fR); 352 c1->appendColorComponentF(color2.fG); 353 c1->appendColorComponentF(color2.fB); 354 retval->insertObject("C1", std::move(c1)); 355 356 retval->insertObject("Domain", SkPDFMakeArray(0, 1)); 357 358 retval->insertInt("FunctionType", 2); 359 retval->insertScalar("N", 1.0f); 360 361 return retval; 362 } 363 364 static std::unique_ptr<SkPDFDict> gradientStitchCode(const SkShaderBase::GradientInfo& info) { 365 auto retval = SkPDFMakeDict(); 366 367 // normalize color stops 368 int colorCount = info.fColorCount; 369 std::vector<SkColor4f> colors(info.fColors, info.fColors + colorCount); 370 std::vector<SkScalar> colorOffsets(info.fColorOffsets, info.fColorOffsets + colorCount); 371 372 int i = 1; 373 while (i < colorCount - 1) { 374 // ensure stops are in order 375 if (colorOffsets[i - 1] > colorOffsets[i]) { 376 colorOffsets[i] = colorOffsets[i - 1]; 377 } 378 379 // remove points that are between 2 coincident points 380 if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) { 381 colorCount -= 1; 382 colors.erase(colors.begin() + i); 383 colorOffsets.erase(colorOffsets.begin() + i); 384 } else { 385 i++; 386 } 387 } 388 // find coincident points and slightly move them over 389 for (i = 1; i < colorCount - 1; i++) { 390 if (colorOffsets[i - 1] == colorOffsets[i]) { 391 colorOffsets[i] += 0.00001f; 392 } 393 } 394 // check if last 2 stops coincide 395 if (colorOffsets[i - 1] == colorOffsets[i]) { 396 colorOffsets[i - 1] -= 0.00001f; 397 } 398 399 // no need for a stitch function if there are only 2 stops. 400 if (colorCount == 2) { 401 return createInterpolationFunction(colors[0], colors[1]); 402 } 403 404 auto encode = SkPDFMakeArray(); 405 auto bounds = SkPDFMakeArray(); 406 auto functions = SkPDFMakeArray(); 407 408 retval->insertObject("Domain", SkPDFMakeArray(0, 1)); 409 retval->insertInt("FunctionType", 3); 410 411 for (int idx = 1; idx < colorCount; idx++) { 412 if (idx > 1) { 413 bounds->appendScalar(colorOffsets[idx-1]); 414 } 415 416 encode->appendScalar(0); 417 encode->appendScalar(1.0f); 418 419 functions->appendObject(createInterpolationFunction(colors[idx-1], colors[idx])); 420 } 421 422 retval->insertObject("Encode", std::move(encode)); 423 retval->insertObject("Bounds", std::move(bounds)); 424 retval->insertObject("Functions", std::move(functions)); 425 426 return retval; 427 } 428 429 /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ 430 static void tileModeCode(SkTileMode mode, SkDynamicMemoryWStream* result) { 431 if (mode == SkTileMode::kRepeat) { 432 result->writeText("dup truncate sub\n"); // Get the fractional part. 433 result->writeText("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1) 434 return; 435 } 436 437 if (mode == SkTileMode::kMirror) { 438 // In Preview 11.0 (1033.3) `a n mod r eq` (with a and n both integers, r integer or real) 439 // early aborts the function when false would be put on the stack. 440 // Work around this by re-writing `t 2 mod 1 eq` as `t 2 mod 0 gt`. 441 442 // Map t mod 2 into [0, 1, 1, 0]. 443 // Code Stack t 444 result->writeText("abs " // +t 445 "dup " // +t.s +t.s 446 "truncate " // +t.s +t 447 "dup " // +t.s +t +t 448 "cvi " // +t.s +t +T 449 "2 mod " // +t.s +t (+T mod 2) 450 /*"1 eq "*/ "0 gt " // +t.s +t true|false 451 "3 1 roll " // true|false +t.s +t 452 "sub " // true|false 0.s 453 "exch " // 0.s true|false 454 "{1 exch sub} if\n"); // 1 - 0.s|0.s 455 } 456 } 457 458 /** 459 * Returns PS function code that applies inverse perspective 460 * to a x, y point. 461 * The function assumes that the stack has at least two elements, 462 * and that the top 2 elements are numeric values. 463 * After executing this code on a PS stack, the last 2 elements are updated 464 * while the rest of the stack is preserved intact. 465 * inversePerspectiveMatrix is the inverse perspective matrix. 466 */ 467 static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix, 468 SkDynamicMemoryWStream* code) { 469 if (!inversePerspectiveMatrix.hasPerspective()) { 470 return; 471 } 472 473 // Perspective matrix should be: 474 // 1 0 0 475 // 0 1 0 476 // p0 p1 p2 477 478 const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; 479 const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; 480 const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; 481 482 // y = y / (p2 + p0 x + p1 y) 483 // x = x / (p2 + p0 x + p1 y) 484 485 // Input on stack: x y 486 code->writeText(" dup "); // x y y 487 SkPDFUtils::AppendScalar(p1, code); // x y y p1 488 code->writeText(" mul " // x y y*p1 489 " 2 index "); // x y y*p1 x 490 SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0 491 code->writeText(" mul "); // x y y*p1 x*p0 492 SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2 493 code->writeText(" add " // x y y*p1 x*p0+p2 494 "add " // x y y*p1+x*p0+p2 495 "3 1 roll " // y*p1+x*p0+p2 x y 496 "2 index " // z x y y*p1+x*p0+p2 497 "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) 498 "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x 499 "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 500 "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) 501 "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) 502 } 503 504 static void linearCode(const SkShaderBase::GradientInfo& info, 505 const SkMatrix& perspectiveRemover, 506 SkDynamicMemoryWStream* function) { 507 function->writeText("{"); 508 509 apply_perspective_to_coordinates(perspectiveRemover, function); 510 511 function->writeText("pop\n"); // Just ditch the y value. 512 tileModeCode((SkTileMode)info.fTileMode, function); 513 gradient_function_code(info, function); 514 function->writeText("}"); 515 } 516 517 static void radialCode(const SkShaderBase::GradientInfo& info, 518 const SkMatrix& perspectiveRemover, 519 SkDynamicMemoryWStream* function) { 520 function->writeText("{"); 521 522 apply_perspective_to_coordinates(perspectiveRemover, function); 523 524 // Find the distance from the origin. 525 function->writeText("dup " // x y y 526 "mul " // x y^2 527 "exch " // y^2 x 528 "dup " // y^2 x x 529 "mul " // y^2 x^2 530 "add " // y^2+x^2 531 "sqrt\n"); // sqrt(y^2+x^2) 532 533 tileModeCode((SkTileMode)info.fTileMode, function); 534 gradient_function_code(info, function); 535 function->writeText("}"); 536 } 537 538 /* Conical gradient shader, based on the Canvas spec for radial gradients 539 See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient 540 */ 541 static void twoPointConicalCode(const SkShaderBase::GradientInfo& info, 542 const SkMatrix& perspectiveRemover, 543 SkDynamicMemoryWStream* function) { 544 SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX; 545 SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY; 546 SkScalar r0 = info.fRadius[0]; 547 SkScalar dr = info.fRadius[1] - info.fRadius[0]; 548 SkScalar a = dx * dx + dy * dy - dr * dr; 549 550 // First compute t, if the pixel falls outside the cone, then we'll end 551 // with 'false' on the stack, otherwise we'll push 'true' with t below it 552 553 // We start with a stack of (x y), copy it and then consume one copy in 554 // order to calculate b and the other to calculate c. 555 function->writeText("{"); 556 557 apply_perspective_to_coordinates(perspectiveRemover, function); 558 559 function->writeText("2 copy "); 560 561 // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr). 562 SkPDFUtils::AppendScalar(dy, function); 563 function->writeText(" mul exch "); 564 SkPDFUtils::AppendScalar(dx, function); 565 function->writeText(" mul add "); 566 SkPDFUtils::AppendScalar(r0 * dr, function); 567 function->writeText(" add -2 mul dup dup mul\n"); 568 569 // c = x^2 + y^2 + radius0^2 570 function->writeText("4 2 roll dup mul exch dup mul add "); 571 SkPDFUtils::AppendScalar(r0 * r0, function); 572 function->writeText(" sub dup 4 1 roll\n"); 573 574 // Contents of the stack at this point: c, b, b^2, c 575 576 // if a = 0, then we collapse to a simpler linear case 577 if (a == 0) { 578 579 // t = -c/b 580 function->writeText("pop pop div neg dup "); 581 582 // compute radius(t) 583 SkPDFUtils::AppendScalar(dr, function); 584 function->writeText(" mul "); 585 SkPDFUtils::AppendScalar(r0, function); 586 function->writeText(" add\n"); 587 588 // if r(t) < 0, then it's outside the cone 589 function->writeText("0 lt {pop false} {true} ifelse\n"); 590 591 } else { 592 593 // quadratic case: the Canvas spec wants the largest 594 // root t for which radius(t) > 0 595 596 // compute the discriminant (b^2 - 4ac) 597 SkPDFUtils::AppendScalar(a * 4, function); 598 function->writeText(" mul sub dup\n"); 599 600 // if d >= 0, proceed 601 function->writeText("0 ge {\n"); 602 603 // an intermediate value we'll use to compute the roots: 604 // q = -0.5 * (b +/- sqrt(d)) 605 function->writeText("sqrt exch dup 0 lt {exch -1 mul} if"); 606 function->writeText(" add -0.5 mul dup\n"); 607 608 // first root = q / a 609 SkPDFUtils::AppendScalar(a, function); 610 function->writeText(" div\n"); 611 612 // second root = c / q 613 function->writeText("3 1 roll div\n"); 614 615 // put the larger root on top of the stack 616 function->writeText("2 copy gt {exch} if\n"); 617 618 // compute radius(t) for larger root 619 function->writeText("dup "); 620 SkPDFUtils::AppendScalar(dr, function); 621 function->writeText(" mul "); 622 SkPDFUtils::AppendScalar(r0, function); 623 function->writeText(" add\n"); 624 625 // if r(t) > 0, we have our t, pop off the smaller root and we're done 626 function->writeText(" 0 gt {exch pop true}\n"); 627 628 // otherwise, throw out the larger one and try the smaller root 629 function->writeText("{pop dup\n"); 630 SkPDFUtils::AppendScalar(dr, function); 631 function->writeText(" mul "); 632 SkPDFUtils::AppendScalar(r0, function); 633 function->writeText(" add\n"); 634 635 // if r(t) < 0, push false, otherwise the smaller root is our t 636 function->writeText("0 le {pop false} {true} ifelse\n"); 637 function->writeText("} ifelse\n"); 638 639 // d < 0, clear the stack and push false 640 function->writeText("} {pop pop pop false} ifelse\n"); 641 } 642 643 // if the pixel is in the cone, proceed to compute a color 644 function->writeText("{"); 645 tileModeCode((SkTileMode)info.fTileMode, function); 646 gradient_function_code(info, function); 647 648 // otherwise, just write black 649 // TODO: Correctly draw gradients_local_persepective, need to mask out this black 650 // The "gradients" gm works as falls into the 8.7.4.5.4 "Type 3 (Radial) Shadings" case. 651 function->writeText("} {0 0 0} ifelse }"); 652 } 653 654 static void sweepCode(const SkShaderBase::GradientInfo& info, 655 const SkMatrix& perspectiveRemover, 656 SkDynamicMemoryWStream* function) { 657 function->writeText("{exch atan 360 div\n"); 658 const SkScalar bias = info.fPoint[1].y(); 659 if (bias != 0.0f) { 660 SkPDFUtils::AppendScalar(bias, function); 661 function->writeText(" add\n"); 662 } 663 const SkScalar scale = info.fPoint[1].x(); 664 if (scale != 1.0f) { 665 SkPDFUtils::AppendScalar(scale, function); 666 function->writeText(" mul\n"); 667 } 668 tileModeCode((SkTileMode)info.fTileMode, function); 669 gradient_function_code(info, function); 670 function->writeText("}"); 671 } 672 673 674 // catch cases where the inner just touches the outer circle 675 // and make the inner circle just inside the outer one to match raster 676 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) { 677 // detect touching circles 678 SkScalar distance = SkPoint::Distance(p1, p2); 679 SkScalar subtractRadii = fabs(r1 - r2); 680 if (fabs(distance - subtractRadii) < 0.002f) { 681 if (r1 > r2) { 682 r1 += 0.002f; 683 } else { 684 r2 += 0.002f; 685 } 686 } 687 } 688 689 // Finds affine and persp such that in = affine * persp. 690 // but it returns the inverse of perspective matrix. 691 static bool split_perspective(const SkMatrix in, SkMatrix* affine, 692 SkMatrix* perspectiveInverse) { 693 const SkScalar p2 = in[SkMatrix::kMPersp2]; 694 695 if (SkScalarNearlyZero(p2)) { 696 return false; 697 } 698 699 const SkScalar zero = SkIntToScalar(0); 700 const SkScalar one = SkIntToScalar(1); 701 702 const SkScalar sx = in[SkMatrix::kMScaleX]; 703 const SkScalar kx = in[SkMatrix::kMSkewX]; 704 const SkScalar tx = in[SkMatrix::kMTransX]; 705 const SkScalar ky = in[SkMatrix::kMSkewY]; 706 const SkScalar sy = in[SkMatrix::kMScaleY]; 707 const SkScalar ty = in[SkMatrix::kMTransY]; 708 const SkScalar p0 = in[SkMatrix::kMPersp0]; 709 const SkScalar p1 = in[SkMatrix::kMPersp1]; 710 711 // Perspective matrix would be: 712 // 1 0 0 713 // 0 1 0 714 // p0 p1 p2 715 // But we need the inverse of persp. 716 perspectiveInverse->setAll(one, zero, zero, 717 zero, one, zero, 718 -p0/p2, -p1/p2, 1/p2); 719 720 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2, 721 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2, 722 zero, zero, one); 723 724 return true; 725 } 726 727 static SkPDFIndirectReference make_ps_function(std::unique_ptr<SkStreamAsset> psCode, 728 std::unique_ptr<SkPDFArray> domain, 729 std::unique_ptr<SkPDFObject> range, 730 SkPDFDocument* doc) { 731 std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict(); 732 dict->insertInt("FunctionType", 4); 733 dict->insertObject("Domain", std::move(domain)); 734 dict->insertObject("Range", std::move(range)); 735 return SkPDFStreamOut(std::move(dict), std::move(psCode), doc); 736 } 737 738 static SkPDFIndirectReference make_function_shader(SkPDFDocument* doc, 739 const SkPDFGradientShader::Key& state) { 740 SkPoint transformPoints[2]; 741 const SkShaderBase::GradientInfo& info = state.fInfo; 742 SkMatrix finalMatrix = state.fCanvasTransform; 743 finalMatrix.preConcat(state.fShaderTransform); 744 745 bool doStitchFunctions = (state.fType == SkShaderBase::GradientType::kLinear || 746 state.fType == SkShaderBase::GradientType::kRadial || 747 state.fType == SkShaderBase::GradientType::kConical) && 748 (info.fTileMode == SkTileMode::kClamp || 749 info.fTileMode == SkTileMode::kDecal) && 750 !finalMatrix.hasPerspective() && 751 !is_premul(info); 752 753 enum class ShadingType : int32_t { 754 Function = 1, 755 Axial = 2, 756 Radial = 3, 757 FreeFormGouraudTriangleMesh = 4, 758 LatticeFormGouraudTriangleMesh = 5, 759 CoonsPatchMesh = 6, 760 TensorProductPatchMesh = 7, 761 } shadingType; 762 763 auto pdfShader = SkPDFMakeDict(); 764 if (doStitchFunctions) { 765 pdfShader->insertObject("Function", gradientStitchCode(info)); 766 767 if (info.fTileMode == SkTileMode::kClamp) { 768 auto extend = SkPDFMakeArray(); 769 extend->reserve(2); 770 extend->appendBool(true); 771 extend->appendBool(true); 772 pdfShader->insertObject("Extend", std::move(extend)); 773 } 774 775 std::unique_ptr<SkPDFArray> coords; 776 switch (state.fType) { 777 case SkShaderBase::GradientType::kLinear: { 778 shadingType = ShadingType::Axial; 779 const SkPoint& pt1 = info.fPoint[0]; 780 const SkPoint& pt2 = info.fPoint[1]; 781 coords = SkPDFMakeArray(pt1.x(), pt1.y(), 782 pt2.x(), pt2.y()); 783 } break; 784 case SkShaderBase::GradientType::kRadial: { 785 shadingType = ShadingType::Radial; 786 const SkPoint& pt1 = info.fPoint[0]; 787 coords = SkPDFMakeArray(pt1.x(), pt1.y(), 0, 788 pt1.x(), pt1.y(), info.fRadius[0]); 789 } break; 790 case SkShaderBase::GradientType::kConical: { 791 shadingType = ShadingType::Radial; 792 SkScalar r1 = info.fRadius[0]; 793 SkScalar r2 = info.fRadius[1]; 794 SkPoint pt1 = info.fPoint[0]; 795 SkPoint pt2 = info.fPoint[1]; 796 FixUpRadius(pt1, r1, pt2, r2); 797 798 coords = SkPDFMakeArray(pt1.x(), pt1.y(), r1, 799 pt2.x(), pt2.y(), r2); 800 break; 801 } 802 case SkShaderBase::GradientType::kSweep: 803 case SkShaderBase::GradientType::kNone: 804 default: 805 SkASSERT(false); 806 return SkPDFIndirectReference(); 807 } 808 pdfShader->insertObject("Coords", std::move(coords)); 809 } else { 810 shadingType = ShadingType::Function; 811 812 // Transform the coordinate space for the type of gradient. 813 transformPoints[0] = info.fPoint[0]; 814 transformPoints[1] = info.fPoint[1]; 815 switch (state.fType) { 816 case SkShaderBase::GradientType::kLinear: 817 break; 818 case SkShaderBase::GradientType::kRadial: 819 transformPoints[1] = transformPoints[0]; 820 transformPoints[1].fX += info.fRadius[0]; 821 break; 822 case SkShaderBase::GradientType::kConical: { 823 transformPoints[1] = transformPoints[0]; 824 transformPoints[1].fX += SK_Scalar1; 825 break; 826 } 827 case SkShaderBase::GradientType::kSweep: 828 transformPoints[1] = transformPoints[0]; 829 transformPoints[1].fX += SK_Scalar1; 830 break; 831 case SkShaderBase::GradientType::kNone: 832 default: 833 return SkPDFIndirectReference(); 834 } 835 836 // Move any scaling (assuming a unit gradient) or translation 837 // (and rotation for linear gradient), of the final gradient from 838 // info.fPoints to the matrix (updating bbox appropriately). Now 839 // the gradient can be drawn on on the unit segment. 840 SkMatrix mapperMatrix; 841 unit_to_points_matrix(transformPoints, &mapperMatrix); 842 843 finalMatrix.preConcat(mapperMatrix); 844 845 // Preserves as much as possible in the final matrix, and only removes 846 // the perspective. The inverse of the perspective is stored in 847 // perspectiveInverseOnly matrix and has 3 useful numbers 848 // (p0, p1, p2), while everything else is either 0 or 1. 849 // In this way the shader will handle it eficiently, with minimal code. 850 SkMatrix perspectiveInverseOnly = SkMatrix::I(); 851 if (finalMatrix.hasPerspective()) { 852 if (!split_perspective(finalMatrix, 853 &finalMatrix, &perspectiveInverseOnly)) { 854 return SkPDFIndirectReference(); 855 } 856 } 857 858 SkRect bbox; 859 bbox.set(state.fBBox); 860 if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) { 861 return SkPDFIndirectReference(); 862 } 863 864 SkDynamicMemoryWStream functionCode; 865 switch (state.fType) { 866 case SkShaderBase::GradientType::kLinear: 867 linearCode(info, perspectiveInverseOnly, &functionCode); 868 break; 869 case SkShaderBase::GradientType::kRadial: 870 radialCode(info, perspectiveInverseOnly, &functionCode); 871 break; 872 case SkShaderBase::GradientType::kConical: { 873 // The two point radial gradient further references state.fInfo 874 // in translating from x, y coordinates to the t parameter. So, we have 875 // to transform the points and radii according to the calculated matrix. 876 auto inverseMapperMatrix = mapperMatrix.invert(); 877 if (!inverseMapperMatrix) { 878 return SkPDFIndirectReference(); 879 } 880 SkShaderBase::GradientInfo infoCopy = info; 881 inverseMapperMatrix->mapPoints(infoCopy.fPoint); 882 infoCopy.fRadius[0] = inverseMapperMatrix->mapRadius(info.fRadius[0]); 883 infoCopy.fRadius[1] = inverseMapperMatrix->mapRadius(info.fRadius[1]); 884 twoPointConicalCode(infoCopy, perspectiveInverseOnly, &functionCode); 885 } break; 886 case SkShaderBase::GradientType::kSweep: 887 sweepCode(info, perspectiveInverseOnly, &functionCode); 888 break; 889 default: 890 SkASSERT(false); 891 } 892 pdfShader->insertObject( 893 "Domain", SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom())); 894 895 auto domain = SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom()); 896 std::unique_ptr<SkPDFArray> rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1); 897 pdfShader->insertRef("Function", 898 make_ps_function(functionCode.detachAsStream(), std::move(domain), 899 std::move(rangeObject), doc)); 900 } 901 902 pdfShader->insertInt("ShadingType", SkToS32(shadingType)); 903 pdfShader->insertName("ColorSpace", "DeviceRGB"); 904 905 SkPDFDict pdfFunctionShader("Pattern"); 906 pdfFunctionShader.insertInt("PatternType", 2); 907 pdfFunctionShader.insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix)); 908 pdfFunctionShader.insertObject("Shading", std::move(pdfShader)); 909 return doc->emit(pdfFunctionShader); 910 } 911 912 static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc, 913 SkPDFGradientShader::Key key, 914 bool makeAlphaShader); 915 916 static std::unique_ptr<SkPDFDict> get_gradient_resource_dict(SkPDFIndirectReference functionShader, 917 SkPDFIndirectReference gState) { 918 std::vector<SkPDFIndirectReference> patternShaders; 919 if (functionShader != SkPDFIndirectReference()) { 920 patternShaders.push_back(functionShader); 921 } 922 std::vector<SkPDFIndirectReference> graphicStates; 923 if (gState != SkPDFIndirectReference()) { 924 graphicStates.push_back(gState); 925 } 926 return SkPDFMakeResourceDict(std::move(graphicStates), 927 std::move(patternShaders), 928 std::vector<SkPDFIndirectReference>(), 929 std::vector<SkPDFIndirectReference>()); 930 } 931 932 // Creates a content stream which fills the pattern P0 across bounds. 933 // @param gsIndex A graphics state resource index to apply, or <0 if no 934 // graphics state to apply. 935 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex, 936 int patternIndex, 937 SkRect& bounds) { 938 SkDynamicMemoryWStream content; 939 if (gsIndex >= 0) { 940 SkPDFUtils::ApplyGraphicState(gsIndex, &content); 941 } 942 SkPDFUtils::ApplyPattern(patternIndex, &content); 943 SkPDFUtils::AppendRectangle(bounds, &content); 944 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPathFillType::kEvenOdd, &content); 945 return content.detachAsStream(); 946 } 947 948 static bool gradient_has_alpha(const SkPDFGradientShader::Key& key) { 949 SkASSERT(key.fType != SkShaderBase::GradientType::kNone); 950 for (int i = 0; i < key.fInfo.fColorCount; i++) { 951 if (!key.fInfo.fColors[i].isOpaque()) { 952 return true; 953 } 954 } 955 return false; 956 } 957 958 // warning: does not set fHash on new key. (Both callers need to change fields.) 959 static SkPDFGradientShader::Key clone_key(const SkPDFGradientShader::Key& k) { 960 SkPDFGradientShader::Key clone = { 961 k.fType, 962 k.fInfo, // change pointers later. 963 std::unique_ptr<SkColor4f[]>(new SkColor4f[k.fInfo.fColorCount]), 964 std::unique_ptr<SkScalar[]>(new SkScalar[k.fInfo.fColorCount]), 965 k.fCanvasTransform, 966 k.fShaderTransform, 967 k.fBBox, 0}; 968 clone.fInfo.fColors = clone.fColors.get(); 969 clone.fInfo.fColorOffsets = clone.fStops.get(); 970 for (int i = 0; i < clone.fInfo.fColorCount; i++) { 971 clone.fInfo.fColorOffsets[i] = k.fInfo.fColorOffsets[i]; 972 clone.fInfo.fColors[i] = k.fInfo.fColors[i]; 973 } 974 return clone; 975 } 976 977 static SkPDFIndirectReference create_smask_graphic_state(SkPDFDocument* doc, 978 const SkPDFGradientShader::Key& state) { 979 SkASSERT(state.fType != SkShaderBase::GradientType::kNone); 980 SkPDFGradientShader::Key luminosityState = clone_key(state); 981 for (int i = 0; i < luminosityState.fInfo.fColorCount; i++) { 982 float alpha = luminosityState.fInfo.fColors[i].fA; 983 luminosityState.fInfo.fColors[i] = SkColor4f{alpha, alpha, alpha, 1.0f}; 984 } 985 luminosityState.fInfo.fGradientFlags &= ~SkGradientShader::kInterpolateColorsInPremul_Flag; 986 luminosityState.fHash = hash(luminosityState); 987 988 SkASSERT(!gradient_has_alpha(luminosityState)); 989 SkPDFIndirectReference luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false); 990 std::unique_ptr<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader, 991 SkPDFIndirectReference()); 992 SkRect bbox = SkRect::Make(state.fBBox); 993 SkPDFIndirectReference alphaMask = 994 SkPDFMakeFormXObject(doc, 995 create_pattern_fill_content(-1, luminosityShader.fValue, bbox), 996 SkPDFUtils::RectToArray(bbox), 997 std::move(resources), 998 SkMatrix::I(), 999 "DeviceRGB"); 1000 return SkPDFGraphicState::GetSMaskGraphicState( 1001 alphaMask, false, SkPDFGraphicState::kLuminosity_SMaskMode, doc); 1002 } 1003 1004 static SkPDFIndirectReference make_alpha_function_shader(SkPDFDocument* doc, 1005 const SkPDFGradientShader::Key& state) { 1006 SkASSERT(state.fType != SkShaderBase::GradientType::kNone); 1007 SkPDFGradientShader::Key opaqueState = clone_key(state); 1008 const bool keepAlpha = is_premul(opaqueState.fInfo); 1009 if (!keepAlpha) { 1010 for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) { 1011 opaqueState.fInfo.fColors[i].fA = 1.0f; 1012 } 1013 opaqueState.fHash = hash(opaqueState); 1014 1015 SkASSERT(!gradient_has_alpha(opaqueState)); 1016 } 1017 SkRect bbox = SkRect::Make(state.fBBox); 1018 SkPDFIndirectReference colorShader = find_pdf_shader(doc, std::move(opaqueState), false); 1019 if (!colorShader) { 1020 return SkPDFIndirectReference(); 1021 } 1022 // Create resource dict with alpha graphics state as G0 and 1023 // pattern shader as P0, then write content stream. 1024 SkPDFIndirectReference alphaGsRef = create_smask_graphic_state(doc, state); 1025 1026 std::unique_ptr<SkPDFDict> resourceDict = get_gradient_resource_dict(colorShader, alphaGsRef); 1027 1028 std::unique_ptr<SkStreamAsset> colorStream = 1029 create_pattern_fill_content(alphaGsRef.fValue, colorShader.fValue, bbox); 1030 std::unique_ptr<SkPDFDict> alphaFunctionShader = SkPDFMakeDict(); 1031 SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader.get(), bbox, 1032 std::move(resourceDict), SkMatrix::I()); 1033 return SkPDFStreamOut(std::move(alphaFunctionShader), std::move(colorStream), doc); 1034 } 1035 1036 static SkPDFGradientShader::Key make_key(const SkShader* shader, 1037 const SkMatrix& canvasTransform, 1038 const SkIRect& bbox) { 1039 SkPDFGradientShader::Key key = { 1040 SkShaderBase::GradientType::kNone, 1041 {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkTileMode::kClamp, 0}, 1042 nullptr, 1043 nullptr, 1044 canvasTransform, 1045 SkPDFUtils::GetShaderLocalMatrix(shader), 1046 bbox, 0}; 1047 key.fType = as_SB(shader)->asGradient(&key.fInfo); 1048 SkASSERT(SkShaderBase::GradientType::kNone != key.fType); 1049 SkASSERT(key.fInfo.fColorCount > 0); 1050 key.fColors = std::make_unique<SkColor4f[]>(key.fInfo.fColorCount); 1051 key.fStops = std::make_unique<SkScalar[]>(key.fInfo.fColorCount); 1052 key.fInfo.fColors = key.fColors.get(); 1053 key.fInfo.fColorOffsets = key.fStops.get(); 1054 as_SB(shader)->asGradient(&key.fInfo); 1055 if (is_premul(key.fInfo)) { 1056 bool changedByPremul = false; 1057 for (auto&& c : SkSpan(key.fInfo.fColors, key.fInfo.fColorCount)) { 1058 if (c.fA != 1.0) { 1059 changedByPremul = true; 1060 } 1061 SkRGBA4f<kPremul_SkAlphaType> pm = c.premul(); 1062 c = SkColor4f{pm.fR, pm.fG, pm.fB, pm.fA}; 1063 } 1064 if (!changedByPremul) { 1065 key.fInfo.fGradientFlags &= ~SkGradientShader::kInterpolateColorsInPremul_Flag; 1066 } 1067 } 1068 key.fHash = hash(key); 1069 return key; 1070 } 1071 1072 static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc, 1073 SkPDFGradientShader::Key key, 1074 bool makeAlphaShader) { 1075 auto& gradientPatternMap = doc->fGradientPatternMap; 1076 if (SkPDFIndirectReference* ptr = gradientPatternMap.find(key)) { 1077 return *ptr; 1078 } 1079 SkPDFIndirectReference pdfShader; 1080 if (makeAlphaShader) { 1081 pdfShader = make_alpha_function_shader(doc, key); 1082 } else { 1083 pdfShader = make_function_shader(doc, key); 1084 } 1085 gradientPatternMap.set(std::move(key), pdfShader); 1086 return pdfShader; 1087 } 1088 1089 SkPDFIndirectReference SkPDFGradientShader::Make(SkPDFDocument* doc, 1090 SkShader* shader, 1091 const SkMatrix& canvasTransform, 1092 const SkIRect& bbox) { 1093 SkASSERT(shader); 1094 SkASSERT(as_SB(shader)->asGradient() != SkShaderBase::GradientType::kNone); 1095 SkPDFGradientShader::Key key = make_key(shader, canvasTransform, bbox); 1096 const bool makeAlphaShader = gradient_has_alpha(key); 1097 return find_pdf_shader(doc, std::move(key), makeAlphaShader); 1098 }