blob: 612ff8707481b9c31e76f874c7da4fd303cebf4f [file] [log] [blame]
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002 * Copyright 2011 Google Inc.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00003 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00004 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00006 */
7
epoger@google.comec3ed6a2011-07-28 14:26:00 +00008
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00009#include "SkPDFShader.h"
10
vandebo@chromium.org421d6442011-07-20 17:39:01 +000011#include "SkData.h"
halcanaryfb62b3d2015-01-21 09:59:14 -080012#include "SkPDFCanon.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000013#include "SkPDFDevice.h"
halcanary989da4a2016-03-21 14:33:17 -070014#include "SkPDFDocument.h"
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +000015#include "SkPDFFormXObject.h"
16#include "SkPDFGraphicState.h"
commit-bot@chromium.org47401352013-07-23 21:49:29 +000017#include "SkPDFResourceDict.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000018#include "SkPDFUtils.h"
19#include "SkScalar.h"
20#include "SkStream.h"
twiz@google.com316338a2011-03-09 23:14:04 +000021#include "SkTemplates.h"
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000022
fmalita7b4d4c72015-02-12 13:56:50 -080023static bool inverse_transform_bbox(const SkMatrix& matrix, SkRect* bbox) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000024 SkMatrix inverse;
vandebo@chromium.orgb0549902012-04-13 20:45:46 +000025 if (!matrix.invert(&inverse)) {
vandebo@chromium.org386dfc02012-04-17 22:31:52 +000026 return false;
vandebo@chromium.orgb0549902012-04-13 20:45:46 +000027 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000028 inverse.mapRect(bbox);
vandebo@chromium.org386dfc02012-04-17 22:31:52 +000029 return true;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000030}
31
32static 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);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000039 matrix->preScale(mag, mag);
commit-bot@chromium.orgace22692013-06-12 21:33:02 +000040 matrix->postTranslate(pts[0].fX, pts[0].fY);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000041}
42
halcanaryeb92cb32016-07-15 13:41:27 -070043static const int kColorComponents = 3;
44typedef uint8_t ColorTuple[kColorComponents];
45
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000046/* 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
djsollen@google.com53648ab2013-04-03 12:34:16 +000048 component), leaving the result in component order on the stack. It assumes
49 there are always 3 components per color.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000050 @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 */
halcanaryeb92cb32016-07-15 13:41:27 -070055static void interpolateColorCode(SkScalar range, const ColorTuple& curColor,
56 const ColorTuple& prevColor,
halcanaryd11c7262016-03-25 05:52:57 -070057 SkDynamicMemoryWStream* result) {
edisonn@google.com81353232013-09-17 17:17:47 +000058 SkASSERT(range != SkIntToScalar(0));
djsollen@google.com53648ab2013-04-03 12:34:16 +000059
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000060 // Figure out how to scale each color component.
djsollen@google.com53648ab2013-04-03 12:34:16 +000061 SkScalar multiplier[kColorComponents];
62 for (int i = 0; i < kColorComponents; i++) {
halcanaryeb92cb32016-07-15 13:41:27 -070063 static const SkScalar kColorScale = SkScalarInvert(255);
64 multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000065 }
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.
djsollen@google.com53648ab2013-04-03 12:34:16 +000070 bool dupInput[kColorComponents];
71 dupInput[kColorComponents - 1] = false;
72 for (int i = kColorComponents - 2; i >= 0; i--) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000073 dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
74 }
75
76 if (!dupInput[0] && multiplier[0] == 0) {
halcanaryd11c7262016-03-25 05:52:57 -070077 result->writeText("pop ");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000078 }
79
djsollen@google.com53648ab2013-04-03 12:34:16 +000080 for (int i = 0; i < kColorComponents; i++) {
vandebo@chromium.orgb8622042012-10-22 20:12:40 +000081 // If the next components needs t and this component will consume a
82 // copy, make another copy.
83 if (dupInput[i] && multiplier[i] != 0) {
halcanaryd11c7262016-03-25 05:52:57 -070084 result->writeText("dup ");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000085 }
86
87 if (multiplier[i] == 0) {
halcanaryeb92cb32016-07-15 13:41:27 -070088 SkPDFUtils::AppendColorComponent(prevColor[i], result);
halcanaryd11c7262016-03-25 05:52:57 -070089 result->writeText(" ");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000090 } else {
91 if (multiplier[i] != 1) {
halcanaryd11c7262016-03-25 05:52:57 -070092 SkPDFUtils::AppendScalar(multiplier[i], result);
93 result->writeText(" mul ");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000094 }
95 if (prevColor[i] != 0) {
halcanaryeb92cb32016-07-15 13:41:27 -070096 SkPDFUtils::AppendColorComponent(prevColor[i], result);
halcanaryd11c7262016-03-25 05:52:57 -070097 result->writeText(" add ");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +000098 }
99 }
100
101 if (dupInput[i]) {
halcanaryd11c7262016-03-25 05:52:57 -0700102 result->writeText("exch\n");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000103 }
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 */
128static void gradientFunctionCode(const SkShader::GradientInfo& info,
halcanaryd11c7262016-03-25 05:52:57 -0700129 SkDynamicMemoryWStream* result) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000130 /* 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 */
cabaniere75cdcb2016-06-17 12:38:53 -0700135
twiz@google.com316338a2011-03-09 23:14:04 +0000136 SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
137 ColorTuple *colorData = colorDataAlloc.get();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000138 for (int i = 0; i < info.fColorCount; i++) {
halcanaryeb92cb32016-07-15 13:41:27 -0700139 colorData[i][0] = SkColorGetR(info.fColors[i]);
140 colorData[i][1] = SkColorGetG(info.fColors[i]);
141 colorData[i][2] = SkColorGetB(info.fColors[i]);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000142 }
143
144 // Clamp the initial color.
halcanaryd11c7262016-03-25 05:52:57 -0700145 result->writeText("dup 0 le {pop ");
halcanaryeb92cb32016-07-15 13:41:27 -0700146 SkPDFUtils::AppendColorComponent(colorData[0][0], result);
halcanaryd11c7262016-03-25 05:52:57 -0700147 result->writeText(" ");
halcanaryeb92cb32016-07-15 13:41:27 -0700148 SkPDFUtils::AppendColorComponent(colorData[0][1], result);
halcanaryd11c7262016-03-25 05:52:57 -0700149 result->writeText(" ");
halcanaryeb92cb32016-07-15 13:41:27 -0700150 SkPDFUtils::AppendColorComponent(colorData[0][2], result);
halcanaryd11c7262016-03-25 05:52:57 -0700151 result->writeText(" }\n");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000152
153 // The gradient colors.
edisonn@google.com81353232013-09-17 17:17:47 +0000154 int gradients = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000155 for (int i = 1 ; i < info.fColorCount; i++) {
edisonn@google.com81353232013-09-17 17:17:47 +0000156 if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) {
157 continue;
158 }
159 gradients++;
160
halcanaryd11c7262016-03-25 05:52:57 -0700161 result->writeText("{dup ");
162 SkPDFUtils::AppendScalar(info.fColorOffsets[i], result);
163 result->writeText(" le {");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000164 if (info.fColorOffsets[i - 1] != 0) {
halcanaryd11c7262016-03-25 05:52:57 -0700165 SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result);
166 result->writeText(" sub\n");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000167 }
168
169 interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
djsollen@google.com53648ab2013-04-03 12:34:16 +0000170 colorData[i], colorData[i - 1], result);
halcanaryd11c7262016-03-25 05:52:57 -0700171 result->writeText("}\n");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000172 }
173
174 // Clamp the final color.
halcanaryd11c7262016-03-25 05:52:57 -0700175 result->writeText("{pop ");
halcanaryeb92cb32016-07-15 13:41:27 -0700176 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result);
halcanaryd11c7262016-03-25 05:52:57 -0700177 result->writeText(" ");
halcanaryeb92cb32016-07-15 13:41:27 -0700178 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result);
halcanaryd11c7262016-03-25 05:52:57 -0700179 result->writeText(" ");
halcanaryeb92cb32016-07-15 13:41:27 -0700180 SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000181
edisonn@google.com81353232013-09-17 17:17:47 +0000182 for (int i = 0 ; i < gradients + 1; i++) {
halcanaryd11c7262016-03-25 05:52:57 -0700183 result->writeText("} ifelse\n");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000184 }
185}
186
cabaniere75cdcb2016-06-17 12:38:53 -0700187static 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>();
halcanaryeb92cb32016-07-15 13:41:27 -0700192 c0->appendColorComponent(color1[0]);
193 c0->appendColorComponent(color1[1]);
194 c0->appendColorComponent(color1[2]);
cabaniere75cdcb2016-06-17 12:38:53 -0700195 retval->insertObject("C0", std::move(c0));
196
197 auto c1 = sk_make_sp<SkPDFArray>();
halcanaryeb92cb32016-07-15 13:41:27 -0700198 c1->appendColorComponent(color2[0]);
199 c1->appendColorComponent(color2[1]);
200 c1->appendColorComponent(color2[2]);
cabaniere75cdcb2016-06-17 12:38:53 -0700201 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
214static 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();
cabaniere75cdcb2016-06-17 12:38:53 -0700251 for (int i = 0; i < colorCount; i++) {
halcanaryeb92cb32016-07-15 13:41:27 -0700252 colorData[i][0] = SkColorGetR(colors[i]);
253 colorData[i][1] = SkColorGetG(colors[i]);
254 colorData[i][2] = SkColorGetB(colors[i]);
cabaniere75cdcb2016-06-17 12:38:53 -0700255 }
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
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000289/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
halcanaryd11c7262016-03-25 05:52:57 -0700290static void tileModeCode(SkShader::TileMode mode,
291 SkDynamicMemoryWStream* result) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000292 if (mode == SkShader::kRepeat_TileMode) {
halcanaryd11c7262016-03-25 05:52:57 -0700293 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)
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000295 return;
296 }
297
298 if (mode == SkShader::kMirror_TileMode) {
299 // Map t mod 2 into [0, 1, 1, 0].
300 // Code Stack
halcanaryd11c7262016-03-25 05:52:57 -0700301 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
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000312 }
313}
314
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000315/**
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 */
halcanaryd11c7262016-03-25 05:52:57 -0700324static void apply_perspective_to_coordinates(
325 const SkMatrix& inversePerspectiveMatrix,
326 SkDynamicMemoryWStream* code) {
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000327 if (!inversePerspectiveMatrix.hasPerspective()) {
halcanaryd11c7262016-03-25 05:52:57 -0700328 return;
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000329 }
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
halcanaryd11c7262016-03-25 05:52:57 -0700344 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)
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000360}
361
halcanaryd11c7262016-03-25 05:52:57 -0700362static void linearCode(const SkShader::GradientInfo& info,
363 const SkMatrix& perspectiveRemover,
364 SkDynamicMemoryWStream* function) {
365 function->writeText("{");
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000366
halcanaryd11c7262016-03-25 05:52:57 -0700367 apply_perspective_to_coordinates(perspectiveRemover, function);
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000368
halcanaryd11c7262016-03-25 05:52:57 -0700369 function->writeText("pop\n"); // Just ditch the y value.
370 tileModeCode(info.fTileMode, function);
371 gradientFunctionCode(info, function);
372 function->writeText("}");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000373}
374
halcanaryd11c7262016-03-25 05:52:57 -0700375static void radialCode(const SkShader::GradientInfo& info,
376 const SkMatrix& perspectiveRemover,
377 SkDynamicMemoryWStream* function) {
378 function->writeText("{");
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000379
halcanaryd11c7262016-03-25 05:52:57 -0700380 apply_perspective_to_coordinates(perspectiveRemover, function);
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000381
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000382 // Find the distance from the origin.
halcanaryd11c7262016-03-25 05:52:57 -0700383 function->writeText("dup " // x y y
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000384 "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
halcanaryd11c7262016-03-25 05:52:57 -0700391 tileModeCode(info.fTileMode, function);
392 gradientFunctionCode(info, function);
393 function->writeText("}");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000394}
395
rileya@google.com62197282012-07-10 20:05:23 +0000396/* Conical gradient shader, based on the Canvas spec for radial gradients
rmistry@google.comd6176b02012-08-23 18:14:13 +0000397 See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
rileya@google.com62197282012-07-10 20:05:23 +0000398 */
halcanaryd11c7262016-03-25 05:52:57 -0700399static void twoPointConicalCode(const SkShader::GradientInfo& info,
400 const SkMatrix& perspectiveRemover,
401 SkDynamicMemoryWStream* function) {
rileya@google.com62197282012-07-10 20:05:23 +0000402 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];
rmistry@google.comd6176b02012-08-23 18:14:13 +0000406 SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) -
rileya@google.com62197282012-07-10 20:05:23 +0000407 SkScalarMul(dr, dr);
408
409 // First compute t, if the pixel falls outside the cone, then we'll end
410 // with 'false' on the stack, otherwise we'll push 'true' with t below it
411
412 // We start with a stack of (x y), copy it and then consume one copy in
413 // order to calculate b and the other to calculate c.
halcanaryd11c7262016-03-25 05:52:57 -0700414 function->writeText("{");
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000415
halcanaryd11c7262016-03-25 05:52:57 -0700416 apply_perspective_to_coordinates(perspectiveRemover, function);
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000417
halcanaryd11c7262016-03-25 05:52:57 -0700418 function->writeText("2 copy ");
rileya@google.com62197282012-07-10 20:05:23 +0000419
420 // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
halcanaryd11c7262016-03-25 05:52:57 -0700421 SkPDFUtils::AppendScalar(dy, function);
422 function->writeText(" mul exch ");
423 SkPDFUtils::AppendScalar(dx, function);
424 function->writeText(" mul add ");
425 SkPDFUtils::AppendScalar(SkScalarMul(r0, dr), function);
426 function->writeText(" add -2 mul dup dup mul\n");
rileya@google.com62197282012-07-10 20:05:23 +0000427
428 // c = x^2 + y^2 + radius0^2
halcanaryd11c7262016-03-25 05:52:57 -0700429 function->writeText("4 2 roll dup mul exch dup mul add ");
430 SkPDFUtils::AppendScalar(SkScalarMul(r0, r0), function);
431 function->writeText(" sub dup 4 1 roll\n");
rileya@google.com62197282012-07-10 20:05:23 +0000432
433 // Contents of the stack at this point: c, b, b^2, c
434
435 // if a = 0, then we collapse to a simpler linear case
436 if (a == 0) {
437
438 // t = -c/b
halcanaryd11c7262016-03-25 05:52:57 -0700439 function->writeText("pop pop div neg dup ");
rileya@google.com62197282012-07-10 20:05:23 +0000440
441 // compute radius(t)
halcanaryd11c7262016-03-25 05:52:57 -0700442 SkPDFUtils::AppendScalar(dr, function);
443 function->writeText(" mul ");
444 SkPDFUtils::AppendScalar(r0, function);
445 function->writeText(" add\n");
rileya@google.com62197282012-07-10 20:05:23 +0000446
447 // if r(t) < 0, then it's outside the cone
halcanaryd11c7262016-03-25 05:52:57 -0700448 function->writeText("0 lt {pop false} {true} ifelse\n");
rileya@google.com62197282012-07-10 20:05:23 +0000449
450 } else {
451
452 // quadratic case: the Canvas spec wants the largest
453 // root t for which radius(t) > 0
rmistry@google.comd6176b02012-08-23 18:14:13 +0000454
rileya@google.com62197282012-07-10 20:05:23 +0000455 // compute the discriminant (b^2 - 4ac)
halcanaryd11c7262016-03-25 05:52:57 -0700456 SkPDFUtils::AppendScalar(SkScalarMul(SkIntToScalar(4), a), function);
457 function->writeText(" mul sub dup\n");
rileya@google.com62197282012-07-10 20:05:23 +0000458
459 // if d >= 0, proceed
halcanaryd11c7262016-03-25 05:52:57 -0700460 function->writeText("0 ge {\n");
rileya@google.com62197282012-07-10 20:05:23 +0000461
462 // an intermediate value we'll use to compute the roots:
463 // q = -0.5 * (b +/- sqrt(d))
halcanaryd11c7262016-03-25 05:52:57 -0700464 function->writeText("sqrt exch dup 0 lt {exch -1 mul} if");
465 function->writeText(" add -0.5 mul dup\n");
rileya@google.com62197282012-07-10 20:05:23 +0000466
467 // first root = q / a
halcanaryd11c7262016-03-25 05:52:57 -0700468 SkPDFUtils::AppendScalar(a, function);
469 function->writeText(" div\n");
rileya@google.com62197282012-07-10 20:05:23 +0000470
471 // second root = c / q
halcanaryd11c7262016-03-25 05:52:57 -0700472 function->writeText("3 1 roll div\n");
rileya@google.com62197282012-07-10 20:05:23 +0000473
474 // put the larger root on top of the stack
halcanaryd11c7262016-03-25 05:52:57 -0700475 function->writeText("2 copy gt {exch} if\n");
rileya@google.com62197282012-07-10 20:05:23 +0000476
477 // compute radius(t) for larger root
halcanaryd11c7262016-03-25 05:52:57 -0700478 function->writeText("dup ");
479 SkPDFUtils::AppendScalar(dr, function);
480 function->writeText(" mul ");
481 SkPDFUtils::AppendScalar(r0, function);
482 function->writeText(" add\n");
rileya@google.com62197282012-07-10 20:05:23 +0000483
484 // if r(t) > 0, we have our t, pop off the smaller root and we're done
halcanaryd11c7262016-03-25 05:52:57 -0700485 function->writeText(" 0 gt {exch pop true}\n");
rileya@google.com62197282012-07-10 20:05:23 +0000486
487 // otherwise, throw out the larger one and try the smaller root
halcanaryd11c7262016-03-25 05:52:57 -0700488 function->writeText("{pop dup\n");
489 SkPDFUtils::AppendScalar(dr, function);
490 function->writeText(" mul ");
491 SkPDFUtils::AppendScalar(r0, function);
492 function->writeText(" add\n");
rileya@google.com62197282012-07-10 20:05:23 +0000493
494 // if r(t) < 0, push false, otherwise the smaller root is our t
halcanaryd11c7262016-03-25 05:52:57 -0700495 function->writeText("0 le {pop false} {true} ifelse\n");
496 function->writeText("} ifelse\n");
rileya@google.com62197282012-07-10 20:05:23 +0000497
498 // d < 0, clear the stack and push false
halcanaryd11c7262016-03-25 05:52:57 -0700499 function->writeText("} {pop pop pop false} ifelse\n");
rileya@google.com62197282012-07-10 20:05:23 +0000500 }
501
502 // if the pixel is in the cone, proceed to compute a color
halcanaryd11c7262016-03-25 05:52:57 -0700503 function->writeText("{");
504 tileModeCode(info.fTileMode, function);
505 gradientFunctionCode(info, function);
rileya@google.com62197282012-07-10 20:05:23 +0000506
507 // otherwise, just write black
halcanaryd11c7262016-03-25 05:52:57 -0700508 function->writeText("} {0 0 0} ifelse }");
rileya@google.com62197282012-07-10 20:05:23 +0000509}
510
halcanaryd11c7262016-03-25 05:52:57 -0700511static void sweepCode(const SkShader::GradientInfo& info,
512 const SkMatrix& perspectiveRemover,
513 SkDynamicMemoryWStream* function) {
514 function->writeText("{exch atan 360 div\n");
515 tileModeCode(info.fTileMode, function);
516 gradientFunctionCode(info, function);
517 function->writeText("}");
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000518}
519
Florin Malitac54d8db2014-12-10 12:02:16 -0500520static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) {
521 SkAutoCanvasRestore acr(canvas, true);
522 canvas->concat(matrix);
523 canvas->drawBitmap(bm, 0, 0);
524}
525
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000526class SkPDFShader::State {
527public:
528 SkShader::GradientType fType;
529 SkShader::GradientInfo fInfo;
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000530 SkAutoFree fColorData; // This provides storage for arrays in fInfo.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000531 SkMatrix fCanvasTransform;
532 SkMatrix fShaderTransform;
533 SkIRect fBBox;
534
535 SkBitmap fImage;
halcanary895f3f02016-04-01 11:51:00 -0700536 SkBitmapKey fBitmapKey;
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000537 SkShader::TileMode fImageTileModes[2];
538
reedfe630452016-03-25 09:08:00 -0700539 State(SkShader* shader, const SkMatrix& canvasTransform,
fmalitac3796c72015-01-13 08:06:11 -0800540 const SkIRect& bbox, SkScalar rasterScale);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000541
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000542 bool operator==(const State& b) const;
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000543
544 SkPDFShader::State* CreateAlphaToLuminosityState() const;
545 SkPDFShader::State* CreateOpaqueState() const;
546
547 bool GradientHasAlpha() const;
548
549private:
550 State(const State& other);
551 State operator=(const State& rhs);
552 void AllocateGradientInfoStorage();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000553};
554
halcanary530ea8e2015-01-23 06:17:35 -0800555////////////////////////////////////////////////////////////////////////////////
556
halcanary2e3f9d82015-02-27 12:41:03 -0800557SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
halcanary389666b2016-04-01 11:43:39 -0700558 : SkPDFDict("Pattern"), fShaderState(state) {
559 state->fImage.reset();
560}
halcanary530ea8e2015-01-23 06:17:35 -0800561
halcanary547019e2015-03-24 08:12:55 -0700562SkPDFFunctionShader::~SkPDFFunctionShader() {}
halcanarybc59ac62015-01-23 04:18:53 -0800563
halcanary530ea8e2015-01-23 06:17:35 -0800564bool SkPDFFunctionShader::equals(const SkPDFShader::State& state) const {
565 return state == *fShaderState;
halcanaryfb62b3d2015-01-21 09:59:14 -0800566}
567
halcanary530ea8e2015-01-23 06:17:35 -0800568////////////////////////////////////////////////////////////////////////////////
569
halcanary2e3f9d82015-02-27 12:41:03 -0800570SkPDFAlphaFunctionShader::SkPDFAlphaFunctionShader(SkPDFShader::State* state)
halcanary389666b2016-04-01 11:43:39 -0700571 : fShaderState(state) {
572 state->fImage.reset();
573}
halcanary530ea8e2015-01-23 06:17:35 -0800574
575bool SkPDFAlphaFunctionShader::equals(const SkPDFShader::State& state) const {
576 return state == *fShaderState;
577}
578
halcanary2e3f9d82015-02-27 12:41:03 -0800579SkPDFAlphaFunctionShader::~SkPDFAlphaFunctionShader() {}
halcanary530ea8e2015-01-23 06:17:35 -0800580
halcanary530ea8e2015-01-23 06:17:35 -0800581////////////////////////////////////////////////////////////////////////////////
582
halcanary2e3f9d82015-02-27 12:41:03 -0800583SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state)
halcanary389666b2016-04-01 11:43:39 -0700584 : fShaderState(state) {
585 state->fImage.reset();
586}
halcanary530ea8e2015-01-23 06:17:35 -0800587
588bool SkPDFImageShader::equals(const SkPDFShader::State& state) const {
589 return state == *fShaderState;
590}
591
halcanary547019e2015-03-24 08:12:55 -0700592SkPDFImageShader::~SkPDFImageShader() {}
halcanary530ea8e2015-01-23 06:17:35 -0800593
halcanary530ea8e2015-01-23 06:17:35 -0800594////////////////////////////////////////////////////////////////////////////////
595
596static SkPDFObject* get_pdf_shader_by_state(
halcanary989da4a2016-03-21 14:33:17 -0700597 SkPDFDocument* doc,
halcanary792c80f2015-02-20 07:21:05 -0800598 SkScalar dpi,
halcanaryb8fb9932016-03-28 07:58:30 -0700599 std::unique_ptr<SkPDFShader::State>* autoState) {
halcanary530ea8e2015-01-23 06:17:35 -0800600 const SkPDFShader::State& state = **autoState;
halcanary989da4a2016-03-21 14:33:17 -0700601 SkPDFCanon* canon = doc->canon();
halcanarybc59ac62015-01-23 04:18:53 -0800602 if (state.fType == SkShader::kNone_GradientType && state.fImage.isNull()) {
vandebo@chromium.orgda6c5692012-06-28 21:37:20 +0000603 // TODO(vandebo) This drops SKComposeShader on the floor. We could
604 // handle compose shader by pulling things up to a layer, drawing with
605 // the first shader, applying the xfer mode and drawing again with the
606 // second shader, then applying the layer to the original drawing.
halcanary96fcdcc2015-08-27 07:41:13 -0700607 return nullptr;
halcanary530ea8e2015-01-23 06:17:35 -0800608 } else if (state.fType == SkShader::kNone_GradientType) {
halcanary792c80f2015-02-20 07:21:05 -0800609 SkPDFObject* shader = canon->findImageShader(state);
halcanary530ea8e2015-01-23 06:17:35 -0800610 return shader ? SkRef(shader)
halcanary989da4a2016-03-21 14:33:17 -0700611 : SkPDFImageShader::Create(doc, dpi, autoState);
halcanary792c80f2015-02-20 07:21:05 -0800612 } else if (state.GradientHasAlpha()) {
613 SkPDFObject* shader = canon->findAlphaShader(state);
614 return shader ? SkRef(shader)
halcanary989da4a2016-03-21 14:33:17 -0700615 : SkPDFAlphaFunctionShader::Create(doc, dpi, autoState);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000616 } else {
halcanary792c80f2015-02-20 07:21:05 -0800617 SkPDFObject* shader = canon->findFunctionShader(state);
618 return shader ? SkRef(shader)
619 : SkPDFFunctionShader::Create(canon, autoState);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000620 }
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000621}
622
623// static
halcanary989da4a2016-03-21 14:33:17 -0700624SkPDFObject* SkPDFShader::GetPDFShader(SkPDFDocument* doc,
halcanary792c80f2015-02-20 07:21:05 -0800625 SkScalar dpi,
reedfe630452016-03-25 09:08:00 -0700626 SkShader* shader,
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000627 const SkMatrix& matrix,
fmalitac3796c72015-01-13 08:06:11 -0800628 const SkIRect& surfaceBBox,
629 SkScalar rasterScale) {
halcanaryb8fb9932016-03-28 07:58:30 -0700630 std::unique_ptr<SkPDFShader::State> state(new State(shader, matrix, surfaceBBox, rasterScale));
halcanary989da4a2016-03-21 14:33:17 -0700631 return get_pdf_shader_by_state(doc, dpi, &state);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000632}
633
halcanary8103a342016-03-08 15:10:16 -0800634static sk_sp<SkPDFDict> get_gradient_resource_dict(
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000635 SkPDFObject* functionShader,
636 SkPDFObject* gState) {
halcanary2b861552015-04-09 13:27:40 -0700637 SkTDArray<SkPDFObject*> patterns;
638 if (functionShader) {
639 patterns.push(functionShader);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000640 }
halcanary2b861552015-04-09 13:27:40 -0700641 SkTDArray<SkPDFObject*> graphicStates;
642 if (gState) {
643 graphicStates.push(gState);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000644 }
halcanary8103a342016-03-08 15:10:16 -0800645 return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000646}
647
648static void populate_tiling_pattern_dict(SkPDFDict* pattern,
halcanary547019e2015-03-24 08:12:55 -0700649 SkRect& bbox,
650 SkPDFDict* resources,
651 const SkMatrix& matrix) {
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000652 const int kTiling_PatternType = 1;
653 const int kColoredTilingPattern_PaintType = 1;
654 const int kConstantSpacing_TilingType = 1;
655
656 pattern->insertName("Type", "Pattern");
657 pattern->insertInt("PatternType", kTiling_PatternType);
658 pattern->insertInt("PaintType", kColoredTilingPattern_PaintType);
659 pattern->insertInt("TilingType", kConstantSpacing_TilingType);
halcanarya25b3372015-04-27 14:00:09 -0700660 pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox));
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000661 pattern->insertScalar("XStep", bbox.width());
662 pattern->insertScalar("YStep", bbox.height());
halcanarye94ea622016-03-09 07:52:09 -0800663 pattern->insertObject("Resources", sk_ref_sp(resources));
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000664 if (!matrix.isIdentity()) {
halcanarya25b3372015-04-27 14:00:09 -0700665 pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000666 }
667}
668
669/**
670 * Creates a content stream which fills the pattern P0 across bounds.
671 * @param gsIndex A graphics state resource index to apply, or <0 if no
672 * graphics state to apply.
673 */
halcanary29ed2ae2016-06-29 06:31:32 -0700674static SkStreamAsset* create_pattern_fill_content(int gsIndex, SkRect& bounds) {
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000675 SkDynamicMemoryWStream content;
676 if (gsIndex >= 0) {
677 SkPDFUtils::ApplyGraphicState(gsIndex, &content);
678 }
679 SkPDFUtils::ApplyPattern(0, &content);
680 SkPDFUtils::AppendRectangle(bounds, &content);
681 SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType,
682 &content);
683
684 return content.detachAsStream();
685}
686
687/**
688 * Creates a ExtGState with the SMask set to the luminosityShader in
689 * luminosity mode. The shader pattern extends to the bbox.
690 */
halcanary1437c1e2016-03-13 18:30:24 -0700691static sk_sp<SkPDFObject> create_smask_graphic_state(
halcanary989da4a2016-03-21 14:33:17 -0700692 SkPDFDocument* doc, SkScalar dpi, const SkPDFShader::State& state) {
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000693 SkRect bbox;
halcanarybc59ac62015-01-23 04:18:53 -0800694 bbox.set(state.fBBox);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000695
halcanaryb8fb9932016-03-28 07:58:30 -0700696 std::unique_ptr<SkPDFShader::State> alphaToLuminosityState(
halcanarybc59ac62015-01-23 04:18:53 -0800697 state.CreateAlphaToLuminosityState());
halcanary48810a02016-03-07 14:57:50 -0800698 sk_sp<SkPDFObject> luminosityShader(
halcanary989da4a2016-03-21 14:33:17 -0700699 get_pdf_shader_by_state(doc, dpi, &alphaToLuminosityState));
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000700
halcanary29ed2ae2016-06-29 06:31:32 -0700701 std::unique_ptr<SkStreamAsset> alphaStream(create_pattern_fill_content(-1, bbox));
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000702
halcanary8103a342016-03-08 15:10:16 -0800703 auto resources =
704 get_gradient_resource_dict(luminosityShader.get(), nullptr);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000705
halcanary4b1e17e2016-07-27 14:49:46 -0700706 auto alphaMask = SkPDFMakeFormXObject(std::move(alphaStream),
707 SkPDFUtils::RectToArray(bbox),
708 std::move(resources),
halcanary5abbb442016-07-29 08:41:33 -0700709 SkMatrix::I(),
halcanary4b1e17e2016-07-27 14:49:46 -0700710 "DeviceRGB");
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000711 return SkPDFGraphicState::GetSMaskGraphicState(
712 alphaMask.get(), false,
halcanary989da4a2016-03-21 14:33:17 -0700713 SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000714}
715
halcanary530ea8e2015-01-23 06:17:35 -0800716SkPDFAlphaFunctionShader* SkPDFAlphaFunctionShader::Create(
halcanary989da4a2016-03-21 14:33:17 -0700717 SkPDFDocument* doc,
halcanary792c80f2015-02-20 07:21:05 -0800718 SkScalar dpi,
halcanaryb8fb9932016-03-28 07:58:30 -0700719 std::unique_ptr<SkPDFShader::State>* autoState) {
halcanarybc59ac62015-01-23 04:18:53 -0800720 const SkPDFShader::State& state = **autoState;
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000721 SkRect bbox;
halcanarybc59ac62015-01-23 04:18:53 -0800722 bbox.set(state.fBBox);
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000723
halcanaryb8fb9932016-03-28 07:58:30 -0700724 std::unique_ptr<SkPDFShader::State> opaqueState(state.CreateOpaqueState());
halcanarybc59ac62015-01-23 04:18:53 -0800725
halcanary48810a02016-03-07 14:57:50 -0800726 sk_sp<SkPDFObject> colorShader(
halcanary989da4a2016-03-21 14:33:17 -0700727 get_pdf_shader_by_state(doc, dpi, &opaqueState));
halcanarybc59ac62015-01-23 04:18:53 -0800728 if (!colorShader) {
halcanary96fcdcc2015-08-27 07:41:13 -0700729 return nullptr;
halcanarybc59ac62015-01-23 04:18:53 -0800730 }
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000731
732 // Create resource dict with alpha graphics state as G0 and
733 // pattern shader as P0, then write content stream.
halcanary989da4a2016-03-21 14:33:17 -0700734 auto alphaGs = create_smask_graphic_state(doc, dpi, state);
halcanarybc59ac62015-01-23 04:18:53 -0800735
736 SkPDFAlphaFunctionShader* alphaFunctionShader =
mtklein18300a32016-03-16 13:53:35 -0700737 new SkPDFAlphaFunctionShader(autoState->release());
halcanarybc59ac62015-01-23 04:18:53 -0800738
halcanary8103a342016-03-08 15:10:16 -0800739 auto resourceDict =
740 get_gradient_resource_dict(colorShader.get(), alphaGs.get());
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000741
halcanary29ed2ae2016-06-29 06:31:32 -0700742 std::unique_ptr<SkStreamAsset> colorStream(
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000743 create_pattern_fill_content(0, bbox));
halcanaryac0e00d2016-07-27 11:12:23 -0700744 alphaFunctionShader->setData(std::move(colorStream));
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000745
halcanary547019e2015-03-24 08:12:55 -0700746 populate_tiling_pattern_dict(alphaFunctionShader, bbox, resourceDict.get(),
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000747 SkMatrix::I());
halcanary989da4a2016-03-21 14:33:17 -0700748 doc->canon()->addAlphaShader(alphaFunctionShader);
halcanarybc59ac62015-01-23 04:18:53 -0800749 return alphaFunctionShader;
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000750}
751
edisonn@google.com83d8eda2013-10-24 13:19:28 +0000752// Finds affine and persp such that in = affine * persp.
753// but it returns the inverse of perspective matrix.
754static bool split_perspective(const SkMatrix in, SkMatrix* affine,
755 SkMatrix* perspectiveInverse) {
756 const SkScalar p2 = in[SkMatrix::kMPersp2];
757
758 if (SkScalarNearlyZero(p2)) {
759 return false;
760 }
761
762 const SkScalar zero = SkIntToScalar(0);
763 const SkScalar one = SkIntToScalar(1);
764
765 const SkScalar sx = in[SkMatrix::kMScaleX];
766 const SkScalar kx = in[SkMatrix::kMSkewX];
767 const SkScalar tx = in[SkMatrix::kMTransX];
768 const SkScalar ky = in[SkMatrix::kMSkewY];
769 const SkScalar sy = in[SkMatrix::kMScaleY];
770 const SkScalar ty = in[SkMatrix::kMTransY];
771 const SkScalar p0 = in[SkMatrix::kMPersp0];
772 const SkScalar p1 = in[SkMatrix::kMPersp1];
773
774 // Perspective matrix would be:
775 // 1 0 0
776 // 0 1 0
777 // p0 p1 p2
778 // But we need the inverse of persp.
779 perspectiveInverse->setAll(one, zero, zero,
780 zero, one, zero,
781 -p0/p2, -p1/p2, 1/p2);
782
783 affine->setAll(sx - p0 * tx / p2, kx - p1 * tx / p2, tx / p2,
784 ky - p0 * ty / p2, sy - p1 * ty / p2, ty / p2,
785 zero, zero, one);
786
787 return true;
788}
789
halcanary1437c1e2016-03-13 18:30:24 -0700790sk_sp<SkPDFArray> SkPDFShader::MakeRangeObject() {
791 auto range = sk_make_sp<SkPDFArray>();
halcanarybc59ac62015-01-23 04:18:53 -0800792 range->reserve(6);
793 range->appendInt(0);
794 range->appendInt(1);
795 range->appendInt(0);
796 range->appendInt(1);
797 range->appendInt(0);
798 range->appendInt(1);
799 return range;
800}
halcanarybc59ac62015-01-23 04:18:53 -0800801
halcanaryd11c7262016-03-25 05:52:57 -0700802static sk_sp<SkPDFStream> make_ps_function(
803 std::unique_ptr<SkStreamAsset> psCode,
804 SkPDFArray* domain,
805 sk_sp<SkPDFObject> range) {
halcanaryac0e00d2016-07-27 11:12:23 -0700806 auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
halcanarybc59ac62015-01-23 04:18:53 -0800807 result->insertInt("FunctionType", 4);
halcanarye94ea622016-03-09 07:52:09 -0800808 result->insertObject("Domain", sk_ref_sp(domain));
halcanary1437c1e2016-03-13 18:30:24 -0700809 result->insertObject("Range", std::move(range));
halcanarybc59ac62015-01-23 04:18:53 -0800810 return result;
811}
812
cabaniere75cdcb2016-06-17 12:38:53 -0700813// catch cases where the inner just touches the outer circle
814// and make the inner circle just inside the outer one to match raster
815static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
816 // detect touching circles
817 SkScalar distance = SkPoint::Distance(p1, p2);
818 SkScalar subtractRadii = fabs(r1 - r2);
819 if (fabs(distance - subtractRadii) < 0.002f) {
820 if (r1 > r2) {
821 r1 += 0.002f;
822 } else {
823 r2 += 0.002f;
824 }
825 }
826}
827
halcanary530ea8e2015-01-23 06:17:35 -0800828SkPDFFunctionShader* SkPDFFunctionShader::Create(
halcanaryb8fb9932016-03-28 07:58:30 -0700829 SkPDFCanon* canon, std::unique_ptr<SkPDFShader::State>* autoState) {
halcanarybc59ac62015-01-23 04:18:53 -0800830 const SkPDFShader::State& state = **autoState;
831
halcanaryd11c7262016-03-25 05:52:57 -0700832 void (*codeFunction)(const SkShader::GradientInfo& info,
833 const SkMatrix& perspectiveRemover,
834 SkDynamicMemoryWStream* function) = nullptr;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000835 SkPoint transformPoints[2];
halcanarybc59ac62015-01-23 04:18:53 -0800836 const SkShader::GradientInfo* info = &state.fInfo;
halcanarybc59ac62015-01-23 04:18:53 -0800837 SkMatrix finalMatrix = state.fCanvasTransform;
838 finalMatrix.preConcat(state.fShaderTransform);
commit-bot@chromium.orgace22692013-06-12 21:33:02 +0000839
cabaniere75cdcb2016-06-17 12:38:53 -0700840 bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType ||
841 state.fType == SkShader::kRadial_GradientType ||
842 state.fType == SkShader::kConical_GradientType) &&
843 info->fTileMode == SkShader::kClamp_TileMode &&
844 !finalMatrix.hasPerspective();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000845
halcanaryece83922016-03-08 08:32:12 -0800846 auto domain = sk_make_sp<SkPDFArray>();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000847
cabaniere75cdcb2016-06-17 12:38:53 -0700848 int32_t shadingType = 1;
849 auto pdfShader = sk_make_sp<SkPDFDict>();
halcanaryfb62b3d2015-01-21 09:59:14 -0800850 // The two point radial gradient further references
halcanarybc59ac62015-01-23 04:18:53 -0800851 // state.fInfo
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000852 // in translating from x, y coordinates to the t parameter. So, we have
853 // to transform the points and radii according to the calculated matrix.
cabaniere75cdcb2016-06-17 12:38:53 -0700854 if (doStitchFunctions) {
855 pdfShader->insertObject("Function", gradientStitchCode(*info));
856 shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3;
857
858 auto extend = sk_make_sp<SkPDFArray>();
859 extend->reserve(2);
860 extend->appendBool(true);
861 extend->appendBool(true);
862 pdfShader->insertObject("Extend", std::move(extend));
863
864 auto coords = sk_make_sp<SkPDFArray>();
865 if (state.fType == SkShader::kConical_GradientType) {
866 coords->reserve(6);
867 SkScalar r1 = info->fRadius[0];
868 SkScalar r2 = info->fRadius[1];
869 SkPoint pt1 = info->fPoint[0];
870 SkPoint pt2 = info->fPoint[1];
871 FixUpRadius(pt1, r1, pt2, r2);
872
873 coords->appendScalar(pt1.fX);
874 coords->appendScalar(pt1.fY);
875 coords->appendScalar(r1);
876
877 coords->appendScalar(pt2.fX);
878 coords->appendScalar(pt2.fY);
879 coords->appendScalar(r2);
880 } else if (state.fType == SkShader::kRadial_GradientType) {
881 coords->reserve(6);
882 const SkPoint& pt1 = info->fPoint[0];
883
884 coords->appendScalar(pt1.fX);
885 coords->appendScalar(pt1.fY);
886 coords->appendScalar(0);
887
888 coords->appendScalar(pt1.fX);
889 coords->appendScalar(pt1.fY);
890 coords->appendScalar(info->fRadius[0]);
891 } else {
892 coords->reserve(4);
893 const SkPoint& pt1 = info->fPoint[0];
894 const SkPoint& pt2 = info->fPoint[1];
895
896 coords->appendScalar(pt1.fX);
897 coords->appendScalar(pt1.fY);
898
899 coords->appendScalar(pt2.fX);
900 coords->appendScalar(pt2.fY);
901 }
902
903 pdfShader->insertObject("Coords", std::move(coords));
904 } else {
905 // Depending on the type of the gradient, we want to transform the
906 // coordinate space in different ways.
907 transformPoints[0] = info->fPoint[0];
908 transformPoints[1] = info->fPoint[1];
909 switch (state.fType) {
910 case SkShader::kLinear_GradientType:
911 codeFunction = &linearCode;
912 break;
913 case SkShader::kRadial_GradientType:
914 transformPoints[1] = transformPoints[0];
915 transformPoints[1].fX += info->fRadius[0];
916 codeFunction = &radialCode;
917 break;
918 case SkShader::kConical_GradientType: {
919 transformPoints[1] = transformPoints[0];
920 transformPoints[1].fX += SK_Scalar1;
921 codeFunction = &twoPointConicalCode;
922 break;
923 }
924 case SkShader::kSweep_GradientType:
925 transformPoints[1] = transformPoints[0];
926 transformPoints[1].fX += SK_Scalar1;
927 codeFunction = &sweepCode;
928 break;
929 case SkShader::kColor_GradientType:
930 case SkShader::kNone_GradientType:
931 default:
932 return nullptr;
933 }
934
935 // Move any scaling (assuming a unit gradient) or translation
936 // (and rotation for linear gradient), of the final gradient from
937 // info->fPoints to the matrix (updating bbox appropriately). Now
938 // the gradient can be drawn on on the unit segment.
939 SkMatrix mapperMatrix;
940 unitToPointsMatrix(transformPoints, &mapperMatrix);
941
942 finalMatrix.preConcat(mapperMatrix);
943
944 // Preserves as much as posible in the final matrix, and only removes
945 // the perspective. The inverse of the perspective is stored in
946 // perspectiveInverseOnly matrix and has 3 useful numbers
947 // (p0, p1, p2), while everything else is either 0 or 1.
948 // In this way the shader will handle it eficiently, with minimal code.
949 SkMatrix perspectiveInverseOnly = SkMatrix::I();
950 if (finalMatrix.hasPerspective()) {
951 if (!split_perspective(finalMatrix,
952 &finalMatrix, &perspectiveInverseOnly)) {
953 return nullptr;
954 }
955 }
956
957 SkRect bbox;
958 bbox.set(state.fBBox);
959 if (!inverse_transform_bbox(finalMatrix, &bbox)) {
halcanary96fcdcc2015-08-27 07:41:13 -0700960 return nullptr;
vandebo@chromium.orgb0549902012-04-13 20:45:46 +0000961 }
cabaniere75cdcb2016-06-17 12:38:53 -0700962 domain->reserve(4);
963 domain->appendScalar(bbox.fLeft);
964 domain->appendScalar(bbox.fRight);
965 domain->appendScalar(bbox.fTop);
966 domain->appendScalar(bbox.fBottom);
967
968 SkDynamicMemoryWStream functionCode;
969
970 if (state.fType == SkShader::kConical_GradientType) {
971 SkShader::GradientInfo twoPointRadialInfo = *info;
972 SkMatrix inverseMapperMatrix;
973 if (!mapperMatrix.invert(&inverseMapperMatrix)) {
974 return nullptr;
975 }
976 inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
977 twoPointRadialInfo.fRadius[0] =
978 inverseMapperMatrix.mapRadius(info->fRadius[0]);
979 twoPointRadialInfo.fRadius[1] =
980 inverseMapperMatrix.mapRadius(info->fRadius[1]);
981 codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode);
982 } else {
983 codeFunction(*info, perspectiveInverseOnly, &functionCode);
984 }
985
986 pdfShader->insertObject("Domain", sk_ref_sp(domain.get()));
987
988 // Call canon->makeRangeObject() instead of
989 // SkPDFShader::MakeRangeObject() so that the canon can
990 // deduplicate.
991 std::unique_ptr<SkStreamAsset> functionStream(
992 functionCode.detachAsStream());
993 auto function = make_ps_function(std::move(functionStream), domain.get(),
994 canon->makeRangeObject());
995 pdfShader->insertObjRef("Function", std::move(function));
vandebo@chromium.orgda912d62011-03-08 18:31:02 +0000996 }
997
cabaniere75cdcb2016-06-17 12:38:53 -0700998 pdfShader->insertInt("ShadingType", shadingType);
reed@google.comc789cf12011-07-20 12:14:33 +0000999 pdfShader->insertName("ColorSpace", "DeviceRGB");
halcanarybc59ac62015-01-23 04:18:53 -08001000
halcanaryece83922016-03-08 08:32:12 -08001001 sk_sp<SkPDFFunctionShader> pdfFunctionShader(
mtklein18300a32016-03-16 13:53:35 -07001002 new SkPDFFunctionShader(autoState->release()));
halcanarybc59ac62015-01-23 04:18:53 -08001003 pdfFunctionShader->insertInt("PatternType", 2);
halcanarya25b3372015-04-27 14:00:09 -07001004 pdfFunctionShader->insertObject("Matrix",
1005 SkPDFUtils::MatrixToArray(finalMatrix));
halcanary8103a342016-03-08 15:10:16 -08001006 pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
halcanarybc59ac62015-01-23 04:18:53 -08001007
halcanaryfcad44b2016-03-06 14:47:10 -08001008 canon->addFunctionShader(pdfFunctionShader.get());
1009 return pdfFunctionShader.release();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001010}
1011
halcanary530ea8e2015-01-23 06:17:35 -08001012SkPDFImageShader* SkPDFImageShader::Create(
halcanary989da4a2016-03-21 14:33:17 -07001013 SkPDFDocument* doc,
halcanary792c80f2015-02-20 07:21:05 -08001014 SkScalar dpi,
halcanaryb8fb9932016-03-28 07:58:30 -07001015 std::unique_ptr<SkPDFShader::State>* autoState) {
halcanarybc59ac62015-01-23 04:18:53 -08001016 const SkPDFShader::State& state = **autoState;
1017
1018 state.fImage.lockPixels();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001019
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001020 // The image shader pattern cell will be drawn into a separate device
1021 // in pattern cell space (no scaling on the bitmap, though there may be
1022 // translations so that all content is in the device, coordinates > 0).
1023
1024 // Map clip bounds to shader space to ensure the device is large enough
1025 // to handle fake clamping.
halcanarybc59ac62015-01-23 04:18:53 -08001026 SkMatrix finalMatrix = state.fCanvasTransform;
1027 finalMatrix.preConcat(state.fShaderTransform);
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001028 SkRect deviceBounds;
halcanarybc59ac62015-01-23 04:18:53 -08001029 deviceBounds.set(state.fBBox);
fmalita7b4d4c72015-02-12 13:56:50 -08001030 if (!inverse_transform_bbox(finalMatrix, &deviceBounds)) {
halcanary96fcdcc2015-08-27 07:41:13 -07001031 return nullptr;
vandebo@chromium.org386dfc02012-04-17 22:31:52 +00001032 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001033
halcanarybc59ac62015-01-23 04:18:53 -08001034 const SkBitmap* image = &state.fImage;
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001035 SkRect bitmapBounds;
1036 image->getBounds(&bitmapBounds);
1037
1038 // For tiling modes, the bounds should be extended to include the bitmap,
1039 // otherwise the bitmap gets clipped out and the shader is empty and awful.
1040 // For clamp modes, we're only interested in the clip region, whether
1041 // or not the main bitmap is in it.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001042 SkShader::TileMode tileModes[2];
halcanarybc59ac62015-01-23 04:18:53 -08001043 tileModes[0] = state.fImageTileModes[0];
1044 tileModes[1] = state.fImageTileModes[1];
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001045 if (tileModes[0] != SkShader::kClamp_TileMode ||
1046 tileModes[1] != SkShader::kClamp_TileMode) {
1047 deviceBounds.join(bitmapBounds);
1048 }
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001049
reed@google.come1ca7052013-12-17 19:22:07 +00001050 SkISize size = SkISize::Make(SkScalarRoundToInt(deviceBounds.width()),
1051 SkScalarRoundToInt(deviceBounds.height()));
halcanary48810a02016-03-07 14:57:50 -08001052 sk_sp<SkPDFDevice> patternDevice(
halcanary989da4a2016-03-21 14:33:17 -07001053 SkPDFDevice::CreateUnflipped(size, dpi, doc));
halcanarya1f1ee92015-02-20 06:17:26 -08001054 SkCanvas canvas(patternDevice.get());
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001055
1056 SkRect patternBBox;
1057 image->getBounds(&patternBBox);
1058
1059 // Translate the canvas so that the bitmap origin is at (0, 0).
1060 canvas.translate(-deviceBounds.left(), -deviceBounds.top());
1061 patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
1062 // Undo the translation in the final matrix
1063 finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top());
1064
1065 // If the bitmap is out of bounds (i.e. clamp mode where we only see the
1066 // stretched sides), canvas will clip this out and the extraneous data
1067 // won't be saved to the PDF.
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001068 canvas.drawBitmap(*image, 0, 0);
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001069
1070 SkScalar width = SkIntToScalar(image->width());
1071 SkScalar height = SkIntToScalar(image->height());
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001072
1073 // Tiling is implied. First we handle mirroring.
1074 if (tileModes[0] == SkShader::kMirror_TileMode) {
1075 SkMatrix xMirror;
1076 xMirror.setScale(-1, 1);
1077 xMirror.postTranslate(2 * width, 0);
Florin Malitac54d8db2014-12-10 12:02:16 -05001078 drawBitmapMatrix(&canvas, *image, xMirror);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001079 patternBBox.fRight += width;
1080 }
1081 if (tileModes[1] == SkShader::kMirror_TileMode) {
1082 SkMatrix yMirror;
vandebo@chromium.org663515b2012-01-05 18:45:27 +00001083 yMirror.setScale(SK_Scalar1, -SK_Scalar1);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001084 yMirror.postTranslate(0, 2 * height);
Florin Malitac54d8db2014-12-10 12:02:16 -05001085 drawBitmapMatrix(&canvas, *image, yMirror);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001086 patternBBox.fBottom += height;
1087 }
1088 if (tileModes[0] == SkShader::kMirror_TileMode &&
1089 tileModes[1] == SkShader::kMirror_TileMode) {
1090 SkMatrix mirror;
1091 mirror.setScale(-1, -1);
1092 mirror.postTranslate(2 * width, 2 * height);
Florin Malitac54d8db2014-12-10 12:02:16 -05001093 drawBitmapMatrix(&canvas, *image, mirror);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001094 }
1095
1096 // Then handle Clamping, which requires expanding the pattern canvas to
1097 // cover the entire surfaceBBox.
1098
1099 // If both x and y are in clamp mode, we start by filling in the corners.
1100 // (Which are just a rectangles of the corner colors.)
1101 if (tileModes[0] == SkShader::kClamp_TileMode &&
1102 tileModes[1] == SkShader::kClamp_TileMode) {
1103 SkPaint paint;
1104 SkRect rect;
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001105 rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001106 if (!rect.isEmpty()) {
1107 paint.setColor(image->getColor(0, 0));
1108 canvas.drawRect(rect, paint);
1109 }
1110
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001111 rect = SkRect::MakeLTRB(width, deviceBounds.top(),
1112 deviceBounds.right(), 0);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001113 if (!rect.isEmpty()) {
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001114 paint.setColor(image->getColor(image->width() - 1, 0));
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001115 canvas.drawRect(rect, paint);
1116 }
1117
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001118 rect = SkRect::MakeLTRB(width, height,
1119 deviceBounds.right(), deviceBounds.bottom());
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001120 if (!rect.isEmpty()) {
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001121 paint.setColor(image->getColor(image->width() - 1,
1122 image->height() - 1));
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001123 canvas.drawRect(rect, paint);
1124 }
1125
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001126 rect = SkRect::MakeLTRB(deviceBounds.left(), height,
1127 0, deviceBounds.bottom());
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001128 if (!rect.isEmpty()) {
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001129 paint.setColor(image->getColor(0, image->height() - 1));
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001130 canvas.drawRect(rect, paint);
1131 }
1132 }
1133
1134 // Then expand the left, right, top, then bottom.
1135 if (tileModes[0] == SkShader::kClamp_TileMode) {
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001136 SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height());
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001137 if (deviceBounds.left() < 0) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001138 SkBitmap left;
1139 SkAssertResult(image->extractSubset(&left, subset));
1140
1141 SkMatrix leftMatrix;
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001142 leftMatrix.setScale(-deviceBounds.left(), 1);
1143 leftMatrix.postTranslate(deviceBounds.left(), 0);
Florin Malitac54d8db2014-12-10 12:02:16 -05001144 drawBitmapMatrix(&canvas, left, leftMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001145
1146 if (tileModes[1] == SkShader::kMirror_TileMode) {
vandebo@chromium.org663515b2012-01-05 18:45:27 +00001147 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001148 leftMatrix.postTranslate(0, 2 * height);
Florin Malitac54d8db2014-12-10 12:02:16 -05001149 drawBitmapMatrix(&canvas, left, leftMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001150 }
vandebo@chromium.orgbe2048a2011-05-02 15:24:01 +00001151 patternBBox.fLeft = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001152 }
1153
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001154 if (deviceBounds.right() > width) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001155 SkBitmap right;
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001156 subset.offset(image->width() - 1, 0);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001157 SkAssertResult(image->extractSubset(&right, subset));
1158
1159 SkMatrix rightMatrix;
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001160 rightMatrix.setScale(deviceBounds.right() - width, 1);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001161 rightMatrix.postTranslate(width, 0);
Florin Malitac54d8db2014-12-10 12:02:16 -05001162 drawBitmapMatrix(&canvas, right, rightMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001163
1164 if (tileModes[1] == SkShader::kMirror_TileMode) {
vandebo@chromium.org663515b2012-01-05 18:45:27 +00001165 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001166 rightMatrix.postTranslate(0, 2 * height);
Florin Malitac54d8db2014-12-10 12:02:16 -05001167 drawBitmapMatrix(&canvas, right, rightMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001168 }
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001169 patternBBox.fRight = deviceBounds.width();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001170 }
1171 }
1172
1173 if (tileModes[1] == SkShader::kClamp_TileMode) {
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001174 SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1);
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001175 if (deviceBounds.top() < 0) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001176 SkBitmap top;
1177 SkAssertResult(image->extractSubset(&top, subset));
1178
1179 SkMatrix topMatrix;
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001180 topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
1181 topMatrix.postTranslate(0, deviceBounds.top());
Florin Malitac54d8db2014-12-10 12:02:16 -05001182 drawBitmapMatrix(&canvas, top, topMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001183
1184 if (tileModes[0] == SkShader::kMirror_TileMode) {
1185 topMatrix.postScale(-1, 1);
1186 topMatrix.postTranslate(2 * width, 0);
Florin Malitac54d8db2014-12-10 12:02:16 -05001187 drawBitmapMatrix(&canvas, top, topMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001188 }
vandebo@chromium.orgbe2048a2011-05-02 15:24:01 +00001189 patternBBox.fTop = 0;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001190 }
1191
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001192 if (deviceBounds.bottom() > height) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001193 SkBitmap bottom;
vandebo@chromium.org54ff85c2012-03-09 17:18:50 +00001194 subset.offset(0, image->height() - 1);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001195 SkAssertResult(image->extractSubset(&bottom, subset));
1196
1197 SkMatrix bottomMatrix;
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001198 bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001199 bottomMatrix.postTranslate(0, height);
Florin Malitac54d8db2014-12-10 12:02:16 -05001200 drawBitmapMatrix(&canvas, bottom, bottomMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001201
1202 if (tileModes[0] == SkShader::kMirror_TileMode) {
1203 bottomMatrix.postScale(-1, 1);
1204 bottomMatrix.postTranslate(2 * width, 0);
Florin Malitac54d8db2014-12-10 12:02:16 -05001205 drawBitmapMatrix(&canvas, bottom, bottomMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001206 }
commit-bot@chromium.orge324cc62013-08-21 23:10:45 +00001207 patternBBox.fBottom = deviceBounds.height();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001208 }
1209 }
1210
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001211 // Put the canvas into the pattern stream (fContent).
mtklein18300a32016-03-16 13:53:35 -07001212 SkPDFImageShader* imageShader = new SkPDFImageShader(autoState->release());
halcanaryac0e00d2016-07-27 11:12:23 -07001213 imageShader->setData(patternDevice->content());
halcanarybc59ac62015-01-23 04:18:53 -08001214
halcanary8103a342016-03-08 15:10:16 -08001215 auto resourceDict = patternDevice->makeResourceDict();
halcanary792c80f2015-02-20 07:21:05 -08001216 populate_tiling_pattern_dict(imageShader, patternBBox,
halcanary6d622702015-03-25 08:45:42 -07001217 resourceDict.get(), finalMatrix);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001218
halcanarybc59ac62015-01-23 04:18:53 -08001219 imageShader->fShaderState->fImage.unlockPixels();
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001220
halcanary989da4a2016-03-21 14:33:17 -07001221 doc->canon()->addImageShader(imageShader);
halcanarybc59ac62015-01-23 04:18:53 -08001222 return imageShader;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001223}
1224
1225bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
1226 if (fType != b.fType ||
1227 fCanvasTransform != b.fCanvasTransform ||
1228 fShaderTransform != b.fShaderTransform ||
1229 fBBox != b.fBBox) {
1230 return false;
1231 }
1232
1233 if (fType == SkShader::kNone_GradientType) {
halcanary895f3f02016-04-01 11:51:00 -07001234 if (fBitmapKey != b.fBitmapKey ||
1235 fBitmapKey.id() == 0 ||
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001236 fImageTileModes[0] != b.fImageTileModes[0] ||
1237 fImageTileModes[1] != b.fImageTileModes[1]) {
1238 return false;
1239 }
1240 } else {
1241 if (fInfo.fColorCount != b.fInfo.fColorCount ||
1242 memcmp(fInfo.fColors, b.fInfo.fColors,
1243 sizeof(SkColor) * fInfo.fColorCount) != 0 ||
1244 memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets,
1245 sizeof(SkScalar) * fInfo.fColorCount) != 0 ||
1246 fInfo.fPoint[0] != b.fInfo.fPoint[0] ||
1247 fInfo.fTileMode != b.fInfo.fTileMode) {
1248 return false;
1249 }
1250
1251 switch (fType) {
1252 case SkShader::kLinear_GradientType:
1253 if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) {
1254 return false;
1255 }
1256 break;
1257 case SkShader::kRadial_GradientType:
1258 if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) {
1259 return false;
1260 }
1261 break;
reed@google.com49085332012-06-11 18:54:07 +00001262 case SkShader::kConical_GradientType:
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001263 if (fInfo.fPoint[1] != b.fInfo.fPoint[1] ||
1264 fInfo.fRadius[0] != b.fInfo.fRadius[0] ||
1265 fInfo.fRadius[1] != b.fInfo.fRadius[1]) {
1266 return false;
1267 }
1268 break;
1269 case SkShader::kSweep_GradientType:
1270 case SkShader::kNone_GradientType:
1271 case SkShader::kColor_GradientType:
1272 break;
1273 }
1274 }
1275 return true;
1276}
1277
reedfe630452016-03-25 09:08:00 -07001278SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform,
fmalitac3796c72015-01-13 08:06:11 -08001279 const SkIRect& bbox, SkScalar rasterScale)
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001280 : fCanvasTransform(canvasTransform),
halcanary895f3f02016-04-01 11:51:00 -07001281 fBBox(bbox) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001282 fInfo.fColorCount = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001283 fInfo.fColors = nullptr;
1284 fInfo.fColorOffsets = nullptr;
reedfe630452016-03-25 09:08:00 -07001285 fShaderTransform = shader->getLocalMatrix();
vandebo@chromium.orge1bc2742011-06-21 22:26:39 +00001286 fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode;
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001287
reedfe630452016-03-25 09:08:00 -07001288 fType = shader->asAGradient(&fInfo);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001289
1290 if (fType == SkShader::kNone_GradientType) {
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001291 SkMatrix matrix;
reedfe630452016-03-25 09:08:00 -07001292 if (shader->isABitmap(&fImage, &matrix, fImageTileModes)) {
reedf5822822015-08-19 11:46:38 -07001293 SkASSERT(matrix.isIdentity());
1294 } else {
fmalitac3796c72015-01-13 08:06:11 -08001295 // Generic fallback for unsupported shaders:
1296 // * allocate a bbox-sized bitmap
1297 // * shade the whole area
1298 // * use the result as a bitmap shader
1299
fmalita7b4d4c72015-02-12 13:56:50 -08001300 // bbox is in device space. While that's exactly what we want for sizing our bitmap,
1301 // we need to map it into shader space for adjustments (to match
1302 // SkPDFImageShader::Create's behavior).
1303 SkRect shaderRect = SkRect::Make(bbox);
1304 if (!inverse_transform_bbox(canvasTransform, &shaderRect)) {
1305 fImage.reset();
1306 return;
1307 }
1308
fmalitac3796c72015-01-13 08:06:11 -08001309 // Clamp the bitmap size to about 1M pixels
1310 static const SkScalar kMaxBitmapArea = 1024 * 1024;
1311 SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height();
1312 if (bitmapArea > kMaxBitmapArea) {
reed80ea19c2015-05-12 10:37:34 -07001313 rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea);
fmalitac3796c72015-01-13 08:06:11 -08001314 }
1315
1316 SkISize size = SkISize::Make(SkScalarRoundToInt(rasterScale * bbox.width()),
1317 SkScalarRoundToInt(rasterScale * bbox.height()));
fmalita7b4d4c72015-02-12 13:56:50 -08001318 SkSize scale = SkSize::Make(SkIntToScalar(size.width()) / shaderRect.width(),
1319 SkIntToScalar(size.height()) / shaderRect.height());
fmalitac3796c72015-01-13 08:06:11 -08001320
1321 fImage.allocN32Pixels(size.width(), size.height());
1322 fImage.eraseColor(SK_ColorTRANSPARENT);
1323
1324 SkPaint p;
reedfe630452016-03-25 09:08:00 -07001325 p.setShader(sk_ref_sp(shader));
fmalitac3796c72015-01-13 08:06:11 -08001326
1327 SkCanvas canvas(fImage);
1328 canvas.scale(scale.width(), scale.height());
fmalita7b4d4c72015-02-12 13:56:50 -08001329 canvas.translate(-shaderRect.x(), -shaderRect.y());
fmalitac3796c72015-01-13 08:06:11 -08001330 canvas.drawPaint(p);
1331
fmalita7b4d4c72015-02-12 13:56:50 -08001332 fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
fmalitac3796c72015-01-13 08:06:11 -08001333 fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001334 }
halcanary895f3f02016-04-01 11:51:00 -07001335 fBitmapKey = SkBitmapKey(fImage);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001336 } else {
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +00001337 AllocateGradientInfoStorage();
reedfe630452016-03-25 09:08:00 -07001338 shader->asAGradient(&fInfo);
vandebo@chromium.orgda912d62011-03-08 18:31:02 +00001339 }
1340}
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +00001341
1342SkPDFShader::State::State(const SkPDFShader::State& other)
1343 : fType(other.fType),
1344 fCanvasTransform(other.fCanvasTransform),
1345 fShaderTransform(other.fShaderTransform),
1346 fBBox(other.fBBox)
1347{
1348 // Only gradients supported for now, since that is all that is used.
1349 // If needed, image state copy constructor can be added here later.
1350 SkASSERT(fType != SkShader::kNone_GradientType);
1351
1352 if (fType != SkShader::kNone_GradientType) {
1353 fInfo = other.fInfo;
1354
1355 AllocateGradientInfoStorage();
1356 for (int i = 0; i < fInfo.fColorCount; i++) {
1357 fInfo.fColors[i] = other.fInfo.fColors[i];
1358 fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i];
1359 }
1360 }
1361}
1362
1363/**
1364 * Create a copy of this gradient state with alpha assigned to RGB luminousity.
1365 * Only valid for gradient states.
1366 */
1367SkPDFShader::State* SkPDFShader::State::CreateAlphaToLuminosityState() const {
1368 SkASSERT(fType != SkShader::kNone_GradientType);
1369
1370 SkPDFShader::State* newState = new SkPDFShader::State(*this);
1371
1372 for (int i = 0; i < fInfo.fColorCount; i++) {
1373 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
1374 newState->fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
1375 }
1376
1377 return newState;
1378}
1379
1380/**
1381 * Create a copy of this gradient state with alpha set to fully opaque
1382 * Only valid for gradient states.
1383 */
1384SkPDFShader::State* SkPDFShader::State::CreateOpaqueState() const {
1385 SkASSERT(fType != SkShader::kNone_GradientType);
1386
1387 SkPDFShader::State* newState = new SkPDFShader::State(*this);
1388 for (int i = 0; i < fInfo.fColorCount; i++) {
1389 newState->fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i],
1390 SK_AlphaOPAQUE);
1391 }
1392
1393 return newState;
1394}
1395
1396/**
1397 * Returns true if state is a gradient and the gradient has alpha.
1398 */
1399bool SkPDFShader::State::GradientHasAlpha() const {
1400 if (fType == SkShader::kNone_GradientType) {
1401 return false;
1402 }
1403
1404 for (int i = 0; i < fInfo.fColorCount; i++) {
1405 SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
1406 if (alpha != SK_AlphaOPAQUE) {
1407 return true;
1408 }
1409 }
1410 return false;
1411}
1412
1413void SkPDFShader::State::AllocateGradientInfoStorage() {
1414 fColorData.set(sk_malloc_throw(
1415 fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar))));
1416 fInfo.fColors = reinterpret_cast<SkColor*>(fColorData.get());
1417 fInfo.fColorOffsets =
1418 reinterpret_cast<SkScalar*>(fInfo.fColors + fInfo.fColorCount);
1419}