Coverage Report

Created: 2024-09-14 07:19

/src/skia/src/pdf/SkPDFShader.cpp
Line
Count
Source (jump to first uncovered line)
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/SkBitmap.h"
11
#include "include/core/SkCanvas.h"
12
#include "include/core/SkImage.h"
13
#include "include/core/SkImageInfo.h"
14
#include "include/core/SkPaint.h"
15
#include "include/core/SkRefCnt.h"
16
#include "include/core/SkSamplingOptions.h"
17
#include "include/core/SkScalar.h"
18
#include "include/core/SkShader.h"
19
#include "include/core/SkSize.h"
20
#include "include/core/SkStream.h"
21
#include "include/core/SkSurface.h"
22
#include "include/core/SkTileMode.h"
23
#include "include/private/base/SkTPin.h"
24
#include "src/core/SkDevice.h"
25
#include "src/core/SkTHash.h"
26
#include "src/pdf/SkKeyedImage.h"
27
#include "src/pdf/SkPDFDevice.h"
28
#include "src/pdf/SkPDFDocumentPriv.h"
29
#include "src/pdf/SkPDFGradientShader.h"
30
#include "src/pdf/SkPDFUtils.h"
31
#include "src/shaders/SkShaderBase.h"
32
33
#include <memory>
34
#include <utility>
35
36
0
static void draw(SkCanvas* canvas, const SkImage* image, SkColor4f paintColor) {
37
0
    SkPaint paint(paintColor);
38
0
    canvas->drawImage(image, 0, 0, SkSamplingOptions(), &paint);
39
0
}
40
41
0
static SkBitmap to_bitmap(const SkImage* image) {
42
0
    SkBitmap bitmap;
43
0
    if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
44
0
        bitmap.allocN32Pixels(image->width(), image->height());
45
0
        bitmap.eraseColor(0x00000000);
46
0
    }
47
0
    return bitmap;
48
0
}
49
50
static void draw_matrix(SkCanvas* canvas, const SkImage* image,
51
0
                        const SkMatrix& matrix, SkColor4f paintColor) {
52
0
    SkAutoCanvasRestore acr(canvas, true);
53
0
    canvas->concat(matrix);
54
0
    draw(canvas, image, paintColor);
55
0
}
56
57
static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm,
58
0
                               const SkMatrix& matrix, SkColor4f paintColor) {
59
0
    SkAutoCanvasRestore acr(canvas, true);
60
0
    canvas->concat(matrix);
61
0
    SkPaint paint(paintColor);
62
0
    canvas->drawImage(bm.asImage(), 0, 0, SkSamplingOptions(), &paint);
63
0
}
64
65
static void fill_color_from_bitmap(SkCanvas* canvas,
66
                                   float left, float top, float right, float bottom,
67
0
                                   const SkBitmap& bitmap, int x, int y, float alpha) {
68
0
    SkRect rect{left, top, right, bottom};
69
0
    if (!rect.isEmpty()) {
70
0
        SkColor4f color = SkColor4f::FromColor(bitmap.getColor(x, y));
71
0
        SkPaint paint(SkColor4f{color.fR, color.fG, color.fB, alpha * color.fA});
72
0
        canvas->drawRect(rect, paint);
73
0
    }
74
0
}
75
76
0
static SkMatrix scale_translate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty) {
77
0
    SkMatrix m;
78
0
    m.setScaleTranslate(sx, sy, tx, ty);
79
0
    return m;
80
0
}
81
82
0
static bool is_tiled(SkTileMode m) { return SkTileMode::kMirror == m || SkTileMode::kRepeat == m; }
83
84
static SkPDFIndirectReference make_image_shader(SkPDFDocument* doc,
85
                                                SkMatrix finalMatrix,
86
                                                SkTileMode tileModesX,
87
                                                SkTileMode tileModesY,
88
                                                SkRect bBox,
89
                                                const SkImage* image,
90
0
                                                SkColor4f paintColor) {
91
    // The image shader pattern cell will be drawn into a separate device
92
    // in pattern cell space (no scaling on the bitmap, though there may be
93
    // translations so that all content is in the device, coordinates > 0).
94
95
    // Map clip bounds to shader space to ensure the device is large enough
96
    // to handle fake clamping.
97
98
0
    SkRect deviceBounds = bBox;
99
0
    if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
100
0
        return SkPDFIndirectReference();
101
0
    }
102
103
0
    SkRect bitmapBounds = SkRect::MakeSize(SkSize::Make(image->dimensions()));
104
105
    // For tiling modes, the bounds should be extended to include the bitmap,
106
    // otherwise the bitmap gets clipped out and the shader is empty and awful.
107
    // For clamp modes, we're only interested in the clip region, whether
108
    // or not the main bitmap is in it.
109
0
    if (is_tiled(tileModesX) || is_tiled(tileModesY)) {
110
0
        deviceBounds.join(bitmapBounds);
111
0
    }
112
113
0
    SkISize patternDeviceSize = {SkScalarCeilToInt(deviceBounds.width()),
114
0
                                 SkScalarCeilToInt(deviceBounds.height())};
115
0
    auto patternDevice = sk_make_sp<SkPDFDevice>(patternDeviceSize, doc);
116
0
    SkCanvas canvas(patternDevice);
117
118
0
    SkRect patternBBox = SkRect::MakeSize(SkSize::Make(image->dimensions()));
119
0
    SkScalar width = patternBBox.width();
120
0
    SkScalar height = patternBBox.height();
121
122
    // Translate the canvas so that the bitmap origin is at (0, 0).
123
0
    canvas.translate(-deviceBounds.left(), -deviceBounds.top());
124
0
    patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
125
    // Undo the translation in the final matrix
126
0
    finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top());
127
128
    // If the bitmap is out of bounds (i.e. clamp mode where we only see the
129
    // stretched sides), canvas will clip this out and the extraneous data
130
    // won't be saved to the PDF.
131
0
    draw(&canvas, image, paintColor);
132
133
    // Tiling is implied.  First we handle mirroring.
134
0
    if (tileModesX == SkTileMode::kMirror) {
135
0
        draw_matrix(&canvas, image, scale_translate(-1, 1, 2 * width, 0), paintColor);
136
0
        patternBBox.fRight += width;
137
0
    }
138
0
    if (tileModesY == SkTileMode::kMirror) {
139
0
        draw_matrix(&canvas, image, scale_translate(1, -1, 0, 2 * height), paintColor);
140
0
        patternBBox.fBottom += height;
141
0
    }
142
0
    if (tileModesX == SkTileMode::kMirror && tileModesY == SkTileMode::kMirror) {
143
0
        draw_matrix(&canvas, image, scale_translate(-1, -1, 2 * width, 2 * height), paintColor);
144
0
    }
145
146
    // Then handle Clamping, which requires expanding the pattern canvas to
147
    // cover the entire surfaceBBox.
148
149
0
    SkBitmap bitmap;
150
0
    if (tileModesX == SkTileMode::kClamp || tileModesY == SkTileMode::kClamp) {
151
        // For now, the easiest way to access the colors in the corners and sides is
152
        // to just make a bitmap from the image.
153
0
        bitmap = to_bitmap(image);
154
0
    }
155
156
    // If both x and y are in clamp mode, we start by filling in the corners.
157
    // (Which are just a rectangles of the corner colors.)
158
0
    if (tileModesX == SkTileMode::kClamp && tileModesY == SkTileMode::kClamp) {
159
0
        SkASSERT(!bitmap.drawsNothing());
160
161
0
        fill_color_from_bitmap(&canvas, deviceBounds.left(), deviceBounds.top(), 0, 0,
162
0
                               bitmap, 0, 0, paintColor.fA);
163
164
0
        fill_color_from_bitmap(&canvas, width, deviceBounds.top(), deviceBounds.right(), 0,
165
0
                               bitmap, bitmap.width() - 1, 0, paintColor.fA);
166
167
0
        fill_color_from_bitmap(&canvas, width, height, deviceBounds.right(), deviceBounds.bottom(),
168
0
                               bitmap, bitmap.width() - 1, bitmap.height() - 1, paintColor.fA);
169
170
0
        fill_color_from_bitmap(&canvas, deviceBounds.left(), height, 0, deviceBounds.bottom(),
171
0
                               bitmap, 0, bitmap.height() - 1, paintColor.fA);
172
0
    }
173
174
    // Then expand the left, right, top, then bottom.
175
0
    if (tileModesX == SkTileMode::kClamp) {
176
0
        SkASSERT(!bitmap.drawsNothing());
177
0
        SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height());
178
0
        if (deviceBounds.left() < 0) {
179
0
            SkBitmap left;
180
0
            SkAssertResult(bitmap.extractSubset(&left, subset));
181
182
0
            SkMatrix leftMatrix = scale_translate(-deviceBounds.left(), 1, deviceBounds.left(), 0);
183
0
            draw_bitmap_matrix(&canvas, left, leftMatrix, paintColor);
184
185
0
            if (tileModesY == SkTileMode::kMirror) {
186
0
                leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
187
0
                leftMatrix.postTranslate(0, 2 * height);
188
0
                draw_bitmap_matrix(&canvas, left, leftMatrix, paintColor);
189
0
            }
190
0
            patternBBox.fLeft = 0;
191
0
        }
192
193
0
        if (deviceBounds.right() > width) {
194
0
            SkBitmap right;
195
0
            subset.offset(bitmap.width() - 1, 0);
196
0
            SkAssertResult(bitmap.extractSubset(&right, subset));
197
198
0
            SkMatrix rightMatrix = scale_translate(deviceBounds.right() - width, 1, width, 0);
199
0
            draw_bitmap_matrix(&canvas, right, rightMatrix, paintColor);
200
201
0
            if (tileModesY == SkTileMode::kMirror) {
202
0
                rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
203
0
                rightMatrix.postTranslate(0, 2 * height);
204
0
                draw_bitmap_matrix(&canvas, right, rightMatrix, paintColor);
205
0
            }
206
0
            patternBBox.fRight = deviceBounds.width();
207
0
        }
208
0
    }
209
0
    if (tileModesX == SkTileMode::kDecal) {
210
0
        if (deviceBounds.left() < 0) {
211
0
            patternBBox.fLeft = 0;
212
0
        }
213
0
        if (deviceBounds.right() > width) {
214
0
            patternBBox.fRight = deviceBounds.width();
215
0
        }
216
0
    }
217
218
0
    if (tileModesY == SkTileMode::kClamp) {
219
0
        SkASSERT(!bitmap.drawsNothing());
220
0
        SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1);
221
0
        if (deviceBounds.top() < 0) {
222
0
            SkBitmap top;
223
0
            SkAssertResult(bitmap.extractSubset(&top, subset));
224
225
0
            SkMatrix topMatrix = scale_translate(1, -deviceBounds.top(), 0, deviceBounds.top());
226
0
            draw_bitmap_matrix(&canvas, top, topMatrix, paintColor);
227
228
0
            if (tileModesX == SkTileMode::kMirror) {
229
0
                topMatrix.postScale(-1, 1);
230
0
                topMatrix.postTranslate(2 * width, 0);
231
0
                draw_bitmap_matrix(&canvas, top, topMatrix, paintColor);
232
0
            }
233
0
            patternBBox.fTop = 0;
234
0
        }
235
236
0
        if (deviceBounds.bottom() > height) {
237
0
            SkBitmap bottom;
238
0
            subset.offset(0, bitmap.height() - 1);
239
0
            SkAssertResult(bitmap.extractSubset(&bottom, subset));
240
241
0
            SkMatrix bottomMatrix = scale_translate(1, deviceBounds.bottom() - height, 0, height);
242
0
            draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paintColor);
243
244
0
            if (tileModesX == SkTileMode::kMirror) {
245
0
                bottomMatrix.postScale(-1, 1);
246
0
                bottomMatrix.postTranslate(2 * width, 0);
247
0
                draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paintColor);
248
0
            }
249
0
            patternBBox.fBottom = deviceBounds.height();
250
0
        }
251
0
    }
252
0
    if (tileModesY == SkTileMode::kDecal) {
253
0
        if (deviceBounds.top() < 0) {
254
0
            patternBBox.fTop = 0;
255
0
        }
256
0
        if (deviceBounds.bottom() > height) {
257
0
            patternBBox.fBottom = deviceBounds.height();
258
0
        }
259
0
    }
260
261
0
    auto imageShader = patternDevice->content();
262
0
    std::unique_ptr<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
263
0
    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
264
0
    SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox,
265
0
                                          std::move(resourceDict), finalMatrix);
266
0
    return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc);
267
0
}
268
269
// Generic fallback for unsupported shaders:
270
//  * allocate a surfaceBBox-sized bitmap
271
//  * shade the whole area
272
//  * use the result as a bitmap shader
273
static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc,
274
                                                   SkShader* shader,
275
                                                   const SkMatrix& canvasTransform,
276
                                                   const SkIRect& surfaceBBox,
277
0
                                                   SkColor4f paintColor) {
278
    // surfaceBBox is in device space. While that's exactly what we
279
    // want for sizing our bitmap, we need to map it into
280
    // shader space for adjustments (to match
281
    // MakeImageShader's behavior).
282
0
    SkRect shaderRect = SkRect::Make(surfaceBBox);
283
0
    if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
284
0
        return SkPDFIndirectReference();
285
0
    }
286
    // Clamp the bitmap size to about 1M pixels
287
0
    static const int kMaxBitmapArea = 1024 * 1024;
288
0
    SkScalar bitmapArea = (float)surfaceBBox.width() * (float)surfaceBBox.height();
289
0
    SkScalar rasterScale = 1.0f;
290
0
    if (bitmapArea > (float)kMaxBitmapArea) {
291
0
        rasterScale *= SkScalarSqrt((float)kMaxBitmapArea / bitmapArea);
292
0
    }
293
294
0
    SkISize size = {
295
0
        SkTPin(SkScalarCeilToInt(rasterScale * surfaceBBox.width()),  1, kMaxBitmapArea),
296
0
        SkTPin(SkScalarCeilToInt(rasterScale * surfaceBBox.height()), 1, kMaxBitmapArea)};
297
0
    SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
298
0
                    SkIntToScalar(size.height()) / shaderRect.height()};
299
300
0
    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size.width(), size.height()));
301
0
    SkASSERT(surface);
302
0
    SkCanvas* canvas = surface->getCanvas();
303
0
    canvas->clear(SK_ColorTRANSPARENT);
304
305
0
    SkPaint p(paintColor);
306
0
    p.setShader(sk_ref_sp(shader));
307
308
0
    canvas->scale(scale.width(), scale.height());
309
0
    canvas->translate(-shaderRect.x(), -shaderRect.y());
310
0
    canvas->drawPaint(p);
311
312
0
    auto shaderTransform = SkMatrix::Translate(shaderRect.x(), shaderRect.y());
313
0
    shaderTransform.preScale(1 / scale.width(), 1 / scale.height());
314
315
0
    sk_sp<SkImage> image = surface->makeImageSnapshot();
316
0
    SkASSERT(image);
317
0
    return make_image_shader(doc,
318
0
                             SkMatrix::Concat(canvasTransform, shaderTransform),
319
0
                             SkTileMode::kClamp, SkTileMode::kClamp,
320
0
                             SkRect::Make(surfaceBBox),
321
0
                             image.get(),
322
0
                             paintColor);
323
0
}
324
325
0
static SkColor4f adjust_color(SkShader* shader, SkColor4f paintColor) {
326
0
    if (SkImage* img = shader->isAImage(nullptr, (SkTileMode*)nullptr)) {
327
0
        if (img->isAlphaOnly()) {
328
0
            return paintColor;
329
0
        }
330
0
    }
331
0
    return SkColor4f{0, 0, 0, paintColor.fA};  // only preserve the alpha.
332
0
}
333
334
SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
335
                                       SkShader* shader,
336
                                       const SkMatrix& canvasTransform,
337
                                       const SkIRect& surfaceBBox,
338
0
                                       SkColor4f paintColor) {
339
0
    SkASSERT(shader);
340
0
    SkASSERT(doc);
341
0
    if (as_SB(shader)->asGradient() != SkShaderBase::GradientType::kNone) {
342
0
        return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
343
0
    }
344
0
    if (surfaceBBox.isEmpty()) {
345
0
        return SkPDFIndirectReference();
346
0
    }
347
0
    SkBitmap image;
348
349
0
    paintColor = adjust_color(shader, paintColor);
350
0
    SkMatrix shaderTransform;
351
0
    SkTileMode imageTileModes[2];
352
0
    if (SkImage* skimg = shader->isAImage(&shaderTransform, imageTileModes)) {
353
0
        SkMatrix finalMatrix = SkMatrix::Concat(canvasTransform, shaderTransform);
354
0
        SkPDFImageShaderKey key = {
355
0
            finalMatrix,
356
0
            surfaceBBox,
357
0
            SkBitmapKeyFromImage(skimg),
358
0
            {imageTileModes[0], imageTileModes[1]},
359
0
            paintColor};
360
0
        SkPDFIndirectReference* shaderPtr = doc->fImageShaderMap.find(key);
361
0
        if (shaderPtr) {
362
0
            return *shaderPtr;
363
0
        }
364
0
        SkPDFIndirectReference pdfShader =
365
0
                make_image_shader(doc,
366
0
                                  finalMatrix,
367
0
                                  imageTileModes[0],
368
0
                                  imageTileModes[1],
369
0
                                  SkRect::Make(surfaceBBox),
370
0
                                  skimg,
371
0
                                  paintColor);
372
0
        doc->fImageShaderMap.set(std::move(key), pdfShader);
373
0
        return pdfShader;
374
0
    }
375
    // Don't bother to de-dup fallback shader.
376
0
    return make_fallback_shader(doc, shader, canvasTransform, surfaceBBox, paintColor);
377
0
}