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
inverse_transform_bbox(const SkMatrix & matrix,SkRect * bbox)23 static bool inverse_transform_bbox(const SkMatrix& matrix, SkRect* bbox) {
24 SkMatrix inverse;
25 if (!matrix.invert(&inverse)) {
26 return false;
27 }
28 inverse.mapRect(bbox);
29 return true;
30 }
31
unitToPointsMatrix(const SkPoint pts[2],SkMatrix * matrix)32 static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) {
33 SkVector vec = pts[1] - pts[0];
34 SkScalar mag = vec.length();
35 SkScalar inv = mag ? SkScalarInvert(mag) : 0;
36
37 vec.scale(inv);
38 matrix->setSinCos(vec.fY, vec.fX);
39 matrix->preScale(mag, mag);
40 matrix->postTranslate(pts[0].fX, pts[0].fY);
41 }
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 */
interpolateColorCode(SkScalar range,const ColorTuple & curColor,const ColorTuple & prevColor,SkDynamicMemoryWStream * result)55 static void interpolateColorCode(SkScalar range, const ColorTuple& curColor,
56 const ColorTuple& prevColor,
57 SkDynamicMemoryWStream* result) {
58 SkASSERT(range != SkIntToScalar(0));
59
60 // Figure out how to scale each color component.
61 SkScalar multiplier[kColorComponents];
62 for (int i = 0; i < kColorComponents; i++) {
63 static const SkScalar kColorScale = SkScalarInvert(255);
64 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 dupInput[kColorComponents - 1] = false;
72 for (int i = kColorComponents - 2; i >= 0; i--) {
73 dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
74 }
75
76 if (!dupInput[0] && multiplier[0] == 0) {
77 result->writeText("pop ");
78 }
79
80 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 if (dupInput[i] && multiplier[i] != 0) {
84 result->writeText("dup ");
85 }
86
87 if (multiplier[i] == 0) {
88 SkPDFUtils::AppendColorComponent(prevColor[i], result);
89 result->writeText(" ");
90 } else {
91 if (multiplier[i] != 1) {
92 SkPDFUtils::AppendScalar(multiplier[i], result);
93 result->writeText(" mul ");
94 }
95 if (prevColor[i] != 0) {
96 SkPDFUtils::AppendColorComponent(prevColor[i], result);
97 result->writeText(" add ");
98 }
99 }
100
101 if (dupInput[i]) {
102 result->writeText("exch\n");
103 }
104 }
105 }
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 */
gradientFunctionCode(const SkShader::GradientInfo & info,SkDynamicMemoryWStream * result)128 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 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
137 ColorTuple *colorData = colorDataAlloc.get();
138 for (int i = 0; i < info.fColorCount; i++) {
139 colorData[i][0] = SkColorGetR(info.fColors[i]);
140 colorData[i][1] = SkColorGetG(info.fColors[i]);
141 colorData[i][2] = SkColorGetB(info.fColors[i]);
142 }
143
144 // Clamp the initial color.
145 result->writeText("dup 0 le {pop ");
146 SkPDFUtils::AppendColorComponent(colorData[0][0], result);
147 result->writeText(" ");
148 SkPDFUtils::AppendColorComponent(colorData[0][1], result);
149 result->writeText(" ");
150 SkPDFUtils::AppendColorComponent(colorData[0][2], result);
151 result->writeText(" }\n");
152
153 // The gradient colors.
154 int gradients = 0;
155 for (int i = 1 ; i < info.fColorCount; i++) {
156 if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) {
157 continue;
158 }
159 gradients++;
160
161 result->writeText("{dup ");
162 SkPDFUtils::AppendScalar(info.fColorOffsets[i], result);
163 result->writeText(" le {");
164 if (info.fColorOffsets[i - 1] != 0) {
165 SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result);
166 result->writeText(" sub\n");
167 }
168
169 interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
170 colorData[i], colorData[i - 1], result);
171 result->writeText("}\n");
172 }
173
174 // Clamp the final color.
175 result->writeText("{pop ");
176 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result);
177 result->writeText(" ");
178 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result);
179 result->writeText(" ");
180 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result);
181
182 for (int i = 0 ; i < gradients + 1; i++) {
183 result->writeText("} ifelse\n");
184 }
185 }
186
createInterpolationFunction(const ColorTuple & color1,const ColorTuple & color2)187 static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
188 const ColorTuple& color2) {
189 auto retval = sk_make_sp<SkPDFDict>();
190
191 auto c0 = sk_make_sp<SkPDFArray>();
192 c0->appendColorComponent(color1[0]);
193 c0->appendColorComponent(color1[1]);
194 c0->appendColorComponent(color1[2]);
195 retval->insertObject("C0", std::move(c0));
196
197 auto c1 = sk_make_sp<SkPDFArray>();
198 c1->appendColorComponent(color2[0]);
199 c1->appendColorComponent(color2[1]);
200 c1->appendColorComponent(color2[2]);
201 retval->insertObject("C1", std::move(c1));
202
203 auto domain = sk_make_sp<SkPDFArray>();
204 domain->appendScalar(0);
205 domain->appendScalar(1.0f);
206 retval->insertObject("Domain", std::move(domain));
207
208 retval->insertInt("FunctionType", 2);
209 retval->insertScalar("N", 1.0f);
210
211 return retval;
212 }
213
gradientStitchCode(const SkShader::GradientInfo & info)214 static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) {
215 auto retval = sk_make_sp<SkPDFDict>();
216
217 // normalize color stops
218 int colorCount = info.fColorCount;
219 SkTDArray<SkColor> colors(info.fColors, colorCount);
220 SkTDArray<SkScalar> colorOffsets(info.fColorOffsets, colorCount);
221
222 int i = 1;
223 while (i < colorCount - 1) {
224 // ensure stops are in order
225 if (colorOffsets[i - 1] > colorOffsets[i]) {
226 colorOffsets[i] = colorOffsets[i - 1];
227 }
228
229 // remove points that are between 2 coincident points
230 if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) {
231 colorCount -= 1;
232 colors.remove(i);
233 colorOffsets.remove(i);
234 } else {
235 i++;
236 }
237 }
238 // find coincident points and slightly move them over
239 for (i = 1; i < colorCount - 1; i++) {
240 if (colorOffsets[i - 1] == colorOffsets[i]) {
241 colorOffsets[i] += 0.00001f;
242 }
243 }
244 // check if last 2 stops coincide
245 if (colorOffsets[i - 1] == colorOffsets[i]) {
246 colorOffsets[i - 1] -= 0.00001f;
247 }
248
249 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount);
250 ColorTuple *colorData = colorDataAlloc.get();
251 for (int i = 0; i < colorCount; i++) {
252 colorData[i][0] = SkColorGetR(colors[i]);
253 colorData[i][1] = SkColorGetG(colors[i]);
254 colorData[i][2] = SkColorGetB(colors[i]);
255 }
256
257 // no need for a stitch function if there are only 2 stops.
258 if (colorCount == 2)
259 return createInterpolationFunction(colorData[0], colorData[1]);
260
261 auto encode = sk_make_sp<SkPDFArray>();
262 auto bounds = sk_make_sp<SkPDFArray>();
263 auto functions = sk_make_sp<SkPDFArray>();
264
265 auto domain = sk_make_sp<SkPDFArray>();
266 domain->appendScalar(0);
267 domain->appendScalar(1.0f);
268 retval->insertObject("Domain", std::move(domain));
269 retval->insertInt("FunctionType", 3);
270
271 for (int i = 1; i < colorCount; i++) {
272 if (i > 1) {
273 bounds->appendScalar(colorOffsets[i-1]);
274 }
275
276 encode->appendScalar(0);
277 encode->appendScalar(1.0f);
278
279 functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i]));
280 }
281
282 retval->insertObject("Encode", std::move(encode));
283 retval->insertObject("Bounds", std::move(bounds));
284 retval->insertObject("Functions", std::move(functions));
285
286 return retval;
287 }
288
289 /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
tileModeCode(SkShader::TileMode mode,SkDynamicMemoryWStream * result)290 static void tileModeCode(SkShader::TileMode mode,
291 SkDynamicMemoryWStream* result) {
292 if (mode == SkShader::kRepeat_TileMode) {
293 result->writeText("dup truncate sub\n"); // Get the fractional part.
294 result->writeText("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1)
295 return;
296 }
297
298 if (mode == SkShader::kMirror_TileMode) {
299 // Map t mod 2 into [0, 1, 1, 0].
300 // Code Stack
301 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 "{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 */
apply_perspective_to_coordinates(const SkMatrix & inversePerspectiveMatrix,SkDynamicMemoryWStream * code)324 static void apply_perspective_to_coordinates(
325 const SkMatrix& inversePerspectiveMatrix,
326 SkDynamicMemoryWStream* code) {
327 if (!inversePerspectiveMatrix.hasPerspective()) {
328 return;
329 }
330
331 // Perspective matrix should be:
332 // 1 0 0
333 // 0 1 0
334 // p0 p1 p2
335
336 const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
337 const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
338 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 code->writeText(" dup "); // x y y
345 SkPDFUtils::AppendScalar(p1, code); // x y y p1
346 code->writeText(" mul " // x y y*p1
347 " 2 index "); // x y y*p1 x
348 SkPDFUtils::AppendScalar(p0, code); // x y y p1 x p0
349 code->writeText(" mul "); // x y y*p1 x*p0
350 SkPDFUtils::AppendScalar(p2, code); // x y y p1 x*p0 p2
351 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 "exch\n"); // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
360 }
361
linearCode(const SkShader::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)362 static void linearCode(const SkShader::GradientInfo& info,
363 const SkMatrix& perspectiveRemover,
364 SkDynamicMemoryWStream* function) {
365 function->writeText("{");
366
367 apply_perspective_to_coordinates(perspectiveRemover, function);
368
369 function->writeText("pop\n"); // Just ditch the y value.
370 tileModeCode(info.fTileMode, function);
371 gradientFunctionCode(info, function);
372 function->writeText("}");
373 }
374
radialCode(const SkShader::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)375 static void radialCode(const SkShader::GradientInfo& info,
376 const SkMatrix& perspectiveRemover,
377 SkDynamicMemoryWStream* function) {
378 function->writeText("{");
379
380 apply_perspective_to_coordinates(perspectiveRemover, function);
381
382 // Find the distance from the origin.
383 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 "sqrt\n"); // sqrt(y^2+x^2)
390
391 tileModeCode(info.fTileMode, function);
392 gradientFunctionCode(info, function);
393 function->writeText("}");
394 }
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 */
twoPointConicalCode(const SkShader::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)399 static void twoPointConicalCode(const SkShader::GradientInfo& info,
400 const SkMatrix& perspectiveRemover,
401 SkDynamicMemoryWStream* function) {
402 SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
403 SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
404 SkScalar r0 = info.fRadius[0];
405 SkScalar dr = info.fRadius[1] - info.fRadius[0];
406 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 function->writeText("{");
414
415 apply_perspective_to_coordinates(perspectiveRemover, function);
416
417 function->writeText("2 copy ");
418
419 // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
420 SkPDFUtils::AppendScalar(dy, function);
421 function->writeText(" mul exch ");
422 SkPDFUtils::AppendScalar(dx, function);
423 function->writeText(" mul add ");
424 SkPDFUtils::AppendScalar(r0 * dr, function);
425 function->writeText(" add -2 mul dup dup mul\n");
426
427 // c = x^2 + y^2 + radius0^2
428 function->writeText("4 2 roll dup mul exch dup mul add ");
429 SkPDFUtils::AppendScalar(r0 * r0, function);
430 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 if (a == 0) {
436
437 // t = -c/b
438 function->writeText("pop pop div neg dup ");
439
440 // compute radius(t)
441 SkPDFUtils::AppendScalar(dr, function);
442 function->writeText(" mul ");
443 SkPDFUtils::AppendScalar(r0, function);
444 function->writeText(" add\n");
445
446 // if r(t) < 0, then it's outside the cone
447 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 SkPDFUtils::AppendScalar(a * 4, function);
456 function->writeText(" mul sub dup\n");
457
458 // if d >= 0, proceed
459 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 function->writeText("sqrt exch dup 0 lt {exch -1 mul} if");
464 function->writeText(" add -0.5 mul dup\n");
465
466 // first root = q / a
467 SkPDFUtils::AppendScalar(a, function);
468 function->writeText(" div\n");
469
470 // second root = c / q
471 function->writeText("3 1 roll div\n");
472
473 // put the larger root on top of the stack
474 function->writeText("2 copy gt {exch} if\n");
475
476 // compute radius(t) for larger root
477 function->writeText("dup ");
478 SkPDFUtils::AppendScalar(dr, function);
479 function->writeText(" mul ");
480 SkPDFUtils::AppendScalar(r0, function);
481 function->writeText(" add\n");
482
483 // if r(t) > 0, we have our t, pop off the smaller root and we're done
484 function->writeText(" 0 gt {exch pop true}\n");
485
486 // otherwise, throw out the larger one and try the smaller root
487 function->writeText("{pop dup\n");
488 SkPDFUtils::AppendScalar(dr, function);
489 function->writeText(" mul ");
490 SkPDFUtils::AppendScalar(r0, function);
491 function->writeText(" add\n");
492
493 // if r(t) < 0, push false, otherwise the smaller root is our t
494 function->writeText("0 le {pop false} {true} ifelse\n");
495 function->writeText("} ifelse\n");
496
497 // d < 0, clear the stack and push false
498 function->writeText("} {pop pop pop false} ifelse\n");
499 }
500
501 // if the pixel is in the cone, proceed to compute a color
502 function->writeText("{");
503 tileModeCode(info.fTileMode, function);
504 gradientFunctionCode(info, function);
505
506 // otherwise, just write black
507 function->writeText("} {0 0 0} ifelse }");
508 }
509
sweepCode(const SkShader::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)510 static void sweepCode(const SkShader::GradientInfo& info,
511 const SkMatrix& perspectiveRemover,
512 SkDynamicMemoryWStream* function) {
513 function->writeText("{exch atan 360 div\n");
514 tileModeCode(info.fTileMode, function);
515 gradientFunctionCode(info, function);
516 function->writeText("}");
517 }
518
drawBitmapMatrix(SkCanvas * canvas,const SkBitmap & bm,const SkMatrix & matrix)519 static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) {
520 SkAutoCanvasRestore acr(canvas, true);
521 canvas->concat(matrix);
522 canvas->drawBitmap(bm, 0, 0);
523 }
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
get_pdf_shader_by_state(SkPDFDocument * doc,SkScalar dpi,SkPDFShader::State state,SkBitmap image)538 static sk_sp<SkPDFObject> get_pdf_shader_by_state(
539 SkPDFDocument* doc,
540 SkScalar dpi,
541 SkPDFShader::State state,
542 SkBitmap image) {
543 SkPDFCanon* canon = doc->canon();
544 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 return nullptr;
550 } else if (state.fType == SkShader::kNone_GradientType) {
551 sk_sp<SkPDFObject> shader = canon->findImageShader(state);
552 if (!shader) {
553 shader = make_image_shader(doc, dpi, state, std::move(image));
554 canon->addImageShader(shader, std::move(state));
555 }
556 return shader;
557 } else if (state.GradientHasAlpha()) {
558 sk_sp<SkPDFObject> shader = canon->findAlphaShader(state);
559 if (!shader) {
560 shader = make_alpha_function_shader(doc, dpi, state);
561 canon->addAlphaShader(shader, std::move(state));
562 }
563 return shader;
564 } else {
565 sk_sp<SkPDFObject> shader = canon->findFunctionShader(state);
566 if (!shader) {
567 shader = make_function_shader(canon, state);
568 canon->addFunctionShader(shader, std::move(state));
569 }
570 return shader;
571 }
572 }
573
GetPDFShader(SkPDFDocument * doc,SkScalar dpi,SkShader * shader,const SkMatrix & matrix,const SkIRect & surfaceBBox,SkScalar rasterScale)574 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 if (surfaceBBox.isEmpty()) {
581 return nullptr;
582 }
583 SkBitmap image;
584 State state(shader, matrix, surfaceBBox, rasterScale, &image);
585 return get_pdf_shader_by_state(
586 doc, dpi, std::move(state), std::move(image));
587 }
588
get_gradient_resource_dict(SkPDFObject * functionShader,SkPDFObject * gState)589 static sk_sp<SkPDFDict> get_gradient_resource_dict(
590 SkPDFObject* functionShader,
591 SkPDFObject* gState) {
592 SkTDArray<SkPDFObject*> patterns;
593 if (functionShader) {
594 patterns.push(functionShader);
595 }
596 SkTDArray<SkPDFObject*> graphicStates;
597 if (gState) {
598 graphicStates.push(gState);
599 }
600 return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr);
601 }
602
populate_tiling_pattern_dict(SkPDFDict * pattern,SkRect & bbox,sk_sp<SkPDFDict> resources,const SkMatrix & matrix)603 static void populate_tiling_pattern_dict(SkPDFDict* pattern,
604 SkRect& bbox,
605 sk_sp<SkPDFDict> resources,
606 const SkMatrix& matrix) {
607 const int kTiling_PatternType = 1;
608 const int kColoredTilingPattern_PaintType = 1;
609 const int kConstantSpacing_TilingType = 1;
610
611 pattern->insertName("Type", "Pattern");
612 pattern->insertInt("PatternType", kTiling_PatternType);
613 pattern->insertInt("PaintType", kColoredTilingPattern_PaintType);
614 pattern->insertInt("TilingType", kConstantSpacing_TilingType);
615 pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox));
616 pattern->insertScalar("XStep", bbox.width());
617 pattern->insertScalar("YStep", bbox.height());
618 pattern->insertObject("Resources", std::move(resources));
619 if (!matrix.isIdentity()) {
620 pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
621 }
622 }
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 */
create_pattern_fill_content(int gsIndex,SkRect & bounds)629 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(
630 int gsIndex, SkRect& bounds) {
631 SkDynamicMemoryWStream content;
632 if (gsIndex >= 0) {
633 SkPDFUtils::ApplyGraphicState(gsIndex, &content);
634 }
635 SkPDFUtils::ApplyPattern(0, &content);
636 SkPDFUtils::AppendRectangle(bounds, &content);
637 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType,
638 &content);
639
640 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 */
create_smask_graphic_state(SkPDFDocument * doc,SkScalar dpi,const SkPDFShader::State & state)647 static sk_sp<SkPDFObject> create_smask_graphic_state(
648 SkPDFDocument* doc, SkScalar dpi, const SkPDFShader::State& state) {
649 SkRect bbox;
650 bbox.set(state.fBBox);
651
652 sk_sp<SkPDFObject> luminosityShader(
653 get_pdf_shader_by_state(doc, dpi, state.MakeAlphaToLuminosityState(),
654 SkBitmap()));
655
656 std::unique_ptr<SkStreamAsset> alphaStream(create_pattern_fill_content(-1, bbox));
657
658 sk_sp<SkPDFDict> resources =
659 get_gradient_resource_dict(luminosityShader.get(), nullptr);
660
661 sk_sp<SkPDFObject> alphaMask =
662 SkPDFMakeFormXObject(std::move(alphaStream),
663 SkPDFUtils::RectToArray(bbox),
664 std::move(resources),
665 SkMatrix::I(),
666 "DeviceRGB");
667 return SkPDFGraphicState::GetSMaskGraphicState(
668 std::move(alphaMask), false,
669 SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
670 }
671
make_alpha_function_shader(SkPDFDocument * doc,SkScalar dpi,const SkPDFShader::State & state)672 static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
673 SkScalar dpi,
674 const SkPDFShader::State& state) {
675 SkRect bbox;
676 bbox.set(state.fBBox);
677
678 SkPDFShader::State opaqueState(state.MakeOpaqueState());
679
680 sk_sp<SkPDFObject> colorShader(
681 get_pdf_shader_by_state(doc, dpi, std::move(opaqueState), SkBitmap()));
682 if (!colorShader) {
683 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 sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, dpi, state);
689
690 sk_sp<SkPDFDict> resourceDict =
691 get_gradient_resource_dict(colorShader.get(), alphaGs.get());
692
693 std::unique_ptr<SkStreamAsset> colorStream(
694 create_pattern_fill_content(0, bbox));
695 auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream));
696
697 populate_tiling_pattern_dict(alphaFunctionShader->dict(), bbox,
698 std::move(resourceDict), SkMatrix::I());
699 return alphaFunctionShader;
700 }
701
702 // Finds affine and persp such that in = affine * persp.
703 // but it returns the inverse of perspective matrix.
split_perspective(const SkMatrix in,SkMatrix * affine,SkMatrix * perspectiveInverse)704 static bool split_perspective(const SkMatrix in, SkMatrix* affine,
705 SkMatrix* perspectiveInverse) {
706 const SkScalar p2 = in[SkMatrix::kMPersp2];
707
708 if (SkScalarNearlyZero(p2)) {
709 return false;
710 }
711
712 const SkScalar zero = SkIntToScalar(0);
713 const SkScalar one = SkIntToScalar(1);
714
715 const SkScalar sx = in[SkMatrix::kMScaleX];
716 const SkScalar kx = in[SkMatrix::kMSkewX];
717 const SkScalar tx = in[SkMatrix::kMTransX];
718 const SkScalar ky = in[SkMatrix::kMSkewY];
719 const SkScalar sy = in[SkMatrix::kMScaleY];
720 const SkScalar ty = in[SkMatrix::kMTransY];
721 const SkScalar p0 = in[SkMatrix::kMPersp0];
722 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 perspectiveInverse->setAll(one, zero, zero,
730 zero, one, zero,
731 -p0/p2, -p1/p2, 1/p2);
732
733 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2,
734 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2,
735 zero, zero, one);
736
737 return true;
738 }
739
MakeRangeObject()740 sk_sp<SkPDFArray> SkPDFShader::MakeRangeObject() {
741 auto range = sk_make_sp<SkPDFArray>();
742 range->reserve(6);
743 range->appendInt(0);
744 range->appendInt(1);
745 range->appendInt(0);
746 range->appendInt(1);
747 range->appendInt(0);
748 range->appendInt(1);
749 return range;
750 }
751
make_ps_function(std::unique_ptr<SkStreamAsset> psCode,sk_sp<SkPDFArray> domain,sk_sp<SkPDFObject> range)752 static sk_sp<SkPDFStream> make_ps_function(
753 std::unique_ptr<SkStreamAsset> psCode,
754 sk_sp<SkPDFArray> domain,
755 sk_sp<SkPDFObject> range) {
756 auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
757 result->dict()->insertInt("FunctionType", 4);
758 result->dict()->insertObject("Domain", std::move(domain));
759 result->dict()->insertObject("Range", std::move(range));
760 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
FixUpRadius(const SkPoint & p1,SkScalar & r1,const SkPoint & p2,SkScalar & r2)765 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
766 // detect touching circles
767 SkScalar distance = SkPoint::Distance(p1, p2);
768 SkScalar subtractRadii = fabs(r1 - r2);
769 if (fabs(distance - subtractRadii) < 0.002f) {
770 if (r1 > r2) {
771 r1 += 0.002f;
772 } else {
773 r2 += 0.002f;
774 }
775 }
776 }
777
make_function_shader(SkPDFCanon * canon,const SkPDFShader::State & state)778 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 SkDynamicMemoryWStream* function) = nullptr;
783 SkPoint transformPoints[2];
784 const SkShader::GradientInfo* info = &state.fInfo;
785 SkMatrix finalMatrix = state.fCanvasTransform;
786 finalMatrix.preConcat(state.fShaderTransform);
787
788 bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType ||
789 state.fType == SkShader::kRadial_GradientType ||
790 state.fType == SkShader::kConical_GradientType) &&
791 info->fTileMode == SkShader::kClamp_TileMode &&
792 !finalMatrix.hasPerspective();
793
794 auto domain = sk_make_sp<SkPDFArray>();
795
796 int32_t shadingType = 1;
797 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 if (doStitchFunctions) {
803 pdfShader->insertObject("Function", gradientStitchCode(*info));
804 shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3;
805
806 auto extend = sk_make_sp<SkPDFArray>();
807 extend->reserve(2);
808 extend->appendBool(true);
809 extend->appendBool(true);
810 pdfShader->insertObject("Extend", std::move(extend));
811
812 auto coords = sk_make_sp<SkPDFArray>();
813 if (state.fType == SkShader::kConical_GradientType) {
814 coords->reserve(6);
815 SkScalar r1 = info->fRadius[0];
816 SkScalar r2 = info->fRadius[1];
817 SkPoint pt1 = info->fPoint[0];
818 SkPoint pt2 = info->fPoint[1];
819 FixUpRadius(pt1, r1, pt2, r2);
820
821 coords->appendScalar(pt1.fX);
822 coords->appendScalar(pt1.fY);
823 coords->appendScalar(r1);
824
825 coords->appendScalar(pt2.fX);
826 coords->appendScalar(pt2.fY);
827 coords->appendScalar(r2);
828 } else if (state.fType == SkShader::kRadial_GradientType) {
829 coords->reserve(6);
830 const SkPoint& pt1 = info->fPoint[0];
831
832 coords->appendScalar(pt1.fX);
833 coords->appendScalar(pt1.fY);
834 coords->appendScalar(0);
835
836 coords->appendScalar(pt1.fX);
837 coords->appendScalar(pt1.fY);
838 coords->appendScalar(info->fRadius[0]);
839 } else {
840 coords->reserve(4);
841 const SkPoint& pt1 = info->fPoint[0];
842 const SkPoint& pt2 = info->fPoint[1];
843
844 coords->appendScalar(pt1.fX);
845 coords->appendScalar(pt1.fY);
846
847 coords->appendScalar(pt2.fX);
848 coords->appendScalar(pt2.fY);
849 }
850
851 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 transformPoints[0] = info->fPoint[0];
856 transformPoints[1] = info->fPoint[1];
857 switch (state.fType) {
858 case SkShader::kLinear_GradientType:
859 codeFunction = &linearCode;
860 break;
861 case SkShader::kRadial_GradientType:
862 transformPoints[1] = transformPoints[0];
863 transformPoints[1].fX += info->fRadius[0];
864 codeFunction = &radialCode;
865 break;
866 case SkShader::kConical_GradientType: {
867 transformPoints[1] = transformPoints[0];
868 transformPoints[1].fX += SK_Scalar1;
869 codeFunction = &twoPointConicalCode;
870 break;
871 }
872 case SkShader::kSweep_GradientType:
873 transformPoints[1] = transformPoints[0];
874 transformPoints[1].fX += SK_Scalar1;
875 codeFunction = &sweepCode;
876 break;
877 case SkShader::kColor_GradientType:
878 case SkShader::kNone_GradientType:
879 default:
880 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 unitToPointsMatrix(transformPoints, &mapperMatrix);
889
890 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 SkMatrix perspectiveInverseOnly = SkMatrix::I();
898 if (finalMatrix.hasPerspective()) {
899 if (!split_perspective(finalMatrix,
900 &finalMatrix, &perspectiveInverseOnly)) {
901 return nullptr;
902 }
903 }
904
905 SkRect bbox;
906 bbox.set(state.fBBox);
907 if (!inverse_transform_bbox(finalMatrix, &bbox)) {
908 return nullptr;
909 }
910 domain->reserve(4);
911 domain->appendScalar(bbox.fLeft);
912 domain->appendScalar(bbox.fRight);
913 domain->appendScalar(bbox.fTop);
914 domain->appendScalar(bbox.fBottom);
915
916 SkDynamicMemoryWStream functionCode;
917
918 if (state.fType == SkShader::kConical_GradientType) {
919 SkShader::GradientInfo twoPointRadialInfo = *info;
920 SkMatrix inverseMapperMatrix;
921 if (!mapperMatrix.invert(&inverseMapperMatrix)) {
922 return nullptr;
923 }
924 inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
925 twoPointRadialInfo.fRadius[0] =
926 inverseMapperMatrix.mapRadius(info->fRadius[0]);
927 twoPointRadialInfo.fRadius[1] =
928 inverseMapperMatrix.mapRadius(info->fRadius[1]);
929 codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode);
930 } else {
931 codeFunction(*info, perspectiveInverseOnly, &functionCode);
932 }
933
934 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 functionCode.detachAsStream());
941 sk_sp<SkPDFStream> function = make_ps_function(std::move(functionStream),
942 std::move(domain),
943 canon->makeRangeObject());
944 pdfShader->insertObjRef("Function", std::move(function));
945 }
946
947 pdfShader->insertInt("ShadingType", shadingType);
948 pdfShader->insertName("ColorSpace", "DeviceRGB");
949
950 auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern");
951 pdfFunctionShader->insertInt("PatternType", 2);
952 pdfFunctionShader->insertObject("Matrix",
953 SkPDFUtils::MatrixToArray(finalMatrix));
954 pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
955
956 return pdfFunctionShader;
957 }
958
make_image_shader(SkPDFDocument * doc,SkScalar dpi,const SkPDFShader::State & state,SkBitmap image)959 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
960 SkScalar dpi,
961 const SkPDFShader::State& state,
962 SkBitmap image) {
963 SkASSERT(state.fBitmapKey ==
964 (SkBitmapKey{image.getSubset(), image.getGenerationID()}));
965 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 SkMatrix finalMatrix = state.fCanvasTransform;
974 finalMatrix.preConcat(state.fShaderTransform);
975 SkRect deviceBounds;
976 deviceBounds.set(state.fBBox);
977 if (!inverse_transform_bbox(finalMatrix, &deviceBounds)) {
978 return nullptr;
979 }
980
981 SkRect bitmapBounds;
982 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 tileModes[0] = state.fImageTileModes[0];
990 tileModes[1] = state.fImageTileModes[1];
991 if (tileModes[0] != SkShader::kClamp_TileMode ||
992 tileModes[1] != SkShader::kClamp_TileMode) {
993 deviceBounds.join(bitmapBounds);
994 }
995
996 SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()),
997 SkScalarRoundToInt(deviceBounds.height()));
998 sk_sp<SkPDFDevice> patternDevice(
999 SkPDFDevice::CreateUnflipped(size, dpi, doc));
1000 SkCanvas canvas(patternDevice.get());
1001
1002 SkRect patternBBox;
1003 image.getBounds(&patternBBox);
1004
1005 // Translate the canvas so that the bitmap origin is at (0, 0).
1006 canvas.translate(-deviceBounds.left(), -deviceBounds.top());
1007 patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
1008 // Undo the translation in the final matrix
1009 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 canvas.drawBitmap(image, 0, 0);
1015
1016 SkScalar width = SkIntToScalar(image.width());
1017 SkScalar height = SkIntToScalar(image.height());
1018
1019 // Tiling is implied. First we handle mirroring.
1020 if (tileModes[0] == SkShader::kMirror_TileMode) {
1021 SkMatrix xMirror;
1022 xMirror.setScale(-1, 1);
1023 xMirror.postTranslate(2 * width, 0);
1024 drawBitmapMatrix(&canvas, image, xMirror);
1025 patternBBox.fRight += width;
1026 }
1027 if (tileModes[1] == SkShader::kMirror_TileMode) {
1028 SkMatrix yMirror;
1029 yMirror.setScale(SK_Scalar1, -SK_Scalar1);
1030 yMirror.postTranslate(0, 2 * height);
1031 drawBitmapMatrix(&canvas, image, yMirror);
1032 patternBBox.fBottom += height;
1033 }
1034 if (tileModes[0] == SkShader::kMirror_TileMode &&
1035 tileModes[1] == SkShader::kMirror_TileMode) {
1036 SkMatrix mirror;
1037 mirror.setScale(-1, -1);
1038 mirror.postTranslate(2 * width, 2 * height);
1039 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 if (tileModes[0] == SkShader::kClamp_TileMode &&
1048 tileModes[1] == SkShader::kClamp_TileMode) {
1049 SkPaint paint;
1050 SkRect rect;
1051 rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0);
1052 if (!rect.isEmpty()) {
1053 paint.setColor(image.getColor(0, 0));
1054 canvas.drawRect(rect, paint);
1055 }
1056
1057 rect = SkRect::MakeLTRB(width, deviceBounds.top(),
1058 deviceBounds.right(), 0);
1059 if (!rect.isEmpty()) {
1060 paint.setColor(image.getColor(image.width() - 1, 0));
1061 canvas.drawRect(rect, paint);
1062 }
1063
1064 rect = SkRect::MakeLTRB(width, height,
1065 deviceBounds.right(), deviceBounds.bottom());
1066 if (!rect.isEmpty()) {
1067 paint.setColor(image.getColor(image.width() - 1,
1068 image.height() - 1));
1069 canvas.drawRect(rect, paint);
1070 }
1071
1072 rect = SkRect::MakeLTRB(deviceBounds.left(), height,
1073 0, deviceBounds.bottom());
1074 if (!rect.isEmpty()) {
1075 paint.setColor(image.getColor(0, image.height() - 1));
1076 canvas.drawRect(rect, paint);
1077 }
1078 }
1079
1080 // Then expand the left, right, top, then bottom.
1081 if (tileModes[0] == SkShader::kClamp_TileMode) {
1082 SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image.height());
1083 if (deviceBounds.left() < 0) {
1084 SkBitmap left;
1085 SkAssertResult(image.extractSubset(&left, subset));
1086
1087 SkMatrix leftMatrix;
1088 leftMatrix.setScale(-deviceBounds.left(), 1);
1089 leftMatrix.postTranslate(deviceBounds.left(), 0);
1090 drawBitmapMatrix(&canvas, left, leftMatrix);
1091
1092 if (tileModes[1] == SkShader::kMirror_TileMode) {
1093 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
1094 leftMatrix.postTranslate(0, 2 * height);
1095 drawBitmapMatrix(&canvas, left, leftMatrix);
1096 }
1097 patternBBox.fLeft = 0;
1098 }
1099
1100 if (deviceBounds.right() > width) {
1101 SkBitmap right;
1102 subset.offset(image.width() - 1, 0);
1103 SkAssertResult(image.extractSubset(&right, subset));
1104
1105 SkMatrix rightMatrix;
1106 rightMatrix.setScale(deviceBounds.right() - width, 1);
1107 rightMatrix.postTranslate(width, 0);
1108 drawBitmapMatrix(&canvas, right, rightMatrix);
1109
1110 if (tileModes[1] == SkShader::kMirror_TileMode) {
1111 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
1112 rightMatrix.postTranslate(0, 2 * height);
1113 drawBitmapMatrix(&canvas, right, rightMatrix);
1114 }
1115 patternBBox.fRight = deviceBounds.width();
1116 }
1117 }
1118
1119 if (tileModes[1] == SkShader::kClamp_TileMode) {
1120 SkIRect subset = SkIRect::MakeXYWH(0, 0, image.width(), 1);
1121 if (deviceBounds.top() < 0) {
1122 SkBitmap top;
1123 SkAssertResult(image.extractSubset(&top, subset));
1124
1125 SkMatrix topMatrix;
1126 topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
1127 topMatrix.postTranslate(0, deviceBounds.top());
1128 drawBitmapMatrix(&canvas, top, topMatrix);
1129
1130 if (tileModes[0] == SkShader::kMirror_TileMode) {
1131 topMatrix.postScale(-1, 1);
1132 topMatrix.postTranslate(2 * width, 0);
1133 drawBitmapMatrix(&canvas, top, topMatrix);
1134 }
1135 patternBBox.fTop = 0;
1136 }
1137
1138 if (deviceBounds.bottom() > height) {
1139 SkBitmap bottom;
1140 subset.offset(0, image.height() - 1);
1141 SkAssertResult(image.extractSubset(&bottom, subset));
1142
1143 SkMatrix bottomMatrix;
1144 bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
1145 bottomMatrix.postTranslate(0, height);
1146 drawBitmapMatrix(&canvas, bottom, bottomMatrix);
1147
1148 if (tileModes[0] == SkShader::kMirror_TileMode) {
1149 bottomMatrix.postScale(-1, 1);
1150 bottomMatrix.postTranslate(2 * width, 0);
1151 drawBitmapMatrix(&canvas, bottom, bottomMatrix);
1152 }
1153 patternBBox.fBottom = deviceBounds.height();
1154 }
1155 }
1156
1157 auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content());
1158 populate_tiling_pattern_dict(imageShader->dict(), patternBBox,
1159 patternDevice->makeResourceDict(), finalMatrix);
1160 return imageShader;
1161 }
1162
operator ==(const SkPDFShader::State & b) const1163 bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
1164 if (fType != b.fType ||
1165 fCanvasTransform != b.fCanvasTransform ||
1166 fShaderTransform != b.fShaderTransform ||
1167 fBBox != b.fBBox) {
1168 return false;
1169 }
1170
1171 if (fType == SkShader::kNone_GradientType) {
1172 if (fBitmapKey != b.fBitmapKey ||
1173 fBitmapKey.fID == 0 ||
1174 fImageTileModes[0] != b.fImageTileModes[0] ||
1175 fImageTileModes[1] != b.fImageTileModes[1]) {
1176 return false;
1177 }
1178 } else {
1179 if (fInfo.fColorCount != b.fInfo.fColorCount ||
1180 memcmp(fInfo.fColors, b.fInfo.fColors,
1181 sizeof(SkColor) * fInfo.fColorCount) != 0 ||
1182 memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets,
1183 sizeof(SkScalar) * fInfo.fColorCount) != 0 ||
1184 fInfo.fPoint[0] != b.fInfo.fPoint[0] ||
1185 fInfo.fTileMode != b.fInfo.fTileMode) {
1186 return false;
1187 }
1188
1189 switch (fType) {
1190 case SkShader::kLinear_GradientType:
1191 if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) {
1192 return false;
1193 }
1194 break;
1195 case SkShader::kRadial_GradientType:
1196 if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) {
1197 return false;
1198 }
1199 break;
1200 case SkShader::kConical_GradientType:
1201 if (fInfo.fPoint[1] != b.fInfo.fPoint[1] ||
1202 fInfo.fRadius[0] != b.fInfo.fRadius[0] ||
1203 fInfo.fRadius[1] != b.fInfo.fRadius[1]) {
1204 return false;
1205 }
1206 break;
1207 case SkShader::kSweep_GradientType:
1208 case SkShader::kNone_GradientType:
1209 case SkShader::kColor_GradientType:
1210 break;
1211 }
1212 }
1213 return true;
1214 }
1215
State(SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & bbox,SkScalar rasterScale,SkBitmap * imageDst)1216 SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform,
1217 const SkIRect& bbox, SkScalar rasterScale,
1218 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 , fShaderTransform{SkMatrix::I()}
1224 , fBBox(bbox)
1225 , fBitmapKey{{0, 0, 0, 0}, 0}
1226 , fImageTileModes{SkShader::kClamp_TileMode,
1227 SkShader::kClamp_TileMode} {
1228 SkASSERT(imageDst);
1229 fInfo.fColorCount = 0;
1230 fInfo.fColors = nullptr;
1231 fInfo.fColorOffsets = nullptr;
1232 fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode;
1233 fType = shader->asAGradient(&fInfo);
1234
1235 if (fType != SkShader::kNone_GradientType) {
1236 fBitmapKey = SkBitmapKey{{0, 0, 0, 0}, 0};
1237 fShaderTransform = shader->getLocalMatrix();
1238 this->allocateGradientInfoStorage();
1239 shader->asAGradient(&fInfo);
1240 return;
1241 }
1242 if (SkImage* skimg = shader->isAImage(&fShaderTransform, fImageTileModes)) {
1243 // TODO(halcanary): delay converting to bitmap.
1244 if (skimg->asLegacyBitmap(imageDst, SkImage::kRO_LegacyBitmapMode)) {
1245 fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()};
1246 return;
1247 }
1248 }
1249 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 SkRect shaderRect = SkRect::Make(bbox);
1260 if (!inverse_transform_bbox(canvasTransform, &shaderRect)) {
1261 imageDst->reset();
1262 return;
1263 }
1264
1265 // Clamp the bitmap size to about 1M pixels
1266 static const SkScalar kMaxBitmapArea = 1024 * 1024;
1267 SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height();
1268 if (bitmapArea > kMaxBitmapArea) {
1269 rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea);
1270 }
1271
1272 SkISize size = SkISize::Make(SkScalarRoundToInt(rasterScale * bbox.width()),
1273 SkScalarRoundToInt(rasterScale * bbox.height()));
1274 SkSize scale = SkSize::Make(SkIntToScalar(size.width()) / shaderRect.width(),
1275 SkIntToScalar(size.height()) / shaderRect.height());
1276
1277 imageDst->allocN32Pixels(size.width(), size.height());
1278 imageDst->eraseColor(SK_ColorTRANSPARENT);
1279
1280 SkPaint p;
1281 p.setShader(sk_ref_sp(shader));
1282
1283 SkCanvas canvas(*imageDst);
1284 canvas.scale(scale.width(), scale.height());
1285 canvas.translate(-shaderRect.x(), -shaderRect.y());
1286 canvas.drawPaint(p);
1287
1288 fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
1289 fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
1290 fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()};
1291 }
1292
State(const SkPDFShader::State & other)1293 SkPDFShader::State::State(const SkPDFShader::State& other)
1294 : fType(other.fType),
1295 fCanvasTransform(other.fCanvasTransform),
1296 fShaderTransform(other.fShaderTransform),
1297 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 SkASSERT(fType != SkShader::kNone_GradientType);
1302
1303 if (fType != SkShader::kNone_GradientType) {
1304 fInfo = other.fInfo;
1305
1306 this->allocateGradientInfoStorage();
1307 for (int i = 0; i < fInfo.fColorCount; i++) {
1308 fInfo.fColors[i] = other.fInfo.fColors[i];
1309 fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i];
1310 }
1311 }
1312 }
1313
1314 /**
1315 * Create a copy of this gradient state with alpha assigned to RGB luminousity.
1316 * Only valid for gradient states.
1317 */
MakeAlphaToLuminosityState() const1318 SkPDFShader::State SkPDFShader::State::MakeAlphaToLuminosityState() const {
1319 SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0}));
1320 SkASSERT(fType != SkShader::kNone_GradientType);
1321
1322 SkPDFShader::State newState(*this);
1323
1324 for (int i = 0; i < fInfo.fColorCount; i++) {
1325 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
1326 newState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
1327 }
1328
1329 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 */
MakeOpaqueState() const1336 SkPDFShader::State SkPDFShader::State::MakeOpaqueState() const {
1337 SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0}));
1338 SkASSERT(fType != SkShader::kNone_GradientType);
1339
1340 SkPDFShader::State newState(*this);
1341 for (int i = 0; i < fInfo.fColorCount; i++) {
1342 newState.fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i],
1343 SK_AlphaOPAQUE);
1344 }
1345
1346 return newState;
1347 }
1348
1349 /**
1350 * Returns true if state is a gradient and the gradient has alpha.
1351 */
GradientHasAlpha() const1352 bool SkPDFShader::State::GradientHasAlpha() const {
1353 if (fType == SkShader::kNone_GradientType) {
1354 return false;
1355 }
1356
1357 for (int i = 0; i < fInfo.fColorCount; i++) {
1358 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
1359 if (alpha != SK_AlphaOPAQUE) {
1360 return true;
1361 }
1362 }
1363 return false;
1364 }
1365
allocateGradientInfoStorage()1366 void SkPDFShader::State::allocateGradientInfoStorage() {
1367 fColors.reset(new SkColor[fInfo.fColorCount]);
1368 fStops.reset(new SkScalar[fInfo.fColorCount]);
1369 fInfo.fColors = fColors.get();
1370 fInfo.fColorOffsets = fStops.get();
1371 }
1372