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