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 "SkPDFGradientShader.h" 9 10 #include "SkOpts.h" 11 #include "SkPDFDocument.h" 12 #include "SkPDFFormXObject.h" 13 #include "SkPDFResourceDict.h" 14 #include "SkPDFUtils.h" 15 16 static uint32_t hash(const SkShader::GradientInfo& v) { 17 uint32_t buffer[] = { 18 (uint32_t)v.fColorCount, 19 SkOpts::hash(v.fColors, v.fColorCount * sizeof(SkColor)), 20 SkOpts::hash(v.fColorOffsets, v.fColorCount * sizeof(SkScalar)), 21 SkOpts::hash(v.fPoint, 2 * sizeof(SkPoint)), 22 SkOpts::hash(v.fRadius, 2 * sizeof(SkScalar)), 23 (uint32_t)v.fTileMode, 24 v.fGradientFlags, 25 }; 26 return SkOpts::hash(buffer, sizeof(buffer)); 27 } 28 29 static uint32_t hash(const SkPDFGradientShader::Key& k) { 30 uint32_t buffer[] = { 31 (uint32_t)k.fType, 32 hash(k.fInfo), 33 SkOpts::hash(&k.fCanvasTransform, sizeof(SkMatrix)), 34 SkOpts::hash(&k.fShaderTransform, sizeof(SkMatrix)), 35 SkOpts::hash(&k.fBBox, sizeof(SkIRect)) 36 }; 37 return SkOpts::hash(buffer, sizeof(buffer)); 38 } 39 40 static void unit_to_points_matrix(const SkPoint pts[2], SkMatrix* matrix) { 41 SkVector vec = pts[1] - pts[0]; 42 SkScalar mag = vec.length(); 43 SkScalar inv = mag ? SkScalarInvert(mag) : 0; 44 45 vec.scale(inv); 46 matrix->setSinCos(vec.fY, vec.fX); 47 matrix->preScale(mag, mag); 48 matrix->postTranslate(pts[0].fX, pts[0].fY); 49 } 50 51 static const int kColorComponents = 3; 52 typedef uint8_t ColorTuple[kColorComponents]; 53 54 /* Assumes t + startOffset is on the stack and does a linear interpolation on t 55 between startOffset and endOffset from prevColor to curColor (for each color 56 component), leaving the result in component order on the stack. It assumes 57 there are always 3 components per color. 58 @param range endOffset - startOffset 59 @param curColor[components] The current color components. 60 @param prevColor[components] The previous color components. 61 @param result The result ps function. 62 */ 63 static void interpolate_color_code(SkScalar range, const ColorTuple& curColor, 64 const ColorTuple& prevColor, 65 SkDynamicMemoryWStream* result) { 66 SkASSERT(range != SkIntToScalar(0)); 67 68 // Figure out how to scale each color component. 69 SkScalar multiplier[kColorComponents]; 70 for (int i = 0; i < kColorComponents; i++) { 71 static const SkScalar kColorScale = SkScalarInvert(255); 72 multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range; 73 } 74 75 // Calculate when we no longer need to keep a copy of the input parameter t. 76 // If the last component to use t is i, then dupInput[0..i - 1] = true 77 // and dupInput[i .. components] = false. 78 bool dupInput[kColorComponents]; 79 dupInput[kColorComponents - 1] = false; 80 for (int i = kColorComponents - 2; i >= 0; i--) { 81 dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0; 82 } 83 84 if (!dupInput[0] && multiplier[0] == 0) { 85 result->writeText("pop "); 86 } 87 88 for (int i = 0; i < kColorComponents; i++) { 89 // If the next components needs t and this component will consume a 90 // copy, make another copy. 91 if (dupInput[i] && multiplier[i] != 0) { 92 result->writeText("dup "); 93 } 94 95 if (multiplier[i] == 0) { 96 SkPDFUtils::AppendColorComponent(prevColor[i], result); 97 result->writeText(" "); 98 } else { 99 if (multiplier[i] != 1) { 100 SkPDFUtils::AppendScalar(multiplier[i], result); 101 result->writeText(" mul "); 102 } 103 if (prevColor[i] != 0) { 104 SkPDFUtils::AppendColorComponent(prevColor[i], result); 105 result->writeText(" add "); 106 } 107 } 108 109 if (dupInput[i]) { 110 result->writeText("exch\n"); 111 } 112 } 113 } 114 115 /* Generate Type 4 function code to map t=[0,1) to the passed gradient, 116 clamping at the edges of the range. The generated code will be of the form: 117 if (t < 0) { 118 return colorData[0][r,g,b]; 119 } else { 120 if (t < info.fColorOffsets[1]) { 121 return linearinterpolation(colorData[0][r,g,b], 122 colorData[1][r,g,b]); 123 } else { 124 if (t < info.fColorOffsets[2]) { 125 return linearinterpolation(colorData[1][r,g,b], 126 colorData[2][r,g,b]); 127 } else { 128 129 ... } else { 130 return colorData[info.fColorCount - 1][r,g,b]; 131 } 132 ... 133 } 134 } 135 */ 136 static void gradient_function_code(const SkShader::GradientInfo& info, 137 SkDynamicMemoryWStream* result) { 138 /* We want to linearly interpolate from the previous color to the next. 139 Scale the colors from 0..255 to 0..1 and determine the multipliers 140 for interpolation. 141 C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. 142 */ 143 144 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount); 145 ColorTuple *colorData = colorDataAlloc.get(); 146 for (int i = 0; i < info.fColorCount; i++) { 147 colorData[i][0] = SkColorGetR(info.fColors[i]); 148 colorData[i][1] = SkColorGetG(info.fColors[i]); 149 colorData[i][2] = SkColorGetB(info.fColors[i]); 150 } 151 152 // Clamp the initial color. 153 result->writeText("dup 0 le {pop "); 154 SkPDFUtils::AppendColorComponent(colorData[0][0], result); 155 result->writeText(" "); 156 SkPDFUtils::AppendColorComponent(colorData[0][1], result); 157 result->writeText(" "); 158 SkPDFUtils::AppendColorComponent(colorData[0][2], result); 159 result->writeText(" }\n"); 160 161 // The gradient colors. 162 int gradients = 0; 163 for (int i = 1 ; i < info.fColorCount; i++) { 164 if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) { 165 continue; 166 } 167 gradients++; 168 169 result->writeText("{dup "); 170 SkPDFUtils::AppendScalar(info.fColorOffsets[i], result); 171 result->writeText(" le {"); 172 if (info.fColorOffsets[i - 1] != 0) { 173 SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result); 174 result->writeText(" sub\n"); 175 } 176 177 interpolate_color_code(info.fColorOffsets[i] - info.fColorOffsets[i - 1], 178 colorData[i], colorData[i - 1], result); 179 result->writeText("}\n"); 180 } 181 182 // Clamp the final color. 183 result->writeText("{pop "); 184 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result); 185 result->writeText(" "); 186 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result); 187 result->writeText(" "); 188 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result); 189 190 for (int i = 0 ; i < gradients + 1; i++) { 191 result->writeText("} ifelse\n"); 192 } 193 } 194 195 static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1, 196 const ColorTuple& color2) { 197 auto retval = sk_make_sp<SkPDFDict>(); 198 199 auto c0 = sk_make_sp<SkPDFArray>(); 200 c0->appendColorComponent(color1[0]); 201 c0->appendColorComponent(color1[1]); 202 c0->appendColorComponent(color1[2]); 203 retval->insertObject("C0", std::move(c0)); 204 205 auto c1 = sk_make_sp<SkPDFArray>(); 206 c1->appendColorComponent(color2[0]); 207 c1->appendColorComponent(color2[1]); 208 c1->appendColorComponent(color2[2]); 209 retval->insertObject("C1", std::move(c1)); 210 211 auto domain = sk_make_sp<SkPDFArray>(); 212 domain->appendScalar(0); 213 domain->appendScalar(1.0f); 214 retval->insertObject("Domain", std::move(domain)); 215 216 retval->insertInt("FunctionType", 2); 217 retval->insertScalar("N", 1.0f); 218 219 return retval; 220 } 221 222 static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) { 223 auto retval = sk_make_sp<SkPDFDict>(); 224 225 // normalize color stops 226 int colorCount = info.fColorCount; 227 SkTDArray<SkColor> colors(info.fColors, colorCount); 228 SkTDArray<SkScalar> colorOffsets(info.fColorOffsets, colorCount); 229 230 int i = 1; 231 while (i < colorCount - 1) { 232 // ensure stops are in order 233 if (colorOffsets[i - 1] > colorOffsets[i]) { 234 colorOffsets[i] = colorOffsets[i - 1]; 235 } 236 237 // remove points that are between 2 coincident points 238 if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) { 239 colorCount -= 1; 240 colors.remove(i); 241 colorOffsets.remove(i); 242 } else { 243 i++; 244 } 245 } 246 // find coincident points and slightly move them over 247 for (i = 1; i < colorCount - 1; i++) { 248 if (colorOffsets[i - 1] == colorOffsets[i]) { 249 colorOffsets[i] += 0.00001f; 250 } 251 } 252 // check if last 2 stops coincide 253 if (colorOffsets[i - 1] == colorOffsets[i]) { 254 colorOffsets[i - 1] -= 0.00001f; 255 } 256 257 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount); 258 ColorTuple *colorData = colorDataAlloc.get(); 259 for (int i = 0; i < colorCount; i++) { 260 colorData[i][0] = SkColorGetR(colors[i]); 261 colorData[i][1] = SkColorGetG(colors[i]); 262 colorData[i][2] = SkColorGetB(colors[i]); 263 } 264 265 // no need for a stitch function if there are only 2 stops. 266 if (colorCount == 2) 267 return createInterpolationFunction(colorData[0], colorData[1]); 268 269 auto encode = sk_make_sp<SkPDFArray>(); 270 auto bounds = sk_make_sp<SkPDFArray>(); 271 auto functions = sk_make_sp<SkPDFArray>(); 272 273 auto domain = sk_make_sp<SkPDFArray>(); 274 domain->appendScalar(0); 275 domain->appendScalar(1.0f); 276 retval->insertObject("Domain", std::move(domain)); 277 retval->insertInt("FunctionType", 3); 278 279 for (int i = 1; i < colorCount; i++) { 280 if (i > 1) { 281 bounds->appendScalar(colorOffsets[i-1]); 282 } 283 284 encode->appendScalar(0); 285 encode->appendScalar(1.0f); 286 287 functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i])); 288 } 289 290 retval->insertObject("Encode", std::move(encode)); 291 retval->insertObject("Bounds", std::move(bounds)); 292 retval->insertObject("Functions", std::move(functions)); 293 294 return retval; 295 } 296 297 /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ 298 static void tileModeCode(SkShader::TileMode mode, 299 SkDynamicMemoryWStream* result) { 300 if (mode == SkShader::kRepeat_TileMode) { 301 result->writeText("dup truncate sub\n"); // Get the fractional part. 302 result->writeText("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1) 303 return; 304 } 305 306 if (mode == SkShader::kMirror_TileMode) { 307 // Map t mod 2 into [0, 1, 1, 0]. 308 // Code Stack 309 result->writeText("abs " // Map negative to positive. 310 "dup " // t.s t.s 311 "truncate " // t.s t 312 "dup " // t.s t t 313 "cvi " // t.s t T 314 "2 mod " // t.s t (i mod 2) 315 "1 eq " // t.s t true|false 316 "3 1 roll " // true|false t.s t 317 "sub " // true|false 0.s 318 "exch " // 0.s true|false 319 "{1 exch sub} if\n"); // 1 - 0.s|0.s 320 } 321 } 322 323 /** 324 * Returns PS function code that applies inverse perspective 325 * to a x, y point. 326 * The function assumes that the stack has at least two elements, 327 * and that the top 2 elements are numeric values. 328 * After executing this code on a PS stack, the last 2 elements are updated 329 * while the rest of the stack is preserved intact. 330 * inversePerspectiveMatrix is the inverse perspective matrix. 331 */ 332 static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix, 333 SkDynamicMemoryWStream* code) { 334 if (!inversePerspectiveMatrix.hasPerspective()) { 335 return; 336 } 337 338 // Perspective matrix should be: 339 // 1 0 0 340 // 0 1 0 341 // p0 p1 p2 342 343 const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; 344 const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; 345 const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; 346 347 // y = y / (p2 + p0 x + p1 y) 348 // x = x / (p2 + p0 x + p1 y) 349 350 // Input on stack: x y 351 code->writeText(" dup "); // x y y 352 SkPDFUtils::AppendScalar(p1, code); // x y y p1 353 code->writeText(" mul " // x y y*p1 354 " 2 index "); // x y y*p1 x 355 SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0 356 code->writeText(" mul "); // x y y*p1 x*p0 357 SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2 358 code->writeText(" add " // x y y*p1 x*p0+p2 359 "add " // x y y*p1+x*p0+p2 360 "3 1 roll " // y*p1+x*p0+p2 x y 361 "2 index " // z x y y*p1+x*p0+p2 362 "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) 363 "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x 364 "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 365 "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) 366 "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) 367 } 368 369 static void linearCode(const SkShader::GradientInfo& info, 370 const SkMatrix& perspectiveRemover, 371 SkDynamicMemoryWStream* function) { 372 function->writeText("{"); 373 374 apply_perspective_to_coordinates(perspectiveRemover, function); 375 376 function->writeText("pop\n"); // Just ditch the y value. 377 tileModeCode(info.fTileMode, function); 378 gradient_function_code(info, function); 379 function->writeText("}"); 380 } 381 382 static void radialCode(const SkShader::GradientInfo& info, 383 const SkMatrix& perspectiveRemover, 384 SkDynamicMemoryWStream* function) { 385 function->writeText("{"); 386 387 apply_perspective_to_coordinates(perspectiveRemover, function); 388 389 // Find the distance from the origin. 390 function->writeText("dup " // x y y 391 "mul " // x y^2 392 "exch " // y^2 x 393 "dup " // y^2 x x 394 "mul " // y^2 x^2 395 "add " // y^2+x^2 396 "sqrt\n"); // sqrt(y^2+x^2) 397 398 tileModeCode(info.fTileMode, function); 399 gradient_function_code(info, function); 400 function->writeText("}"); 401 } 402 403 /* Conical gradient shader, based on the Canvas spec for radial gradients 404 See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient 405 */ 406 static void twoPointConicalCode(const SkShader::GradientInfo& info, 407 const SkMatrix& perspectiveRemover, 408 SkDynamicMemoryWStream* function) { 409 SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX; 410 SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY; 411 SkScalar r0 = info.fRadius[0]; 412 SkScalar dr = info.fRadius[1] - info.fRadius[0]; 413 SkScalar a = dx * dx + dy * dy - dr * dr; 414 415 // First compute t, if the pixel falls outside the cone, then we'll end 416 // with 'false' on the stack, otherwise we'll push 'true' with t below it 417 418 // We start with a stack of (x y), copy it and then consume one copy in 419 // order to calculate b and the other to calculate c. 420 function->writeText("{"); 421 422 apply_perspective_to_coordinates(perspectiveRemover, function); 423 424 function->writeText("2 copy "); 425 426 // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr). 427 SkPDFUtils::AppendScalar(dy, function); 428 function->writeText(" mul exch "); 429 SkPDFUtils::AppendScalar(dx, function); 430 function->writeText(" mul add "); 431 SkPDFUtils::AppendScalar(r0 * dr, function); 432 function->writeText(" add -2 mul dup dup mul\n"); 433 434 // c = x^2 + y^2 + radius0^2 435 function->writeText("4 2 roll dup mul exch dup mul add "); 436 SkPDFUtils::AppendScalar(r0 * r0, function); 437 function->writeText(" sub dup 4 1 roll\n"); 438 439 // Contents of the stack at this point: c, b, b^2, c 440 441 // if a = 0, then we collapse to a simpler linear case 442 if (a == 0) { 443 444 // t = -c/b 445 function->writeText("pop pop div neg dup "); 446 447 // compute radius(t) 448 SkPDFUtils::AppendScalar(dr, function); 449 function->writeText(" mul "); 450 SkPDFUtils::AppendScalar(r0, function); 451 function->writeText(" add\n"); 452 453 // if r(t) < 0, then it's outside the cone 454 function->writeText("0 lt {pop false} {true} ifelse\n"); 455 456 } else { 457 458 // quadratic case: the Canvas spec wants the largest 459 // root t for which radius(t) > 0 460 461 // compute the discriminant (b^2 - 4ac) 462 SkPDFUtils::AppendScalar(a * 4, function); 463 function->writeText(" mul sub dup\n"); 464 465 // if d >= 0, proceed 466 function->writeText("0 ge {\n"); 467 468 // an intermediate value we'll use to compute the roots: 469 // q = -0.5 * (b +/- sqrt(d)) 470 function->writeText("sqrt exch dup 0 lt {exch -1 mul} if"); 471 function->writeText(" add -0.5 mul dup\n"); 472 473 // first root = q / a 474 SkPDFUtils::AppendScalar(a, function); 475 function->writeText(" div\n"); 476 477 // second root = c / q 478 function->writeText("3 1 roll div\n"); 479 480 // put the larger root on top of the stack 481 function->writeText("2 copy gt {exch} if\n"); 482 483 // compute radius(t) for larger root 484 function->writeText("dup "); 485 SkPDFUtils::AppendScalar(dr, function); 486 function->writeText(" mul "); 487 SkPDFUtils::AppendScalar(r0, function); 488 function->writeText(" add\n"); 489 490 // if r(t) > 0, we have our t, pop off the smaller root and we're done 491 function->writeText(" 0 gt {exch pop true}\n"); 492 493 // otherwise, throw out the larger one and try the smaller root 494 function->writeText("{pop dup\n"); 495 SkPDFUtils::AppendScalar(dr, function); 496 function->writeText(" mul "); 497 SkPDFUtils::AppendScalar(r0, function); 498 function->writeText(" add\n"); 499 500 // if r(t) < 0, push false, otherwise the smaller root is our t 501 function->writeText("0 le {pop false} {true} ifelse\n"); 502 function->writeText("} ifelse\n"); 503 504 // d < 0, clear the stack and push false 505 function->writeText("} {pop pop pop false} ifelse\n"); 506 } 507 508 // if the pixel is in the cone, proceed to compute a color 509 function->writeText("{"); 510 tileModeCode(info.fTileMode, function); 511 gradient_function_code(info, function); 512 513 // otherwise, just write black 514 function->writeText("} {0 0 0} ifelse }"); 515 } 516 517 static void sweepCode(const SkShader::GradientInfo& info, 518 const SkMatrix& perspectiveRemover, 519 SkDynamicMemoryWStream* function) { 520 function->writeText("{exch atan 360 div\n"); 521 tileModeCode(info.fTileMode, function); 522 gradient_function_code(info, function); 523 function->writeText("}"); 524 } 525 526 527 // catch cases where the inner just touches the outer circle 528 // and make the inner circle just inside the outer one to match raster 529 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) { 530 // detect touching circles 531 SkScalar distance = SkPoint::Distance(p1, p2); 532 SkScalar subtractRadii = fabs(r1 - r2); 533 if (fabs(distance - subtractRadii) < 0.002f) { 534 if (r1 > r2) { 535 r1 += 0.002f; 536 } else { 537 r2 += 0.002f; 538 } 539 } 540 } 541 542 // Finds affine and persp such that in = affine * persp. 543 // but it returns the inverse of perspective matrix. 544 static bool split_perspective(const SkMatrix in, SkMatrix* affine, 545 SkMatrix* perspectiveInverse) { 546 const SkScalar p2 = in[SkMatrix::kMPersp2]; 547 548 if (SkScalarNearlyZero(p2)) { 549 return false; 550 } 551 552 const SkScalar zero = SkIntToScalar(0); 553 const SkScalar one = SkIntToScalar(1); 554 555 const SkScalar sx = in[SkMatrix::kMScaleX]; 556 const SkScalar kx = in[SkMatrix::kMSkewX]; 557 const SkScalar tx = in[SkMatrix::kMTransX]; 558 const SkScalar ky = in[SkMatrix::kMSkewY]; 559 const SkScalar sy = in[SkMatrix::kMScaleY]; 560 const SkScalar ty = in[SkMatrix::kMTransY]; 561 const SkScalar p0 = in[SkMatrix::kMPersp0]; 562 const SkScalar p1 = in[SkMatrix::kMPersp1]; 563 564 // Perspective matrix would be: 565 // 1 0 0 566 // 0 1 0 567 // p0 p1 p2 568 // But we need the inverse of persp. 569 perspectiveInverse->setAll(one, zero, zero, 570 zero, one, zero, 571 -p0/p2, -p1/p2, 1/p2); 572 573 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2, 574 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2, 575 zero, zero, one); 576 577 return true; 578 } 579 580 static sk_sp<SkPDFArray> make_range_object() { 581 auto range = sk_make_sp<SkPDFArray>(); 582 range->reserve(6); 583 range->appendInt(0); 584 range->appendInt(1); 585 range->appendInt(0); 586 range->appendInt(1); 587 range->appendInt(0); 588 range->appendInt(1); 589 return range; 590 } 591 592 static sk_sp<SkPDFStream> make_ps_function( 593 std::unique_ptr<SkStreamAsset> psCode, 594 sk_sp<SkPDFArray> domain, 595 sk_sp<SkPDFObject> range) { 596 auto result = sk_make_sp<SkPDFStream>(std::move(psCode)); 597 result->dict()->insertInt("FunctionType", 4); 598 result->dict()->insertObject("Domain", std::move(domain)); 599 result->dict()->insertObject("Range", std::move(range)); 600 return result; 601 } 602 603 604 static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon, 605 const SkPDFGradientShader::Key& state) { 606 SkPoint transformPoints[2]; 607 const SkShader::GradientInfo& info = state.fInfo; 608 SkMatrix finalMatrix = state.fCanvasTransform; 609 finalMatrix.preConcat(state.fShaderTransform); 610 611 bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType || 612 state.fType == SkShader::kRadial_GradientType || 613 state.fType == SkShader::kConical_GradientType) && 614 info.fTileMode == SkShader::kClamp_TileMode && 615 !finalMatrix.hasPerspective(); 616 617 auto domain = sk_make_sp<SkPDFArray>(); 618 619 int32_t shadingType = 1; 620 auto pdfShader = sk_make_sp<SkPDFDict>(); 621 // The two point radial gradient further references 622 // state.fInfo 623 // in translating from x, y coordinates to the t parameter. So, we have 624 // to transform the points and radii according to the calculated matrix. 625 if (doStitchFunctions) { 626 pdfShader->insertObject("Function", gradientStitchCode(info)); 627 shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3; 628 629 auto extend = sk_make_sp<SkPDFArray>(); 630 extend->reserve(2); 631 extend->appendBool(true); 632 extend->appendBool(true); 633 pdfShader->insertObject("Extend", std::move(extend)); 634 635 auto coords = sk_make_sp<SkPDFArray>(); 636 if (state.fType == SkShader::kConical_GradientType) { 637 coords->reserve(6); 638 SkScalar r1 = info.fRadius[0]; 639 SkScalar r2 = info.fRadius[1]; 640 SkPoint pt1 = info.fPoint[0]; 641 SkPoint pt2 = info.fPoint[1]; 642 FixUpRadius(pt1, r1, pt2, r2); 643 644 coords->appendScalar(pt1.fX); 645 coords->appendScalar(pt1.fY); 646 coords->appendScalar(r1); 647 648 coords->appendScalar(pt2.fX); 649 coords->appendScalar(pt2.fY); 650 coords->appendScalar(r2); 651 } else if (state.fType == SkShader::kRadial_GradientType) { 652 coords->reserve(6); 653 const SkPoint& pt1 = info.fPoint[0]; 654 655 coords->appendScalar(pt1.fX); 656 coords->appendScalar(pt1.fY); 657 coords->appendScalar(0); 658 659 coords->appendScalar(pt1.fX); 660 coords->appendScalar(pt1.fY); 661 coords->appendScalar(info.fRadius[0]); 662 } else { 663 coords->reserve(4); 664 const SkPoint& pt1 = info.fPoint[0]; 665 const SkPoint& pt2 = info.fPoint[1]; 666 667 coords->appendScalar(pt1.fX); 668 coords->appendScalar(pt1.fY); 669 670 coords->appendScalar(pt2.fX); 671 coords->appendScalar(pt2.fY); 672 } 673 674 pdfShader->insertObject("Coords", std::move(coords)); 675 } else { 676 // Depending on the type of the gradient, we want to transform the 677 // coordinate space in different ways. 678 transformPoints[0] = info.fPoint[0]; 679 transformPoints[1] = info.fPoint[1]; 680 switch (state.fType) { 681 case SkShader::kLinear_GradientType: 682 break; 683 case SkShader::kRadial_GradientType: 684 transformPoints[1] = transformPoints[0]; 685 transformPoints[1].fX += info.fRadius[0]; 686 break; 687 case SkShader::kConical_GradientType: { 688 transformPoints[1] = transformPoints[0]; 689 transformPoints[1].fX += SK_Scalar1; 690 break; 691 } 692 case SkShader::kSweep_GradientType: 693 transformPoints[1] = transformPoints[0]; 694 transformPoints[1].fX += SK_Scalar1; 695 break; 696 case SkShader::kColor_GradientType: 697 case SkShader::kNone_GradientType: 698 default: 699 return nullptr; 700 } 701 702 // Move any scaling (assuming a unit gradient) or translation 703 // (and rotation for linear gradient), of the final gradient from 704 // info.fPoints to the matrix (updating bbox appropriately). Now 705 // the gradient can be drawn on on the unit segment. 706 SkMatrix mapperMatrix; 707 unit_to_points_matrix(transformPoints, &mapperMatrix); 708 709 finalMatrix.preConcat(mapperMatrix); 710 711 // Preserves as much as posible in the final matrix, and only removes 712 // the perspective. The inverse of the perspective is stored in 713 // perspectiveInverseOnly matrix and has 3 useful numbers 714 // (p0, p1, p2), while everything else is either 0 or 1. 715 // In this way the shader will handle it eficiently, with minimal code. 716 SkMatrix perspectiveInverseOnly = SkMatrix::I(); 717 if (finalMatrix.hasPerspective()) { 718 if (!split_perspective(finalMatrix, 719 &finalMatrix, &perspectiveInverseOnly)) { 720 return nullptr; 721 } 722 } 723 724 SkRect bbox; 725 bbox.set(state.fBBox); 726 if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) { 727 return nullptr; 728 } 729 domain->reserve(4); 730 domain->appendScalar(bbox.fLeft); 731 domain->appendScalar(bbox.fRight); 732 domain->appendScalar(bbox.fTop); 733 domain->appendScalar(bbox.fBottom); 734 735 SkDynamicMemoryWStream functionCode; 736 737 SkShader::GradientInfo infoCopy = info; 738 739 if (state.fType == SkShader::kConical_GradientType) { 740 SkMatrix inverseMapperMatrix; 741 if (!mapperMatrix.invert(&inverseMapperMatrix)) { 742 return nullptr; 743 } 744 inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2); 745 infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]); 746 infoCopy.fRadius[1] = inverseMapperMatrix.mapRadius(info.fRadius[1]); 747 } 748 switch (state.fType) { 749 case SkShader::kLinear_GradientType: 750 linearCode(infoCopy, perspectiveInverseOnly, &functionCode); 751 break; 752 case SkShader::kRadial_GradientType: 753 radialCode(infoCopy, perspectiveInverseOnly, &functionCode); 754 break; 755 case SkShader::kConical_GradientType: 756 twoPointConicalCode(infoCopy, perspectiveInverseOnly, &functionCode); 757 break; 758 case SkShader::kSweep_GradientType: 759 sweepCode(infoCopy, perspectiveInverseOnly, &functionCode); 760 break; 761 default: 762 SkASSERT(false); 763 } 764 pdfShader->insertObject("Domain", domain); 765 766 sk_sp<SkPDFArray>& rangeObject = canon->fRangeObject; 767 if (!rangeObject) { 768 rangeObject = make_range_object(); 769 } 770 pdfShader->insertObjRef("Function", 771 make_ps_function(functionCode.detachAsStream(), std::move(domain), 772 rangeObject)); 773 } 774 775 pdfShader->insertInt("ShadingType", shadingType); 776 pdfShader->insertName("ColorSpace", "DeviceRGB"); 777 778 auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern"); 779 pdfFunctionShader->insertInt("PatternType", 2); 780 pdfFunctionShader->insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix)); 781 pdfFunctionShader->insertObject("Shading", std::move(pdfShader)); 782 783 return pdfFunctionShader; 784 } 785 786 static sk_sp<SkPDFObject> find_pdf_shader(SkPDFDocument* doc, 787 SkPDFGradientShader::Key key, 788 bool keyHasAlpha); 789 790 static sk_sp<SkPDFDict> get_gradient_resource_dict(SkPDFObject* functionShader, 791 SkPDFObject* gState) { 792 SkTDArray<SkPDFObject*> patterns; 793 if (functionShader) { 794 patterns.push(functionShader); 795 } 796 SkTDArray<SkPDFObject*> graphicStates; 797 if (gState) { 798 graphicStates.push(gState); 799 } 800 return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr); 801 } 802 803 // Creates a content stream which fills the pattern P0 across bounds. 804 // @param gsIndex A graphics state resource index to apply, or <0 if no 805 // graphics state to apply. 806 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex, SkRect& bounds) { 807 SkDynamicMemoryWStream content; 808 if (gsIndex >= 0) { 809 SkPDFUtils::ApplyGraphicState(gsIndex, &content); 810 } 811 SkPDFUtils::ApplyPattern(0, &content); 812 SkPDFUtils::AppendRectangle(bounds, &content); 813 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, &content); 814 return content.detachAsStream(); 815 } 816 817 static bool gradient_has_alpha(const SkPDFGradientShader::Key& key) { 818 SkASSERT(key.fType != SkShader::kNone_GradientType); 819 for (int i = 0; i < key.fInfo.fColorCount; i++) { 820 if ((SkAlpha)SkColorGetA(key.fInfo.fColors[i]) != SK_AlphaOPAQUE) { 821 return true; 822 } 823 } 824 return false; 825 } 826 827 // warning: does not set fHash on new key. (Both callers need to change fields.) 828 static SkPDFGradientShader::Key clone_key(const SkPDFGradientShader::Key& k) { 829 SkPDFGradientShader::Key clone = { 830 k.fType, 831 k.fInfo, // change pointers later. 832 std::unique_ptr<SkColor[]>(new SkColor[k.fInfo.fColorCount]), 833 std::unique_ptr<SkScalar[]>(new SkScalar[k.fInfo.fColorCount]), 834 k.fCanvasTransform, 835 k.fShaderTransform, 836 k.fBBox, 0}; 837 clone.fInfo.fColors = clone.fColors.get(); 838 clone.fInfo.fColorOffsets = clone.fStops.get(); 839 for (int i = 0; i < clone.fInfo.fColorCount; i++) { 840 clone.fInfo.fColorOffsets[i] = k.fInfo.fColorOffsets[i]; 841 clone.fInfo.fColors[i] = k.fInfo.fColors[i]; 842 } 843 return clone; 844 } 845 846 static sk_sp<SkPDFObject> create_smask_graphic_state(SkPDFDocument* doc, 847 const SkPDFGradientShader::Key& state) { 848 SkASSERT(state.fType != SkShader::kNone_GradientType); 849 SkPDFGradientShader::Key luminosityState = clone_key(state); 850 for (int i = 0; i < luminosityState.fInfo.fColorCount; i++) { 851 SkAlpha alpha = SkColorGetA(luminosityState.fInfo.fColors[i]); 852 luminosityState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); 853 } 854 luminosityState.fHash = hash(luminosityState); 855 856 SkASSERT(!gradient_has_alpha(luminosityState)); 857 sk_sp<SkPDFObject> luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false); 858 sk_sp<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader.get(), nullptr); 859 SkRect bbox = SkRect::Make(state.fBBox); 860 sk_sp<SkPDFObject> alphaMask = SkPDFMakeFormXObject(create_pattern_fill_content(-1, bbox), 861 SkPDFUtils::RectToArray(bbox), 862 std::move(resources), 863 SkMatrix::I(), 864 "DeviceRGB"); 865 return SkPDFGraphicState::GetSMaskGraphicState( 866 std::move(alphaMask), false, 867 SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon()); 868 } 869 870 static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc, 871 const SkPDFGradientShader::Key& state) { 872 SkASSERT(state.fType != SkShader::kNone_GradientType); 873 SkPDFGradientShader::Key opaqueState = clone_key(state); 874 for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) { 875 opaqueState.fInfo.fColors[i] = SkColorSetA(opaqueState.fInfo.fColors[i], SK_AlphaOPAQUE); 876 } 877 opaqueState.fHash = hash(opaqueState); 878 879 SkASSERT(!gradient_has_alpha(opaqueState)); 880 SkRect bbox = SkRect::Make(state.fBBox); 881 sk_sp<SkPDFObject> colorShader = find_pdf_shader(doc, std::move(opaqueState), false); 882 if (!colorShader) { 883 return nullptr; 884 } 885 886 // Create resource dict with alpha graphics state as G0 and 887 // pattern shader as P0, then write content stream. 888 sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, state); 889 890 sk_sp<SkPDFDict> resourceDict = 891 get_gradient_resource_dict(colorShader.get(), alphaGs.get()); 892 893 std::unique_ptr<SkStreamAsset> colorStream(create_pattern_fill_content(0, bbox)); 894 auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream)); 895 896 SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader->dict(), bbox, 897 std::move(resourceDict), SkMatrix::I()); 898 return alphaFunctionShader; 899 } 900 901 static SkPDFGradientShader::Key make_key(const SkShader* shader, 902 const SkMatrix& canvasTransform, 903 const SkIRect& bbox) { 904 SkPDFGradientShader::Key key = { 905 SkShader::kNone_GradientType, 906 {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkShader::kClamp_TileMode, 0}, 907 nullptr, 908 nullptr, 909 canvasTransform, 910 SkPDFUtils::GetShaderLocalMatrix(shader), 911 bbox, 0}; 912 key.fType = shader->asAGradient(&key.fInfo); 913 SkASSERT(SkShader::kNone_GradientType != key.fType); 914 SkASSERT(key.fInfo.fColorCount > 0); 915 key.fColors.reset(new SkColor[key.fInfo.fColorCount]); 916 key.fStops.reset(new SkScalar[key.fInfo.fColorCount]); 917 key.fInfo.fColors = key.fColors.get(); 918 key.fInfo.fColorOffsets = key.fStops.get(); 919 (void)shader->asAGradient(&key.fInfo); 920 key.fHash = hash(key); 921 return key; 922 } 923 924 static sk_sp<SkPDFObject> find_pdf_shader(SkPDFDocument* doc, 925 SkPDFGradientShader::Key key, 926 bool keyHasAlpha) { 927 SkASSERT(gradient_has_alpha(key) == keyHasAlpha); 928 SkPDFCanon* canon = doc->canon(); 929 if (sk_sp<SkPDFObject>* ptr = canon->fGradientPatternMap.find(key)) { 930 return *ptr; 931 } 932 sk_sp<SkPDFObject> pdfShader; 933 if (keyHasAlpha) { 934 pdfShader = make_alpha_function_shader(doc, key); 935 } else { 936 pdfShader = make_function_shader(canon, key); 937 } 938 canon->fGradientPatternMap.set(std::move(key), pdfShader); 939 return pdfShader; 940 } 941 942 sk_sp<SkPDFObject> SkPDFGradientShader::Make(SkPDFDocument* doc, 943 SkShader* shader, 944 const SkMatrix& canvasTransform, 945 const SkIRect& bbox) { 946 SkASSERT(shader); 947 SkASSERT(SkShader::kNone_GradientType != shader->asAGradient(nullptr)); 948 SkPDFGradientShader::Key key = make_key(shader, canvasTransform, bbox); 949 bool alpha = gradient_has_alpha(key); 950 return find_pdf_shader(doc, std::move(key), alpha); 951 } 952