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