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