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 "SkPDFDocument.h"
13 #include "SkPDFDevice.h"
14 #include "SkPDFDocumentPriv.h"
15 #include "SkPDFFormXObject.h"
16 #include "SkPDFGradientShader.h"
17 #include "SkPDFGraphicState.h"
18 #include "SkPDFResourceDict.h"
19 #include "SkPDFUtils.h"
20 #include "SkScalar.h"
21 #include "SkStream.h"
22 #include "SkSurface.h"
23 #include "SkTemplates.h"
24
25
draw_image_matrix(SkCanvas * canvas,const SkImage * img,const SkMatrix & matrix,const SkPaint & paint)26 static void draw_image_matrix(SkCanvas* canvas, const SkImage* img,
27 const SkMatrix& matrix, const SkPaint& paint) {
28 SkAutoCanvasRestore acr(canvas, true);
29 canvas->concat(matrix);
30 canvas->drawImage(img, 0, 0, &paint);
31 }
32
draw_bitmap_matrix(SkCanvas * canvas,const SkBitmap & bm,const SkMatrix & matrix,const SkPaint & paint)33 static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm,
34 const SkMatrix& matrix, const SkPaint& paint) {
35 SkAutoCanvasRestore acr(canvas, true);
36 canvas->concat(matrix);
37 canvas->drawBitmap(bm, 0, 0, &paint);
38 }
39
make_image_shader(SkPDFDocument * doc,const SkPDFImageShaderKey & key,SkImage * image)40 static SkPDFIndirectReference make_image_shader(SkPDFDocument* doc,
41 const SkPDFImageShaderKey& key,
42 SkImage* image) {
43 SkASSERT(image);
44
45 // The image shader pattern cell will be drawn into a separate device
46 // in pattern cell space (no scaling on the bitmap, though there may be
47 // translations so that all content is in the device, coordinates > 0).
48
49 // Map clip bounds to shader space to ensure the device is large enough
50 // to handle fake clamping.
51 SkMatrix finalMatrix = key.fCanvasTransform;
52 finalMatrix.preConcat(key.fShaderTransform);
53 SkRect deviceBounds = SkRect::Make(key.fBBox);
54 if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
55 return SkPDFIndirectReference();
56 }
57
58 SkRect bitmapBounds = SkRect::Make(image->bounds());
59
60 // For tiling modes, the bounds should be extended to include the bitmap,
61 // otherwise the bitmap gets clipped out and the shader is empty and awful.
62 // For clamp modes, we're only interested in the clip region, whether
63 // or not the main bitmap is in it.
64 SkShader::TileMode tileModes[2];
65 tileModes[0] = key.fImageTileModes[0];
66 tileModes[1] = key.fImageTileModes[1];
67 if (tileModes[0] != SkShader::kClamp_TileMode ||
68 tileModes[1] != SkShader::kClamp_TileMode) {
69 deviceBounds.join(bitmapBounds);
70 }
71
72 SkISize patternDeviceSize = {SkScalarCeilToInt(deviceBounds.width()),
73 SkScalarCeilToInt(deviceBounds.height())};
74 auto patternDevice = sk_make_sp<SkPDFDevice>(patternDeviceSize, doc);
75 SkCanvas canvas(patternDevice);
76
77 SkRect patternBBox = SkRect::Make(image->bounds());
78
79 // Translate the canvas so that the bitmap origin is at (0, 0).
80 canvas.translate(-deviceBounds.left(), -deviceBounds.top());
81 patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
82 // Undo the translation in the final matrix
83 finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top());
84
85 // If the bitmap is out of bounds (i.e. clamp mode where we only see the
86 // stretched sides), canvas will clip this out and the extraneous data
87 // won't be saved to the PDF.
88 canvas.drawImage(image, 0, 0);
89
90 SkScalar width = SkIntToScalar(image->width());
91 SkScalar height = SkIntToScalar(image->height());
92
93 SkPaint paint;
94 paint.setColor(key.fPaintColor);
95 // Tiling is implied. First we handle mirroring.
96 if (tileModes[0] == SkShader::kMirror_TileMode) {
97 SkMatrix xMirror;
98 xMirror.setScale(-1, 1);
99 xMirror.postTranslate(2 * width, 0);
100 draw_image_matrix(&canvas, image, xMirror, paint);
101 patternBBox.fRight += width;
102 }
103 if (tileModes[1] == SkShader::kMirror_TileMode) {
104 SkMatrix yMirror;
105 yMirror.setScale(SK_Scalar1, -SK_Scalar1);
106 yMirror.postTranslate(0, 2 * height);
107 draw_image_matrix(&canvas, image, yMirror, paint);
108 patternBBox.fBottom += height;
109 }
110 if (tileModes[0] == SkShader::kMirror_TileMode &&
111 tileModes[1] == SkShader::kMirror_TileMode) {
112 SkMatrix mirror;
113 mirror.setScale(-1, -1);
114 mirror.postTranslate(2 * width, 2 * height);
115 draw_image_matrix(&canvas, image, mirror, paint);
116 }
117
118 // Then handle Clamping, which requires expanding the pattern canvas to
119 // cover the entire surfaceBBox.
120
121 SkBitmap bitmap;
122 if (tileModes[0] == SkShader::kClamp_TileMode ||
123 tileModes[1] == SkShader::kClamp_TileMode) {
124 // For now, the easiest way to access the colors in the corners and sides is
125 // to just make a bitmap from the image.
126 if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
127 bitmap.allocN32Pixels(image->width(), image->height());
128 bitmap.eraseColor(0x00000000);
129 }
130 }
131
132 // If both x and y are in clamp mode, we start by filling in the corners.
133 // (Which are just a rectangles of the corner colors.)
134 if (tileModes[0] == SkShader::kClamp_TileMode &&
135 tileModes[1] == SkShader::kClamp_TileMode) {
136 SkASSERT(!bitmap.drawsNothing());
137 SkPaint paint;
138 SkRect rect;
139 rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0);
140 if (!rect.isEmpty()) {
141 paint.setColor(bitmap.getColor(0, 0));
142 canvas.drawRect(rect, paint);
143 }
144
145 rect = SkRect::MakeLTRB(width, deviceBounds.top(),
146 deviceBounds.right(), 0);
147 if (!rect.isEmpty()) {
148 paint.setColor(bitmap.getColor(bitmap.width() - 1, 0));
149 canvas.drawRect(rect, paint);
150 }
151
152 rect = SkRect::MakeLTRB(width, height,
153 deviceBounds.right(), deviceBounds.bottom());
154 if (!rect.isEmpty()) {
155 paint.setColor(bitmap.getColor(bitmap.width() - 1,
156 bitmap.height() - 1));
157 canvas.drawRect(rect, paint);
158 }
159
160 rect = SkRect::MakeLTRB(deviceBounds.left(), height,
161 0, deviceBounds.bottom());
162 if (!rect.isEmpty()) {
163 paint.setColor(bitmap.getColor(0, bitmap.height() - 1));
164 canvas.drawRect(rect, paint);
165 }
166 }
167
168 // Then expand the left, right, top, then bottom.
169 if (tileModes[0] == SkShader::kClamp_TileMode) {
170 SkASSERT(!bitmap.drawsNothing());
171 SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height());
172 if (deviceBounds.left() < 0) {
173 SkBitmap left;
174 SkAssertResult(bitmap.extractSubset(&left, subset));
175
176 SkMatrix leftMatrix;
177 leftMatrix.setScale(-deviceBounds.left(), 1);
178 leftMatrix.postTranslate(deviceBounds.left(), 0);
179 draw_bitmap_matrix(&canvas, left, leftMatrix, paint);
180
181 if (tileModes[1] == SkShader::kMirror_TileMode) {
182 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
183 leftMatrix.postTranslate(0, 2 * height);
184 draw_bitmap_matrix(&canvas, left, leftMatrix, paint);
185 }
186 patternBBox.fLeft = 0;
187 }
188
189 if (deviceBounds.right() > width) {
190 SkBitmap right;
191 subset.offset(bitmap.width() - 1, 0);
192 SkAssertResult(bitmap.extractSubset(&right, subset));
193
194 SkMatrix rightMatrix;
195 rightMatrix.setScale(deviceBounds.right() - width, 1);
196 rightMatrix.postTranslate(width, 0);
197 draw_bitmap_matrix(&canvas, right, rightMatrix, paint);
198
199 if (tileModes[1] == SkShader::kMirror_TileMode) {
200 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
201 rightMatrix.postTranslate(0, 2 * height);
202 draw_bitmap_matrix(&canvas, right, rightMatrix, paint);
203 }
204 patternBBox.fRight = deviceBounds.width();
205 }
206 }
207
208 if (tileModes[1] == SkShader::kClamp_TileMode) {
209 SkASSERT(!bitmap.drawsNothing());
210 SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1);
211 if (deviceBounds.top() < 0) {
212 SkBitmap top;
213 SkAssertResult(bitmap.extractSubset(&top, subset));
214
215 SkMatrix topMatrix;
216 topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
217 topMatrix.postTranslate(0, deviceBounds.top());
218 draw_bitmap_matrix(&canvas, top, topMatrix, paint);
219
220 if (tileModes[0] == SkShader::kMirror_TileMode) {
221 topMatrix.postScale(-1, 1);
222 topMatrix.postTranslate(2 * width, 0);
223 draw_bitmap_matrix(&canvas, top, topMatrix, paint);
224 }
225 patternBBox.fTop = 0;
226 }
227
228 if (deviceBounds.bottom() > height) {
229 SkBitmap bottom;
230 subset.offset(0, bitmap.height() - 1);
231 SkAssertResult(bitmap.extractSubset(&bottom, subset));
232
233 SkMatrix bottomMatrix;
234 bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
235 bottomMatrix.postTranslate(0, height);
236 draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paint);
237
238 if (tileModes[0] == SkShader::kMirror_TileMode) {
239 bottomMatrix.postScale(-1, 1);
240 bottomMatrix.postTranslate(2 * width, 0);
241 draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paint);
242 }
243 patternBBox.fBottom = deviceBounds.height();
244 }
245 }
246
247 auto imageShader = patternDevice->content();
248 std::unique_ptr<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
249 std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
250 SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox,
251 std::move(resourceDict), finalMatrix);
252 return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc);
253 }
254
255 // Generic fallback for unsupported shaders:
256 // * allocate a surfaceBBox-sized bitmap
257 // * shade the whole area
258 // * use the result as a bitmap shader
make_fallback_shader(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & surfaceBBox,SkColor paintColor)259 static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc,
260 SkShader* shader,
261 const SkMatrix& canvasTransform,
262 const SkIRect& surfaceBBox,
263 SkColor paintColor) {
264 // TODO(vandebo) This drops SKComposeShader on the floor. We could
265 // handle compose shader by pulling things up to a layer, drawing with
266 // the first shader, applying the xfer mode and drawing again with the
267 // second shader, then applying the layer to the original drawing.
268 SkPDFImageShaderKey key = {
269 canvasTransform,
270 SkMatrix::I(),
271 surfaceBBox,
272 {{0, 0, 0, 0}, 0}, // don't need the key; won't de-dup.
273 {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode},
274 paintColor};
275
276 key.fShaderTransform = shader->getLocalMatrix();
277
278 // surfaceBBox is in device space. While that's exactly what we
279 // want for sizing our bitmap, we need to map it into
280 // shader space for adjustments (to match
281 // MakeImageShader's behavior).
282 SkRect shaderRect = SkRect::Make(surfaceBBox);
283 if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
284 return SkPDFIndirectReference();
285 }
286 // Clamp the bitmap size to about 1M pixels
287 static const SkScalar kMaxBitmapArea = 1024 * 1024;
288 SkScalar bitmapArea = surfaceBBox.width() * surfaceBBox.height();
289 SkScalar rasterScale = 1.0f;
290 if (bitmapArea > kMaxBitmapArea) {
291 rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea);
292 }
293
294 SkISize size = {SkScalarRoundToInt(rasterScale * surfaceBBox.width()),
295 SkScalarRoundToInt(rasterScale * surfaceBBox.height())};
296 SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
297 SkIntToScalar(size.height()) / shaderRect.height()};
298
299 auto surface = SkSurface::MakeRasterN32Premul(size.width(), size.height());
300 SkCanvas* canvas = surface->getCanvas();
301 canvas->clear(SK_ColorTRANSPARENT);
302
303 SkPaint p;
304 p.setShader(sk_ref_sp(shader));
305 p.setColor(paintColor);
306
307 canvas->scale(scale.width(), scale.height());
308 canvas->translate(-shaderRect.x(), -shaderRect.y());
309 canvas->drawPaint(p);
310
311 key.fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
312 key.fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
313
314 sk_sp<SkImage> image = surface->makeImageSnapshot();
315 return make_image_shader(doc, key, image.get());
316 }
317
adjust_color(SkShader * shader,SkColor paintColor)318 static SkColor adjust_color(SkShader* shader, SkColor paintColor) {
319 if (SkImage* img = shader->isAImage(nullptr, nullptr)) {
320 if (img->isAlphaOnly()) {
321 return paintColor;
322 }
323 }
324 // only preserve the alpha.
325 return paintColor & SK_ColorBLACK;
326 }
327
SkPDFMakeShader(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & surfaceBBox,SkColor paintColor)328 SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
329 SkShader* shader,
330 const SkMatrix& canvasTransform,
331 const SkIRect& surfaceBBox,
332 SkColor paintColor) {
333 SkASSERT(shader);
334 SkASSERT(doc);
335 if (SkShader::kNone_GradientType != shader->asAGradient(nullptr)) {
336 return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
337 }
338 if (surfaceBBox.isEmpty()) {
339 return SkPDFIndirectReference();
340 }
341 SkBitmap image;
342 SkPDFImageShaderKey key = {
343 canvasTransform,
344 SkMatrix::I(),
345 surfaceBBox,
346 {{0, 0, 0, 0}, 0},
347 {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode},
348 adjust_color(shader, paintColor)};
349
350 SkASSERT(shader->asAGradient(nullptr) == SkShader::kNone_GradientType) ;
351 if (SkImage* skimg = shader->isAImage(&key.fShaderTransform, key.fImageTileModes)) {
352 key.fBitmapKey = SkBitmapKeyFromImage(skimg);
353 SkPDFIndirectReference* shaderPtr = doc->fImageShaderMap.find(key);
354 if (shaderPtr) {
355 return *shaderPtr;
356 }
357 SkPDFIndirectReference pdfShader = make_image_shader(doc, key, skimg);
358 doc->fImageShaderMap.set(std::move(key), pdfShader);
359 return pdfShader;
360 }
361 // Don't bother to de-dup fallback shader.
362 return make_fallback_shader(doc, shader, canvasTransform, surfaceBBox, key.fPaintColor);
363 }
364