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 
draw(SkCanvas * canvas,const SkImage * image,SkColor4f paintColor)25 static void draw(SkCanvas* canvas, const SkImage* image, SkColor4f paintColor) {
26     SkPaint paint(paintColor);
27     canvas->drawImage(image, 0, 0, &paint);
28 }
29 
to_bitmap(const SkImage * image)30 static 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 
draw_matrix(SkCanvas * canvas,const SkImage * image,const SkMatrix & matrix,SkColor4f paintColor)39 static 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 
draw_bitmap_matrix(SkCanvas * canvas,const SkBitmap & bm,const SkMatrix & matrix,SkColor4f paintColor)46 static 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 
fill_color_from_bitmap(SkCanvas * canvas,float left,float top,float right,float bottom,const SkBitmap & bitmap,int x,int y,float alpha)54 static 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 
scale_translate(SkScalar sx,SkScalar sy,SkScalar tx,SkScalar ty)65 static 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 
is_tiled(SkTileMode m)71 static bool is_tiled(SkTileMode m) { return SkTileMode::kMirror == m || SkTileMode::kRepeat == m; }
72 
make_image_shader(SkPDFDocument * doc,SkMatrix finalMatrix,SkTileMode tileModesX,SkTileMode tileModesY,SkRect bBox,const SkImage * image,SkColor4f paintColor)73 static 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
make_fallback_shader(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & surfaceBBox,SkColor4f paintColor)262 static 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         SkTClamp(SkScalarCeilToInt(rasterScale * surfaceBBox.width()),  1, kMaxBitmapArea),
292         SkTClamp(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 
adjust_color(SkShader * shader,SkColor4f paintColor)321 static 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 
SkPDFMakeShader(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & surfaceBBox,SkColor4f paintColor)330 SkPDFIndirectReference 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