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 function->writeText("{exch atan 360 div\n");
623 const SkScalar bias = info.fPoint[1].y();
624 if (bias != 0.0f) {
625 SkPDFUtils::AppendScalar(bias, function);
626 function->writeText(" add\n");
627 }
628 const SkScalar scale = info.fPoint[1].x();
629 if (scale != 1.0f) {
630 SkPDFUtils::AppendScalar(scale, function);
631 function->writeText(" mul\n");
632 }
633 tileModeCode((SkTileMode)info.fTileMode, function);
634 gradient_function_code(info, function);
635 function->writeText("}");
636 }
637
638
639 // catch cases where the inner just touches the outer circle
640 // and make the inner circle just inside the outer one to match raster
FixUpRadius(const SkPoint & p1,SkScalar & r1,const SkPoint & p2,SkScalar & r2)641 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
642 // detect touching circles
643 SkScalar distance = SkPoint::Distance(p1, p2);
644 SkScalar subtractRadii = fabs(r1 - r2);
645 if (fabs(distance - subtractRadii) < 0.002f) {
646 if (r1 > r2) {
647 r1 += 0.002f;
648 } else {
649 r2 += 0.002f;
650 }
651 }
652 }
653
654 // Finds affine and persp such that in = affine * persp.
655 // but it returns the inverse of perspective matrix.
split_perspective(const SkMatrix in,SkMatrix * affine,SkMatrix * perspectiveInverse)656 static bool split_perspective(const SkMatrix in, SkMatrix* affine,
657 SkMatrix* perspectiveInverse) {
658 const SkScalar p2 = in[SkMatrix::kMPersp2];
659
660 if (SkScalarNearlyZero(p2)) {
661 return false;
662 }
663
664 const SkScalar zero = SkIntToScalar(0);
665 const SkScalar one = SkIntToScalar(1);
666
667 const SkScalar sx = in[SkMatrix::kMScaleX];
668 const SkScalar kx = in[SkMatrix::kMSkewX];
669 const SkScalar tx = in[SkMatrix::kMTransX];
670 const SkScalar ky = in[SkMatrix::kMSkewY];
671 const SkScalar sy = in[SkMatrix::kMScaleY];
672 const SkScalar ty = in[SkMatrix::kMTransY];
673 const SkScalar p0 = in[SkMatrix::kMPersp0];
674 const SkScalar p1 = in[SkMatrix::kMPersp1];
675
676 // Perspective matrix would be:
677 // 1 0 0
678 // 0 1 0
679 // p0 p1 p2
680 // But we need the inverse of persp.
681 perspectiveInverse->setAll(one, zero, zero,
682 zero, one, zero,
683 -p0/p2, -p1/p2, 1/p2);
684
685 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2,
686 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2,
687 zero, zero, one);
688
689 return true;
690 }
691
make_ps_function(std::unique_ptr<SkStreamAsset> psCode,std::unique_ptr<SkPDFArray> domain,std::unique_ptr<SkPDFObject> range,SkPDFDocument * doc)692 static SkPDFIndirectReference make_ps_function(std::unique_ptr<SkStreamAsset> psCode,
693 std::unique_ptr<SkPDFArray> domain,
694 std::unique_ptr<SkPDFObject> range,
695 SkPDFDocument* doc) {
696 std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
697 dict->insertInt("FunctionType", 4);
698 dict->insertObject("Domain", std::move(domain));
699 dict->insertObject("Range", std::move(range));
700 return SkPDFStreamOut(std::move(dict), std::move(psCode), doc);
701 }
702
make_function_shader(SkPDFDocument * doc,const SkPDFGradientShader::Key & state)703 static SkPDFIndirectReference make_function_shader(SkPDFDocument* doc,
704 const SkPDFGradientShader::Key& state) {
705 SkPoint transformPoints[2];
706 const SkShaderBase::GradientInfo& info = state.fInfo;
707 SkMatrix finalMatrix = state.fCanvasTransform;
708 finalMatrix.preConcat(state.fShaderTransform);
709
710 bool doStitchFunctions = (state.fType == SkShaderBase::GradientType::kLinear ||
711 state.fType == SkShaderBase::GradientType::kRadial ||
712 state.fType == SkShaderBase::GradientType::kConical) &&
713 (info.fTileMode == SkTileMode::kClamp ||
714 info.fTileMode == SkTileMode::kDecal) &&
715 !finalMatrix.hasPerspective();
716
717 enum class ShadingType : int32_t {
718 Function = 1,
719 Axial = 2,
720 Radial = 3,
721 FreeFormGouraudTriangleMesh = 4,
722 LatticeFormGouraudTriangleMesh = 5,
723 CoonsPatchMesh = 6,
724 TensorProductPatchMesh = 7,
725 } shadingType;
726
727 auto pdfShader = SkPDFMakeDict();
728 if (doStitchFunctions) {
729 pdfShader->insertObject("Function", gradientStitchCode(info));
730
731 if (info.fTileMode == SkTileMode::kClamp) {
732 auto extend = SkPDFMakeArray();
733 extend->reserve(2);
734 extend->appendBool(true);
735 extend->appendBool(true);
736 pdfShader->insertObject("Extend", std::move(extend));
737 }
738
739 std::unique_ptr<SkPDFArray> coords;
740 switch (state.fType) {
741 case SkShaderBase::GradientType::kLinear: {
742 shadingType = ShadingType::Axial;
743 const SkPoint& pt1 = info.fPoint[0];
744 const SkPoint& pt2 = info.fPoint[1];
745 coords = SkPDFMakeArray(pt1.x(), pt1.y(),
746 pt2.x(), pt2.y());
747 } break;
748 case SkShaderBase::GradientType::kRadial: {
749 shadingType = ShadingType::Radial;
750 const SkPoint& pt1 = info.fPoint[0];
751 coords = SkPDFMakeArray(pt1.x(), pt1.y(), 0,
752 pt1.x(), pt1.y(), info.fRadius[0]);
753 } break;
754 case SkShaderBase::GradientType::kConical: {
755 shadingType = ShadingType::Radial;
756 SkScalar r1 = info.fRadius[0];
757 SkScalar r2 = info.fRadius[1];
758 SkPoint pt1 = info.fPoint[0];
759 SkPoint pt2 = info.fPoint[1];
760 FixUpRadius(pt1, r1, pt2, r2);
761
762 coords = SkPDFMakeArray(pt1.x(), pt1.y(), r1,
763 pt2.x(), pt2.y(), r2);
764 break;
765 }
766 case SkShaderBase::GradientType::kSweep:
767 case SkShaderBase::GradientType::kNone:
768 default:
769 SkASSERT(false);
770 return SkPDFIndirectReference();
771 }
772 pdfShader->insertObject("Coords", std::move(coords));
773 } else {
774 shadingType = ShadingType::Function;
775
776 // Transform the coordinate space for the type of gradient.
777 transformPoints[0] = info.fPoint[0];
778 transformPoints[1] = info.fPoint[1];
779 switch (state.fType) {
780 case SkShaderBase::GradientType::kLinear:
781 break;
782 case SkShaderBase::GradientType::kRadial:
783 transformPoints[1] = transformPoints[0];
784 transformPoints[1].fX += info.fRadius[0];
785 break;
786 case SkShaderBase::GradientType::kConical: {
787 transformPoints[1] = transformPoints[0];
788 transformPoints[1].fX += SK_Scalar1;
789 break;
790 }
791 case SkShaderBase::GradientType::kSweep:
792 transformPoints[1] = transformPoints[0];
793 transformPoints[1].fX += SK_Scalar1;
794 break;
795 case SkShaderBase::GradientType::kNone:
796 default:
797 return SkPDFIndirectReference();
798 }
799
800 // Move any scaling (assuming a unit gradient) or translation
801 // (and rotation for linear gradient), of the final gradient from
802 // info.fPoints to the matrix (updating bbox appropriately). Now
803 // the gradient can be drawn on on the unit segment.
804 SkMatrix mapperMatrix;
805 unit_to_points_matrix(transformPoints, &mapperMatrix);
806
807 finalMatrix.preConcat(mapperMatrix);
808
809 // Preserves as much as possible in the final matrix, and only removes
810 // the perspective. The inverse of the perspective is stored in
811 // perspectiveInverseOnly matrix and has 3 useful numbers
812 // (p0, p1, p2), while everything else is either 0 or 1.
813 // In this way the shader will handle it eficiently, with minimal code.
814 SkMatrix perspectiveInverseOnly = SkMatrix::I();
815 if (finalMatrix.hasPerspective()) {
816 if (!split_perspective(finalMatrix,
817 &finalMatrix, &perspectiveInverseOnly)) {
818 return SkPDFIndirectReference();
819 }
820 }
821
822 SkRect bbox;
823 bbox.set(state.fBBox);
824 if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) {
825 return SkPDFIndirectReference();
826 }
827
828 SkDynamicMemoryWStream functionCode;
829 switch (state.fType) {
830 case SkShaderBase::GradientType::kLinear:
831 linearCode(info, perspectiveInverseOnly, &functionCode);
832 break;
833 case SkShaderBase::GradientType::kRadial:
834 radialCode(info, perspectiveInverseOnly, &functionCode);
835 break;
836 case SkShaderBase::GradientType::kConical: {
837 // The two point radial gradient further references state.fInfo
838 // in translating from x, y coordinates to the t parameter. So, we have
839 // to transform the points and radii according to the calculated matrix.
840 SkShaderBase::GradientInfo infoCopy = info;
841 SkMatrix inverseMapperMatrix;
842 if (!mapperMatrix.invert(&inverseMapperMatrix)) {
843 return SkPDFIndirectReference();
844 }
845 inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2);
846 infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]);
847 infoCopy.fRadius[1] = inverseMapperMatrix.mapRadius(info.fRadius[1]);
848 twoPointConicalCode(infoCopy, perspectiveInverseOnly, &functionCode);
849 } break;
850 case SkShaderBase::GradientType::kSweep:
851 sweepCode(info, perspectiveInverseOnly, &functionCode);
852 break;
853 default:
854 SkASSERT(false);
855 }
856 pdfShader->insertObject(
857 "Domain", SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom()));
858
859 auto domain = SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom());
860 std::unique_ptr<SkPDFArray> rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1);
861 pdfShader->insertRef("Function",
862 make_ps_function(functionCode.detachAsStream(), std::move(domain),
863 std::move(rangeObject), doc));
864 }
865
866 pdfShader->insertInt("ShadingType", SkToS32(shadingType));
867 pdfShader->insertName("ColorSpace", "DeviceRGB");
868
869 SkPDFDict pdfFunctionShader("Pattern");
870 pdfFunctionShader.insertInt("PatternType", 2);
871 pdfFunctionShader.insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix));
872 pdfFunctionShader.insertObject("Shading", std::move(pdfShader));
873 return doc->emit(pdfFunctionShader);
874 }
875
876 static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
877 SkPDFGradientShader::Key key,
878 bool keyHasAlpha);
879
get_gradient_resource_dict(SkPDFIndirectReference functionShader,SkPDFIndirectReference gState)880 static std::unique_ptr<SkPDFDict> get_gradient_resource_dict(SkPDFIndirectReference functionShader,
881 SkPDFIndirectReference gState) {
882 std::vector<SkPDFIndirectReference> patternShaders;
883 if (functionShader != SkPDFIndirectReference()) {
884 patternShaders.push_back(functionShader);
885 }
886 std::vector<SkPDFIndirectReference> graphicStates;
887 if (gState != SkPDFIndirectReference()) {
888 graphicStates.push_back(gState);
889 }
890 return SkPDFMakeResourceDict(std::move(graphicStates),
891 std::move(patternShaders),
892 std::vector<SkPDFIndirectReference>(),
893 std::vector<SkPDFIndirectReference>());
894 }
895
896 // Creates a content stream which fills the pattern P0 across bounds.
897 // @param gsIndex A graphics state resource index to apply, or <0 if no
898 // graphics state to apply.
create_pattern_fill_content(int gsIndex,int patternIndex,SkRect & bounds)899 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex,
900 int patternIndex,
901 SkRect& bounds) {
902 SkDynamicMemoryWStream content;
903 if (gsIndex >= 0) {
904 SkPDFUtils::ApplyGraphicState(gsIndex, &content);
905 }
906 SkPDFUtils::ApplyPattern(patternIndex, &content);
907 SkPDFUtils::AppendRectangle(bounds, &content);
908 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPathFillType::kEvenOdd, &content);
909 return content.detachAsStream();
910 }
911
gradient_has_alpha(const SkPDFGradientShader::Key & key)912 static bool gradient_has_alpha(const SkPDFGradientShader::Key& key) {
913 SkASSERT(key.fType != SkShaderBase::GradientType::kNone);
914 for (int i = 0; i < key.fInfo.fColorCount; i++) {
915 if ((SkAlpha)SkColorGetA(key.fInfo.fColors[i]) != SK_AlphaOPAQUE) {
916 return true;
917 }
918 }
919 return false;
920 }
921
922 // warning: does not set fHash on new key. (Both callers need to change fields.)
clone_key(const SkPDFGradientShader::Key & k)923 static SkPDFGradientShader::Key clone_key(const SkPDFGradientShader::Key& k) {
924 SkPDFGradientShader::Key clone = {
925 k.fType,
926 k.fInfo, // change pointers later.
927 std::unique_ptr<SkColor[]>(new SkColor[k.fInfo.fColorCount]),
928 std::unique_ptr<SkScalar[]>(new SkScalar[k.fInfo.fColorCount]),
929 k.fCanvasTransform,
930 k.fShaderTransform,
931 k.fBBox, 0};
932 clone.fInfo.fColors = clone.fColors.get();
933 clone.fInfo.fColorOffsets = clone.fStops.get();
934 for (int i = 0; i < clone.fInfo.fColorCount; i++) {
935 clone.fInfo.fColorOffsets[i] = k.fInfo.fColorOffsets[i];
936 clone.fInfo.fColors[i] = k.fInfo.fColors[i];
937 }
938 return clone;
939 }
940
create_smask_graphic_state(SkPDFDocument * doc,const SkPDFGradientShader::Key & state)941 static SkPDFIndirectReference create_smask_graphic_state(SkPDFDocument* doc,
942 const SkPDFGradientShader::Key& state) {
943 SkASSERT(state.fType != SkShaderBase::GradientType::kNone);
944 SkPDFGradientShader::Key luminosityState = clone_key(state);
945 for (int i = 0; i < luminosityState.fInfo.fColorCount; i++) {
946 SkAlpha alpha = SkColorGetA(luminosityState.fInfo.fColors[i]);
947 luminosityState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
948 }
949 luminosityState.fHash = hash(luminosityState);
950
951 SkASSERT(!gradient_has_alpha(luminosityState));
952 SkPDFIndirectReference luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false);
953 std::unique_ptr<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader,
954 SkPDFIndirectReference());
955 SkRect bbox = SkRect::Make(state.fBBox);
956 SkPDFIndirectReference alphaMask =
957 SkPDFMakeFormXObject(doc,
958 create_pattern_fill_content(-1, luminosityShader.fValue, bbox),
959 SkPDFUtils::RectToArray(bbox),
960 std::move(resources),
961 SkMatrix::I(),
962 "DeviceRGB");
963 return SkPDFGraphicState::GetSMaskGraphicState(
964 alphaMask, false, SkPDFGraphicState::kLuminosity_SMaskMode, doc);
965 }
966
make_alpha_function_shader(SkPDFDocument * doc,const SkPDFGradientShader::Key & state)967 static SkPDFIndirectReference make_alpha_function_shader(SkPDFDocument* doc,
968 const SkPDFGradientShader::Key& state) {
969 SkASSERT(state.fType != SkShaderBase::GradientType::kNone);
970 SkPDFGradientShader::Key opaqueState = clone_key(state);
971 for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) {
972 opaqueState.fInfo.fColors[i] = SkColorSetA(opaqueState.fInfo.fColors[i], SK_AlphaOPAQUE);
973 }
974 opaqueState.fHash = hash(opaqueState);
975
976 SkASSERT(!gradient_has_alpha(opaqueState));
977 SkRect bbox = SkRect::Make(state.fBBox);
978 SkPDFIndirectReference colorShader = find_pdf_shader(doc, std::move(opaqueState), false);
979 if (!colorShader) {
980 return SkPDFIndirectReference();
981 }
982 // Create resource dict with alpha graphics state as G0 and
983 // pattern shader as P0, then write content stream.
984 SkPDFIndirectReference alphaGsRef = create_smask_graphic_state(doc, state);
985
986 std::unique_ptr<SkPDFDict> resourceDict = get_gradient_resource_dict(colorShader, alphaGsRef);
987
988 std::unique_ptr<SkStreamAsset> colorStream =
989 create_pattern_fill_content(alphaGsRef.fValue, colorShader.fValue, bbox);
990 std::unique_ptr<SkPDFDict> alphaFunctionShader = SkPDFMakeDict();
991 SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader.get(), bbox,
992 std::move(resourceDict), SkMatrix::I());
993 return SkPDFStreamOut(std::move(alphaFunctionShader), std::move(colorStream), doc);
994 }
995
make_key(const SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & bbox)996 static SkPDFGradientShader::Key make_key(const SkShader* shader,
997 const SkMatrix& canvasTransform,
998 const SkIRect& bbox) {
999 SkPDFGradientShader::Key key = {
1000 SkShaderBase::GradientType::kNone,
1001 {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkTileMode::kClamp, 0},
1002 nullptr,
1003 nullptr,
1004 canvasTransform,
1005 SkPDFUtils::GetShaderLocalMatrix(shader),
1006 bbox, 0};
1007 key.fType = as_SB(shader)->asGradient(&key.fInfo);
1008 SkASSERT(SkShaderBase::GradientType::kNone != key.fType);
1009 SkASSERT(key.fInfo.fColorCount > 0);
1010 key.fColors.reset(new SkColor[key.fInfo.fColorCount]);
1011 key.fStops.reset(new SkScalar[key.fInfo.fColorCount]);
1012 key.fInfo.fColors = key.fColors.get();
1013 key.fInfo.fColorOffsets = key.fStops.get();
1014 as_SB(shader)->asGradient(&key.fInfo);
1015 key.fHash = hash(key);
1016 return key;
1017 }
1018
find_pdf_shader(SkPDFDocument * doc,SkPDFGradientShader::Key key,bool keyHasAlpha)1019 static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
1020 SkPDFGradientShader::Key key,
1021 bool keyHasAlpha) {
1022 SkASSERT(gradient_has_alpha(key) == keyHasAlpha);
1023 auto& gradientPatternMap = doc->fGradientPatternMap;
1024 if (SkPDFIndirectReference* ptr = gradientPatternMap.find(key)) {
1025 return *ptr;
1026 }
1027 SkPDFIndirectReference pdfShader;
1028 if (keyHasAlpha) {
1029 pdfShader = make_alpha_function_shader(doc, key);
1030 } else {
1031 pdfShader = make_function_shader(doc, key);
1032 }
1033 gradientPatternMap.set(std::move(key), pdfShader);
1034 return pdfShader;
1035 }
1036
Make(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & bbox)1037 SkPDFIndirectReference SkPDFGradientShader::Make(SkPDFDocument* doc,
1038 SkShader* shader,
1039 const SkMatrix& canvasTransform,
1040 const SkIRect& bbox) {
1041 SkASSERT(shader);
1042 SkASSERT(as_SB(shader)->asGradient() != SkShaderBase::GradientType::kNone);
1043 SkPDFGradientShader::Key key = make_key(shader, canvasTransform, bbox);
1044 bool alpha = gradient_has_alpha(key);
1045 return find_pdf_shader(doc, std::move(key), alpha);
1046 }
1047