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