xref: /third_party/skia/src/pdf/SkPDFShader.cpp (revision cb93a386)
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
26static void draw(SkCanvas* canvas, const SkImage* image, SkColor4f paintColor) {
27    SkPaint paint(paintColor);
28    canvas->drawImage(image, 0, 0, SkSamplingOptions(), &paint);
29}
30
31static 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
40static 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
47static 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
55static 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
66static 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
72static bool is_tiled(SkTileMode m) { return SkTileMode::kMirror == m || SkTileMode::kRepeat == m; }
73
74static 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
263static 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
322static 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
331SkPDFIndirectReference 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