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