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