xref: /aosp_15_r20/external/skia/src/pdf/SkPDFGradientShader.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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