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 = dx * dx + dy * dy - dr * dr; 407 408 // First compute t, if the pixel falls outside the cone, then we'll end 409 // with 'false' on the stack, otherwise we'll push 'true' with t below it 410 411 // We start with a stack of (x y), copy it and then consume one copy in 412 // order to calculate b and the other to calculate c. 413 function->writeText("{"); 414 415 apply_perspective_to_coordinates(perspectiveRemover, function); 416 417 function->writeText("2 copy "); 418 419 // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr). 420 SkPDFUtils::AppendScalar(dy, function); 421 function->writeText(" mul exch "); 422 SkPDFUtils::AppendScalar(dx, function); 423 function->writeText(" mul add "); 424 SkPDFUtils::AppendScalar(r0 * dr, function); 425 function->writeText(" add -2 mul dup dup mul\n"); 426 427 // c = x^2 + y^2 + radius0^2 428 function->writeText("4 2 roll dup mul exch dup mul add "); 429 SkPDFUtils::AppendScalar(r0 * r0, function); 430 function->writeText(" sub dup 4 1 roll\n"); 431 432 // Contents of the stack at this point: c, b, b^2, c 433 434 // if a = 0, then we collapse to a simpler linear case 435 if (a == 0) { 436 437 // t = -c/b 438 function->writeText("pop pop div neg dup "); 439 440 // compute radius(t) 441 SkPDFUtils::AppendScalar(dr, function); 442 function->writeText(" mul "); 443 SkPDFUtils::AppendScalar(r0, function); 444 function->writeText(" add\n"); 445 446 // if r(t) < 0, then it's outside the cone 447 function->writeText("0 lt {pop false} {true} ifelse\n"); 448 449 } else { 450 451 // quadratic case: the Canvas spec wants the largest 452 // root t for which radius(t) > 0 453 454 // compute the discriminant (b^2 - 4ac) 455 SkPDFUtils::AppendScalar(a * 4, function); 456 function->writeText(" mul sub dup\n"); 457 458 // if d >= 0, proceed 459 function->writeText("0 ge {\n"); 460 461 // an intermediate value we'll use to compute the roots: 462 // q = -0.5 * (b +/- sqrt(d)) 463 function->writeText("sqrt exch dup 0 lt {exch -1 mul} if"); 464 function->writeText(" add -0.5 mul dup\n"); 465 466 // first root = q / a 467 SkPDFUtils::AppendScalar(a, function); 468 function->writeText(" div\n"); 469 470 // second root = c / q 471 function->writeText("3 1 roll div\n"); 472 473 // put the larger root on top of the stack 474 function->writeText("2 copy gt {exch} if\n"); 475 476 // compute radius(t) for larger root 477 function->writeText("dup "); 478 SkPDFUtils::AppendScalar(dr, function); 479 function->writeText(" mul "); 480 SkPDFUtils::AppendScalar(r0, function); 481 function->writeText(" add\n"); 482 483 // if r(t) > 0, we have our t, pop off the smaller root and we're done 484 function->writeText(" 0 gt {exch pop true}\n"); 485 486 // otherwise, throw out the larger one and try the smaller root 487 function->writeText("{pop dup\n"); 488 SkPDFUtils::AppendScalar(dr, function); 489 function->writeText(" mul "); 490 SkPDFUtils::AppendScalar(r0, function); 491 function->writeText(" add\n"); 492 493 // if r(t) < 0, push false, otherwise the smaller root is our t 494 function->writeText("0 le {pop false} {true} ifelse\n"); 495 function->writeText("} ifelse\n"); 496 497 // d < 0, clear the stack and push false 498 function->writeText("} {pop pop pop false} ifelse\n"); 499 } 500 501 // if the pixel is in the cone, proceed to compute a color 502 function->writeText("{"); 503 tileModeCode(info.fTileMode, function); 504 gradientFunctionCode(info, function); 505 506 // otherwise, just write black 507 function->writeText("} {0 0 0} ifelse }"); 508 } 509 510 static void sweepCode(const SkShader::GradientInfo& info, 511 const SkMatrix& perspectiveRemover, 512 SkDynamicMemoryWStream* function) { 513 function->writeText("{exch atan 360 div\n"); 514 tileModeCode(info.fTileMode, function); 515 gradientFunctionCode(info, function); 516 function->writeText("}"); 517 } 518 519 static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) { 520 SkAutoCanvasRestore acr(canvas, true); 521 canvas->concat(matrix); 522 canvas->drawBitmap(bm, 0, 0); 523 } 524 525 //////////////////////////////////////////////////////////////////////////////// 526 527 static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc, 528 SkScalar dpi, 529 const SkPDFShader::State& state); 530 static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon, 531 const SkPDFShader::State& state); 532 533 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc, 534 SkScalar dpi, 535 const SkPDFShader::State& state, 536 SkBitmap image); 537 538 static sk_sp<SkPDFObject> get_pdf_shader_by_state( 539 SkPDFDocument* doc, 540 SkScalar dpi, 541 SkPDFShader::State state, 542 SkBitmap image) { 543 SkPDFCanon* canon = doc->canon(); 544 if (state.fType == SkShader::kNone_GradientType && image.isNull()) { 545 // TODO(vandebo) This drops SKComposeShader on the floor. We could 546 // handle compose shader by pulling things up to a layer, drawing with 547 // the first shader, applying the xfer mode and drawing again with the 548 // second shader, then applying the layer to the original drawing. 549 return nullptr; 550 } else if (state.fType == SkShader::kNone_GradientType) { 551 sk_sp<SkPDFObject> shader = canon->findImageShader(state); 552 if (!shader) { 553 shader = make_image_shader(doc, dpi, state, std::move(image)); 554 canon->addImageShader(shader, std::move(state)); 555 } 556 return shader; 557 } else if (state.GradientHasAlpha()) { 558 sk_sp<SkPDFObject> shader = canon->findAlphaShader(state); 559 if (!shader) { 560 shader = make_alpha_function_shader(doc, dpi, state); 561 canon->addAlphaShader(shader, std::move(state)); 562 } 563 return shader; 564 } else { 565 sk_sp<SkPDFObject> shader = canon->findFunctionShader(state); 566 if (!shader) { 567 shader = make_function_shader(canon, state); 568 canon->addFunctionShader(shader, std::move(state)); 569 } 570 return shader; 571 } 572 } 573 574 sk_sp<SkPDFObject> SkPDFShader::GetPDFShader(SkPDFDocument* doc, 575 SkScalar dpi, 576 SkShader* shader, 577 const SkMatrix& matrix, 578 const SkIRect& surfaceBBox, 579 SkScalar rasterScale) { 580 if (surfaceBBox.isEmpty()) { 581 return nullptr; 582 } 583 SkBitmap image; 584 State state(shader, matrix, surfaceBBox, rasterScale, &image); 585 return get_pdf_shader_by_state( 586 doc, dpi, std::move(state), std::move(image)); 587 } 588 589 static sk_sp<SkPDFDict> get_gradient_resource_dict( 590 SkPDFObject* functionShader, 591 SkPDFObject* gState) { 592 SkTDArray<SkPDFObject*> patterns; 593 if (functionShader) { 594 patterns.push(functionShader); 595 } 596 SkTDArray<SkPDFObject*> graphicStates; 597 if (gState) { 598 graphicStates.push(gState); 599 } 600 return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr); 601 } 602 603 static void populate_tiling_pattern_dict(SkPDFDict* pattern, 604 SkRect& bbox, 605 sk_sp<SkPDFDict> resources, 606 const SkMatrix& matrix) { 607 const int kTiling_PatternType = 1; 608 const int kColoredTilingPattern_PaintType = 1; 609 const int kConstantSpacing_TilingType = 1; 610 611 pattern->insertName("Type", "Pattern"); 612 pattern->insertInt("PatternType", kTiling_PatternType); 613 pattern->insertInt("PaintType", kColoredTilingPattern_PaintType); 614 pattern->insertInt("TilingType", kConstantSpacing_TilingType); 615 pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox)); 616 pattern->insertScalar("XStep", bbox.width()); 617 pattern->insertScalar("YStep", bbox.height()); 618 pattern->insertObject("Resources", std::move(resources)); 619 if (!matrix.isIdentity()) { 620 pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix)); 621 } 622 } 623 624 /** 625 * Creates a content stream which fills the pattern P0 across bounds. 626 * @param gsIndex A graphics state resource index to apply, or <0 if no 627 * graphics state to apply. 628 */ 629 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content( 630 int gsIndex, SkRect& bounds) { 631 SkDynamicMemoryWStream content; 632 if (gsIndex >= 0) { 633 SkPDFUtils::ApplyGraphicState(gsIndex, &content); 634 } 635 SkPDFUtils::ApplyPattern(0, &content); 636 SkPDFUtils::AppendRectangle(bounds, &content); 637 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, 638 &content); 639 640 return std::unique_ptr<SkStreamAsset>(content.detachAsStream()); 641 } 642 643 /** 644 * Creates a ExtGState with the SMask set to the luminosityShader in 645 * luminosity mode. The shader pattern extends to the bbox. 646 */ 647 static sk_sp<SkPDFObject> create_smask_graphic_state( 648 SkPDFDocument* doc, SkScalar dpi, const SkPDFShader::State& state) { 649 SkRect bbox; 650 bbox.set(state.fBBox); 651 652 sk_sp<SkPDFObject> luminosityShader( 653 get_pdf_shader_by_state(doc, dpi, state.MakeAlphaToLuminosityState(), 654 SkBitmap())); 655 656 std::unique_ptr<SkStreamAsset> alphaStream(create_pattern_fill_content(-1, bbox)); 657 658 sk_sp<SkPDFDict> resources = 659 get_gradient_resource_dict(luminosityShader.get(), nullptr); 660 661 sk_sp<SkPDFObject> alphaMask = 662 SkPDFMakeFormXObject(std::move(alphaStream), 663 SkPDFUtils::RectToArray(bbox), 664 std::move(resources), 665 SkMatrix::I(), 666 "DeviceRGB"); 667 return SkPDFGraphicState::GetSMaskGraphicState( 668 std::move(alphaMask), false, 669 SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon()); 670 } 671 672 static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc, 673 SkScalar dpi, 674 const SkPDFShader::State& state) { 675 SkRect bbox; 676 bbox.set(state.fBBox); 677 678 SkPDFShader::State opaqueState(state.MakeOpaqueState()); 679 680 sk_sp<SkPDFObject> colorShader( 681 get_pdf_shader_by_state(doc, dpi, std::move(opaqueState), SkBitmap())); 682 if (!colorShader) { 683 return nullptr; 684 } 685 686 // Create resource dict with alpha graphics state as G0 and 687 // pattern shader as P0, then write content stream. 688 sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, dpi, state); 689 690 sk_sp<SkPDFDict> resourceDict = 691 get_gradient_resource_dict(colorShader.get(), alphaGs.get()); 692 693 std::unique_ptr<SkStreamAsset> colorStream( 694 create_pattern_fill_content(0, bbox)); 695 auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream)); 696 697 populate_tiling_pattern_dict(alphaFunctionShader->dict(), bbox, 698 std::move(resourceDict), SkMatrix::I()); 699 return alphaFunctionShader; 700 } 701 702 // Finds affine and persp such that in = affine * persp. 703 // but it returns the inverse of perspective matrix. 704 static bool split_perspective(const SkMatrix in, SkMatrix* affine, 705 SkMatrix* perspectiveInverse) { 706 const SkScalar p2 = in[SkMatrix::kMPersp2]; 707 708 if (SkScalarNearlyZero(p2)) { 709 return false; 710 } 711 712 const SkScalar zero = SkIntToScalar(0); 713 const SkScalar one = SkIntToScalar(1); 714 715 const SkScalar sx = in[SkMatrix::kMScaleX]; 716 const SkScalar kx = in[SkMatrix::kMSkewX]; 717 const SkScalar tx = in[SkMatrix::kMTransX]; 718 const SkScalar ky = in[SkMatrix::kMSkewY]; 719 const SkScalar sy = in[SkMatrix::kMScaleY]; 720 const SkScalar ty = in[SkMatrix::kMTransY]; 721 const SkScalar p0 = in[SkMatrix::kMPersp0]; 722 const SkScalar p1 = in[SkMatrix::kMPersp1]; 723 724 // Perspective matrix would be: 725 // 1 0 0 726 // 0 1 0 727 // p0 p1 p2 728 // But we need the inverse of persp. 729 perspectiveInverse->setAll(one, zero, zero, 730 zero, one, zero, 731 -p0/p2, -p1/p2, 1/p2); 732 733 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2, 734 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2, 735 zero, zero, one); 736 737 return true; 738 } 739 740 sk_sp<SkPDFArray> SkPDFShader::MakeRangeObject() { 741 auto range = sk_make_sp<SkPDFArray>(); 742 range->reserve(6); 743 range->appendInt(0); 744 range->appendInt(1); 745 range->appendInt(0); 746 range->appendInt(1); 747 range->appendInt(0); 748 range->appendInt(1); 749 return range; 750 } 751 752 static sk_sp<SkPDFStream> make_ps_function( 753 std::unique_ptr<SkStreamAsset> psCode, 754 sk_sp<SkPDFArray> domain, 755 sk_sp<SkPDFObject> range) { 756 auto result = sk_make_sp<SkPDFStream>(std::move(psCode)); 757 result->dict()->insertInt("FunctionType", 4); 758 result->dict()->insertObject("Domain", std::move(domain)); 759 result->dict()->insertObject("Range", std::move(range)); 760 return result; 761 } 762 763 // catch cases where the inner just touches the outer circle 764 // and make the inner circle just inside the outer one to match raster 765 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) { 766 // detect touching circles 767 SkScalar distance = SkPoint::Distance(p1, p2); 768 SkScalar subtractRadii = fabs(r1 - r2); 769 if (fabs(distance - subtractRadii) < 0.002f) { 770 if (r1 > r2) { 771 r1 += 0.002f; 772 } else { 773 r2 += 0.002f; 774 } 775 } 776 } 777 778 static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon, 779 const SkPDFShader::State& state) { 780 void (*codeFunction)(const SkShader::GradientInfo& info, 781 const SkMatrix& perspectiveRemover, 782 SkDynamicMemoryWStream* function) = nullptr; 783 SkPoint transformPoints[2]; 784 const SkShader::GradientInfo* info = &state.fInfo; 785 SkMatrix finalMatrix = state.fCanvasTransform; 786 finalMatrix.preConcat(state.fShaderTransform); 787 788 bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType || 789 state.fType == SkShader::kRadial_GradientType || 790 state.fType == SkShader::kConical_GradientType) && 791 info->fTileMode == SkShader::kClamp_TileMode && 792 !finalMatrix.hasPerspective(); 793 794 auto domain = sk_make_sp<SkPDFArray>(); 795 796 int32_t shadingType = 1; 797 auto pdfShader = sk_make_sp<SkPDFDict>(); 798 // The two point radial gradient further references 799 // state.fInfo 800 // in translating from x, y coordinates to the t parameter. So, we have 801 // to transform the points and radii according to the calculated matrix. 802 if (doStitchFunctions) { 803 pdfShader->insertObject("Function", gradientStitchCode(*info)); 804 shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3; 805 806 auto extend = sk_make_sp<SkPDFArray>(); 807 extend->reserve(2); 808 extend->appendBool(true); 809 extend->appendBool(true); 810 pdfShader->insertObject("Extend", std::move(extend)); 811 812 auto coords = sk_make_sp<SkPDFArray>(); 813 if (state.fType == SkShader::kConical_GradientType) { 814 coords->reserve(6); 815 SkScalar r1 = info->fRadius[0]; 816 SkScalar r2 = info->fRadius[1]; 817 SkPoint pt1 = info->fPoint[0]; 818 SkPoint pt2 = info->fPoint[1]; 819 FixUpRadius(pt1, r1, pt2, r2); 820 821 coords->appendScalar(pt1.fX); 822 coords->appendScalar(pt1.fY); 823 coords->appendScalar(r1); 824 825 coords->appendScalar(pt2.fX); 826 coords->appendScalar(pt2.fY); 827 coords->appendScalar(r2); 828 } else if (state.fType == SkShader::kRadial_GradientType) { 829 coords->reserve(6); 830 const SkPoint& pt1 = info->fPoint[0]; 831 832 coords->appendScalar(pt1.fX); 833 coords->appendScalar(pt1.fY); 834 coords->appendScalar(0); 835 836 coords->appendScalar(pt1.fX); 837 coords->appendScalar(pt1.fY); 838 coords->appendScalar(info->fRadius[0]); 839 } else { 840 coords->reserve(4); 841 const SkPoint& pt1 = info->fPoint[0]; 842 const SkPoint& pt2 = info->fPoint[1]; 843 844 coords->appendScalar(pt1.fX); 845 coords->appendScalar(pt1.fY); 846 847 coords->appendScalar(pt2.fX); 848 coords->appendScalar(pt2.fY); 849 } 850 851 pdfShader->insertObject("Coords", std::move(coords)); 852 } else { 853 // Depending on the type of the gradient, we want to transform the 854 // coordinate space in different ways. 855 transformPoints[0] = info->fPoint[0]; 856 transformPoints[1] = info->fPoint[1]; 857 switch (state.fType) { 858 case SkShader::kLinear_GradientType: 859 codeFunction = &linearCode; 860 break; 861 case SkShader::kRadial_GradientType: 862 transformPoints[1] = transformPoints[0]; 863 transformPoints[1].fX += info->fRadius[0]; 864 codeFunction = &radialCode; 865 break; 866 case SkShader::kConical_GradientType: { 867 transformPoints[1] = transformPoints[0]; 868 transformPoints[1].fX += SK_Scalar1; 869 codeFunction = &twoPointConicalCode; 870 break; 871 } 872 case SkShader::kSweep_GradientType: 873 transformPoints[1] = transformPoints[0]; 874 transformPoints[1].fX += SK_Scalar1; 875 codeFunction = &sweepCode; 876 break; 877 case SkShader::kColor_GradientType: 878 case SkShader::kNone_GradientType: 879 default: 880 return nullptr; 881 } 882 883 // Move any scaling (assuming a unit gradient) or translation 884 // (and rotation for linear gradient), of the final gradient from 885 // info->fPoints to the matrix (updating bbox appropriately). Now 886 // the gradient can be drawn on on the unit segment. 887 SkMatrix mapperMatrix; 888 unitToPointsMatrix(transformPoints, &mapperMatrix); 889 890 finalMatrix.preConcat(mapperMatrix); 891 892 // Preserves as much as posible in the final matrix, and only removes 893 // the perspective. The inverse of the perspective is stored in 894 // perspectiveInverseOnly matrix and has 3 useful numbers 895 // (p0, p1, p2), while everything else is either 0 or 1. 896 // In this way the shader will handle it eficiently, with minimal code. 897 SkMatrix perspectiveInverseOnly = SkMatrix::I(); 898 if (finalMatrix.hasPerspective()) { 899 if (!split_perspective(finalMatrix, 900 &finalMatrix, &perspectiveInverseOnly)) { 901 return nullptr; 902 } 903 } 904 905 SkRect bbox; 906 bbox.set(state.fBBox); 907 if (!inverse_transform_bbox(finalMatrix, &bbox)) { 908 return nullptr; 909 } 910 domain->reserve(4); 911 domain->appendScalar(bbox.fLeft); 912 domain->appendScalar(bbox.fRight); 913 domain->appendScalar(bbox.fTop); 914 domain->appendScalar(bbox.fBottom); 915 916 SkDynamicMemoryWStream functionCode; 917 918 if (state.fType == SkShader::kConical_GradientType) { 919 SkShader::GradientInfo twoPointRadialInfo = *info; 920 SkMatrix inverseMapperMatrix; 921 if (!mapperMatrix.invert(&inverseMapperMatrix)) { 922 return nullptr; 923 } 924 inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); 925 twoPointRadialInfo.fRadius[0] = 926 inverseMapperMatrix.mapRadius(info->fRadius[0]); 927 twoPointRadialInfo.fRadius[1] = 928 inverseMapperMatrix.mapRadius(info->fRadius[1]); 929 codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode); 930 } else { 931 codeFunction(*info, perspectiveInverseOnly, &functionCode); 932 } 933 934 pdfShader->insertObject("Domain", domain); 935 936 // Call canon->makeRangeObject() instead of 937 // SkPDFShader::MakeRangeObject() so that the canon can 938 // deduplicate. 939 std::unique_ptr<SkStreamAsset> functionStream( 940 functionCode.detachAsStream()); 941 sk_sp<SkPDFStream> function = make_ps_function(std::move(functionStream), 942 std::move(domain), 943 canon->makeRangeObject()); 944 pdfShader->insertObjRef("Function", std::move(function)); 945 } 946 947 pdfShader->insertInt("ShadingType", shadingType); 948 pdfShader->insertName("ColorSpace", "DeviceRGB"); 949 950 auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern"); 951 pdfFunctionShader->insertInt("PatternType", 2); 952 pdfFunctionShader->insertObject("Matrix", 953 SkPDFUtils::MatrixToArray(finalMatrix)); 954 pdfFunctionShader->insertObject("Shading", std::move(pdfShader)); 955 956 return pdfFunctionShader; 957 } 958 959 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc, 960 SkScalar dpi, 961 const SkPDFShader::State& state, 962 SkBitmap image) { 963 SkASSERT(state.fBitmapKey == 964 (SkBitmapKey{image.getSubset(), image.getGenerationID()})); 965 SkAutoLockPixels SkAutoLockPixels(image); 966 967 // The image shader pattern cell will be drawn into a separate device 968 // in pattern cell space (no scaling on the bitmap, though there may be 969 // translations so that all content is in the device, coordinates > 0). 970 971 // Map clip bounds to shader space to ensure the device is large enough 972 // to handle fake clamping. 973 SkMatrix finalMatrix = state.fCanvasTransform; 974 finalMatrix.preConcat(state.fShaderTransform); 975 SkRect deviceBounds; 976 deviceBounds.set(state.fBBox); 977 if (!inverse_transform_bbox(finalMatrix, &deviceBounds)) { 978 return nullptr; 979 } 980 981 SkRect bitmapBounds; 982 image.getBounds(&bitmapBounds); 983 984 // For tiling modes, the bounds should be extended to include the bitmap, 985 // otherwise the bitmap gets clipped out and the shader is empty and awful. 986 // For clamp modes, we're only interested in the clip region, whether 987 // or not the main bitmap is in it. 988 SkShader::TileMode tileModes[2]; 989 tileModes[0] = state.fImageTileModes[0]; 990 tileModes[1] = state.fImageTileModes[1]; 991 if (tileModes[0] != SkShader::kClamp_TileMode || 992 tileModes[1] != SkShader::kClamp_TileMode) { 993 deviceBounds.join(bitmapBounds); 994 } 995 996 SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()), 997 SkScalarRoundToInt(deviceBounds.height())); 998 sk_sp<SkPDFDevice> patternDevice( 999 SkPDFDevice::CreateUnflipped(size, dpi, doc)); 1000 SkCanvas canvas(patternDevice.get()); 1001 1002 SkRect patternBBox; 1003 image.getBounds(&patternBBox); 1004 1005 // Translate the canvas so that the bitmap origin is at (0, 0). 1006 canvas.translate(-deviceBounds.left(), -deviceBounds.top()); 1007 patternBBox.offset(-deviceBounds.left(), -deviceBounds.top()); 1008 // Undo the translation in the final matrix 1009 finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top()); 1010 1011 // If the bitmap is out of bounds (i.e. clamp mode where we only see the 1012 // stretched sides), canvas will clip this out and the extraneous data 1013 // won't be saved to the PDF. 1014 canvas.drawBitmap(image, 0, 0); 1015 1016 SkScalar width = SkIntToScalar(image.width()); 1017 SkScalar height = SkIntToScalar(image.height()); 1018 1019 // Tiling is implied. First we handle mirroring. 1020 if (tileModes[0] == SkShader::kMirror_TileMode) { 1021 SkMatrix xMirror; 1022 xMirror.setScale(-1, 1); 1023 xMirror.postTranslate(2 * width, 0); 1024 drawBitmapMatrix(&canvas, image, xMirror); 1025 patternBBox.fRight += width; 1026 } 1027 if (tileModes[1] == SkShader::kMirror_TileMode) { 1028 SkMatrix yMirror; 1029 yMirror.setScale(SK_Scalar1, -SK_Scalar1); 1030 yMirror.postTranslate(0, 2 * height); 1031 drawBitmapMatrix(&canvas, image, yMirror); 1032 patternBBox.fBottom += height; 1033 } 1034 if (tileModes[0] == SkShader::kMirror_TileMode && 1035 tileModes[1] == SkShader::kMirror_TileMode) { 1036 SkMatrix mirror; 1037 mirror.setScale(-1, -1); 1038 mirror.postTranslate(2 * width, 2 * height); 1039 drawBitmapMatrix(&canvas, image, mirror); 1040 } 1041 1042 // Then handle Clamping, which requires expanding the pattern canvas to 1043 // cover the entire surfaceBBox. 1044 1045 // If both x and y are in clamp mode, we start by filling in the corners. 1046 // (Which are just a rectangles of the corner colors.) 1047 if (tileModes[0] == SkShader::kClamp_TileMode && 1048 tileModes[1] == SkShader::kClamp_TileMode) { 1049 SkPaint paint; 1050 SkRect rect; 1051 rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0); 1052 if (!rect.isEmpty()) { 1053 paint.setColor(image.getColor(0, 0)); 1054 canvas.drawRect(rect, paint); 1055 } 1056 1057 rect = SkRect::MakeLTRB(width, deviceBounds.top(), 1058 deviceBounds.right(), 0); 1059 if (!rect.isEmpty()) { 1060 paint.setColor(image.getColor(image.width() - 1, 0)); 1061 canvas.drawRect(rect, paint); 1062 } 1063 1064 rect = SkRect::MakeLTRB(width, height, 1065 deviceBounds.right(), deviceBounds.bottom()); 1066 if (!rect.isEmpty()) { 1067 paint.setColor(image.getColor(image.width() - 1, 1068 image.height() - 1)); 1069 canvas.drawRect(rect, paint); 1070 } 1071 1072 rect = SkRect::MakeLTRB(deviceBounds.left(), height, 1073 0, deviceBounds.bottom()); 1074 if (!rect.isEmpty()) { 1075 paint.setColor(image.getColor(0, image.height() - 1)); 1076 canvas.drawRect(rect, paint); 1077 } 1078 } 1079 1080 // Then expand the left, right, top, then bottom. 1081 if (tileModes[0] == SkShader::kClamp_TileMode) { 1082 SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image.height()); 1083 if (deviceBounds.left() < 0) { 1084 SkBitmap left; 1085 SkAssertResult(image.extractSubset(&left, subset)); 1086 1087 SkMatrix leftMatrix; 1088 leftMatrix.setScale(-deviceBounds.left(), 1); 1089 leftMatrix.postTranslate(deviceBounds.left(), 0); 1090 drawBitmapMatrix(&canvas, left, leftMatrix); 1091 1092 if (tileModes[1] == SkShader::kMirror_TileMode) { 1093 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1); 1094 leftMatrix.postTranslate(0, 2 * height); 1095 drawBitmapMatrix(&canvas, left, leftMatrix); 1096 } 1097 patternBBox.fLeft = 0; 1098 } 1099 1100 if (deviceBounds.right() > width) { 1101 SkBitmap right; 1102 subset.offset(image.width() - 1, 0); 1103 SkAssertResult(image.extractSubset(&right, subset)); 1104 1105 SkMatrix rightMatrix; 1106 rightMatrix.setScale(deviceBounds.right() - width, 1); 1107 rightMatrix.postTranslate(width, 0); 1108 drawBitmapMatrix(&canvas, right, rightMatrix); 1109 1110 if (tileModes[1] == SkShader::kMirror_TileMode) { 1111 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1); 1112 rightMatrix.postTranslate(0, 2 * height); 1113 drawBitmapMatrix(&canvas, right, rightMatrix); 1114 } 1115 patternBBox.fRight = deviceBounds.width(); 1116 } 1117 } 1118 1119 if (tileModes[1] == SkShader::kClamp_TileMode) { 1120 SkIRect subset = SkIRect::MakeXYWH(0, 0, image.width(), 1); 1121 if (deviceBounds.top() < 0) { 1122 SkBitmap top; 1123 SkAssertResult(image.extractSubset(&top, subset)); 1124 1125 SkMatrix topMatrix; 1126 topMatrix.setScale(SK_Scalar1, -deviceBounds.top()); 1127 topMatrix.postTranslate(0, deviceBounds.top()); 1128 drawBitmapMatrix(&canvas, top, topMatrix); 1129 1130 if (tileModes[0] == SkShader::kMirror_TileMode) { 1131 topMatrix.postScale(-1, 1); 1132 topMatrix.postTranslate(2 * width, 0); 1133 drawBitmapMatrix(&canvas, top, topMatrix); 1134 } 1135 patternBBox.fTop = 0; 1136 } 1137 1138 if (deviceBounds.bottom() > height) { 1139 SkBitmap bottom; 1140 subset.offset(0, image.height() - 1); 1141 SkAssertResult(image.extractSubset(&bottom, subset)); 1142 1143 SkMatrix bottomMatrix; 1144 bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height); 1145 bottomMatrix.postTranslate(0, height); 1146 drawBitmapMatrix(&canvas, bottom, bottomMatrix); 1147 1148 if (tileModes[0] == SkShader::kMirror_TileMode) { 1149 bottomMatrix.postScale(-1, 1); 1150 bottomMatrix.postTranslate(2 * width, 0); 1151 drawBitmapMatrix(&canvas, bottom, bottomMatrix); 1152 } 1153 patternBBox.fBottom = deviceBounds.height(); 1154 } 1155 } 1156 1157 auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content()); 1158 populate_tiling_pattern_dict(imageShader->dict(), patternBBox, 1159 patternDevice->makeResourceDict(), finalMatrix); 1160 return imageShader; 1161 } 1162 1163 bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const { 1164 if (fType != b.fType || 1165 fCanvasTransform != b.fCanvasTransform || 1166 fShaderTransform != b.fShaderTransform || 1167 fBBox != b.fBBox) { 1168 return false; 1169 } 1170 1171 if (fType == SkShader::kNone_GradientType) { 1172 if (fBitmapKey != b.fBitmapKey || 1173 fBitmapKey.fID == 0 || 1174 fImageTileModes[0] != b.fImageTileModes[0] || 1175 fImageTileModes[1] != b.fImageTileModes[1]) { 1176 return false; 1177 } 1178 } else { 1179 if (fInfo.fColorCount != b.fInfo.fColorCount || 1180 memcmp(fInfo.fColors, b.fInfo.fColors, 1181 sizeof(SkColor) * fInfo.fColorCount) != 0 || 1182 memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets, 1183 sizeof(SkScalar) * fInfo.fColorCount) != 0 || 1184 fInfo.fPoint[0] != b.fInfo.fPoint[0] || 1185 fInfo.fTileMode != b.fInfo.fTileMode) { 1186 return false; 1187 } 1188 1189 switch (fType) { 1190 case SkShader::kLinear_GradientType: 1191 if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) { 1192 return false; 1193 } 1194 break; 1195 case SkShader::kRadial_GradientType: 1196 if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) { 1197 return false; 1198 } 1199 break; 1200 case SkShader::kConical_GradientType: 1201 if (fInfo.fPoint[1] != b.fInfo.fPoint[1] || 1202 fInfo.fRadius[0] != b.fInfo.fRadius[0] || 1203 fInfo.fRadius[1] != b.fInfo.fRadius[1]) { 1204 return false; 1205 } 1206 break; 1207 case SkShader::kSweep_GradientType: 1208 case SkShader::kNone_GradientType: 1209 case SkShader::kColor_GradientType: 1210 break; 1211 } 1212 } 1213 return true; 1214 } 1215 1216 SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform, 1217 const SkIRect& bbox, SkScalar rasterScale, 1218 SkBitmap* imageDst) 1219 : fType(SkShader::kNone_GradientType) 1220 , fInfo{0, nullptr, nullptr, {{0.0f, 0.0f}, {0.0f, 0.0f}}, 1221 {0.0f, 0.0f}, SkShader::kClamp_TileMode, 0} 1222 , fCanvasTransform(canvasTransform) 1223 , fShaderTransform{SkMatrix::I()} 1224 , fBBox(bbox) 1225 , fBitmapKey{{0, 0, 0, 0}, 0} 1226 , fImageTileModes{SkShader::kClamp_TileMode, 1227 SkShader::kClamp_TileMode} { 1228 SkASSERT(imageDst); 1229 fInfo.fColorCount = 0; 1230 fInfo.fColors = nullptr; 1231 fInfo.fColorOffsets = nullptr; 1232 fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode; 1233 fType = shader->asAGradient(&fInfo); 1234 1235 if (fType != SkShader::kNone_GradientType) { 1236 fBitmapKey = SkBitmapKey{{0, 0, 0, 0}, 0}; 1237 fShaderTransform = shader->getLocalMatrix(); 1238 this->allocateGradientInfoStorage(); 1239 shader->asAGradient(&fInfo); 1240 return; 1241 } 1242 if (SkImage* skimg = shader->isAImage(&fShaderTransform, fImageTileModes)) { 1243 // TODO(halcanary): delay converting to bitmap. 1244 if (skimg->asLegacyBitmap(imageDst, SkImage::kRO_LegacyBitmapMode)) { 1245 fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()}; 1246 return; 1247 } 1248 } 1249 fShaderTransform = shader->getLocalMatrix(); 1250 // Generic fallback for unsupported shaders: 1251 // * allocate a bbox-sized bitmap 1252 // * shade the whole area 1253 // * use the result as a bitmap shader 1254 1255 // bbox is in device space. While that's exactly what we 1256 // want for sizing our bitmap, we need to map it into 1257 // shader space for adjustments (to match 1258 // MakeImageShader's behavior). 1259 SkRect shaderRect = SkRect::Make(bbox); 1260 if (!inverse_transform_bbox(canvasTransform, &shaderRect)) { 1261 imageDst->reset(); 1262 return; 1263 } 1264 1265 // Clamp the bitmap size to about 1M pixels 1266 static const SkScalar kMaxBitmapArea = 1024 * 1024; 1267 SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height(); 1268 if (bitmapArea > kMaxBitmapArea) { 1269 rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea); 1270 } 1271 1272 SkISize size = SkISize::Make(SkScalarRoundToInt(rasterScale * bbox.width()), 1273 SkScalarRoundToInt(rasterScale * bbox.height())); 1274 SkSize scale = SkSize::Make(SkIntToScalar(size.width()) / shaderRect.width(), 1275 SkIntToScalar(size.height()) / shaderRect.height()); 1276 1277 imageDst->allocN32Pixels(size.width(), size.height()); 1278 imageDst->eraseColor(SK_ColorTRANSPARENT); 1279 1280 SkPaint p; 1281 p.setShader(sk_ref_sp(shader)); 1282 1283 SkCanvas canvas(*imageDst); 1284 canvas.scale(scale.width(), scale.height()); 1285 canvas.translate(-shaderRect.x(), -shaderRect.y()); 1286 canvas.drawPaint(p); 1287 1288 fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y()); 1289 fShaderTransform.preScale(1 / scale.width(), 1 / scale.height()); 1290 fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()}; 1291 } 1292 1293 SkPDFShader::State::State(const SkPDFShader::State& other) 1294 : fType(other.fType), 1295 fCanvasTransform(other.fCanvasTransform), 1296 fShaderTransform(other.fShaderTransform), 1297 fBBox(other.fBBox) 1298 { 1299 // Only gradients supported for now, since that is all that is used. 1300 // If needed, image state copy constructor can be added here later. 1301 SkASSERT(fType != SkShader::kNone_GradientType); 1302 1303 if (fType != SkShader::kNone_GradientType) { 1304 fInfo = other.fInfo; 1305 1306 this->allocateGradientInfoStorage(); 1307 for (int i = 0; i < fInfo.fColorCount; i++) { 1308 fInfo.fColors[i] = other.fInfo.fColors[i]; 1309 fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i]; 1310 } 1311 } 1312 } 1313 1314 /** 1315 * Create a copy of this gradient state with alpha assigned to RGB luminousity. 1316 * Only valid for gradient states. 1317 */ 1318 SkPDFShader::State SkPDFShader::State::MakeAlphaToLuminosityState() const { 1319 SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0})); 1320 SkASSERT(fType != SkShader::kNone_GradientType); 1321 1322 SkPDFShader::State newState(*this); 1323 1324 for (int i = 0; i < fInfo.fColorCount; i++) { 1325 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); 1326 newState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); 1327 } 1328 1329 return newState; 1330 } 1331 1332 /** 1333 * Create a copy of this gradient state with alpha set to fully opaque 1334 * Only valid for gradient states. 1335 */ 1336 SkPDFShader::State SkPDFShader::State::MakeOpaqueState() const { 1337 SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0})); 1338 SkASSERT(fType != SkShader::kNone_GradientType); 1339 1340 SkPDFShader::State newState(*this); 1341 for (int i = 0; i < fInfo.fColorCount; i++) { 1342 newState.fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i], 1343 SK_AlphaOPAQUE); 1344 } 1345 1346 return newState; 1347 } 1348 1349 /** 1350 * Returns true if state is a gradient and the gradient has alpha. 1351 */ 1352 bool SkPDFShader::State::GradientHasAlpha() const { 1353 if (fType == SkShader::kNone_GradientType) { 1354 return false; 1355 } 1356 1357 for (int i = 0; i < fInfo.fColorCount; i++) { 1358 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); 1359 if (alpha != SK_AlphaOPAQUE) { 1360 return true; 1361 } 1362 } 1363 return false; 1364 } 1365 1366 void SkPDFShader::State::allocateGradientInfoStorage() { 1367 fColors.reset(new SkColor[fInfo.fColorCount]); 1368 fStops.reset(new SkScalar[fInfo.fColorCount]); 1369 fInfo.fColors = fColors.get(); 1370 fInfo.fColorOffsets = fStops.get(); 1371 } 1372