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