Home | History | Annotate | Download | only in core
      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 
     14 SkBBoxRecord::~SkBBoxRecord() {
     15     fSaveStack.deleteAll();
     16 }
     17 
     18 void SkBBoxRecord::drawOval(const SkRect& rect, const SkPaint& paint) {
     19     if (this->transformBounds(rect, &paint)) {
     20         INHERITED::drawOval(rect, paint);
     21     }
     22 }
     23 
     24 void SkBBoxRecord::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
     25     if (this->transformBounds(rrect.rect(), &paint)) {
     26         INHERITED::drawRRect(rrect, paint);
     27     }
     28 }
     29 
     30 void SkBBoxRecord::drawRect(const SkRect& rect, const SkPaint& paint) {
     31     if (this->transformBounds(rect, &paint)) {
     32         INHERITED::drawRect(rect, paint);
     33     }
     34 }
     35 
     36 void 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 
     43 void 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 
     57 void 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 
     78 void 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 
     87 void 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 
     94 void 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 
    142 void 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 
    150 void 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 
    158 void 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 
    168 void 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 //
    183 static SkScalar hack_373785_amend_pad(SkScalar pad) {
    184     return pad * 4;
    185 }
    186 
    187 void 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 
    207 void 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 
    251 void 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 
    259 void 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 
    277 void 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 
    291 void 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 
    304 void 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 
    314 void 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 
    327 void SkBBoxRecord::willSave() {
    328     fSaveStack.push(NULL);
    329     this->INHERITED::willSave();
    330 }
    331 
    332 SkCanvas::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 
    341 void SkBBoxRecord::willRestore() {
    342     delete fSaveStack.top();
    343     fSaveStack.pop();
    344     this->INHERITED::willRestore();
    345 }
    346 
    347 bool 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