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