1 2/* 3 * Copyright 2012 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9#include "SkBBoxRecord.h" 10#include "SkPatchUtils.h" 11 12#include "SkTextBlob.h" 13 14SkBBoxRecord::~SkBBoxRecord() { 15 fSaveStack.deleteAll(); 16} 17 18void SkBBoxRecord::drawOval(const SkRect& rect, const SkPaint& paint) { 19 if (this->transformBounds(rect, &paint)) { 20 INHERITED::drawOval(rect, paint); 21 } 22} 23 24void SkBBoxRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) { 25 if (this->transformBounds(rrect.rect(), &paint)) { 26 INHERITED::drawRRect(rrect, paint); 27 } 28} 29 30void SkBBoxRecord::drawRect(const SkRect& rect, const SkPaint& paint) { 31 if (this->transformBounds(rect, &paint)) { 32 INHERITED::drawRect(rect, paint); 33 } 34} 35 36void SkBBoxRecord::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, 37 const SkPaint& paint) { 38 if (this->transformBounds(outer.rect(), &paint)) { 39 this->INHERITED::onDrawDRRect(outer, inner, paint); 40 } 41} 42 43void SkBBoxRecord::drawPath(const SkPath& path, const SkPaint& paint) { 44 if (path.isInverseFillType()) { 45 // If path is inverse filled, use the current clip bounds as the 46 // path's device-space bounding box. 47 SkIRect clipBounds; 48 if (this->getClipDeviceBounds(&clipBounds)) { 49 this->handleBBox(SkRect::Make(clipBounds)); 50 INHERITED::drawPath(path, paint); 51 } 52 } else if (this->transformBounds(path.getBounds(), &paint)) { 53 INHERITED::drawPath(path, paint); 54 } 55} 56 57void SkBBoxRecord::drawPoints(PointMode mode, size_t count, const SkPoint pts[], 58 const SkPaint& paint) { 59 SkRect bbox; 60 bbox.set(pts, SkToInt(count)); 61 // Small min width value, just to ensure hairline point bounding boxes aren't empty. 62 // Even though we know hairline primitives are drawn one pixel wide, we do not use a 63 // minimum of 1 because the playback scale factor is unknown at record time. Later 64 // outsets will take care of adding additional padding for antialiasing and rounding out 65 // to integer device coordinates, guaranteeing that the rasterized pixels will be included 66 // in the computed bounds. 67 // Note: The device coordinate outset in SkBBoxHierarchyRecord::handleBBox is currently 68 // done in the recording coordinate space, which is wrong. 69 // http://code.google.com/p/skia/issues/detail?id=1021 70 static const SkScalar kMinWidth = 0.01f; 71 SkScalar halfStrokeWidth = SkMaxScalar(paint.getStrokeWidth(), kMinWidth) / 2; 72 bbox.outset(halfStrokeWidth, halfStrokeWidth); 73 if (this->transformBounds(bbox, &paint)) { 74 INHERITED::drawPoints(mode, count, pts, paint); 75 } 76} 77 78void SkBBoxRecord::drawPaint(const SkPaint& paint) { 79 SkRect bbox; 80 if (this->getClipBounds(&bbox)) { 81 if (this->transformBounds(bbox, &paint)) { 82 INHERITED::drawPaint(paint); 83 } 84 } 85} 86 87void SkBBoxRecord::clear(SkColor color) { 88 SkISize size = this->getDeviceSize(); 89 SkRect bbox = {0, 0, SkIntToScalar(size.width()), SkIntToScalar(size.height())}; 90 this->handleBBox(bbox); 91 INHERITED::clear(color); 92} 93 94void SkBBoxRecord::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, 95 const SkPaint& paint) { 96 SkRect bbox; 97 paint.measureText(text, byteLength, &bbox); 98 SkPaint::FontMetrics metrics; 99 paint.getFontMetrics(&metrics); 100 101 // Vertical and aligned text need to be offset 102 if (paint.isVerticalText()) { 103 SkScalar h = bbox.fBottom - bbox.fTop; 104 if (paint.getTextAlign() == SkPaint::kCenter_Align) { 105 bbox.fTop -= h / 2; 106 bbox.fBottom -= h / 2; 107 } 108 // Pad top and bottom with max extents from FontMetrics 109 bbox.fBottom += metrics.fBottom; 110 bbox.fTop += metrics.fTop; 111 } else { 112 SkScalar w = bbox.fRight - bbox.fLeft; 113 if (paint.getTextAlign() == SkPaint::kCenter_Align) { 114 bbox.fLeft -= w / 2; 115 bbox.fRight -= w / 2; 116 } else if (paint.getTextAlign() == SkPaint::kRight_Align) { 117 bbox.fLeft -= w; 118 bbox.fRight -= w; 119 } 120 // Set vertical bounds to max extents from font metrics 121 bbox.fTop = metrics.fTop; 122 bbox.fBottom = metrics.fBottom; 123 } 124 125 // Pad horizontal bounds on each side by half of max vertical extents (this is sort of 126 // arbitrary, but seems to produce reasonable results, if there were a way of getting max 127 // glyph X-extents to pad by, that may be better here, but FontMetrics fXMin and fXMax seem 128 // incorrect on most platforms (too small in Linux, never even set in Windows). 129 SkScalar pad = (metrics.fBottom - metrics.fTop) / 2; 130 bbox.fLeft -= pad; 131 bbox.fRight += pad; 132 133 bbox.fLeft += x; 134 bbox.fRight += x; 135 bbox.fTop += y; 136 bbox.fBottom += y; 137 if (this->transformBounds(bbox, &paint)) { 138 INHERITED::onDrawText(text, byteLength, x, y, paint); 139 } 140} 141 142void SkBBoxRecord::drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, 143 const SkPaint* paint) { 144 SkRect bbox = {left, top, left + bitmap.width(), top + bitmap.height()}; 145 if (this->transformBounds(bbox, paint)) { 146 INHERITED::drawBitmap(bitmap, left, top, paint); 147 } 148} 149 150void SkBBoxRecord::drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src, 151 const SkRect& dst, const SkPaint* paint, 152 DrawBitmapRectFlags flags) { 153 if (this->transformBounds(dst, paint)) { 154 INHERITED::drawBitmapRectToRect(bitmap, src, dst, paint, flags); 155 } 156} 157 158void SkBBoxRecord::drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& mat, 159 const SkPaint* paint) { 160 SkMatrix m = mat; 161 SkRect bbox = {0, 0, SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height())}; 162 m.mapRect(&bbox); 163 if (this->transformBounds(bbox, paint)) { 164 INHERITED::drawBitmapMatrix(bitmap, mat, paint); 165 } 166} 167 168void SkBBoxRecord::drawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, 169 const SkRect& dst, const SkPaint* paint) { 170 if (this->transformBounds(dst, paint)) { 171 INHERITED::drawBitmapNine(bitmap, center, dst, paint); 172 } 173} 174 175// Hack to work-around https://code.google.com/p/chromium/issues/detail?id=373785 176// This logic assums that 'pad' is enough to add to the left and right to account for 177// big glyphs. For the font in question (a logo font) the glyphs is much wider than just 178// the pointsize (approx 3x wider). 179// As a temp work-around, we scale-up pad. 180// A more correct fix might be to add fontmetrics.fMaxX, but we don't have that value in hand 181// at the moment, and (possibly) the value in the font may not be accurate (but who knows). 182// 183static SkScalar hack_373785_amend_pad(SkScalar pad) { 184 return pad * 4; 185} 186 187void SkBBoxRecord::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], 188 const SkPaint& paint) { 189 SkRect bbox; 190 bbox.set(pos, paint.countText(text, byteLength)); 191 SkPaint::FontMetrics metrics; 192 paint.getFontMetrics(&metrics); 193 bbox.fTop += metrics.fTop; 194 bbox.fBottom += metrics.fBottom; 195 196 // pad on left and right by half of max vertical glyph extents 197 SkScalar pad = (metrics.fTop - metrics.fBottom) / 2; 198 pad = hack_373785_amend_pad(pad); 199 bbox.fLeft += pad; 200 bbox.fRight -= pad; 201 202 if (this->transformBounds(bbox, &paint)) { 203 INHERITED::onDrawPosText(text, byteLength, pos, paint); 204 } 205} 206 207void SkBBoxRecord::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], 208 SkScalar constY, const SkPaint& paint) { 209 size_t numChars = paint.countText(text, byteLength); 210 if (numChars == 0) { 211 return; 212 } 213 214 const SkFlatData* flatPaintData = this->getFlatPaintData(paint); 215 WriteTopBot(paint, *flatPaintData); 216 217 SkScalar top = flatPaintData->topBot()[0]; 218 SkScalar bottom = flatPaintData->topBot()[1]; 219 SkScalar pad = top - bottom; 220 221 SkRect bbox; 222 bbox.fLeft = SK_ScalarMax; 223 bbox.fRight = SK_ScalarMin; 224 225 for (size_t i = 0; i < numChars; ++i) { 226 if (xpos[i] < bbox.fLeft) { 227 bbox.fLeft = xpos[i]; 228 } 229 if (xpos[i] > bbox.fRight) { 230 bbox.fRight = xpos[i]; 231 } 232 } 233 234 // pad horizontally by max glyph height 235 pad = hack_373785_amend_pad(pad); 236 bbox.fLeft += pad; 237 bbox.fRight -= pad; 238 239 bbox.fTop = top + constY; 240 bbox.fBottom = bottom + constY; 241 242 if (!this->transformBounds(bbox, &paint)) { 243 return; 244 } 245 // This is the equivalent of calling: 246 // INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint); 247 // but we filled our flat paint beforehand so that we could get font metrics. 248 drawPosTextHImpl(text, byteLength, xpos, constY, paint, flatPaintData); 249} 250 251void SkBBoxRecord::drawSprite(const SkBitmap& bitmap, int left, int top, 252 const SkPaint* paint) { 253 SkRect bbox; 254 bbox.set(SkIRect::MakeXYWH(left, top, bitmap.width(), bitmap.height())); 255 this->handleBBox(bbox); // directly call handleBBox, matrix is ignored 256 INHERITED::drawSprite(bitmap, left, top, paint); 257} 258 259void SkBBoxRecord::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, 260 const SkMatrix* matrix, const SkPaint& paint) { 261 SkRect bbox = path.getBounds(); 262 SkPaint::FontMetrics metrics; 263 paint.getFontMetrics(&metrics); 264 265 // pad out all sides by the max glyph height above baseline 266 SkScalar pad = metrics.fTop; 267 bbox.fLeft += pad; 268 bbox.fRight -= pad; 269 bbox.fTop += pad; 270 bbox.fBottom -= pad; 271 272 if (this->transformBounds(bbox, &paint)) { 273 INHERITED::onDrawTextOnPath(text, byteLength, path, matrix, paint); 274 } 275} 276 277void SkBBoxRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, 278 const SkPaint& paint) { 279 SkRect bbox = blob->bounds(); 280 bbox.offset(x, y); 281 // FIXME: implement implicit blob bounds! 282 if (bbox.isEmpty()) { 283 this->getClipBounds(&bbox); 284 } 285 286 if (this->transformBounds(bbox, &paint)) { 287 INHERITED::onDrawTextBlob(blob, x, y, paint); 288 } 289} 290 291void SkBBoxRecord::drawVertices(VertexMode mode, int vertexCount, 292 const SkPoint vertices[], const SkPoint texs[], 293 const SkColor colors[], SkXfermode* xfer, 294 const uint16_t indices[], int indexCount, 295 const SkPaint& paint) { 296 SkRect bbox; 297 bbox.set(vertices, vertexCount); 298 if (this->transformBounds(bbox, &paint)) { 299 INHERITED::drawVertices(mode, vertexCount, vertices, texs, 300 colors, xfer, indices, indexCount, paint); 301 } 302} 303 304void SkBBoxRecord::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], 305 const SkPoint texCoords[4], SkXfermode* xmode, 306 const SkPaint& paint) { 307 SkRect bbox; 308 bbox.set(cubics, SkPatchUtils::kNumCtrlPts); 309 if (this->transformBounds(bbox, &paint)) { 310 INHERITED::onDrawPatch(cubics, colors, texCoords, xmode, paint); 311 } 312} 313 314void SkBBoxRecord::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, 315 const SkPaint* paint) { 316 SkRect bounds = picture->cullRect(); 317 // todo: wonder if we should allow passing an optional matrix to transformBounds so we don't 318 // end up transforming the rect twice. 319 if (matrix) { 320 matrix->mapRect(&bounds); 321 } 322 if (this->transformBounds(bounds, paint)) { 323 this->INHERITED::onDrawPicture(picture, matrix, paint); 324 } 325} 326 327void SkBBoxRecord::willSave() { 328 fSaveStack.push(NULL); 329 this->INHERITED::willSave(); 330} 331 332SkCanvas::SaveLayerStrategy SkBBoxRecord::willSaveLayer(const SkRect* bounds, 333 const SkPaint* paint, 334 SaveFlags flags) { 335 // Image filters can affect the effective bounds of primitives drawn inside saveLayer(). 336 // Copy the paint so we can compute the modified bounds in transformBounds(). 337 fSaveStack.push(paint && paint->getImageFilter() ? new SkPaint(*paint) : NULL); 338 return this->INHERITED::willSaveLayer(bounds, paint, flags); 339} 340 341void SkBBoxRecord::willRestore() { 342 delete fSaveStack.top(); 343 fSaveStack.pop(); 344 this->INHERITED::willRestore(); 345} 346 347bool SkBBoxRecord::transformBounds(const SkRect& bounds, const SkPaint* paint) { 348 SkRect outBounds = bounds; 349 outBounds.sort(); 350 351 if (paint) { 352 // account for stroking, path effects, shadows, etc 353 if (paint->canComputeFastBounds()) { 354 SkRect temp; 355 outBounds = paint->computeFastBounds(outBounds, &temp); 356 } else { 357 // set bounds to current clip 358 if (!this->getClipBounds(&outBounds)) { 359 // current clip is empty 360 return false; 361 } 362 } 363 } 364 365 for (int i = fSaveStack.count() - 1; i >= 0; --i) { 366 const SkPaint* paint = fSaveStack.getAt(i); 367 if (paint && paint->canComputeFastBounds()) { 368 SkRect temp; 369 outBounds = paint->computeFastBounds(outBounds, &temp); 370 } 371 } 372 373 if (!outBounds.isEmpty() && !this->quickReject(outBounds)) { 374 this->getTotalMatrix().mapRect(&outBounds); 375 this->handleBBox(outBounds); 376 return true; 377 } 378 379 return false; 380} 381