• 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 #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