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