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