Line data Source code
1 : /*
2 : * Copyright 2011 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 :
9 : #include "SkPDFShader.h"
10 :
11 : #include "SkData.h"
12 : #include "SkPDFCanon.h"
13 : #include "SkPDFDevice.h"
14 : #include "SkPDFDocument.h"
15 : #include "SkPDFFormXObject.h"
16 : #include "SkPDFGraphicState.h"
17 : #include "SkPDFResourceDict.h"
18 : #include "SkPDFUtils.h"
19 : #include "SkScalar.h"
20 : #include "SkStream.h"
21 : #include "SkTemplates.h"
22 :
23 0 : static bool inverse_transform_bbox(const SkMatrix& matrix, SkRect* bbox) {
24 : SkMatrix inverse;
25 0 : if (!matrix.invert(&inverse)) {
26 0 : return false;
27 : }
28 0 : inverse.mapRect(bbox);
29 0 : return true;
30 : }
31 :
32 0 : static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) {
33 0 : SkVector vec = pts[1] - pts[0];
34 0 : SkScalar mag = vec.length();
35 0 : SkScalar inv = mag ? SkScalarInvert(mag) : 0;
36 :
37 0 : vec.scale(inv);
38 0 : matrix->setSinCos(vec.fY, vec.fX);
39 0 : matrix->preScale(mag, mag);
40 0 : matrix->postTranslate(pts[0].fX, pts[0].fY);
41 0 : }
42 :
43 : static const int kColorComponents = 3;
44 : typedef uint8_t ColorTuple[kColorComponents];
45 :
46 : /* Assumes t + startOffset is on the stack and does a linear interpolation on t
47 : between startOffset and endOffset from prevColor to curColor (for each color
48 : component), leaving the result in component order on the stack. It assumes
49 : there are always 3 components per color.
50 : @param range endOffset - startOffset
51 : @param curColor[components] The current color components.
52 : @param prevColor[components] The previous color components.
53 : @param result The result ps function.
54 : */
55 0 : static void interpolateColorCode(SkScalar range, const ColorTuple& curColor,
56 : const ColorTuple& prevColor,
57 : SkDynamicMemoryWStream* result) {
58 0 : SkASSERT(range != SkIntToScalar(0));
59 :
60 : // Figure out how to scale each color component.
61 : SkScalar multiplier[kColorComponents];
62 0 : for (int i = 0; i < kColorComponents; i++) {
63 : static const SkScalar kColorScale = SkScalarInvert(255);
64 0 : multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range;
65 : }
66 :
67 : // Calculate when we no longer need to keep a copy of the input parameter t.
68 : // If the last component to use t is i, then dupInput[0..i - 1] = true
69 : // and dupInput[i .. components] = false.
70 : bool dupInput[kColorComponents];
71 0 : dupInput[kColorComponents - 1] = false;
72 0 : for (int i = kColorComponents - 2; i >= 0; i--) {
73 0 : dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
74 : }
75 :
76 0 : if (!dupInput[0] && multiplier[0] == 0) {
77 0 : result->writeText("pop ");
78 : }
79 :
80 0 : for (int i = 0; i < kColorComponents; i++) {
81 : // If the next components needs t and this component will consume a
82 : // copy, make another copy.
83 0 : if (dupInput[i] && multiplier[i] != 0) {
84 0 : result->writeText("dup ");
85 : }
86 :
87 0 : if (multiplier[i] == 0) {
88 0 : SkPDFUtils::AppendColorComponent(prevColor[i], result);
89 0 : result->writeText(" ");
90 : } else {
91 0 : if (multiplier[i] != 1) {
92 0 : SkPDFUtils::AppendScalar(multiplier[i], result);
93 0 : result->writeText(" mul ");
94 : }
95 0 : if (prevColor[i] != 0) {
96 0 : SkPDFUtils::AppendColorComponent(prevColor[i], result);
97 0 : result->writeText(" add ");
98 : }
99 : }
100 :
101 0 : if (dupInput[i]) {
102 0 : result->writeText("exch\n");
103 : }
104 : }
105 0 : }
106 :
107 : /* Generate Type 4 function code to map t=[0,1) to the passed gradient,
108 : clamping at the edges of the range. The generated code will be of the form:
109 : if (t < 0) {
110 : return colorData[0][r,g,b];
111 : } else {
112 : if (t < info.fColorOffsets[1]) {
113 : return linearinterpolation(colorData[0][r,g,b],
114 : colorData[1][r,g,b]);
115 : } else {
116 : if (t < info.fColorOffsets[2]) {
117 : return linearinterpolation(colorData[1][r,g,b],
118 : colorData[2][r,g,b]);
119 : } else {
120 :
121 : ... } else {
122 : return colorData[info.fColorCount - 1][r,g,b];
123 : }
124 : ...
125 : }
126 : }
127 : */
128 0 : static void gradientFunctionCode(const SkShader::GradientInfo& info,
129 : SkDynamicMemoryWStream* result) {
130 : /* We want to linearly interpolate from the previous color to the next.
131 : Scale the colors from 0..255 to 0..1 and determine the multipliers
132 : for interpolation.
133 : C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}.
134 : */
135 :
136 0 : SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
137 0 : ColorTuple *colorData = colorDataAlloc.get();
138 0 : for (int i = 0; i < info.fColorCount; i++) {
139 0 : colorData[i][0] = SkColorGetR(info.fColors[i]);
140 0 : colorData[i][1] = SkColorGetG(info.fColors[i]);
141 0 : colorData[i][2] = SkColorGetB(info.fColors[i]);
142 : }
143 :
144 : // Clamp the initial color.
145 0 : result->writeText("dup 0 le {pop ");
146 0 : SkPDFUtils::AppendColorComponent(colorData[0][0], result);
147 0 : result->writeText(" ");
148 0 : SkPDFUtils::AppendColorComponent(colorData[0][1], result);
149 0 : result->writeText(" ");
150 0 : SkPDFUtils::AppendColorComponent(colorData[0][2], result);
151 0 : result->writeText(" }\n");
152 :
153 : // The gradient colors.
154 0 : int gradients = 0;
155 0 : for (int i = 1 ; i < info.fColorCount; i++) {
156 0 : if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) {
157 0 : continue;
158 : }
159 0 : gradients++;
160 :
161 0 : result->writeText("{dup ");
162 0 : SkPDFUtils::AppendScalar(info.fColorOffsets[i], result);
163 0 : result->writeText(" le {");
164 0 : if (info.fColorOffsets[i - 1] != 0) {
165 0 : SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result);
166 0 : result->writeText(" sub\n");
167 : }
168 :
169 0 : interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
170 0 : colorData[i], colorData[i - 1], result);
171 0 : result->writeText("}\n");
172 : }
173 :
174 : // Clamp the final color.
175 0 : result->writeText("{pop ");
176 0 : SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result);
177 0 : result->writeText(" ");
178 0 : SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result);
179 0 : result->writeText(" ");
180 0 : SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result);
181 :
182 0 : for (int i = 0 ; i < gradients + 1; i++) {
183 0 : result->writeText("} ifelse\n");
184 : }
185 0 : }
186 :
187 0 : static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
188 : const ColorTuple& color2) {
189 0 : auto retval = sk_make_sp<SkPDFDict>();
190 :
191 0 : auto c0 = sk_make_sp<SkPDFArray>();
192 0 : c0->appendColorComponent(color1[0]);
193 0 : c0->appendColorComponent(color1[1]);
194 0 : c0->appendColorComponent(color1[2]);
195 0 : retval->insertObject("C0", std::move(c0));
196 :
197 0 : auto c1 = sk_make_sp<SkPDFArray>();
198 0 : c1->appendColorComponent(color2[0]);
199 0 : c1->appendColorComponent(color2[1]);
200 0 : c1->appendColorComponent(color2[2]);
201 0 : retval->insertObject("C1", std::move(c1));
202 :
203 0 : auto domain = sk_make_sp<SkPDFArray>();
204 0 : domain->appendScalar(0);
205 0 : domain->appendScalar(1.0f);
206 0 : retval->insertObject("Domain", std::move(domain));
207 :
208 0 : retval->insertInt("FunctionType", 2);
209 0 : retval->insertScalar("N", 1.0f);
210 :
211 0 : return retval;
212 : }
213 :
214 0 : static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) {
215 0 : auto retval = sk_make_sp<SkPDFDict>();
216 :
217 : // normalize color stops
218 0 : int colorCount = info.fColorCount;
219 0 : SkTDArray<SkColor> colors(info.fColors, colorCount);
220 0 : SkTDArray<SkScalar> colorOffsets(info.fColorOffsets, colorCount);
221 :
222 0 : int i = 1;
223 0 : while (i < colorCount - 1) {
224 : // ensure stops are in order
225 0 : if (colorOffsets[i - 1] > colorOffsets[i]) {
226 0 : colorOffsets[i] = colorOffsets[i - 1];
227 : }
228 :
229 : // remove points that are between 2 coincident points
230 0 : if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) {
231 0 : colorCount -= 1;
232 0 : colors.remove(i);
233 0 : colorOffsets.remove(i);
234 : } else {
235 0 : i++;
236 : }
237 : }
238 : // find coincident points and slightly move them over
239 0 : for (i = 1; i < colorCount - 1; i++) {
240 0 : if (colorOffsets[i - 1] == colorOffsets[i]) {
241 0 : colorOffsets[i] += 0.00001f;
242 : }
243 : }
244 : // check if last 2 stops coincide
245 0 : if (colorOffsets[i - 1] == colorOffsets[i]) {
246 0 : colorOffsets[i - 1] -= 0.00001f;
247 : }
248 :
249 0 : SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount);
250 0 : ColorTuple *colorData = colorDataAlloc.get();
251 0 : for (int i = 0; i < colorCount; i++) {
252 0 : colorData[i][0] = SkColorGetR(colors[i]);
253 0 : colorData[i][1] = SkColorGetG(colors[i]);
254 0 : colorData[i][2] = SkColorGetB(colors[i]);
255 : }
256 :
257 : // no need for a stitch function if there are only 2 stops.
258 0 : if (colorCount == 2)
259 0 : return createInterpolationFunction(colorData[0], colorData[1]);
260 :
261 0 : auto encode = sk_make_sp<SkPDFArray>();
262 0 : auto bounds = sk_make_sp<SkPDFArray>();
263 0 : auto functions = sk_make_sp<SkPDFArray>();
264 :
265 0 : auto domain = sk_make_sp<SkPDFArray>();
266 0 : domain->appendScalar(0);
267 0 : domain->appendScalar(1.0f);
268 0 : retval->insertObject("Domain", std::move(domain));
269 0 : retval->insertInt("FunctionType", 3);
270 :
271 0 : for (int i = 1; i < colorCount; i++) {
272 0 : if (i > 1) {
273 0 : bounds->appendScalar(colorOffsets[i-1]);
274 : }
275 :
276 0 : encode->appendScalar(0);
277 0 : encode->appendScalar(1.0f);
278 :
279 0 : functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i]));
280 : }
281 :
282 0 : retval->insertObject("Encode", std::move(encode));
283 0 : retval->insertObject("Bounds", std::move(bounds));
284 0 : retval->insertObject("Functions", std::move(functions));
285 :
286 0 : return retval;
287 : }
288 :
289 : /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
290 0 : static void tileModeCode(SkShader::TileMode mode,
291 : SkDynamicMemoryWStream* result) {
292 0 : if (mode == SkShader::kRepeat_TileMode) {
293 0 : result->writeText("dup truncate sub\n"); // Get the fractional part.
294 0 : result->writeText("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1)
295 0 : return;
296 : }
297 :
298 0 : if (mode == SkShader::kMirror_TileMode) {
299 : // Map t mod 2 into [0, 1, 1, 0].
300 : // Code Stack
301 0 : result->writeText("abs " // Map negative to positive.
302 : "dup " // t.s t.s
303 : "truncate " // t.s t
304 : "dup " // t.s t t
305 : "cvi " // t.s t T
306 : "2 mod " // t.s t (i mod 2)
307 : "1 eq " // t.s t true|false
308 : "3 1 roll " // true|false t.s t
309 : "sub " // true|false 0.s
310 : "exch " // 0.s true|false
311 0 : "{1 exch sub} if\n"); // 1 - 0.s|0.s
312 : }
313 : }
314 :
315 : /**
316 : * Returns PS function code that applies inverse perspective
317 : * to a x, y point.
318 : * The function assumes that the stack has at least two elements,
319 : * and that the top 2 elements are numeric values.
320 : * After executing this code on a PS stack, the last 2 elements are updated
321 : * while the rest of the stack is preserved intact.
322 : * inversePerspectiveMatrix is the inverse perspective matrix.
323 : */
324 0 : static void apply_perspective_to_coordinates(
325 : const SkMatrix& inversePerspectiveMatrix,
326 : SkDynamicMemoryWStream* code) {
327 0 : if (!inversePerspectiveMatrix.hasPerspective()) {
328 0 : return;
329 : }
330 :
331 : // Perspective matrix should be:
332 : // 1 0 0
333 : // 0 1 0
334 : // p0 p1 p2
335 :
336 0 : const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
337 0 : const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
338 0 : const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];
339 :
340 : // y = y / (p2 + p0 x + p1 y)
341 : // x = x / (p2 + p0 x + p1 y)
342 :
343 : // Input on stack: x y
344 0 : code->writeText(" dup "); // x y y
345 0 : SkPDFUtils::AppendScalar(p1, code); // x y y p1
346 0 : code->writeText(" mul " // x y y*p1
347 0 : " 2 index "); // x y y*p1 x
348 0 : SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0
349 0 : code->writeText(" mul "); // x y y*p1 x*p0
350 0 : SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2
351 0 : code->writeText(" add " // x y y*p1 x*p0+p2
352 : "add " // x y y*p1+x*p0+p2
353 : "3 1 roll " // y*p1+x*p0+p2 x y
354 : "2 index " // z x y y*p1+x*p0+p2
355 : "div " // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
356 : "3 1 roll " // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
357 : "exch " // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
358 : "div " // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
359 0 : "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
360 : }
361 :
362 0 : static void linearCode(const SkShader::GradientInfo& info,
363 : const SkMatrix& perspectiveRemover,
364 : SkDynamicMemoryWStream* function) {
365 0 : function->writeText("{");
366 :
367 0 : apply_perspective_to_coordinates(perspectiveRemover, function);
368 :
369 0 : function->writeText("pop\n"); // Just ditch the y value.
370 0 : tileModeCode(info.fTileMode, function);
371 0 : gradientFunctionCode(info, function);
372 0 : function->writeText("}");
373 0 : }
374 :
375 0 : static void radialCode(const SkShader::GradientInfo& info,
376 : const SkMatrix& perspectiveRemover,
377 : SkDynamicMemoryWStream* function) {
378 0 : function->writeText("{");
379 :
380 0 : apply_perspective_to_coordinates(perspectiveRemover, function);
381 :
382 : // Find the distance from the origin.
383 0 : function->writeText("dup " // x y y
384 : "mul " // x y^2
385 : "exch " // y^2 x
386 : "dup " // y^2 x x
387 : "mul " // y^2 x^2
388 : "add " // y^2+x^2
389 0 : "sqrt\n"); // sqrt(y^2+x^2)
390 :
391 0 : tileModeCode(info.fTileMode, function);
392 0 : gradientFunctionCode(info, function);
393 0 : function->writeText("}");
394 0 : }
395 :
396 : /* Conical gradient shader, based on the Canvas spec for radial gradients
397 : See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
398 : */
399 0 : static void twoPointConicalCode(const SkShader::GradientInfo& info,
400 : const SkMatrix& perspectiveRemover,
401 : SkDynamicMemoryWStream* function) {
402 0 : SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
403 0 : SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
404 0 : SkScalar r0 = info.fRadius[0];
405 0 : SkScalar dr = info.fRadius[1] - info.fRadius[0];
406 0 : SkScalar a = dx * dx + dy * dy - dr * dr;
407 :
408 : // First compute t, if the pixel falls outside the cone, then we'll end
409 : // with 'false' on the stack, otherwise we'll push 'true' with t below it
410 :
411 : // We start with a stack of (x y), copy it and then consume one copy in
412 : // order to calculate b and the other to calculate c.
413 0 : function->writeText("{");
414 :
415 0 : apply_perspective_to_coordinates(perspectiveRemover, function);
416 :
417 0 : function->writeText("2 copy ");
418 :
419 : // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
420 0 : SkPDFUtils::AppendScalar(dy, function);
421 0 : function->writeText(" mul exch ");
422 0 : SkPDFUtils::AppendScalar(dx, function);
423 0 : function->writeText(" mul add ");
424 0 : SkPDFUtils::AppendScalar(r0 * dr, function);
425 0 : function->writeText(" add -2 mul dup dup mul\n");
426 :
427 : // c = x^2 + y^2 + radius0^2
428 0 : function->writeText("4 2 roll dup mul exch dup mul add ");
429 0 : SkPDFUtils::AppendScalar(r0 * r0, function);
430 0 : function->writeText(" sub dup 4 1 roll\n");
431 :
432 : // Contents of the stack at this point: c, b, b^2, c
433 :
434 : // if a = 0, then we collapse to a simpler linear case
435 0 : if (a == 0) {
436 :
437 : // t = -c/b
438 0 : function->writeText("pop pop div neg dup ");
439 :
440 : // compute radius(t)
441 0 : SkPDFUtils::AppendScalar(dr, function);
442 0 : function->writeText(" mul ");
443 0 : SkPDFUtils::AppendScalar(r0, function);
444 0 : function->writeText(" add\n");
445 :
446 : // if r(t) < 0, then it's outside the cone
447 0 : function->writeText("0 lt {pop false} {true} ifelse\n");
448 :
449 : } else {
450 :
451 : // quadratic case: the Canvas spec wants the largest
452 : // root t for which radius(t) > 0
453 :
454 : // compute the discriminant (b^2 - 4ac)
455 0 : SkPDFUtils::AppendScalar(a * 4, function);
456 0 : function->writeText(" mul sub dup\n");
457 :
458 : // if d >= 0, proceed
459 0 : function->writeText("0 ge {\n");
460 :
461 : // an intermediate value we'll use to compute the roots:
462 : // q = -0.5 * (b +/- sqrt(d))
463 0 : function->writeText("sqrt exch dup 0 lt {exch -1 mul} if");
464 0 : function->writeText(" add -0.5 mul dup\n");
465 :
466 : // first root = q / a
467 0 : SkPDFUtils::AppendScalar(a, function);
468 0 : function->writeText(" div\n");
469 :
470 : // second root = c / q
471 0 : function->writeText("3 1 roll div\n");
472 :
473 : // put the larger root on top of the stack
474 0 : function->writeText("2 copy gt {exch} if\n");
475 :
476 : // compute radius(t) for larger root
477 0 : function->writeText("dup ");
478 0 : SkPDFUtils::AppendScalar(dr, function);
479 0 : function->writeText(" mul ");
480 0 : SkPDFUtils::AppendScalar(r0, function);
481 0 : function->writeText(" add\n");
482 :
483 : // if r(t) > 0, we have our t, pop off the smaller root and we're done
484 0 : function->writeText(" 0 gt {exch pop true}\n");
485 :
486 : // otherwise, throw out the larger one and try the smaller root
487 0 : function->writeText("{pop dup\n");
488 0 : SkPDFUtils::AppendScalar(dr, function);
489 0 : function->writeText(" mul ");
490 0 : SkPDFUtils::AppendScalar(r0, function);
491 0 : function->writeText(" add\n");
492 :
493 : // if r(t) < 0, push false, otherwise the smaller root is our t
494 0 : function->writeText("0 le {pop false} {true} ifelse\n");
495 0 : function->writeText("} ifelse\n");
496 :
497 : // d < 0, clear the stack and push false
498 0 : function->writeText("} {pop pop pop false} ifelse\n");
499 : }
500 :
501 : // if the pixel is in the cone, proceed to compute a color
502 0 : function->writeText("{");
503 0 : tileModeCode(info.fTileMode, function);
504 0 : gradientFunctionCode(info, function);
505 :
506 : // otherwise, just write black
507 0 : function->writeText("} {0 0 0} ifelse }");
508 0 : }
509 :
510 0 : static void sweepCode(const SkShader::GradientInfo& info,
511 : const SkMatrix& perspectiveRemover,
512 : SkDynamicMemoryWStream* function) {
513 0 : function->writeText("{exch atan 360 div\n");
514 0 : tileModeCode(info.fTileMode, function);
515 0 : gradientFunctionCode(info, function);
516 0 : function->writeText("}");
517 0 : }
518 :
519 0 : static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) {
520 0 : SkAutoCanvasRestore acr(canvas, true);
521 0 : canvas->concat(matrix);
522 0 : canvas->drawBitmap(bm, 0, 0);
523 0 : }
524 :
525 : ////////////////////////////////////////////////////////////////////////////////
526 :
527 : static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
528 : SkScalar dpi,
529 : const SkPDFShader::State& state);
530 : static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
531 : const SkPDFShader::State& state);
532 :
533 : static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
534 : SkScalar dpi,
535 : const SkPDFShader::State& state,
536 : SkBitmap image);
537 :
538 0 : static sk_sp<SkPDFObject> get_pdf_shader_by_state(
539 : SkPDFDocument* doc,
540 : SkScalar dpi,
541 : SkPDFShader::State state,
542 : SkBitmap image) {
543 0 : SkPDFCanon* canon = doc->canon();
544 0 : if (state.fType == SkShader::kNone_GradientType && image.isNull()) {
545 : // TODO(vandebo) This drops SKComposeShader on the floor. We could
546 : // handle compose shader by pulling things up to a layer, drawing with
547 : // the first shader, applying the xfer mode and drawing again with the
548 : // second shader, then applying the layer to the original drawing.
549 0 : return nullptr;
550 0 : } else if (state.fType == SkShader::kNone_GradientType) {
551 0 : sk_sp<SkPDFObject> shader = canon->findImageShader(state);
552 0 : if (!shader) {
553 0 : shader = make_image_shader(doc, dpi, state, std::move(image));
554 0 : canon->addImageShader(shader, std::move(state));
555 : }
556 0 : return shader;
557 0 : } else if (state.GradientHasAlpha()) {
558 0 : sk_sp<SkPDFObject> shader = canon->findAlphaShader(state);
559 0 : if (!shader) {
560 0 : shader = make_alpha_function_shader(doc, dpi, state);
561 0 : canon->addAlphaShader(shader, std::move(state));
562 : }
563 0 : return shader;
564 : } else {
565 0 : sk_sp<SkPDFObject> shader = canon->findFunctionShader(state);
566 0 : if (!shader) {
567 0 : shader = make_function_shader(canon, state);
568 0 : canon->addFunctionShader(shader, std::move(state));
569 : }
570 0 : return shader;
571 : }
572 : }
573 :
574 0 : sk_sp<SkPDFObject> SkPDFShader::GetPDFShader(SkPDFDocument* doc,
575 : SkScalar dpi,
576 : SkShader* shader,
577 : const SkMatrix& matrix,
578 : const SkIRect& surfaceBBox,
579 : SkScalar rasterScale) {
580 0 : if (surfaceBBox.isEmpty()) {
581 0 : return nullptr;
582 : }
583 0 : SkBitmap image;
584 0 : State state(shader, matrix, surfaceBBox, rasterScale, &image);
585 : return get_pdf_shader_by_state(
586 0 : doc, dpi, std::move(state), std::move(image));
587 : }
588 :
589 0 : static sk_sp<SkPDFDict> get_gradient_resource_dict(
590 : SkPDFObject* functionShader,
591 : SkPDFObject* gState) {
592 0 : SkTDArray<SkPDFObject*> patterns;
593 0 : if (functionShader) {
594 0 : patterns.push(functionShader);
595 : }
596 0 : SkTDArray<SkPDFObject*> graphicStates;
597 0 : if (gState) {
598 0 : graphicStates.push(gState);
599 : }
600 0 : return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr);
601 : }
602 :
603 0 : static void populate_tiling_pattern_dict(SkPDFDict* pattern,
604 : SkRect& bbox,
605 : sk_sp<SkPDFDict> resources,
606 : const SkMatrix& matrix) {
607 0 : const int kTiling_PatternType = 1;
608 0 : const int kColoredTilingPattern_PaintType = 1;
609 0 : const int kConstantSpacing_TilingType = 1;
610 :
611 0 : pattern->insertName("Type", "Pattern");
612 0 : pattern->insertInt("PatternType", kTiling_PatternType);
613 0 : pattern->insertInt("PaintType", kColoredTilingPattern_PaintType);
614 0 : pattern->insertInt("TilingType", kConstantSpacing_TilingType);
615 0 : pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox));
616 0 : pattern->insertScalar("XStep", bbox.width());
617 0 : pattern->insertScalar("YStep", bbox.height());
618 0 : pattern->insertObject("Resources", std::move(resources));
619 0 : if (!matrix.isIdentity()) {
620 0 : pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
621 : }
622 0 : }
623 :
624 : /**
625 : * Creates a content stream which fills the pattern P0 across bounds.
626 : * @param gsIndex A graphics state resource index to apply, or <0 if no
627 : * graphics state to apply.
628 : */
629 0 : static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(
630 : int gsIndex, SkRect& bounds) {
631 0 : SkDynamicMemoryWStream content;
632 0 : if (gsIndex >= 0) {
633 0 : SkPDFUtils::ApplyGraphicState(gsIndex, &content);
634 : }
635 0 : SkPDFUtils::ApplyPattern(0, &content);
636 0 : SkPDFUtils::AppendRectangle(bounds, &content);
637 : SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType,
638 0 : &content);
639 :
640 0 : return std::unique_ptr<SkStreamAsset>(content.detachAsStream());
641 : }
642 :
643 : /**
644 : * Creates a ExtGState with the SMask set to the luminosityShader in
645 : * luminosity mode. The shader pattern extends to the bbox.
646 : */
647 0 : static sk_sp<SkPDFObject> create_smask_graphic_state(
648 : SkPDFDocument* doc, SkScalar dpi, const SkPDFShader::State& state) {
649 : SkRect bbox;
650 0 : bbox.set(state.fBBox);
651 :
652 : sk_sp<SkPDFObject> luminosityShader(
653 0 : get_pdf_shader_by_state(doc, dpi, state.MakeAlphaToLuminosityState(),
654 0 : SkBitmap()));
655 :
656 0 : std::unique_ptr<SkStreamAsset> alphaStream(create_pattern_fill_content(-1, bbox));
657 :
658 : sk_sp<SkPDFDict> resources =
659 0 : get_gradient_resource_dict(luminosityShader.get(), nullptr);
660 :
661 : sk_sp<SkPDFObject> alphaMask =
662 0 : SkPDFMakeFormXObject(std::move(alphaStream),
663 0 : SkPDFUtils::RectToArray(bbox),
664 0 : std::move(resources),
665 : SkMatrix::I(),
666 0 : "DeviceRGB");
667 0 : return SkPDFGraphicState::GetSMaskGraphicState(
668 0 : std::move(alphaMask), false,
669 0 : SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
670 : }
671 :
672 0 : static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
673 : SkScalar dpi,
674 : const SkPDFShader::State& state) {
675 : SkRect bbox;
676 0 : bbox.set(state.fBBox);
677 :
678 0 : SkPDFShader::State opaqueState(state.MakeOpaqueState());
679 :
680 : sk_sp<SkPDFObject> colorShader(
681 0 : get_pdf_shader_by_state(doc, dpi, std::move(opaqueState), SkBitmap()));
682 0 : if (!colorShader) {
683 0 : return nullptr;
684 : }
685 :
686 : // Create resource dict with alpha graphics state as G0 and
687 : // pattern shader as P0, then write content stream.
688 0 : sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, dpi, state);
689 :
690 : sk_sp<SkPDFDict> resourceDict =
691 0 : get_gradient_resource_dict(colorShader.get(), alphaGs.get());
692 :
693 : std::unique_ptr<SkStreamAsset> colorStream(
694 0 : create_pattern_fill_content(0, bbox));
695 0 : auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream));
696 :
697 0 : populate_tiling_pattern_dict(alphaFunctionShader->dict(), bbox,
698 0 : std::move(resourceDict), SkMatrix::I());
699 0 : return alphaFunctionShader;
700 : }
701 :
702 : // Finds affine and persp such that in = affine * persp.
703 : // but it returns the inverse of perspective matrix.
704 0 : static bool split_perspective(const SkMatrix in, SkMatrix* affine,
705 : SkMatrix* perspectiveInverse) {
706 0 : const SkScalar p2 = in[SkMatrix::kMPersp2];
707 :
708 0 : if (SkScalarNearlyZero(p2)) {
709 0 : return false;
710 : }
711 :
712 0 : const SkScalar zero = SkIntToScalar(0);
713 0 : const SkScalar one = SkIntToScalar(1);
714 :
715 0 : const SkScalar sx = in[SkMatrix::kMScaleX];
716 0 : const SkScalar kx = in[SkMatrix::kMSkewX];
717 0 : const SkScalar tx = in[SkMatrix::kMTransX];
718 0 : const SkScalar ky = in[SkMatrix::kMSkewY];
719 0 : const SkScalar sy = in[SkMatrix::kMScaleY];
720 0 : const SkScalar ty = in[SkMatrix::kMTransY];
721 0 : const SkScalar p0 = in[SkMatrix::kMPersp0];
722 0 : const SkScalar p1 = in[SkMatrix::kMPersp1];
723 :
724 : // Perspective matrix would be:
725 : // 1 0 0
726 : // 0 1 0
727 : // p0 p1 p2
728 : // But we need the inverse of persp.
729 0 : perspectiveInverse->setAll(one, zero, zero,
730 : zero, one, zero,
731 0 : -p0/p2, -p1/p2, 1/p2);
732 :
733 0 : affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2,
734 0 : ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2,
735 0 : zero, zero, one);
736 :
737 0 : return true;
738 : }
739 :
740 0 : sk_sp<SkPDFArray> SkPDFShader::MakeRangeObject() {
741 0 : auto range = sk_make_sp<SkPDFArray>();
742 0 : range->reserve(6);
743 0 : range->appendInt(0);
744 0 : range->appendInt(1);
745 0 : range->appendInt(0);
746 0 : range->appendInt(1);
747 0 : range->appendInt(0);
748 0 : range->appendInt(1);
749 0 : return range;
750 : }
751 :
752 0 : static sk_sp<SkPDFStream> make_ps_function(
753 : std::unique_ptr<SkStreamAsset> psCode,
754 : sk_sp<SkPDFArray> domain,
755 : sk_sp<SkPDFObject> range) {
756 0 : auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
757 0 : result->dict()->insertInt("FunctionType", 4);
758 0 : result->dict()->insertObject("Domain", std::move(domain));
759 0 : result->dict()->insertObject("Range", std::move(range));
760 0 : return result;
761 : }
762 :
763 : // catch cases where the inner just touches the outer circle
764 : // and make the inner circle just inside the outer one to match raster
765 0 : static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
766 : // detect touching circles
767 0 : SkScalar distance = SkPoint::Distance(p1, p2);
768 0 : SkScalar subtractRadii = fabs(r1 - r2);
769 0 : if (fabs(distance - subtractRadii) < 0.002f) {
770 0 : if (r1 > r2) {
771 0 : r1 += 0.002f;
772 : } else {
773 0 : r2 += 0.002f;
774 : }
775 : }
776 0 : }
777 :
778 0 : static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
779 : const SkPDFShader::State& state) {
780 : void (*codeFunction)(const SkShader::GradientInfo& info,
781 : const SkMatrix& perspectiveRemover,
782 0 : SkDynamicMemoryWStream* function) = nullptr;
783 : SkPoint transformPoints[2];
784 0 : const SkShader::GradientInfo* info = &state.fInfo;
785 0 : SkMatrix finalMatrix = state.fCanvasTransform;
786 0 : finalMatrix.preConcat(state.fShaderTransform);
787 :
788 0 : bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType ||
789 0 : state.fType == SkShader::kRadial_GradientType ||
790 0 : state.fType == SkShader::kConical_GradientType) &&
791 0 : info->fTileMode == SkShader::kClamp_TileMode &&
792 0 : !finalMatrix.hasPerspective();
793 :
794 0 : auto domain = sk_make_sp<SkPDFArray>();
795 :
796 0 : int32_t shadingType = 1;
797 0 : auto pdfShader = sk_make_sp<SkPDFDict>();
798 : // The two point radial gradient further references
799 : // state.fInfo
800 : // in translating from x, y coordinates to the t parameter. So, we have
801 : // to transform the points and radii according to the calculated matrix.
802 0 : if (doStitchFunctions) {
803 0 : pdfShader->insertObject("Function", gradientStitchCode(*info));
804 0 : shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3;
805 :
806 0 : auto extend = sk_make_sp<SkPDFArray>();
807 0 : extend->reserve(2);
808 0 : extend->appendBool(true);
809 0 : extend->appendBool(true);
810 0 : pdfShader->insertObject("Extend", std::move(extend));
811 :
812 0 : auto coords = sk_make_sp<SkPDFArray>();
813 0 : if (state.fType == SkShader::kConical_GradientType) {
814 0 : coords->reserve(6);
815 0 : SkScalar r1 = info->fRadius[0];
816 0 : SkScalar r2 = info->fRadius[1];
817 0 : SkPoint pt1 = info->fPoint[0];
818 0 : SkPoint pt2 = info->fPoint[1];
819 0 : FixUpRadius(pt1, r1, pt2, r2);
820 :
821 0 : coords->appendScalar(pt1.fX);
822 0 : coords->appendScalar(pt1.fY);
823 0 : coords->appendScalar(r1);
824 :
825 0 : coords->appendScalar(pt2.fX);
826 0 : coords->appendScalar(pt2.fY);
827 0 : coords->appendScalar(r2);
828 0 : } else if (state.fType == SkShader::kRadial_GradientType) {
829 0 : coords->reserve(6);
830 0 : const SkPoint& pt1 = info->fPoint[0];
831 :
832 0 : coords->appendScalar(pt1.fX);
833 0 : coords->appendScalar(pt1.fY);
834 0 : coords->appendScalar(0);
835 :
836 0 : coords->appendScalar(pt1.fX);
837 0 : coords->appendScalar(pt1.fY);
838 0 : coords->appendScalar(info->fRadius[0]);
839 : } else {
840 0 : coords->reserve(4);
841 0 : const SkPoint& pt1 = info->fPoint[0];
842 0 : const SkPoint& pt2 = info->fPoint[1];
843 :
844 0 : coords->appendScalar(pt1.fX);
845 0 : coords->appendScalar(pt1.fY);
846 :
847 0 : coords->appendScalar(pt2.fX);
848 0 : coords->appendScalar(pt2.fY);
849 : }
850 :
851 0 : pdfShader->insertObject("Coords", std::move(coords));
852 : } else {
853 : // Depending on the type of the gradient, we want to transform the
854 : // coordinate space in different ways.
855 0 : transformPoints[0] = info->fPoint[0];
856 0 : transformPoints[1] = info->fPoint[1];
857 0 : switch (state.fType) {
858 : case SkShader::kLinear_GradientType:
859 0 : codeFunction = &linearCode;
860 0 : break;
861 : case SkShader::kRadial_GradientType:
862 0 : transformPoints[1] = transformPoints[0];
863 0 : transformPoints[1].fX += info->fRadius[0];
864 0 : codeFunction = &radialCode;
865 0 : break;
866 : case SkShader::kConical_GradientType: {
867 0 : transformPoints[1] = transformPoints[0];
868 0 : transformPoints[1].fX += SK_Scalar1;
869 0 : codeFunction = &twoPointConicalCode;
870 0 : break;
871 : }
872 : case SkShader::kSweep_GradientType:
873 0 : transformPoints[1] = transformPoints[0];
874 0 : transformPoints[1].fX += SK_Scalar1;
875 0 : codeFunction = &sweepCode;
876 0 : break;
877 : case SkShader::kColor_GradientType:
878 : case SkShader::kNone_GradientType:
879 : default:
880 0 : return nullptr;
881 : }
882 :
883 : // Move any scaling (assuming a unit gradient) or translation
884 : // (and rotation for linear gradient), of the final gradient from
885 : // info->fPoints to the matrix (updating bbox appropriately). Now
886 : // the gradient can be drawn on on the unit segment.
887 : SkMatrix mapperMatrix;
888 0 : unitToPointsMatrix(transformPoints, &mapperMatrix);
889 :
890 0 : finalMatrix.preConcat(mapperMatrix);
891 :
892 : // Preserves as much as posible in the final matrix, and only removes
893 : // the perspective. The inverse of the perspective is stored in
894 : // perspectiveInverseOnly matrix and has 3 useful numbers
895 : // (p0, p1, p2), while everything else is either 0 or 1.
896 : // In this way the shader will handle it eficiently, with minimal code.
897 0 : SkMatrix perspectiveInverseOnly = SkMatrix::I();
898 0 : if (finalMatrix.hasPerspective()) {
899 0 : if (!split_perspective(finalMatrix,
900 : &finalMatrix, &perspectiveInverseOnly)) {
901 0 : return nullptr;
902 : }
903 : }
904 :
905 : SkRect bbox;
906 0 : bbox.set(state.fBBox);
907 0 : if (!inverse_transform_bbox(finalMatrix, &bbox)) {
908 0 : return nullptr;
909 : }
910 0 : domain->reserve(4);
911 0 : domain->appendScalar(bbox.fLeft);
912 0 : domain->appendScalar(bbox.fRight);
913 0 : domain->appendScalar(bbox.fTop);
914 0 : domain->appendScalar(bbox.fBottom);
915 :
916 0 : SkDynamicMemoryWStream functionCode;
917 :
918 0 : if (state.fType == SkShader::kConical_GradientType) {
919 0 : SkShader::GradientInfo twoPointRadialInfo = *info;
920 : SkMatrix inverseMapperMatrix;
921 0 : if (!mapperMatrix.invert(&inverseMapperMatrix)) {
922 0 : return nullptr;
923 : }
924 0 : inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
925 0 : twoPointRadialInfo.fRadius[0] =
926 0 : inverseMapperMatrix.mapRadius(info->fRadius[0]);
927 0 : twoPointRadialInfo.fRadius[1] =
928 0 : inverseMapperMatrix.mapRadius(info->fRadius[1]);
929 0 : codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode);
930 : } else {
931 0 : codeFunction(*info, perspectiveInverseOnly, &functionCode);
932 : }
933 :
934 0 : pdfShader->insertObject("Domain", domain);
935 :
936 : // Call canon->makeRangeObject() instead of
937 : // SkPDFShader::MakeRangeObject() so that the canon can
938 : // deduplicate.
939 : std::unique_ptr<SkStreamAsset> functionStream(
940 0 : functionCode.detachAsStream());
941 0 : sk_sp<SkPDFStream> function = make_ps_function(std::move(functionStream),
942 0 : std::move(domain),
943 0 : canon->makeRangeObject());
944 0 : pdfShader->insertObjRef("Function", std::move(function));
945 : }
946 :
947 0 : pdfShader->insertInt("ShadingType", shadingType);
948 0 : pdfShader->insertName("ColorSpace", "DeviceRGB");
949 :
950 0 : auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern");
951 0 : pdfFunctionShader->insertInt("PatternType", 2);
952 0 : pdfFunctionShader->insertObject("Matrix",
953 0 : SkPDFUtils::MatrixToArray(finalMatrix));
954 0 : pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
955 :
956 0 : return pdfFunctionShader;
957 : }
958 :
959 0 : static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
960 : SkScalar dpi,
961 : const SkPDFShader::State& state,
962 : SkBitmap image) {
963 0 : SkASSERT(state.fBitmapKey ==
964 : (SkBitmapKey{image.getSubset(), image.getGenerationID()}));
965 0 : SkAutoLockPixels SkAutoLockPixels(image);
966 :
967 : // The image shader pattern cell will be drawn into a separate device
968 : // in pattern cell space (no scaling on the bitmap, though there may be
969 : // translations so that all content is in the device, coordinates > 0).
970 :
971 : // Map clip bounds to shader space to ensure the device is large enough
972 : // to handle fake clamping.
973 0 : SkMatrix finalMatrix = state.fCanvasTransform;
974 0 : finalMatrix.preConcat(state.fShaderTransform);
975 : SkRect deviceBounds;
976 0 : deviceBounds.set(state.fBBox);
977 0 : if (!inverse_transform_bbox(finalMatrix, &deviceBounds)) {
978 0 : return nullptr;
979 : }
980 :
981 : SkRect bitmapBounds;
982 0 : image.getBounds(&bitmapBounds);
983 :
984 : // For tiling modes, the bounds should be extended to include the bitmap,
985 : // otherwise the bitmap gets clipped out and the shader is empty and awful.
986 : // For clamp modes, we're only interested in the clip region, whether
987 : // or not the main bitmap is in it.
988 : SkShader::TileMode tileModes[2];
989 0 : tileModes[0] = state.fImageTileModes[0];
990 0 : tileModes[1] = state.fImageTileModes[1];
991 0 : if (tileModes[0] != SkShader::kClamp_TileMode ||
992 0 : tileModes[1] != SkShader::kClamp_TileMode) {
993 0 : deviceBounds.join(bitmapBounds);
994 : }
995 :
996 0 : SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()),
997 0 : SkScalarRoundToInt(deviceBounds.height()));
998 : sk_sp<SkPDFDevice> patternDevice(
999 0 : SkPDFDevice::CreateUnflipped(size, dpi, doc));
1000 0 : SkCanvas canvas(patternDevice.get());
1001 :
1002 : SkRect patternBBox;
1003 0 : image.getBounds(&patternBBox);
1004 :
1005 : // Translate the canvas so that the bitmap origin is at (0, 0).
1006 0 : canvas.translate(-deviceBounds.left(), -deviceBounds.top());
1007 0 : patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
1008 : // Undo the translation in the final matrix
1009 0 : finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top());
1010 :
1011 : // If the bitmap is out of bounds (i.e. clamp mode where we only see the
1012 : // stretched sides), canvas will clip this out and the extraneous data
1013 : // won't be saved to the PDF.
1014 0 : canvas.drawBitmap(image, 0, 0);
1015 :
1016 0 : SkScalar width = SkIntToScalar(image.width());
1017 0 : SkScalar height = SkIntToScalar(image.height());
1018 :
1019 : // Tiling is implied. First we handle mirroring.
1020 0 : if (tileModes[0] == SkShader::kMirror_TileMode) {
1021 : SkMatrix xMirror;
1022 0 : xMirror.setScale(-1, 1);
1023 0 : xMirror.postTranslate(2 * width, 0);
1024 0 : drawBitmapMatrix(&canvas, image, xMirror);
1025 0 : patternBBox.fRight += width;
1026 : }
1027 0 : if (tileModes[1] == SkShader::kMirror_TileMode) {
1028 : SkMatrix yMirror;
1029 0 : yMirror.setScale(SK_Scalar1, -SK_Scalar1);
1030 0 : yMirror.postTranslate(0, 2 * height);
1031 0 : drawBitmapMatrix(&canvas, image, yMirror);
1032 0 : patternBBox.fBottom += height;
1033 : }
1034 0 : if (tileModes[0] == SkShader::kMirror_TileMode &&
1035 0 : tileModes[1] == SkShader::kMirror_TileMode) {
1036 : SkMatrix mirror;
1037 0 : mirror.setScale(-1, -1);
1038 0 : mirror.postTranslate(2 * width, 2 * height);
1039 0 : drawBitmapMatrix(&canvas, image, mirror);
1040 : }
1041 :
1042 : // Then handle Clamping, which requires expanding the pattern canvas to
1043 : // cover the entire surfaceBBox.
1044 :
1045 : // If both x and y are in clamp mode, we start by filling in the corners.
1046 : // (Which are just a rectangles of the corner colors.)
1047 0 : if (tileModes[0] == SkShader::kClamp_TileMode &&
1048 0 : tileModes[1] == SkShader::kClamp_TileMode) {
1049 0 : SkPaint paint;
1050 : SkRect rect;
1051 0 : rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0);
1052 0 : if (!rect.isEmpty()) {
1053 0 : paint.setColor(image.getColor(0, 0));
1054 0 : canvas.drawRect(rect, paint);
1055 : }
1056 :
1057 : rect = SkRect::MakeLTRB(width, deviceBounds.top(),
1058 0 : deviceBounds.right(), 0);
1059 0 : if (!rect.isEmpty()) {
1060 0 : paint.setColor(image.getColor(image.width() - 1, 0));
1061 0 : canvas.drawRect(rect, paint);
1062 : }
1063 :
1064 : rect = SkRect::MakeLTRB(width, height,
1065 0 : deviceBounds.right(), deviceBounds.bottom());
1066 0 : if (!rect.isEmpty()) {
1067 0 : paint.setColor(image.getColor(image.width() - 1,
1068 0 : image.height() - 1));
1069 0 : canvas.drawRect(rect, paint);
1070 : }
1071 :
1072 : rect = SkRect::MakeLTRB(deviceBounds.left(), height,
1073 0 : 0, deviceBounds.bottom());
1074 0 : if (!rect.isEmpty()) {
1075 0 : paint.setColor(image.getColor(0, image.height() - 1));
1076 0 : canvas.drawRect(rect, paint);
1077 : }
1078 : }
1079 :
1080 : // Then expand the left, right, top, then bottom.
1081 0 : if (tileModes[0] == SkShader::kClamp_TileMode) {
1082 0 : SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image.height());
1083 0 : if (deviceBounds.left() < 0) {
1084 0 : SkBitmap left;
1085 0 : SkAssertResult(image.extractSubset(&left, subset));
1086 :
1087 : SkMatrix leftMatrix;
1088 0 : leftMatrix.setScale(-deviceBounds.left(), 1);
1089 0 : leftMatrix.postTranslate(deviceBounds.left(), 0);
1090 0 : drawBitmapMatrix(&canvas, left, leftMatrix);
1091 :
1092 0 : if (tileModes[1] == SkShader::kMirror_TileMode) {
1093 0 : leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
1094 0 : leftMatrix.postTranslate(0, 2 * height);
1095 0 : drawBitmapMatrix(&canvas, left, leftMatrix);
1096 : }
1097 0 : patternBBox.fLeft = 0;
1098 : }
1099 :
1100 0 : if (deviceBounds.right() > width) {
1101 0 : SkBitmap right;
1102 0 : subset.offset(image.width() - 1, 0);
1103 0 : SkAssertResult(image.extractSubset(&right, subset));
1104 :
1105 : SkMatrix rightMatrix;
1106 0 : rightMatrix.setScale(deviceBounds.right() - width, 1);
1107 0 : rightMatrix.postTranslate(width, 0);
1108 0 : drawBitmapMatrix(&canvas, right, rightMatrix);
1109 :
1110 0 : if (tileModes[1] == SkShader::kMirror_TileMode) {
1111 0 : rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
1112 0 : rightMatrix.postTranslate(0, 2 * height);
1113 0 : drawBitmapMatrix(&canvas, right, rightMatrix);
1114 : }
1115 0 : patternBBox.fRight = deviceBounds.width();
1116 : }
1117 : }
1118 :
1119 0 : if (tileModes[1] == SkShader::kClamp_TileMode) {
1120 0 : SkIRect subset = SkIRect::MakeXYWH(0, 0, image.width(), 1);
1121 0 : if (deviceBounds.top() < 0) {
1122 0 : SkBitmap top;
1123 0 : SkAssertResult(image.extractSubset(&top, subset));
1124 :
1125 : SkMatrix topMatrix;
1126 0 : topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
1127 0 : topMatrix.postTranslate(0, deviceBounds.top());
1128 0 : drawBitmapMatrix(&canvas, top, topMatrix);
1129 :
1130 0 : if (tileModes[0] == SkShader::kMirror_TileMode) {
1131 0 : topMatrix.postScale(-1, 1);
1132 0 : topMatrix.postTranslate(2 * width, 0);
1133 0 : drawBitmapMatrix(&canvas, top, topMatrix);
1134 : }
1135 0 : patternBBox.fTop = 0;
1136 : }
1137 :
1138 0 : if (deviceBounds.bottom() > height) {
1139 0 : SkBitmap bottom;
1140 0 : subset.offset(0, image.height() - 1);
1141 0 : SkAssertResult(image.extractSubset(&bottom, subset));
1142 :
1143 : SkMatrix bottomMatrix;
1144 0 : bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
1145 0 : bottomMatrix.postTranslate(0, height);
1146 0 : drawBitmapMatrix(&canvas, bottom, bottomMatrix);
1147 :
1148 0 : if (tileModes[0] == SkShader::kMirror_TileMode) {
1149 0 : bottomMatrix.postScale(-1, 1);
1150 0 : bottomMatrix.postTranslate(2 * width, 0);
1151 0 : drawBitmapMatrix(&canvas, bottom, bottomMatrix);
1152 : }
1153 0 : patternBBox.fBottom = deviceBounds.height();
1154 : }
1155 : }
1156 :
1157 0 : auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content());
1158 0 : populate_tiling_pattern_dict(imageShader->dict(), patternBBox,
1159 0 : patternDevice->makeResourceDict(), finalMatrix);
1160 0 : return imageShader;
1161 : }
1162 :
1163 0 : bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
1164 0 : if (fType != b.fType ||
1165 0 : fCanvasTransform != b.fCanvasTransform ||
1166 0 : fShaderTransform != b.fShaderTransform ||
1167 0 : fBBox != b.fBBox) {
1168 0 : return false;
1169 : }
1170 :
1171 0 : if (fType == SkShader::kNone_GradientType) {
1172 0 : if (fBitmapKey != b.fBitmapKey ||
1173 0 : fBitmapKey.fID == 0 ||
1174 0 : fImageTileModes[0] != b.fImageTileModes[0] ||
1175 0 : fImageTileModes[1] != b.fImageTileModes[1]) {
1176 0 : return false;
1177 : }
1178 : } else {
1179 0 : if (fInfo.fColorCount != b.fInfo.fColorCount ||
1180 0 : memcmp(fInfo.fColors, b.fInfo.fColors,
1181 0 : sizeof(SkColor) * fInfo.fColorCount) != 0 ||
1182 0 : memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets,
1183 0 : sizeof(SkScalar) * fInfo.fColorCount) != 0 ||
1184 0 : fInfo.fPoint[0] != b.fInfo.fPoint[0] ||
1185 0 : fInfo.fTileMode != b.fInfo.fTileMode) {
1186 0 : return false;
1187 : }
1188 :
1189 0 : switch (fType) {
1190 : case SkShader::kLinear_GradientType:
1191 0 : if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) {
1192 0 : return false;
1193 : }
1194 0 : break;
1195 : case SkShader::kRadial_GradientType:
1196 0 : if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) {
1197 0 : return false;
1198 : }
1199 0 : break;
1200 : case SkShader::kConical_GradientType:
1201 0 : if (fInfo.fPoint[1] != b.fInfo.fPoint[1] ||
1202 0 : fInfo.fRadius[0] != b.fInfo.fRadius[0] ||
1203 0 : fInfo.fRadius[1] != b.fInfo.fRadius[1]) {
1204 0 : return false;
1205 : }
1206 0 : break;
1207 : case SkShader::kSweep_GradientType:
1208 : case SkShader::kNone_GradientType:
1209 : case SkShader::kColor_GradientType:
1210 0 : break;
1211 : }
1212 : }
1213 0 : return true;
1214 : }
1215 :
1216 0 : SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform,
1217 : const SkIRect& bbox, SkScalar rasterScale,
1218 0 : SkBitmap* imageDst)
1219 : : fType(SkShader::kNone_GradientType)
1220 : , fInfo{0, nullptr, nullptr, {{0.0f, 0.0f}, {0.0f, 0.0f}},
1221 : {0.0f, 0.0f}, SkShader::kClamp_TileMode, 0}
1222 : , fCanvasTransform(canvasTransform)
1223 0 : , fShaderTransform{SkMatrix::I()}
1224 : , fBBox(bbox)
1225 : , fBitmapKey{{0, 0, 0, 0}, 0}
1226 : , fImageTileModes{SkShader::kClamp_TileMode,
1227 0 : SkShader::kClamp_TileMode} {
1228 0 : SkASSERT(imageDst);
1229 0 : fInfo.fColorCount = 0;
1230 0 : fInfo.fColors = nullptr;
1231 0 : fInfo.fColorOffsets = nullptr;
1232 0 : fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode;
1233 0 : fType = shader->asAGradient(&fInfo);
1234 :
1235 0 : if (fType != SkShader::kNone_GradientType) {
1236 0 : fBitmapKey = SkBitmapKey{{0, 0, 0, 0}, 0};
1237 0 : fShaderTransform = shader->getLocalMatrix();
1238 0 : this->allocateGradientInfoStorage();
1239 0 : shader->asAGradient(&fInfo);
1240 0 : return;
1241 : }
1242 0 : if (SkImage* skimg = shader->isAImage(&fShaderTransform, fImageTileModes)) {
1243 : // TODO(halcanary): delay converting to bitmap.
1244 0 : if (skimg->asLegacyBitmap(imageDst, SkImage::kRO_LegacyBitmapMode)) {
1245 0 : fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()};
1246 0 : return;
1247 : }
1248 : }
1249 0 : fShaderTransform = shader->getLocalMatrix();
1250 : // Generic fallback for unsupported shaders:
1251 : // * allocate a bbox-sized bitmap
1252 : // * shade the whole area
1253 : // * use the result as a bitmap shader
1254 :
1255 : // bbox is in device space. While that's exactly what we
1256 : // want for sizing our bitmap, we need to map it into
1257 : // shader space for adjustments (to match
1258 : // MakeImageShader's behavior).
1259 0 : SkRect shaderRect = SkRect::Make(bbox);
1260 0 : if (!inverse_transform_bbox(canvasTransform, &shaderRect)) {
1261 0 : imageDst->reset();
1262 0 : return;
1263 : }
1264 :
1265 : // Clamp the bitmap size to about 1M pixels
1266 : static const SkScalar kMaxBitmapArea = 1024 * 1024;
1267 0 : SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height();
1268 0 : if (bitmapArea > kMaxBitmapArea) {
1269 0 : rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea);
1270 : }
1271 :
1272 0 : SkISize size = {SkScalarRoundToInt(rasterScale * bbox.width()),
1273 0 : SkScalarRoundToInt(rasterScale * bbox.height())};
1274 0 : SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
1275 0 : SkIntToScalar(size.height()) / shaderRect.height()};
1276 :
1277 0 : imageDst->allocN32Pixels(size.width(), size.height());
1278 0 : imageDst->eraseColor(SK_ColorTRANSPARENT);
1279 :
1280 0 : SkPaint p;
1281 0 : p.setShader(sk_ref_sp(shader));
1282 :
1283 0 : SkCanvas canvas(*imageDst);
1284 0 : canvas.scale(scale.width(), scale.height());
1285 0 : canvas.translate(-shaderRect.x(), -shaderRect.y());
1286 0 : canvas.drawPaint(p);
1287 :
1288 0 : fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
1289 0 : fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
1290 0 : fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()};
1291 : }
1292 :
1293 0 : SkPDFShader::State::State(const SkPDFShader::State& other)
1294 0 : : fType(other.fType),
1295 : fCanvasTransform(other.fCanvasTransform),
1296 : fShaderTransform(other.fShaderTransform),
1297 0 : fBBox(other.fBBox)
1298 : {
1299 : // Only gradients supported for now, since that is all that is used.
1300 : // If needed, image state copy constructor can be added here later.
1301 0 : SkASSERT(fType != SkShader::kNone_GradientType);
1302 :
1303 0 : if (fType != SkShader::kNone_GradientType) {
1304 0 : fInfo = other.fInfo;
1305 :
1306 0 : this->allocateGradientInfoStorage();
1307 0 : for (int i = 0; i < fInfo.fColorCount; i++) {
1308 0 : fInfo.fColors[i] = other.fInfo.fColors[i];
1309 0 : fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i];
1310 : }
1311 : }
1312 0 : }
1313 :
1314 : /**
1315 : * Create a copy of this gradient state with alpha assigned to RGB luminousity.
1316 : * Only valid for gradient states.
1317 : */
1318 0 : SkPDFShader::State SkPDFShader::State::MakeAlphaToLuminosityState() const {
1319 0 : SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0}));
1320 0 : SkASSERT(fType != SkShader::kNone_GradientType);
1321 :
1322 0 : SkPDFShader::State newState(*this);
1323 :
1324 0 : for (int i = 0; i < fInfo.fColorCount; i++) {
1325 0 : SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
1326 0 : newState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
1327 : }
1328 :
1329 0 : return newState;
1330 : }
1331 :
1332 : /**
1333 : * Create a copy of this gradient state with alpha set to fully opaque
1334 : * Only valid for gradient states.
1335 : */
1336 0 : SkPDFShader::State SkPDFShader::State::MakeOpaqueState() const {
1337 0 : SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0}));
1338 0 : SkASSERT(fType != SkShader::kNone_GradientType);
1339 :
1340 0 : SkPDFShader::State newState(*this);
1341 0 : for (int i = 0; i < fInfo.fColorCount; i++) {
1342 0 : newState.fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i],
1343 : SK_AlphaOPAQUE);
1344 : }
1345 :
1346 0 : return newState;
1347 : }
1348 :
1349 : /**
1350 : * Returns true if state is a gradient and the gradient has alpha.
1351 : */
1352 0 : bool SkPDFShader::State::GradientHasAlpha() const {
1353 0 : if (fType == SkShader::kNone_GradientType) {
1354 0 : return false;
1355 : }
1356 :
1357 0 : for (int i = 0; i < fInfo.fColorCount; i++) {
1358 0 : SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
1359 0 : if (alpha != SK_AlphaOPAQUE) {
1360 0 : return true;
1361 : }
1362 : }
1363 0 : return false;
1364 : }
1365 :
1366 0 : void SkPDFShader::State::allocateGradientInfoStorage() {
1367 0 : fColors.reset(new SkColor[fInfo.fColorCount]);
1368 0 : fStops.reset(new SkScalar[fInfo.fColorCount]);
1369 0 : fInfo.fColors = fColors.get();
1370 0 : fInfo.fColorOffsets = fStops.get();
1371 0 : }
|