SkRecordDraw.cpp revision 937c9c7eb4e06d4d3bc495e129c7b8103a5d6c0f
1/*
2 * Copyright 2014 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#include "SkRecordDraw.h"
9#include "SkPatchUtils.h"
10
11void SkRecordDraw(const SkRecord& record,
12                  SkCanvas* canvas,
13                  const SkBBoxHierarchy* bbh,
14                  SkDrawPictureCallback* callback) {
15    SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/);
16
17    if (NULL != bbh) {
18        // Draw only ops that affect pixels in the canvas's current clip.
19        // The SkRecord and BBH were recorded in identity space.  This canvas
20        // is not necessarily in that same space.  getClipBounds() returns us
21        // this canvas' clip bounds transformed back into identity space, which
22        // lets us query the BBH.
23        SkRect query = { 0, 0, 0, 0 };
24        (void)canvas->getClipBounds(&query);
25
26        SkTDArray<void*> ops;
27        bbh->search(query, &ops);
28
29        SkRecords::Draw draw(canvas);
30        for (int i = 0; i < ops.count(); i++) {
31            if (NULL != callback && callback->abortDrawing()) {
32                return;
33            }
34            record.visit<void>((uintptr_t)ops[i], draw);  // See FillBounds below.
35        }
36    } else {
37        // Draw all ops.
38        SkRecords::Draw draw(canvas);
39        for (unsigned i = 0; i < record.count(); i++) {
40            if (NULL != callback && callback->abortDrawing()) {
41                return;
42            }
43            record.visit<void>(i, draw);
44        }
45    }
46}
47
48void SkRecordPartialDraw(const SkRecord& record,
49                         SkCanvas* canvas,
50                         const SkRect& clearRect,
51                         unsigned start, unsigned stop) {
52    SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/);
53
54    stop = SkTMin(stop, record.count());
55    SkRecords::PartialDraw draw(canvas, clearRect);
56    for (unsigned i = start; i < stop; i++) {
57        record.visit<void>(i, draw);
58    }
59}
60
61namespace SkRecords {
62
63// FIXME: SkBitmaps are stateful, so we need to copy them to play back in multiple threads.
64static SkBitmap shallow_copy(const SkBitmap& bitmap) {
65    return bitmap;
66}
67
68// NoOps draw nothing.
69template <> void Draw::draw(const NoOp&) {}
70
71#define DRAW(T, call) template <> void Draw::draw(const T& r) { fCanvas->call; }
72DRAW(Restore, restore());
73DRAW(Save, save());
74DRAW(SaveLayer, saveLayer(r.bounds, r.paint, r.flags));
75DRAW(PopCull, popCull());
76DRAW(PushCull, pushCull(r.rect));
77DRAW(Clear, clear(r.color));
78DRAW(SetMatrix, setMatrix(SkMatrix::Concat(fInitialCTM, r.matrix)));
79
80DRAW(ClipPath, clipPath(r.path, r.op, r.doAA));
81DRAW(ClipRRect, clipRRect(r.rrect, r.op, r.doAA));
82DRAW(ClipRect, clipRect(r.rect, r.op, r.doAA));
83DRAW(ClipRegion, clipRegion(r.region, r.op));
84
85DRAW(BeginCommentGroup, beginCommentGroup(r.description));
86DRAW(AddComment, addComment(r.key, r.value));
87DRAW(EndCommentGroup, endCommentGroup());
88
89DRAW(DrawBitmap, drawBitmap(shallow_copy(r.bitmap), r.left, r.top, r.paint));
90DRAW(DrawBitmapMatrix, drawBitmapMatrix(shallow_copy(r.bitmap), r.matrix, r.paint));
91DRAW(DrawBitmapNine, drawBitmapNine(shallow_copy(r.bitmap), r.center, r.dst, r.paint));
92DRAW(DrawBitmapRectToRect,
93        drawBitmapRectToRect(shallow_copy(r.bitmap), r.src, r.dst, r.paint, r.flags));
94DRAW(DrawDRRect, drawDRRect(r.outer, r.inner, r.paint));
95DRAW(DrawOval, drawOval(r.oval, r.paint));
96DRAW(DrawPaint, drawPaint(r.paint));
97DRAW(DrawPath, drawPath(r.path, r.paint));
98DRAW(DrawPatch, drawPatch(r.cubics, r.colors, r.texCoords, r.xmode.get(), r.paint));
99DRAW(DrawPicture, drawPicture(r.picture, r.matrix, r.paint));
100DRAW(DrawPoints, drawPoints(r.mode, r.count, r.pts, r.paint));
101DRAW(DrawPosText, drawPosText(r.text, r.byteLength, r.pos, r.paint));
102DRAW(DrawPosTextH, drawPosTextH(r.text, r.byteLength, r.xpos, r.y, r.paint));
103DRAW(DrawRRect, drawRRect(r.rrect, r.paint));
104DRAW(DrawRect, drawRect(r.rect, r.paint));
105DRAW(DrawSprite, drawSprite(shallow_copy(r.bitmap), r.left, r.top, r.paint));
106DRAW(DrawText, drawText(r.text, r.byteLength, r.x, r.y, r.paint));
107DRAW(DrawTextBlob, drawTextBlob(r.blob, r.x, r.y, r.paint));
108DRAW(DrawTextOnPath, drawTextOnPath(r.text, r.byteLength, r.path, r.matrix, r.paint));
109DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.colors,
110                                r.xmode.get(), r.indices, r.indexCount, r.paint));
111#undef DRAW
112
113
114// This is an SkRecord visitor that fills an SkBBoxHierarchy.
115//
116// The interesting part here is how to calculate bounds for ops which don't
117// have intrinsic bounds.  What is the bounds of a Save or a Translate?
118//
119// We answer this by thinking about a particular definition of bounds: if I
120// don't execute this op, pixels in this rectangle might draw incorrectly.  So
121// the bounds of a Save, a Translate, a Restore, etc. are the union of the
122// bounds of Draw* ops that they might have an effect on.  For any given
123// Save/Restore block, the bounds of the Save, the Restore, and any other
124// non-drawing ("control") ops inside are exactly the union of the bounds of
125// the drawing ops inside that block.
126//
127// To implement this, we keep a stack of active Save blocks.  As we consume ops
128// inside the Save/Restore block, drawing ops are unioned with the bounds of
129// the block, and control ops are stashed away for later.  When we finish the
130// block with a Restore, our bounds are complete, and we go back and fill them
131// in for all the control ops we stashed away.
132class FillBounds : SkNoncopyable {
133public:
134    FillBounds(const SkRecord& record, SkBBoxHierarchy* bbh) : fBounds(record.count()) {
135        // Calculate bounds for all ops.  This won't go quite in order, so we'll need
136        // to store the bounds separately then feed them in to the BBH later in order.
137        const Bounds largest = Bounds::MakeLargest();
138        fCTM = &SkMatrix::I();
139        fCurrentClipBounds = largest;
140        for (fCurrentOp = 0; fCurrentOp < record.count(); fCurrentOp++) {
141            record.visit<void>(fCurrentOp, *this);
142        }
143
144        // If we have any lingering unpaired Saves, simulate restores to make
145        // sure all ops in those Save blocks have their bounds calculated.
146        while (!fSaveStack.isEmpty()) {
147            this->popSaveBlock();
148        }
149
150        // Any control ops not part of any Save/Restore block draw everywhere.
151        while (!fControlIndices.isEmpty()) {
152            this->popControl(largest);
153        }
154
155        // Finally feed all stored bounds into the BBH.  They'll be returned in this order.
156        SkASSERT(NULL != bbh);
157        for (uintptr_t i = 0; i < record.count(); i++) {
158            if (!fBounds[i].isEmpty()) {
159                bbh->insert((void*)i, fBounds[i], true/*ok to defer*/);
160            }
161        }
162        bbh->flushDeferredInserts();
163    }
164
165    template <typename T> void operator()(const T& op) {
166        this->updateCTM(op);
167        this->updateClipBounds(op);
168        this->trackBounds(op);
169    }
170
171private:
172    // In this file, SkRect are in local coordinates, Bounds are translated back to identity space.
173    typedef SkRect Bounds;
174
175    struct SaveBounds {
176        int controlOps;        // Number of control ops in this Save block, including the Save.
177        Bounds bounds;         // Bounds of everything in the block.
178        const SkPaint* paint;  // Unowned.  If set, adjusts the bounds of all ops in this block.
179    };
180
181    template <typename T> void updateCTM(const T&) { /* most ops don't change the CTM */ }
182    void updateCTM(const Restore& op)   { fCTM = &op.matrix; }
183    void updateCTM(const SetMatrix& op) { fCTM = &op.matrix; }
184
185    template <typename T> void updateClipBounds(const T&) { /* most ops don't change the clip */ }
186    // Each of these devBounds fields is the state of the device bounds after the op.
187    // So Restore's devBounds are those bounds saved by its paired Save or SaveLayer.
188    void updateClipBounds(const Restore& op)    { fCurrentClipBounds = Bounds::Make(op.devBounds); }
189    void updateClipBounds(const ClipPath& op)   { fCurrentClipBounds = Bounds::Make(op.devBounds); }
190    void updateClipBounds(const ClipRRect& op)  { fCurrentClipBounds = Bounds::Make(op.devBounds); }
191    void updateClipBounds(const ClipRect& op)   { fCurrentClipBounds = Bounds::Make(op.devBounds); }
192    void updateClipBounds(const ClipRegion& op) { fCurrentClipBounds = Bounds::Make(op.devBounds); }
193    void updateClipBounds(const SaveLayer& op)  {
194        if (op.bounds) {
195            fCurrentClipBounds.intersect(this->adjustAndMap(*op.bounds, op.paint));
196        }
197    }
198
199    // The bounds of these ops must be calculated when we hit the Restore
200    // from the bounds of the ops in the same Save block.
201    void trackBounds(const Save&)          { this->pushSaveBlock(NULL); }
202    void trackBounds(const SaveLayer& op)  { this->pushSaveBlock(op.paint); }
203    void trackBounds(const Restore&) { fBounds[fCurrentOp] = this->popSaveBlock(); }
204
205    void trackBounds(const SetMatrix&)         { this->pushControl(); }
206    void trackBounds(const ClipRect&)          { this->pushControl(); }
207    void trackBounds(const ClipRRect&)         { this->pushControl(); }
208    void trackBounds(const ClipPath&)          { this->pushControl(); }
209    void trackBounds(const ClipRegion&)        { this->pushControl(); }
210    void trackBounds(const PushCull&)          { this->pushControl(); }
211    void trackBounds(const PopCull&)           { this->pushControl(); }
212    void trackBounds(const BeginCommentGroup&) { this->pushControl(); }
213    void trackBounds(const AddComment&)        { this->pushControl(); }
214    void trackBounds(const EndCommentGroup&)   { this->pushControl(); }
215
216    // For all other ops, we can calculate and store the bounds directly now.
217    template <typename T> void trackBounds(const T& op) {
218        fBounds[fCurrentOp] = this->bounds(op);
219        this->updateSaveBounds(fBounds[fCurrentOp]);
220    }
221
222    void pushSaveBlock(const SkPaint* paint) {
223        // Starting a new Save block.  Push a new entry to represent that.
224        SaveBounds sb = { 0, Bounds::MakeEmpty(), paint };
225        fSaveStack.push(sb);
226        this->pushControl();
227    }
228
229    static bool PaintMayAffectTransparentBlack(const SkPaint* paint) {
230        // FIXME: this is very conservative
231        return paint && (paint->getImageFilter() || paint->getColorFilter());
232    }
233
234    Bounds popSaveBlock() {
235        // We're done the Save block.  Apply the block's bounds to all control ops inside it.
236        SaveBounds sb;
237        fSaveStack.pop(&sb);
238
239        // If the paint affects transparent black, we can't trust any of our calculated bounds.
240        const Bounds& bounds =
241            PaintMayAffectTransparentBlack(sb.paint) ? fCurrentClipBounds : sb.bounds;
242
243        while (sb.controlOps --> 0) {
244            this->popControl(bounds);
245        }
246
247        // This whole Save block may be part another Save block.
248        this->updateSaveBounds(bounds);
249
250        // If called from a real Restore (not a phony one for balance), it'll need the bounds.
251        return bounds;
252    }
253
254    void pushControl() {
255        fControlIndices.push(fCurrentOp);
256        if (!fSaveStack.isEmpty()) {
257            fSaveStack.top().controlOps++;
258        }
259    }
260
261    void popControl(const Bounds& bounds) {
262        fBounds[fControlIndices.top()] = bounds;
263        fControlIndices.pop();
264    }
265
266    void updateSaveBounds(const Bounds& bounds) {
267        // If we're in a Save block, expand its bounds to cover these bounds too.
268        if (!fSaveStack.isEmpty()) {
269            fSaveStack.top().bounds.join(bounds);
270        }
271    }
272
273    // FIXME: this method could use better bounds
274    Bounds bounds(const DrawText&) const { return fCurrentClipBounds; }
275
276    Bounds bounds(const Clear&) const { return Bounds::MakeLargest(); }  // Ignores the clip.
277    Bounds bounds(const DrawPaint&) const { return fCurrentClipBounds; }
278    Bounds bounds(const NoOp&)  const { return Bounds::MakeEmpty(); }    // NoOps don't draw.
279
280    Bounds bounds(const DrawSprite& op) const {
281        const SkBitmap& bm = op.bitmap;
282        return Bounds::MakeXYWH(op.left, op.top, bm.width(), bm.height());  // Ignores the matrix.
283    }
284
285    Bounds bounds(const DrawRect& op) const { return this->adjustAndMap(op.rect, &op.paint); }
286    Bounds bounds(const DrawOval& op) const { return this->adjustAndMap(op.oval, &op.paint); }
287    Bounds bounds(const DrawRRect& op) const {
288        return this->adjustAndMap(op.rrect.rect(), &op.paint);
289    }
290    Bounds bounds(const DrawDRRect& op) const {
291        return this->adjustAndMap(op.outer.rect(), &op.paint);
292    }
293
294    Bounds bounds(const DrawBitmapRectToRect& op) const {
295        return this->adjustAndMap(op.dst, op.paint);
296    }
297    Bounds bounds(const DrawBitmapNine& op) const {
298        return this->adjustAndMap(op.dst, op.paint);
299    }
300    Bounds bounds(const DrawBitmap& op) const {
301        const SkBitmap& bm = op.bitmap;
302        return this->adjustAndMap(SkRect::MakeXYWH(op.left, op.top, bm.width(), bm.height()),
303                                  op.paint);
304    }
305    Bounds bounds(const DrawBitmapMatrix& op) const {
306        const SkBitmap& bm = op.bitmap;
307        SkRect dst = SkRect::MakeWH(bm.width(), bm.height());
308        op.matrix.mapRect(&dst);
309        return this->adjustAndMap(dst, op.paint);
310    }
311
312    Bounds bounds(const DrawPath& op) const {
313        return op.path.isInverseFillType() ? fCurrentClipBounds
314                                           : this->adjustAndMap(op.path.getBounds(), &op.paint);
315    }
316    Bounds bounds(const DrawPoints& op) const {
317        SkRect dst;
318        dst.set(op.pts, op.count);
319
320        // Pad the bounding box a little to make sure hairline points' bounds aren't empty.
321        SkScalar stroke = SkMaxScalar(op.paint.getStrokeWidth(), 0.01f);
322        dst.outset(stroke/2, stroke/2);
323
324        return this->adjustAndMap(dst, &op.paint);
325    }
326    Bounds bounds(const DrawPatch& op) const {
327        SkRect dst;
328        dst.set(op.cubics, SkPatchUtils::kNumCtrlPts);
329        return this->adjustAndMap(dst, &op.paint);
330    }
331    Bounds bounds(const DrawVertices& op) const {
332        SkRect dst;
333        dst.set(op.vertices, op.vertexCount);
334        return this->adjustAndMap(dst, &op.paint);
335    }
336
337    Bounds bounds(const DrawPicture& op) const {
338        SkRect dst = op.picture->cullRect();
339        if (op.matrix) {
340            op.matrix->mapRect(&dst);
341        }
342        return this->adjustAndMap(dst, op.paint);
343    }
344
345    Bounds bounds(const DrawPosText& op) const {
346        const int N = op.paint.countText(op.text, op.byteLength);
347        if (N == 0) {
348            return Bounds::MakeEmpty();
349        }
350
351        SkRect dst;
352        dst.set(op.pos, N);
353        AdjustTextForFontMetrics(&dst, op.paint);
354        return this->adjustAndMap(dst, &op.paint);
355    }
356    Bounds bounds(const DrawPosTextH& op) const {
357        const int N = op.paint.countText(op.text, op.byteLength);
358        if (N == 0) {
359            return Bounds::MakeEmpty();
360        }
361
362        SkScalar left = op.xpos[0], right = op.xpos[0];
363        for (int i = 1; i < N; i++) {
364            left  = SkMinScalar(left,  op.xpos[i]);
365            right = SkMaxScalar(right, op.xpos[i]);
366        }
367        SkRect dst = { left, op.y, right, op.y };
368        AdjustTextForFontMetrics(&dst, op.paint);
369        return this->adjustAndMap(dst, &op.paint);
370    }
371    Bounds bounds(const DrawTextOnPath& op) const {
372        SkRect dst = op.path.getBounds();
373
374        // Pad all sides by the maximum padding in any direction we'd normally apply.
375        SkRect pad = { 0, 0, 0, 0};
376        AdjustTextForFontMetrics(&pad, op.paint);
377
378        // That maximum padding happens to always be the right pad today.
379        SkASSERT(pad.fLeft == -pad.fRight);
380        SkASSERT(pad.fTop  == -pad.fBottom);
381        SkASSERT(pad.fRight > pad.fBottom);
382        dst.outset(pad.fRight, pad.fRight);
383
384        return this->adjustAndMap(dst, &op.paint);
385    }
386
387    Bounds bounds(const DrawTextBlob& op) const {
388        SkRect dst = op.blob->bounds();
389        dst.offset(op.x, op.y);
390        // TODO: remove when implicit bounds are plumbed through
391        if (dst.isEmpty()) {
392            return fCurrentClipBounds;
393        }
394        return this->adjustAndMap(dst, &op.paint);
395    }
396
397    static void AdjustTextForFontMetrics(SkRect* rect, const SkPaint& paint) {
398#ifdef SK_DEBUG
399        SkRect correct = *rect;
400#endif
401        const SkScalar yPad = 2.0f * paint.getTextSize(),  // In practice, this seems to be enough.
402                       xPad = 4.0f * yPad;                 // Hack for very wide Github logo font.
403        rect->outset(xPad, yPad);
404#ifdef SK_DEBUG
405        SkPaint::FontMetrics metrics;
406        paint.getFontMetrics(&metrics);
407        correct.fLeft   += metrics.fXMin;
408        correct.fTop    += metrics.fTop;
409        correct.fRight  += metrics.fXMax;
410        correct.fBottom += metrics.fBottom;
411        // See skia:2862 for why we ignore small text sizes.
412        SkASSERTF(paint.getTextSize() < 0.001f || rect->contains(correct),
413                  "%f %f %f %f vs. %f %f %f %f\n",
414                  -xPad, -yPad, +xPad, +yPad,
415                  metrics.fXMin, metrics.fTop, metrics.fXMax, metrics.fBottom);
416#endif
417    }
418
419    // Returns true if rect was meaningfully adjusted for the effects of paint,
420    // false if the paint could affect the rect in unknown ways.
421    static bool AdjustForPaint(const SkPaint* paint, SkRect* rect) {
422        if (paint) {
423            if (paint->canComputeFastBounds()) {
424                *rect = paint->computeFastBounds(*rect, rect);
425                return true;
426            }
427            return false;
428        }
429        return true;
430    }
431
432    // Adjust rect for all paints that may affect its geometry, then map it to identity space.
433    Bounds adjustAndMap(SkRect rect, const SkPaint* paint) const {
434        // Inverted rectangles really confuse our BBHs.
435        rect.sort();
436
437        // Adjust the rect for its own paint.
438        if (!AdjustForPaint(paint, &rect)) {
439            // The paint could do anything to our bounds.  The only safe answer is the current clip.
440            return fCurrentClipBounds;
441        }
442
443        // Adjust rect for all the paints from the SaveLayers we're inside.
444        for (int i = fSaveStack.count() - 1; i >= 0; i--) {
445            if (!AdjustForPaint(fSaveStack[i].paint, &rect)) {
446                // Same deal as above.
447                return fCurrentClipBounds;
448            }
449        }
450
451        // Map the rect back to identity space.
452        fCTM->mapRect(&rect);
453
454        // Nothing can draw outside the current clip.
455        // (Only bounded ops call into this method, so oddballs like Clear don't matter here.)
456        rect.intersect(fCurrentClipBounds);
457        return rect;
458    }
459
460    // Conservative identity-space bounds for each op in the SkRecord.
461    SkAutoTMalloc<Bounds> fBounds;
462
463    // We walk fCurrentOp through the SkRecord, as we go using updateCTM()
464    // and updateClipBounds() to maintain the exact CTM (fCTM) and conservative
465    // identity-space bounds of the current clip (fCurrentClipBounds).
466    unsigned fCurrentOp;
467    const SkMatrix* fCTM;
468    Bounds fCurrentClipBounds;
469
470    // Used to track the bounds of Save/Restore blocks and the control ops inside them.
471    SkTDArray<SaveBounds> fSaveStack;
472    SkTDArray<unsigned>   fControlIndices;
473};
474
475}  // namespace SkRecords
476
477void SkRecordFillBounds(const SkRecord& record, SkBBoxHierarchy* bbh) {
478    SkRecords::FillBounds(record, bbh);
479}
480