xref: /aosp_15_r20/external/skia/src/pdf/SkPDFShader.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 
draw(SkCanvas * canvas,const SkImage * image,SkColor4f paintColor)36 static void draw(SkCanvas* canvas, const SkImage* image, SkColor4f paintColor) {
37     SkPaint paint(paintColor);
38     canvas->drawImage(image, 0, 0, SkSamplingOptions(), &paint);
39 }
40 
to_bitmap(const SkImage * image)41 static SkBitmap to_bitmap(const SkImage* image) {
42     SkBitmap bitmap;
43     if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
44         bitmap.allocN32Pixels(image->width(), image->height());
45         bitmap.eraseColor(0x00000000);
46     }
47     return bitmap;
48 }
49 
draw_matrix(SkCanvas * canvas,const SkImage * image,const SkMatrix & matrix,SkColor4f paintColor)50 static void draw_matrix(SkCanvas* canvas, const SkImage* image,
51                         const SkMatrix& matrix, SkColor4f paintColor) {
52     SkAutoCanvasRestore acr(canvas, true);
53     canvas->concat(matrix);
54     draw(canvas, image, paintColor);
55 }
56 
draw_bitmap_matrix(SkCanvas * canvas,const SkBitmap & bm,const SkMatrix & matrix,SkColor4f paintColor)57 static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm,
58                                const SkMatrix& matrix, SkColor4f paintColor) {
59     SkAutoCanvasRestore acr(canvas, true);
60     canvas->concat(matrix);
61     SkPaint paint(paintColor);
62     canvas->drawImage(bm.asImage(), 0, 0, SkSamplingOptions(), &paint);
63 }
64 
fill_color_from_bitmap(SkCanvas * canvas,float left,float top,float right,float bottom,const SkBitmap & bitmap,int x,int y,float alpha)65 static void fill_color_from_bitmap(SkCanvas* canvas,
66                                    float left, float top, float right, float bottom,
67                                    const SkBitmap& bitmap, int x, int y, float alpha) {
68     SkRect rect{left, top, right, bottom};
69     if (!rect.isEmpty()) {
70         SkColor4f color = SkColor4f::FromColor(bitmap.getColor(x, y));
71         SkPaint paint(SkColor4f{color.fR, color.fG, color.fB, alpha * color.fA});
72         canvas->drawRect(rect, paint);
73     }
74 }
75 
scale_translate(SkScalar sx,SkScalar sy,SkScalar tx,SkScalar ty)76 static SkMatrix scale_translate(SkScalar sx, SkScalar sy, SkScalar tx, SkScalar ty) {
77     SkMatrix m;
78     m.setScaleTranslate(sx, sy, tx, ty);
79     return m;
80 }
81 
is_tiled(SkTileMode m)82 static bool is_tiled(SkTileMode m) { return SkTileMode::kMirror == m || SkTileMode::kRepeat == m; }
83 
make_image_shader(SkPDFDocument * doc,SkMatrix finalMatrix,SkTileMode tileModesX,SkTileMode tileModesY,SkRect bBox,const SkImage * image,SkColor4f paintColor)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                                                 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     SkRect deviceBounds = bBox;
99     if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
100         return SkPDFIndirectReference();
101     }
102 
103     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     if (is_tiled(tileModesX) || is_tiled(tileModesY)) {
110         deviceBounds.join(bitmapBounds);
111     }
112 
113     SkISize patternDeviceSize = {SkScalarCeilToInt(deviceBounds.width()),
114                                  SkScalarCeilToInt(deviceBounds.height())};
115     auto patternDevice = sk_make_sp<SkPDFDevice>(patternDeviceSize, doc);
116     SkCanvas canvas(patternDevice);
117 
118     SkRect patternBBox = SkRect::MakeSize(SkSize::Make(image->dimensions()));
119     SkScalar width = patternBBox.width();
120     SkScalar height = patternBBox.height();
121 
122     // Translate the canvas so that the bitmap origin is at (0, 0).
123     canvas.translate(-deviceBounds.left(), -deviceBounds.top());
124     patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
125     // Undo the translation in the final matrix
126     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     draw(&canvas, image, paintColor);
132 
133     // Tiling is implied.  First we handle mirroring.
134     if (tileModesX == SkTileMode::kMirror) {
135         draw_matrix(&canvas, image, scale_translate(-1, 1, 2 * width, 0), paintColor);
136         patternBBox.fRight += width;
137     }
138     if (tileModesY == SkTileMode::kMirror) {
139         draw_matrix(&canvas, image, scale_translate(1, -1, 0, 2 * height), paintColor);
140         patternBBox.fBottom += height;
141     }
142     if (tileModesX == SkTileMode::kMirror && tileModesY == SkTileMode::kMirror) {
143         draw_matrix(&canvas, image, scale_translate(-1, -1, 2 * width, 2 * height), paintColor);
144     }
145 
146     // Then handle Clamping, which requires expanding the pattern canvas to
147     // cover the entire surfaceBBox.
148 
149     SkBitmap bitmap;
150     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         bitmap = to_bitmap(image);
154     }
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     if (tileModesX == SkTileMode::kClamp && tileModesY == SkTileMode::kClamp) {
159         SkASSERT(!bitmap.drawsNothing());
160 
161         fill_color_from_bitmap(&canvas, deviceBounds.left(), deviceBounds.top(), 0, 0,
162                                bitmap, 0, 0, paintColor.fA);
163 
164         fill_color_from_bitmap(&canvas, width, deviceBounds.top(), deviceBounds.right(), 0,
165                                bitmap, bitmap.width() - 1, 0, paintColor.fA);
166 
167         fill_color_from_bitmap(&canvas, width, height, deviceBounds.right(), deviceBounds.bottom(),
168                                bitmap, bitmap.width() - 1, bitmap.height() - 1, paintColor.fA);
169 
170         fill_color_from_bitmap(&canvas, deviceBounds.left(), height, 0, deviceBounds.bottom(),
171                                bitmap, 0, bitmap.height() - 1, paintColor.fA);
172     }
173 
174     // Then expand the left, right, top, then bottom.
175     if (tileModesX == SkTileMode::kClamp) {
176         SkASSERT(!bitmap.drawsNothing());
177         SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height());
178         if (deviceBounds.left() < 0) {
179             SkBitmap left;
180             SkAssertResult(bitmap.extractSubset(&left, subset));
181 
182             SkMatrix leftMatrix = scale_translate(-deviceBounds.left(), 1, deviceBounds.left(), 0);
183             draw_bitmap_matrix(&canvas, left, leftMatrix, paintColor);
184 
185             if (tileModesY == SkTileMode::kMirror) {
186                 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
187                 leftMatrix.postTranslate(0, 2 * height);
188                 draw_bitmap_matrix(&canvas, left, leftMatrix, paintColor);
189             }
190             patternBBox.fLeft = 0;
191         }
192 
193         if (deviceBounds.right() > width) {
194             SkBitmap right;
195             subset.offset(bitmap.width() - 1, 0);
196             SkAssertResult(bitmap.extractSubset(&right, subset));
197 
198             SkMatrix rightMatrix = scale_translate(deviceBounds.right() - width, 1, width, 0);
199             draw_bitmap_matrix(&canvas, right, rightMatrix, paintColor);
200 
201             if (tileModesY == SkTileMode::kMirror) {
202                 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
203                 rightMatrix.postTranslate(0, 2 * height);
204                 draw_bitmap_matrix(&canvas, right, rightMatrix, paintColor);
205             }
206             patternBBox.fRight = deviceBounds.width();
207         }
208     }
209     if (tileModesX == SkTileMode::kDecal) {
210         if (deviceBounds.left() < 0) {
211             patternBBox.fLeft = 0;
212         }
213         if (deviceBounds.right() > width) {
214             patternBBox.fRight = deviceBounds.width();
215         }
216     }
217 
218     if (tileModesY == SkTileMode::kClamp) {
219         SkASSERT(!bitmap.drawsNothing());
220         SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1);
221         if (deviceBounds.top() < 0) {
222             SkBitmap top;
223             SkAssertResult(bitmap.extractSubset(&top, subset));
224 
225             SkMatrix topMatrix = scale_translate(1, -deviceBounds.top(), 0, deviceBounds.top());
226             draw_bitmap_matrix(&canvas, top, topMatrix, paintColor);
227 
228             if (tileModesX == SkTileMode::kMirror) {
229                 topMatrix.postScale(-1, 1);
230                 topMatrix.postTranslate(2 * width, 0);
231                 draw_bitmap_matrix(&canvas, top, topMatrix, paintColor);
232             }
233             patternBBox.fTop = 0;
234         }
235 
236         if (deviceBounds.bottom() > height) {
237             SkBitmap bottom;
238             subset.offset(0, bitmap.height() - 1);
239             SkAssertResult(bitmap.extractSubset(&bottom, subset));
240 
241             SkMatrix bottomMatrix = scale_translate(1, deviceBounds.bottom() - height, 0, height);
242             draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paintColor);
243 
244             if (tileModesX == SkTileMode::kMirror) {
245                 bottomMatrix.postScale(-1, 1);
246                 bottomMatrix.postTranslate(2 * width, 0);
247                 draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paintColor);
248             }
249             patternBBox.fBottom = deviceBounds.height();
250         }
251     }
252     if (tileModesY == SkTileMode::kDecal) {
253         if (deviceBounds.top() < 0) {
254             patternBBox.fTop = 0;
255         }
256         if (deviceBounds.bottom() > height) {
257             patternBBox.fBottom = deviceBounds.height();
258         }
259     }
260 
261     auto imageShader = patternDevice->content();
262     std::unique_ptr<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
263     std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
264     SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox,
265                                           std::move(resourceDict), finalMatrix);
266     return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc);
267 }
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
make_fallback_shader(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & surfaceBBox,SkColor4f paintColor)273 static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc,
274                                                    SkShader* shader,
275                                                    const SkMatrix& canvasTransform,
276                                                    const SkIRect& surfaceBBox,
277                                                    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     SkRect shaderRect = SkRect::Make(surfaceBBox);
283     if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
284         return SkPDFIndirectReference();
285     }
286     // Clamp the bitmap size to about 1M pixels
287     static const int kMaxBitmapArea = 1024 * 1024;
288     SkScalar bitmapArea = (float)surfaceBBox.width() * (float)surfaceBBox.height();
289     SkScalar rasterScale = 1.0f;
290     if (bitmapArea > (float)kMaxBitmapArea) {
291         rasterScale *= SkScalarSqrt((float)kMaxBitmapArea / bitmapArea);
292     }
293 
294     SkISize size = {
295         SkTPin(SkScalarCeilToInt(rasterScale * surfaceBBox.width()),  1, kMaxBitmapArea),
296         SkTPin(SkScalarCeilToInt(rasterScale * surfaceBBox.height()), 1, kMaxBitmapArea)};
297     SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
298                     SkIntToScalar(size.height()) / shaderRect.height()};
299 
300     auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size.width(), size.height()));
301     SkASSERT(surface);
302     SkCanvas* canvas = surface->getCanvas();
303     canvas->clear(SK_ColorTRANSPARENT);
304 
305     SkPaint p(paintColor);
306     p.setShader(sk_ref_sp(shader));
307 
308     canvas->scale(scale.width(), scale.height());
309     canvas->translate(-shaderRect.x(), -shaderRect.y());
310     canvas->drawPaint(p);
311 
312     auto shaderTransform = SkMatrix::Translate(shaderRect.x(), shaderRect.y());
313     shaderTransform.preScale(1 / scale.width(), 1 / scale.height());
314 
315     sk_sp<SkImage> image = surface->makeImageSnapshot();
316     SkASSERT(image);
317     return make_image_shader(doc,
318                              SkMatrix::Concat(canvasTransform, shaderTransform),
319                              SkTileMode::kClamp, SkTileMode::kClamp,
320                              SkRect::Make(surfaceBBox),
321                              image.get(),
322                              paintColor);
323 }
324 
adjust_color(SkShader * shader,SkColor4f paintColor)325 static SkColor4f adjust_color(SkShader* shader, SkColor4f paintColor) {
326     if (SkImage* img = shader->isAImage(nullptr, (SkTileMode*)nullptr)) {
327         if (img->isAlphaOnly()) {
328             return paintColor;
329         }
330     }
331     return SkColor4f{0, 0, 0, paintColor.fA};  // only preserve the alpha.
332 }
333 
SkPDFMakeShader(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & surfaceBBox,SkColor4f paintColor)334 SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
335                                        SkShader* shader,
336                                        const SkMatrix& canvasTransform,
337                                        const SkIRect& surfaceBBox,
338                                        SkColor4f paintColor) {
339     SkASSERT(shader);
340     SkASSERT(doc);
341     if (as_SB(shader)->asGradient() != SkShaderBase::GradientType::kNone) {
342         return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
343     }
344     if (surfaceBBox.isEmpty()) {
345         return SkPDFIndirectReference();
346     }
347     SkBitmap image;
348 
349     paintColor = adjust_color(shader, paintColor);
350     SkMatrix shaderTransform;
351     SkTileMode imageTileModes[2];
352     if (SkImage* skimg = shader->isAImage(&shaderTransform, imageTileModes)) {
353         SkMatrix finalMatrix = SkMatrix::Concat(canvasTransform, shaderTransform);
354         SkPDFImageShaderKey key = {
355             finalMatrix,
356             surfaceBBox,
357             SkBitmapKeyFromImage(skimg),
358             {imageTileModes[0], imageTileModes[1]},
359             paintColor};
360         SkPDFIndirectReference* shaderPtr = doc->fImageShaderMap.find(key);
361         if (shaderPtr) {
362             return *shaderPtr;
363         }
364         SkPDFIndirectReference pdfShader =
365                 make_image_shader(doc,
366                                   finalMatrix,
367                                   imageTileModes[0],
368                                   imageTileModes[1],
369                                   SkRect::Make(surfaceBBox),
370                                   skimg,
371                                   paintColor);
372         doc->fImageShaderMap.set(std::move(key), pdfShader);
373         return pdfShader;
374     }
375     // Don't bother to de-dup fallback shader.
376     return make_fallback_shader(doc, shader, canvasTransform, surfaceBBox, paintColor);
377 }
378