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