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