1 /* 2 * Copyright 2011 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 9 #include "SkPDFShader.h" 10 11 #include "SkData.h" 12 #include "SkPDFCanon.h" 13 #include "SkPDFDevice.h" 14 #include "SkPDFDocument.h" 15 #include "SkPDFFormXObject.h" 16 #include "SkPDFGraphicState.h" 17 #include "SkPDFResourceDict.h" 18 #include "SkPDFUtils.h" 19 #include "SkScalar.h" 20 #include "SkStream.h" 21 #include "SkTemplates.h" 22 23 static bool inverse_transform_bbox(const SkMatrix& matrix, SkRect* bbox) { 24 SkMatrix inverse; 25 if (!matrix.invert(&inverse)) { 26 return false; 27 } 28 inverse.mapRect(bbox); 29 return true; 30 } 31 32 static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) { 33 SkVector vec = pts[1] - pts[0]; 34 SkScalar mag = vec.length(); 35 SkScalar inv = mag ? SkScalarInvert(mag) : 0; 36 37 vec.scale(inv); 38 matrix->setSinCos(vec.fY, vec.fX); 39 matrix->preScale(mag, mag); 40 matrix->postTranslate(pts[0].fX, pts[0].fY); 41 } 42 43 static const int kColorComponents = 3; 44 typedef uint8_t ColorTuple[kColorComponents]; 45 46 /* Assumes t + startOffset is on the stack and does a linear interpolation on t 47 between startOffset and endOffset from prevColor to curColor (for each color 48 component), leaving the result in component order on the stack. It assumes 49 there are always 3 components per color. 50 @param range endOffset - startOffset 51 @param curColor[components] The current color components. 52 @param prevColor[components] The previous color components. 53 @param result The result ps function. 54 */ 55 static void interpolateColorCode(SkScalar range, const ColorTuple& curColor, 56 const ColorTuple& prevColor, 57 SkDynamicMemoryWStream* result) { 58 SkASSERT(range != SkIntToScalar(0)); 59 60 // Figure out how to scale each color component. 61 SkScalar multiplier[kColorComponents]; 62 for (int i = 0; i < kColorComponents; i++) { 63 static const SkScalar kColorScale = SkScalarInvert(255); 64 multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range; 65 } 66 67 // Calculate when we no longer need to keep a copy of the input parameter t. 68 // If the last component to use t is i, then dupInput[0..i - 1] = true 69 // and dupInput[i .. components] = false. 70 bool dupInput[kColorComponents]; 71 dupInput[kColorComponents - 1] = false; 72 for (int i = kColorComponents - 2; i >= 0; i--) { 73 dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0; 74 } 75 76 if (!dupInput[0] && multiplier[0] == 0) { 77 result->writeText("pop "); 78 } 79 80 for (int i = 0; i < kColorComponents; i++) { 81 // If the next components needs t and this component will consume a 82 // copy, make another copy. 83 if (dupInput[i] && multiplier[i] != 0) { 84 result->writeText("dup "); 85 } 86 87 if (multiplier[i] == 0) { 88 SkPDFUtils::AppendColorComponent(prevColor[i], result); 89 result->writeText(" "); 90 } else { 91 if (multiplier[i] != 1) { 92 SkPDFUtils::AppendScalar(multiplier[i], result); 93 result->writeText(" mul "); 94 } 95 if (prevColor[i] != 0) { 96 SkPDFUtils::AppendColorComponent(prevColor[i], result); 97 result->writeText(" add "); 98 } 99 } 100 101 if (dupInput[i]) { 102 result->writeText("exch\n"); 103 } 104 } 105 } 106 107 /* Generate Type 4 function code to map t=[0,1) to the passed gradient, 108 clamping at the edges of the range. The generated code will be of the form: 109 if (t < 0) { 110 return colorData[0][r,g,b]; 111 } else { 112 if (t < info.fColorOffsets[1]) { 113 return linearinterpolation(colorData[0][r,g,b], 114 colorData[1][r,g,b]); 115 } else { 116 if (t < info.fColorOffsets[2]) { 117 return linearinterpolation(colorData[1][r,g,b], 118 colorData[2][r,g,b]); 119 } else { 120 121 ... } else { 122 return colorData[info.fColorCount - 1][r,g,b]; 123 } 124 ... 125 } 126 } 127 */ 128 static void gradientFunctionCode(const SkShader::GradientInfo& info, 129 SkDynamicMemoryWStream* result) { 130 /* We want to linearly interpolate from the previous color to the next. 131 Scale the colors from 0..255 to 0..1 and determine the multipliers 132 for interpolation. 133 C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. 134 */ 135 136 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount); 137 ColorTuple *colorData = colorDataAlloc.get(); 138 for (int i = 0; i < info.fColorCount; i++) { 139 colorData[i][0] = SkColorGetR(info.fColors[i]); 140 colorData[i][1] = SkColorGetG(info.fColors[i]); 141 colorData[i][2] = SkColorGetB(info.fColors[i]); 142 } 143 144 // Clamp the initial color. 145 result->writeText("dup 0 le {pop "); 146 SkPDFUtils::AppendColorComponent(colorData[0][0], result); 147 result->writeText(" "); 148 SkPDFUtils::AppendColorComponent(colorData[0][1], result); 149 result->writeText(" "); 150 SkPDFUtils::AppendColorComponent(colorData[0][2], result); 151 result->writeText(" }\n"); 152 153 // The gradient colors. 154 int gradients = 0; 155 for (int i = 1 ; i < info.fColorCount; i++) { 156 if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) { 157 continue; 158 } 159 gradients++; 160 161 result->writeText("{dup "); 162 SkPDFUtils::AppendScalar(info.fColorOffsets[i], result); 163 result->writeText(" le {"); 164 if (info.fColorOffsets[i - 1] != 0) { 165 SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result); 166 result->writeText(" sub\n"); 167 } 168 169 interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1], 170 colorData[i], colorData[i - 1], result); 171 result->writeText("}\n"); 172 } 173 174 // Clamp the final color. 175 result->writeText("{pop "); 176 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result); 177 result->writeText(" "); 178 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result); 179 result->writeText(" "); 180 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result); 181 182 for (int i = 0 ; i < gradients + 1; i++) { 183 result->writeText("} ifelse\n"); 184 } 185 } 186 187 static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1, 188 const ColorTuple& color2) { 189 auto retval = sk_make_sp<SkPDFDict>(); 190 191 auto c0 = sk_make_sp<SkPDFArray>(); 192 c0->appendColorComponent(color1[0]); 193 c0->appendColorComponent(color1[1]); 194 c0->appendColorComponent(color1[2]); 195 retval->insertObject("C0", std::move(c0)); 196 197 auto c1 = sk_make_sp<SkPDFArray>(); 198 c1->appendColorComponent(color2[0]); 199 c1->appendColorComponent(color2[1]); 200 c1->appendColorComponent(color2[2]); 201 retval->insertObject("C1", std::move(c1)); 202 203 auto domain = sk_make_sp<SkPDFArray>(); 204 domain->appendScalar(0); 205 domain->appendScalar(1.0f); 206 retval->insertObject("Domain", std::move(domain)); 207 208 retval->insertInt("FunctionType", 2); 209 retval->insertScalar("N", 1.0f); 210 211 return retval; 212 } 213 214 static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) { 215 auto retval = sk_make_sp<SkPDFDict>(); 216 217 // normalize color stops 218 int colorCount = info.fColorCount; 219 SkTDArray<SkColor> colors(info.fColors, colorCount); 220 SkTDArray<SkScalar> colorOffsets(info.fColorOffsets, colorCount); 221 222 int i = 1; 223 while (i < colorCount - 1) { 224 // ensure stops are in order 225 if (colorOffsets[i - 1] > colorOffsets[i]) { 226 colorOffsets[i] = colorOffsets[i - 1]; 227 } 228 229 // remove points that are between 2 coincident points 230 if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) { 231 colorCount -= 1; 232 colors.remove(i); 233 colorOffsets.remove(i); 234 } else { 235 i++; 236 } 237 } 238 // find coincident points and slightly move them over 239 for (i = 1; i < colorCount - 1; i++) { 240 if (colorOffsets[i - 1] == colorOffsets[i]) { 241 colorOffsets[i] += 0.00001f; 242 } 243 } 244 // check if last 2 stops coincide 245 if (colorOffsets[i - 1] == colorOffsets[i]) { 246 colorOffsets[i - 1] -= 0.00001f; 247 } 248 249 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount); 250 ColorTuple *colorData = colorDataAlloc.get(); 251 for (int i = 0; i < colorCount; i++) { 252 colorData[i][0] = SkColorGetR(colors[i]); 253 colorData[i][1] = SkColorGetG(colors[i]); 254 colorData[i][2] = SkColorGetB(colors[i]); 255 } 256 257 // no need for a stitch function if there are only 2 stops. 258 if (colorCount == 2) 259 return createInterpolationFunction(colorData[0], colorData[1]); 260 261 auto encode = sk_make_sp<SkPDFArray>(); 262 auto bounds = sk_make_sp<SkPDFArray>(); 263 auto functions = sk_make_sp<SkPDFArray>(); 264 265 auto domain = sk_make_sp<SkPDFArray>(); 266 domain->appendScalar(0); 267 domain->appendScalar(1.0f); 268 retval->insertObject("Domain", std::move(domain)); 269 retval->insertInt("FunctionType", 3); 270 271 for (int i = 1; i < colorCount; i++) { 272 if (i > 1) { 273 bounds->appendScalar(colorOffsets[i-1]); 274 } 275 276 encode->appendScalar(0); 277 encode->appendScalar(1.0f); 278 279 functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i])); 280 } 281 282 retval->insertObject("Encode", std::move(encode)); 283 retval->insertObject("Bounds", std::move(bounds)); 284 retval->insertObject("Functions", std::move(functions)); 285 286 return retval; 287 } 288 289 /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ 290 static void tileModeCode(SkShader::TileMode mode, 291 SkDynamicMemoryWStream* result) { 292 if (mode == SkShader::kRepeat_TileMode) { 293 result->writeText("dup truncate sub\n"); // Get the fractional part. 294 result->writeText("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1) 295 return; 296 } 297 298 if (mode == SkShader::kMirror_TileMode) { 299 // Map t mod 2 into [0, 1, 1, 0]. 300 // Code Stack 301 result->writeText("abs " // Map negative to positive. 302 "dup " // t.s t.s 303 "truncate " // t.s t 304 "dup " // t.s t t 305 "cvi " // t.s t T 306 "2 mod " // t.s t (i mod 2) 307 "1 eq " // t.s t true|false 308 "3 1 roll " // true|false t.s t 309 "sub " // true|false 0.s 310 "exch " // 0.s true|false 311 "{1 exch sub} if\n"); // 1 - 0.s|0.s 312 } 313 } 314 315 /** 316 * Returns PS function code that applies inverse perspective 317 * to a x, y point. 318 * The function assumes that the stack has at least two elements, 319 * and that the top 2 elements are numeric values. 320 * After executing this code on a PS stack, the last 2 elements are updated 321 * while the rest of the stack is preserved intact. 322 * inversePerspectiveMatrix is the inverse perspective matrix. 323 */ 324 static void apply_perspective_to_coordinates( 325 const SkMatrix& inversePerspectiveMatrix, 326 SkDynamicMemoryWStream* code) { 327 if (!inversePerspectiveMatrix.hasPerspective()) { 328 return; 329 } 330 331 // Perspective matrix should be: 332 // 1 0 0 333 // 0 1 0 334 // p0 p1 p2 335 336 const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0]; 337 const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1]; 338 const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2]; 339 340 // y = y / (p2 + p0 x + p1 y) 341 // x = x / (p2 + p0 x + p1 y) 342 343 // Input on stack: x y 344 code->writeText(" dup "); // x y y 345 SkPDFUtils::AppendScalar(p1, code); // x y y p1 346 code->writeText(" mul " // x y y*p1 347 " 2 index "); // x y y*p1 x 348 SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0 349 code->writeText(" mul "); // x y y*p1 x*p0 350 SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2 351 code->writeText(" add " // x y y*p1 x*p0+p2 352 "add " // x y y*p1+x*p0+p2 353 "3 1 roll " // y*p1+x*p0+p2 x y 354 "2 index " // z x y y*p1+x*p0+p2 355 "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2) 356 "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x 357 "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2 358 "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2) 359 "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2) 360 } 361 362 static void linearCode(const SkShader::GradientInfo& info, 363 const SkMatrix& perspectiveRemover, 364 SkDynamicMemoryWStream* function) { 365 function->writeText("{"); 366 367 apply_perspective_to_coordinates(perspectiveRemover, function); 368 369 function->writeText("pop\n"); // Just ditch the y value. 370 tileModeCode(info.fTileMode, function); 371 gradientFunctionCode(info, function); 372 function->writeText("}"); 373 } 374 375 static void radialCode(const SkShader::GradientInfo& info, 376 const SkMatrix& perspectiveRemover, 377 SkDynamicMemoryWStream* function) { 378 function->writeText("{"); 379 380 apply_perspective_to_coordinates(perspectiveRemover, function); 381 382 // Find the distance from the origin. 383 function->writeText("dup " // x y y 384 "mul " // x y^2 385 "exch " // y^2 x 386 "dup " // y^2 x x 387 "mul " // y^2 x^2 388 "add " // y^2+x^2 389 "sqrt\n"); // sqrt(y^2+x^2) 390 391 tileModeCode(info.fTileMode, function); 392 gradientFunctionCode(info, function); 393 function->writeText("}"); 394 } 395 396 /* Conical gradient shader, based on the Canvas spec for radial gradients 397 See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient 398 */ 399 static void twoPointConicalCode(const SkShader::GradientInfo& info, 400 const SkMatrix& perspectiveRemover, 401 SkDynamicMemoryWStream* function) { 402 SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX; 403 SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY; 404 SkScalar r0 = info.fRadius[0]; 405 SkScalar dr = info.fRadius[1] - info.fRadius[0]; 406 SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) - 407 SkScalarMul(dr, dr); 408 409 // First compute t, if the pixel falls outside the cone, then we'll end 410 // with 'false' on the stack, otherwise we'll push 'true' with t below it 411 412 // We start with a stack of (x y), copy it and then consume one copy in 413 // order to calculate b and the other to calculate c. 414 function->writeText("{"); 415 416 apply_perspective_to_coordinates(perspectiveRemover, function); 417 418 function->writeText("2 copy "); 419 420 // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr). 421 SkPDFUtils::AppendScalar(dy, function); 422 function->writeText(" mul exch "); 423 SkPDFUtils::AppendScalar(dx, function); 424 function->writeText(" mul add "); 425 SkPDFUtils::AppendScalar(SkScalarMul(r0, dr), function); 426 function->writeText(" add -2 mul dup dup mul\n"); 427 428 // c = x^2 + y^2 + radius0^2 429 function->writeText("4 2 roll dup mul exch dup mul add "); 430 SkPDFUtils::AppendScalar(SkScalarMul(r0, r0), function); 431 function->writeText(" sub dup 4 1 roll\n"); 432 433 // Contents of the stack at this point: c, b, b^2, c 434 435 // if a = 0, then we collapse to a simpler linear case 436 if (a == 0) { 437 438 // t = -c/b 439 function->writeText("pop pop div neg dup "); 440 441 // compute radius(t) 442 SkPDFUtils::AppendScalar(dr, function); 443 function->writeText(" mul "); 444 SkPDFUtils::AppendScalar(r0, function); 445 function->writeText(" add\n"); 446 447 // if r(t) < 0, then it's outside the cone 448 function->writeText("0 lt {pop false} {true} ifelse\n"); 449 450 } else { 451 452 // quadratic case: the Canvas spec wants the largest 453 // root t for which radius(t) > 0 454 455 // compute the discriminant (b^2 - 4ac) 456 SkPDFUtils::AppendScalar(SkScalarMul(SkIntToScalar(4), a), function); 457 function->writeText(" mul sub dup\n"); 458 459 // if d >= 0, proceed 460 function->writeText("0 ge {\n"); 461 462 // an intermediate value we'll use to compute the roots: 463 // q = -0.5 * (b +/- sqrt(d)) 464 function->writeText("sqrt exch dup 0 lt {exch -1 mul} if"); 465 function->writeText(" add -0.5 mul dup\n"); 466 467 // first root = q / a 468 SkPDFUtils::AppendScalar(a, function); 469 function->writeText(" div\n"); 470 471 // second root = c / q 472 function->writeText("3 1 roll div\n"); 473 474 // put the larger root on top of the stack 475 function->writeText("2 copy gt {exch} if\n"); 476 477 // compute radius(t) for larger root 478 function->writeText("dup "); 479 SkPDFUtils::AppendScalar(dr, function); 480 function->writeText(" mul "); 481 SkPDFUtils::AppendScalar(r0, function); 482 function->writeText(" add\n"); 483 484 // if r(t) > 0, we have our t, pop off the smaller root and we're done 485 function->writeText(" 0 gt {exch pop true}\n"); 486 487 // otherwise, throw out the larger one and try the smaller root 488 function->writeText("{pop dup\n"); 489 SkPDFUtils::AppendScalar(dr, function); 490 function->writeText(" mul "); 491 SkPDFUtils::AppendScalar(r0, function); 492 function->writeText(" add\n"); 493 494 // if r(t) < 0, push false, otherwise the smaller root is our t 495 function->writeText("0 le {pop false} {true} ifelse\n"); 496 function->writeText("} ifelse\n"); 497 498 // d < 0, clear the stack and push false 499 function->writeText("} {pop pop pop false} ifelse\n"); 500 } 501 502 // if the pixel is in the cone, proceed to compute a color 503 function->writeText("{"); 504 tileModeCode(info.fTileMode, function); 505 gradientFunctionCode(info, function); 506 507 // otherwise, just write black 508 function->writeText("} {0 0 0} ifelse }"); 509 } 510 511 static void sweepCode(const SkShader::GradientInfo& info, 512 const SkMatrix& perspectiveRemover, 513 SkDynamicMemoryWStream* function) { 514 function->writeText("{exch atan 360 div\n"); 515 tileModeCode(info.fTileMode, function); 516 gradientFunctionCode(info, function); 517 function->writeText("}"); 518 } 519 520 static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) { 521 SkAutoCanvasRestore acr(canvas, true); 522 canvas->concat(matrix); 523 canvas->drawBitmap(bm, 0, 0); 524 } 525 526 //////////////////////////////////////////////////////////////////////////////// 527 528 static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc, 529 SkScalar dpi, 530 const SkPDFShader::State& state); 531 static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon, 532 const SkPDFShader::State& state); 533 534 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc, 535 SkScalar dpi, 536 const SkPDFShader::State& state, 537 SkBitmap image); 538 539 static sk_sp<SkPDFObject> get_pdf_shader_by_state( 540 SkPDFDocument* doc, 541 SkScalar dpi, 542 SkPDFShader::State state, 543 SkBitmap image) { 544 SkPDFCanon* canon = doc->canon(); 545 if (state.fType == SkShader::kNone_GradientType && image.isNull()) { 546 // TODO(vandebo) This drops SKComposeShader on the floor. We could 547 // handle compose shader by pulling things up to a layer, drawing with 548 // the first shader, applying the xfer mode and drawing again with the 549 // second shader, then applying the layer to the original drawing. 550 return nullptr; 551 } else if (state.fType == SkShader::kNone_GradientType) { 552 sk_sp<SkPDFObject> shader = canon->findImageShader(state); 553 if (!shader) { 554 shader = make_image_shader(doc, dpi, state, std::move(image)); 555 canon->addImageShader(shader, std::move(state)); 556 } 557 return shader; 558 } else if (state.GradientHasAlpha()) { 559 sk_sp<SkPDFObject> shader = canon->findAlphaShader(state); 560 if (!shader) { 561 shader = make_alpha_function_shader(doc, dpi, state); 562 canon->addAlphaShader(shader, std::move(state)); 563 } 564 return shader; 565 } else { 566 sk_sp<SkPDFObject> shader = canon->findFunctionShader(state); 567 if (!shader) { 568 shader = make_function_shader(canon, state); 569 canon->addFunctionShader(shader, std::move(state)); 570 } 571 return shader; 572 } 573 } 574 575 sk_sp<SkPDFObject> SkPDFShader::GetPDFShader(SkPDFDocument* doc, 576 SkScalar dpi, 577 SkShader* shader, 578 const SkMatrix& matrix, 579 const SkIRect& surfaceBBox, 580 SkScalar rasterScale) { 581 SkBitmap image; 582 State state(shader, matrix, surfaceBBox, rasterScale, &image); 583 return get_pdf_shader_by_state( 584 doc, dpi, std::move(state), std::move(image)); 585 } 586 587 static sk_sp<SkPDFDict> get_gradient_resource_dict( 588 SkPDFObject* functionShader, 589 SkPDFObject* gState) { 590 SkTDArray<SkPDFObject*> patterns; 591 if (functionShader) { 592 patterns.push(functionShader); 593 } 594 SkTDArray<SkPDFObject*> graphicStates; 595 if (gState) { 596 graphicStates.push(gState); 597 } 598 return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr); 599 } 600 601 static void populate_tiling_pattern_dict(SkPDFDict* pattern, 602 SkRect& bbox, 603 sk_sp<SkPDFDict> resources, 604 const SkMatrix& matrix) { 605 const int kTiling_PatternType = 1; 606 const int kColoredTilingPattern_PaintType = 1; 607 const int kConstantSpacing_TilingType = 1; 608 609 pattern->insertName("Type", "Pattern"); 610 pattern->insertInt("PatternType", kTiling_PatternType); 611 pattern->insertInt("PaintType", kColoredTilingPattern_PaintType); 612 pattern->insertInt("TilingType", kConstantSpacing_TilingType); 613 pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox)); 614 pattern->insertScalar("XStep", bbox.width()); 615 pattern->insertScalar("YStep", bbox.height()); 616 pattern->insertObject("Resources", std::move(resources)); 617 if (!matrix.isIdentity()) { 618 pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix)); 619 } 620 } 621 622 /** 623 * Creates a content stream which fills the pattern P0 across bounds. 624 * @param gsIndex A graphics state resource index to apply, or <0 if no 625 * graphics state to apply. 626 */ 627 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content( 628 int gsIndex, SkRect& bounds) { 629 SkDynamicMemoryWStream content; 630 if (gsIndex >= 0) { 631 SkPDFUtils::ApplyGraphicState(gsIndex, &content); 632 } 633 SkPDFUtils::ApplyPattern(0, &content); 634 SkPDFUtils::AppendRectangle(bounds, &content); 635 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, 636 &content); 637 638 return std::unique_ptr<SkStreamAsset>(content.detachAsStream()); 639 } 640 641 /** 642 * Creates a ExtGState with the SMask set to the luminosityShader in 643 * luminosity mode. The shader pattern extends to the bbox. 644 */ 645 static sk_sp<SkPDFObject> create_smask_graphic_state( 646 SkPDFDocument* doc, SkScalar dpi, const SkPDFShader::State& state) { 647 SkRect bbox; 648 bbox.set(state.fBBox); 649 650 sk_sp<SkPDFObject> luminosityShader( 651 get_pdf_shader_by_state(doc, dpi, state.MakeAlphaToLuminosityState(), 652 SkBitmap())); 653 654 std::unique_ptr<SkStreamAsset> alphaStream(create_pattern_fill_content(-1, bbox)); 655 656 sk_sp<SkPDFDict> resources = 657 get_gradient_resource_dict(luminosityShader.get(), nullptr); 658 659 sk_sp<SkPDFObject> alphaMask = 660 SkPDFMakeFormXObject(std::move(alphaStream), 661 SkPDFUtils::RectToArray(bbox), 662 std::move(resources), 663 SkMatrix::I(), 664 "DeviceRGB"); 665 return SkPDFGraphicState::GetSMaskGraphicState( 666 std::move(alphaMask), false, 667 SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon()); 668 } 669 670 static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc, 671 SkScalar dpi, 672 const SkPDFShader::State& state) { 673 SkRect bbox; 674 bbox.set(state.fBBox); 675 676 SkPDFShader::State opaqueState(state.MakeOpaqueState()); 677 678 sk_sp<SkPDFObject> colorShader( 679 get_pdf_shader_by_state(doc, dpi, std::move(opaqueState), SkBitmap())); 680 if (!colorShader) { 681 return nullptr; 682 } 683 684 // Create resource dict with alpha graphics state as G0 and 685 // pattern shader as P0, then write content stream. 686 sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, dpi, state); 687 688 sk_sp<SkPDFDict> resourceDict = 689 get_gradient_resource_dict(colorShader.get(), alphaGs.get()); 690 691 std::unique_ptr<SkStreamAsset> colorStream( 692 create_pattern_fill_content(0, bbox)); 693 auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream)); 694 695 populate_tiling_pattern_dict(alphaFunctionShader->dict(), bbox, 696 std::move(resourceDict), SkMatrix::I()); 697 return alphaFunctionShader; 698 } 699 700 // Finds affine and persp such that in = affine * persp. 701 // but it returns the inverse of perspective matrix. 702 static bool split_perspective(const SkMatrix in, SkMatrix* affine, 703 SkMatrix* perspectiveInverse) { 704 const SkScalar p2 = in[SkMatrix::kMPersp2]; 705 706 if (SkScalarNearlyZero(p2)) { 707 return false; 708 } 709 710 const SkScalar zero = SkIntToScalar(0); 711 const SkScalar one = SkIntToScalar(1); 712 713 const SkScalar sx = in[SkMatrix::kMScaleX]; 714 const SkScalar kx = in[SkMatrix::kMSkewX]; 715 const SkScalar tx = in[SkMatrix::kMTransX]; 716 const SkScalar ky = in[SkMatrix::kMSkewY]; 717 const SkScalar sy = in[SkMatrix::kMScaleY]; 718 const SkScalar ty = in[SkMatrix::kMTransY]; 719 const SkScalar p0 = in[SkMatrix::kMPersp0]; 720 const SkScalar p1 = in[SkMatrix::kMPersp1]; 721 722 // Perspective matrix would be: 723 // 1 0 0 724 // 0 1 0 725 // p0 p1 p2 726 // But we need the inverse of persp. 727 perspectiveInverse->setAll(one, zero, zero, 728 zero, one, zero, 729 -p0/p2, -p1/p2, 1/p2); 730 731 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2, 732 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2, 733 zero, zero, one); 734 735 return true; 736 } 737 738 sk_sp<SkPDFArray> SkPDFShader::MakeRangeObject() { 739 auto range = sk_make_sp<SkPDFArray>(); 740 range->reserve(6); 741 range->appendInt(0); 742 range->appendInt(1); 743 range->appendInt(0); 744 range->appendInt(1); 745 range->appendInt(0); 746 range->appendInt(1); 747 return range; 748 } 749 750 static sk_sp<SkPDFStream> make_ps_function( 751 std::unique_ptr<SkStreamAsset> psCode, 752 sk_sp<SkPDFArray> domain, 753 sk_sp<SkPDFObject> range) { 754 auto result = sk_make_sp<SkPDFStream>(std::move(psCode)); 755 result->dict()->insertInt("FunctionType", 4); 756 result->dict()->insertObject("Domain", std::move(domain)); 757 result->dict()->insertObject("Range", std::move(range)); 758 return result; 759 } 760 761 // catch cases where the inner just touches the outer circle 762 // and make the inner circle just inside the outer one to match raster 763 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) { 764 // detect touching circles 765 SkScalar distance = SkPoint::Distance(p1, p2); 766 SkScalar subtractRadii = fabs(r1 - r2); 767 if (fabs(distance - subtractRadii) < 0.002f) { 768 if (r1 > r2) { 769 r1 += 0.002f; 770 } else { 771 r2 += 0.002f; 772 } 773 } 774 } 775 776 static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon, 777 const SkPDFShader::State& state) { 778 void (*codeFunction)(const SkShader::GradientInfo& info, 779 const SkMatrix& perspectiveRemover, 780 SkDynamicMemoryWStream* function) = nullptr; 781 SkPoint transformPoints[2]; 782 const SkShader::GradientInfo* info = &state.fInfo; 783 SkMatrix finalMatrix = state.fCanvasTransform; 784 finalMatrix.preConcat(state.fShaderTransform); 785 786 bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType || 787 state.fType == SkShader::kRadial_GradientType || 788 state.fType == SkShader::kConical_GradientType) && 789 info->fTileMode == SkShader::kClamp_TileMode && 790 !finalMatrix.hasPerspective(); 791 792 auto domain = sk_make_sp<SkPDFArray>(); 793 794 int32_t shadingType = 1; 795 auto pdfShader = sk_make_sp<SkPDFDict>(); 796 // The two point radial gradient further references 797 // state.fInfo 798 // in translating from x, y coordinates to the t parameter. So, we have 799 // to transform the points and radii according to the calculated matrix. 800 if (doStitchFunctions) { 801 pdfShader->insertObject("Function", gradientStitchCode(*info)); 802 shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3; 803 804 auto extend = sk_make_sp<SkPDFArray>(); 805 extend->reserve(2); 806 extend->appendBool(true); 807 extend->appendBool(true); 808 pdfShader->insertObject("Extend", std::move(extend)); 809 810 auto coords = sk_make_sp<SkPDFArray>(); 811 if (state.fType == SkShader::kConical_GradientType) { 812 coords->reserve(6); 813 SkScalar r1 = info->fRadius[0]; 814 SkScalar r2 = info->fRadius[1]; 815 SkPoint pt1 = info->fPoint[0]; 816 SkPoint pt2 = info->fPoint[1]; 817 FixUpRadius(pt1, r1, pt2, r2); 818 819 coords->appendScalar(pt1.fX); 820 coords->appendScalar(pt1.fY); 821 coords->appendScalar(r1); 822 823 coords->appendScalar(pt2.fX); 824 coords->appendScalar(pt2.fY); 825 coords->appendScalar(r2); 826 } else if (state.fType == SkShader::kRadial_GradientType) { 827 coords->reserve(6); 828 const SkPoint& pt1 = info->fPoint[0]; 829 830 coords->appendScalar(pt1.fX); 831 coords->appendScalar(pt1.fY); 832 coords->appendScalar(0); 833 834 coords->appendScalar(pt1.fX); 835 coords->appendScalar(pt1.fY); 836 coords->appendScalar(info->fRadius[0]); 837 } else { 838 coords->reserve(4); 839 const SkPoint& pt1 = info->fPoint[0]; 840 const SkPoint& pt2 = info->fPoint[1]; 841 842 coords->appendScalar(pt1.fX); 843 coords->appendScalar(pt1.fY); 844 845 coords->appendScalar(pt2.fX); 846 coords->appendScalar(pt2.fY); 847 } 848 849 pdfShader->insertObject("Coords", std::move(coords)); 850 } else { 851 // Depending on the type of the gradient, we want to transform the 852 // coordinate space in different ways. 853 transformPoints[0] = info->fPoint[0]; 854 transformPoints[1] = info->fPoint[1]; 855 switch (state.fType) { 856 case SkShader::kLinear_GradientType: 857 codeFunction = &linearCode; 858 break; 859 case SkShader::kRadial_GradientType: 860 transformPoints[1] = transformPoints[0]; 861 transformPoints[1].fX += info->fRadius[0]; 862 codeFunction = &radialCode; 863 break; 864 case SkShader::kConical_GradientType: { 865 transformPoints[1] = transformPoints[0]; 866 transformPoints[1].fX += SK_Scalar1; 867 codeFunction = &twoPointConicalCode; 868 break; 869 } 870 case SkShader::kSweep_GradientType: 871 transformPoints[1] = transformPoints[0]; 872 transformPoints[1].fX += SK_Scalar1; 873 codeFunction = &sweepCode; 874 break; 875 case SkShader::kColor_GradientType: 876 case SkShader::kNone_GradientType: 877 default: 878 return nullptr; 879 } 880 881 // Move any scaling (assuming a unit gradient) or translation 882 // (and rotation for linear gradient), of the final gradient from 883 // info->fPoints to the matrix (updating bbox appropriately). Now 884 // the gradient can be drawn on on the unit segment. 885 SkMatrix mapperMatrix; 886 unitToPointsMatrix(transformPoints, &mapperMatrix); 887 888 finalMatrix.preConcat(mapperMatrix); 889 890 // Preserves as much as posible in the final matrix, and only removes 891 // the perspective. The inverse of the perspective is stored in 892 // perspectiveInverseOnly matrix and has 3 useful numbers 893 // (p0, p1, p2), while everything else is either 0 or 1. 894 // In this way the shader will handle it eficiently, with minimal code. 895 SkMatrix perspectiveInverseOnly = SkMatrix::I(); 896 if (finalMatrix.hasPerspective()) { 897 if (!split_perspective(finalMatrix, 898 &finalMatrix, &perspectiveInverseOnly)) { 899 return nullptr; 900 } 901 } 902 903 SkRect bbox; 904 bbox.set(state.fBBox); 905 if (!inverse_transform_bbox(finalMatrix, &bbox)) { 906 return nullptr; 907 } 908 domain->reserve(4); 909 domain->appendScalar(bbox.fLeft); 910 domain->appendScalar(bbox.fRight); 911 domain->appendScalar(bbox.fTop); 912 domain->appendScalar(bbox.fBottom); 913 914 SkDynamicMemoryWStream functionCode; 915 916 if (state.fType == SkShader::kConical_GradientType) { 917 SkShader::GradientInfo twoPointRadialInfo = *info; 918 SkMatrix inverseMapperMatrix; 919 if (!mapperMatrix.invert(&inverseMapperMatrix)) { 920 return nullptr; 921 } 922 inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); 923 twoPointRadialInfo.fRadius[0] = 924 inverseMapperMatrix.mapRadius(info->fRadius[0]); 925 twoPointRadialInfo.fRadius[1] = 926 inverseMapperMatrix.mapRadius(info->fRadius[1]); 927 codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode); 928 } else { 929 codeFunction(*info, perspectiveInverseOnly, &functionCode); 930 } 931 932 pdfShader->insertObject("Domain", domain); 933 934 // Call canon->makeRangeObject() instead of 935 // SkPDFShader::MakeRangeObject() so that the canon can 936 // deduplicate. 937 std::unique_ptr<SkStreamAsset> functionStream( 938 functionCode.detachAsStream()); 939 sk_sp<SkPDFStream> function = make_ps_function(std::move(functionStream), 940 std::move(domain), 941 canon->makeRangeObject()); 942 pdfShader->insertObjRef("Function", std::move(function)); 943 } 944 945 pdfShader->insertInt("ShadingType", shadingType); 946 pdfShader->insertName("ColorSpace", "DeviceRGB"); 947 948 auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern"); 949 pdfFunctionShader->insertInt("PatternType", 2); 950 pdfFunctionShader->insertObject("Matrix", 951 SkPDFUtils::MatrixToArray(finalMatrix)); 952 pdfFunctionShader->insertObject("Shading", std::move(pdfShader)); 953 954 return pdfFunctionShader; 955 } 956 957 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc, 958 SkScalar dpi, 959 const SkPDFShader::State& state, 960 SkBitmap image) { 961 SkASSERT(state.fBitmapKey == 962 (SkBitmapKey{image.getSubset(), image.getGenerationID()})); 963 SkAutoLockPixels SkAutoLockPixels(image); 964 965 // The image shader pattern cell will be drawn into a separate device 966 // in pattern cell space (no scaling on the bitmap, though there may be 967 // translations so that all content is in the device, coordinates > 0). 968 969 // Map clip bounds to shader space to ensure the device is large enough 970 // to handle fake clamping. 971 SkMatrix finalMatrix = state.fCanvasTransform; 972 finalMatrix.preConcat(state.fShaderTransform); 973 SkRect deviceBounds; 974 deviceBounds.set(state.fBBox); 975 if (!inverse_transform_bbox(finalMatrix, &deviceBounds)) { 976 return nullptr; 977 } 978 979 SkRect bitmapBounds; 980 image.getBounds(&bitmapBounds); 981 982 // For tiling modes, the bounds should be extended to include the bitmap, 983 // otherwise the bitmap gets clipped out and the shader is empty and awful. 984 // For clamp modes, we're only interested in the clip region, whether 985 // or not the main bitmap is in it. 986 SkShader::TileMode tileModes[2]; 987 tileModes[0] = state.fImageTileModes[0]; 988 tileModes[1] = state.fImageTileModes[1]; 989 if (tileModes[0] != SkShader::kClamp_TileMode || 990 tileModes[1] != SkShader::kClamp_TileMode) { 991 deviceBounds.join(bitmapBounds); 992 } 993 994 SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()), 995 SkScalarRoundToInt(deviceBounds.height())); 996 sk_sp<SkPDFDevice> patternDevice( 997 SkPDFDevice::CreateUnflipped(size, dpi, doc)); 998 SkCanvas canvas(patternDevice.get()); 999 1000 SkRect patternBBox; 1001 image.getBounds(&patternBBox); 1002 1003 // Translate the canvas so that the bitmap origin is at (0, 0). 1004 canvas.translate(-deviceBounds.left(), -deviceBounds.top()); 1005 patternBBox.offset(-deviceBounds.left(), -deviceBounds.top()); 1006 // Undo the translation in the final matrix 1007 finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top()); 1008 1009 // If the bitmap is out of bounds (i.e. clamp mode where we only see the 1010 // stretched sides), canvas will clip this out and the extraneous data 1011 // won't be saved to the PDF. 1012 canvas.drawBitmap(image, 0, 0); 1013 1014 SkScalar width = SkIntToScalar(image.width()); 1015 SkScalar height = SkIntToScalar(image.height()); 1016 1017 // Tiling is implied. First we handle mirroring. 1018 if (tileModes[0] == SkShader::kMirror_TileMode) { 1019 SkMatrix xMirror; 1020 xMirror.setScale(-1, 1); 1021 xMirror.postTranslate(2 * width, 0); 1022 drawBitmapMatrix(&canvas, image, xMirror); 1023 patternBBox.fRight += width; 1024 } 1025 if (tileModes[1] == SkShader::kMirror_TileMode) { 1026 SkMatrix yMirror; 1027 yMirror.setScale(SK_Scalar1, -SK_Scalar1); 1028 yMirror.postTranslate(0, 2 * height); 1029 drawBitmapMatrix(&canvas, image, yMirror); 1030 patternBBox.fBottom += height; 1031 } 1032 if (tileModes[0] == SkShader::kMirror_TileMode && 1033 tileModes[1] == SkShader::kMirror_TileMode) { 1034 SkMatrix mirror; 1035 mirror.setScale(-1, -1); 1036 mirror.postTranslate(2 * width, 2 * height); 1037 drawBitmapMatrix(&canvas, image, mirror); 1038 } 1039 1040 // Then handle Clamping, which requires expanding the pattern canvas to 1041 // cover the entire surfaceBBox. 1042 1043 // If both x and y are in clamp mode, we start by filling in the corners. 1044 // (Which are just a rectangles of the corner colors.) 1045 if (tileModes[0] == SkShader::kClamp_TileMode && 1046 tileModes[1] == SkShader::kClamp_TileMode) { 1047 SkPaint paint; 1048 SkRect rect; 1049 rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0); 1050 if (!rect.isEmpty()) { 1051 paint.setColor(image.getColor(0, 0)); 1052 canvas.drawRect(rect, paint); 1053 } 1054 1055 rect = SkRect::MakeLTRB(width, deviceBounds.top(), 1056 deviceBounds.right(), 0); 1057 if (!rect.isEmpty()) { 1058 paint.setColor(image.getColor(image.width() - 1, 0)); 1059 canvas.drawRect(rect, paint); 1060 } 1061 1062 rect = SkRect::MakeLTRB(width, height, 1063 deviceBounds.right(), deviceBounds.bottom()); 1064 if (!rect.isEmpty()) { 1065 paint.setColor(image.getColor(image.width() - 1, 1066 image.height() - 1)); 1067 canvas.drawRect(rect, paint); 1068 } 1069 1070 rect = SkRect::MakeLTRB(deviceBounds.left(), height, 1071 0, deviceBounds.bottom()); 1072 if (!rect.isEmpty()) { 1073 paint.setColor(image.getColor(0, image.height() - 1)); 1074 canvas.drawRect(rect, paint); 1075 } 1076 } 1077 1078 // Then expand the left, right, top, then bottom. 1079 if (tileModes[0] == SkShader::kClamp_TileMode) { 1080 SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image.height()); 1081 if (deviceBounds.left() < 0) { 1082 SkBitmap left; 1083 SkAssertResult(image.extractSubset(&left, subset)); 1084 1085 SkMatrix leftMatrix; 1086 leftMatrix.setScale(-deviceBounds.left(), 1); 1087 leftMatrix.postTranslate(deviceBounds.left(), 0); 1088 drawBitmapMatrix(&canvas, left, leftMatrix); 1089 1090 if (tileModes[1] == SkShader::kMirror_TileMode) { 1091 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1); 1092 leftMatrix.postTranslate(0, 2 * height); 1093 drawBitmapMatrix(&canvas, left, leftMatrix); 1094 } 1095 patternBBox.fLeft = 0; 1096 } 1097 1098 if (deviceBounds.right() > width) { 1099 SkBitmap right; 1100 subset.offset(image.width() - 1, 0); 1101 SkAssertResult(image.extractSubset(&right, subset)); 1102 1103 SkMatrix rightMatrix; 1104 rightMatrix.setScale(deviceBounds.right() - width, 1); 1105 rightMatrix.postTranslate(width, 0); 1106 drawBitmapMatrix(&canvas, right, rightMatrix); 1107 1108 if (tileModes[1] == SkShader::kMirror_TileMode) { 1109 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1); 1110 rightMatrix.postTranslate(0, 2 * height); 1111 drawBitmapMatrix(&canvas, right, rightMatrix); 1112 } 1113 patternBBox.fRight = deviceBounds.width(); 1114 } 1115 } 1116 1117 if (tileModes[1] == SkShader::kClamp_TileMode) { 1118 SkIRect subset = SkIRect::MakeXYWH(0, 0, image.width(), 1); 1119 if (deviceBounds.top() < 0) { 1120 SkBitmap top; 1121 SkAssertResult(image.extractSubset(&top, subset)); 1122 1123 SkMatrix topMatrix; 1124 topMatrix.setScale(SK_Scalar1, -deviceBounds.top()); 1125 topMatrix.postTranslate(0, deviceBounds.top()); 1126 drawBitmapMatrix(&canvas, top, topMatrix); 1127 1128 if (tileModes[0] == SkShader::kMirror_TileMode) { 1129 topMatrix.postScale(-1, 1); 1130 topMatrix.postTranslate(2 * width, 0); 1131 drawBitmapMatrix(&canvas, top, topMatrix); 1132 } 1133 patternBBox.fTop = 0; 1134 } 1135 1136 if (deviceBounds.bottom() > height) { 1137 SkBitmap bottom; 1138 subset.offset(0, image.height() - 1); 1139 SkAssertResult(image.extractSubset(&bottom, subset)); 1140 1141 SkMatrix bottomMatrix; 1142 bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height); 1143 bottomMatrix.postTranslate(0, height); 1144 drawBitmapMatrix(&canvas, bottom, bottomMatrix); 1145 1146 if (tileModes[0] == SkShader::kMirror_TileMode) { 1147 bottomMatrix.postScale(-1, 1); 1148 bottomMatrix.postTranslate(2 * width, 0); 1149 drawBitmapMatrix(&canvas, bottom, bottomMatrix); 1150 } 1151 patternBBox.fBottom = deviceBounds.height(); 1152 } 1153 } 1154 1155 auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content()); 1156 populate_tiling_pattern_dict(imageShader->dict(), patternBBox, 1157 patternDevice->makeResourceDict(), finalMatrix); 1158 return imageShader; 1159 } 1160 1161 bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const { 1162 if (fType != b.fType || 1163 fCanvasTransform != b.fCanvasTransform || 1164 fShaderTransform != b.fShaderTransform || 1165 fBBox != b.fBBox) { 1166 return false; 1167 } 1168 1169 if (fType == SkShader::kNone_GradientType) { 1170 if (fBitmapKey != b.fBitmapKey || 1171 fBitmapKey.fID == 0 || 1172 fImageTileModes[0] != b.fImageTileModes[0] || 1173 fImageTileModes[1] != b.fImageTileModes[1]) { 1174 return false; 1175 } 1176 } else { 1177 if (fInfo.fColorCount != b.fInfo.fColorCount || 1178 memcmp(fInfo.fColors, b.fInfo.fColors, 1179 sizeof(SkColor) * fInfo.fColorCount) != 0 || 1180 memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets, 1181 sizeof(SkScalar) * fInfo.fColorCount) != 0 || 1182 fInfo.fPoint[0] != b.fInfo.fPoint[0] || 1183 fInfo.fTileMode != b.fInfo.fTileMode) { 1184 return false; 1185 } 1186 1187 switch (fType) { 1188 case SkShader::kLinear_GradientType: 1189 if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) { 1190 return false; 1191 } 1192 break; 1193 case SkShader::kRadial_GradientType: 1194 if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) { 1195 return false; 1196 } 1197 break; 1198 case SkShader::kConical_GradientType: 1199 if (fInfo.fPoint[1] != b.fInfo.fPoint[1] || 1200 fInfo.fRadius[0] != b.fInfo.fRadius[0] || 1201 fInfo.fRadius[1] != b.fInfo.fRadius[1]) { 1202 return false; 1203 } 1204 break; 1205 case SkShader::kSweep_GradientType: 1206 case SkShader::kNone_GradientType: 1207 case SkShader::kColor_GradientType: 1208 break; 1209 } 1210 } 1211 return true; 1212 } 1213 1214 SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform, 1215 const SkIRect& bbox, SkScalar rasterScale, 1216 SkBitmap* imageDst) 1217 : fCanvasTransform(canvasTransform), 1218 fBBox(bbox) { 1219 SkASSERT(imageDst); 1220 fInfo.fColorCount = 0; 1221 fInfo.fColors = nullptr; 1222 fInfo.fColorOffsets = nullptr; 1223 fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode; 1224 fType = shader->asAGradient(&fInfo); 1225 1226 if (fType != SkShader::kNone_GradientType) { 1227 fBitmapKey = SkBitmapKey{{0, 0, 0, 0}, 0}; 1228 fShaderTransform = shader->getLocalMatrix(); 1229 this->allocateGradientInfoStorage(); 1230 shader->asAGradient(&fInfo); 1231 return; 1232 } 1233 if (SkImage* skimg = shader->isAImage(&fShaderTransform, fImageTileModes)) { 1234 // TODO(halcanary): delay converting to bitmap. 1235 if (skimg->asLegacyBitmap(imageDst, SkImage::kRO_LegacyBitmapMode)) { 1236 fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()}; 1237 return; 1238 } 1239 } 1240 fShaderTransform = shader->getLocalMatrix(); 1241 // Generic fallback for unsupported shaders: 1242 // * allocate a bbox-sized bitmap 1243 // * shade the whole area 1244 // * use the result as a bitmap shader 1245 1246 // bbox is in device space. While that's exactly what we 1247 // want for sizing our bitmap, we need to map it into 1248 // shader space for adjustments (to match 1249 // MakeImageShader's behavior). 1250 SkRect shaderRect = SkRect::Make(bbox); 1251 if (!inverse_transform_bbox(canvasTransform, &shaderRect)) { 1252 imageDst->reset(); 1253 return; 1254 } 1255 1256 // Clamp the bitmap size to about 1M pixels 1257 static const SkScalar kMaxBitmapArea = 1024 * 1024; 1258 SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height(); 1259 if (bitmapArea > kMaxBitmapArea) { 1260 rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea); 1261 } 1262 1263 SkISize size = SkISize::Make(SkScalarRoundToInt(rasterScale * bbox.width()), 1264 SkScalarRoundToInt(rasterScale * bbox.height())); 1265 SkSize scale = SkSize::Make(SkIntToScalar(size.width()) / shaderRect.width(), 1266 SkIntToScalar(size.height()) / shaderRect.height()); 1267 1268 imageDst->allocN32Pixels(size.width(), size.height()); 1269 imageDst->eraseColor(SK_ColorTRANSPARENT); 1270 1271 SkPaint p; 1272 p.setShader(sk_ref_sp(shader)); 1273 1274 SkCanvas canvas(*imageDst); 1275 canvas.scale(scale.width(), scale.height()); 1276 canvas.translate(-shaderRect.x(), -shaderRect.y()); 1277 canvas.drawPaint(p); 1278 1279 fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y()); 1280 fShaderTransform.preScale(1 / scale.width(), 1 / scale.height()); 1281 fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()}; 1282 } 1283 1284 SkPDFShader::State::State(const SkPDFShader::State& other) 1285 : fType(other.fType), 1286 fCanvasTransform(other.fCanvasTransform), 1287 fShaderTransform(other.fShaderTransform), 1288 fBBox(other.fBBox) 1289 { 1290 // Only gradients supported for now, since that is all that is used. 1291 // If needed, image state copy constructor can be added here later. 1292 SkASSERT(fType != SkShader::kNone_GradientType); 1293 1294 if (fType != SkShader::kNone_GradientType) { 1295 fInfo = other.fInfo; 1296 1297 this->allocateGradientInfoStorage(); 1298 for (int i = 0; i < fInfo.fColorCount; i++) { 1299 fInfo.fColors[i] = other.fInfo.fColors[i]; 1300 fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i]; 1301 } 1302 } 1303 } 1304 1305 /** 1306 * Create a copy of this gradient state with alpha assigned to RGB luminousity. 1307 * Only valid for gradient states. 1308 */ 1309 SkPDFShader::State SkPDFShader::State::MakeAlphaToLuminosityState() const { 1310 SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0})); 1311 SkASSERT(fType != SkShader::kNone_GradientType); 1312 1313 SkPDFShader::State newState(*this); 1314 1315 for (int i = 0; i < fInfo.fColorCount; i++) { 1316 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); 1317 newState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); 1318 } 1319 1320 return newState; 1321 } 1322 1323 /** 1324 * Create a copy of this gradient state with alpha set to fully opaque 1325 * Only valid for gradient states. 1326 */ 1327 SkPDFShader::State SkPDFShader::State::MakeOpaqueState() const { 1328 SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0})); 1329 SkASSERT(fType != SkShader::kNone_GradientType); 1330 1331 SkPDFShader::State newState(*this); 1332 for (int i = 0; i < fInfo.fColorCount; i++) { 1333 newState.fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i], 1334 SK_AlphaOPAQUE); 1335 } 1336 1337 return newState; 1338 } 1339 1340 /** 1341 * Returns true if state is a gradient and the gradient has alpha. 1342 */ 1343 bool SkPDFShader::State::GradientHasAlpha() const { 1344 if (fType == SkShader::kNone_GradientType) { 1345 return false; 1346 } 1347 1348 for (int i = 0; i < fInfo.fColorCount; i++) { 1349 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); 1350 if (alpha != SK_AlphaOPAQUE) { 1351 return true; 1352 } 1353 } 1354 return false; 1355 } 1356 1357 void SkPDFShader::State::allocateGradientInfoStorage() { 1358 fColors.reset(new SkColor[fInfo.fColorCount]); 1359 fStops.reset(new SkScalar[fInfo.fColorCount]); 1360 fInfo.fColors = fColors.get(); 1361 fInfo.fColorOffsets = fStops.get(); 1362 } 1363