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