• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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