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