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