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