/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 | } |