Index: src/core/SkRecordDraw.h |
diff --git a/src/core/SkRecordDraw.h b/src/core/SkRecordDraw.h |
index f3f00881ea69b2cb1be5892c6935a2b5d16df577..c38447b1ed10b7e096ae1792d6526c999bc0d18c 100644 |
--- a/src/core/SkRecordDraw.h |
+++ b/src/core/SkRecordDraw.h |
@@ -12,6 +12,7 @@ |
#include "SkCanvas.h" |
#include "SkDrawPictureCallback.h" |
#include "SkMatrix.h" |
+#include "SkPatchUtils.h" |
#include "SkRecord.h" |
// Fill a BBH to be used by SkRecordDraw to accelerate playback. |
@@ -74,6 +75,278 @@ private: |
typedef Draw INHERITED; |
}; |
+// This is an SkRecord visitor that fills an SkBBoxHierarchy. |
+// |
+// The interesting part here is how to calculate bounds for ops which don't |
+// have intrinsic bounds. What is the bounds of a Save or a Translate? |
+// |
+// We answer this by thinking about a particular definition of bounds: if I |
+// don't execute this op, pixels in this rectangle might draw incorrectly. So |
+// the bounds of a Save, a Translate, a Restore, etc. are the union of the |
+// bounds of Draw* ops that they might have an effect on. For any given |
+// Save/Restore block, the bounds of the Save, the Restore, and any other |
+// non-drawing ("control") ops inside are exactly the union of the bounds of |
+// the drawing ops inside that block. |
+// |
+// To implement this, we keep a stack of active Save blocks. As we consume ops |
+// inside the Save/Restore block, drawing ops are unioned with the bounds of |
+// the block, and control ops are stashed away for later. When we finish the |
+// block with a Restore, our bounds are complete, and we go back and fill them |
+// in for all the control ops we stashed away. |
+class FillBounds : SkNoncopyable { |
+public: |
+ FillBounds(const SkRect& cullRect, const SkRecord& record, SkBBoxHierarchy* bbh); |
+ |
+ void setCurrentOp(unsigned currentOp) { fCurrentOp = currentOp; } |
+ void cleanUp(); |
+ |
+ template <typename T> void operator()(const T& op) { |
+ this->updateCTM(op); |
+ this->updateClipBounds(op); |
+ this->trackBounds(op); |
+ } |
+ |
+ // In FillBounds, SkRect are in local coordinates, Bounds are translated back to identity space. |
+ typedef SkRect Bounds; |
+ |
+ unsigned currentOp() const { return fCurrentOp; } |
+ const SkMatrix& ctm() const { return *fCTM; } |
+ const Bounds& currentClipBounds() const { return fCurrentClipBounds; } |
+ |
+ Bounds adjustAndMap(SkRect rect, const SkPaint* paint) const; |
+ const Bounds& getBounds(unsigned index) const { return fBounds[index]; } |
+ |
+private: |
+ struct SaveBounds { |
+ int controlOps; // Number of control ops in this Save block, including the Save. |
+ Bounds bounds; // Bounds of everything in the block. |
+ const SkPaint* paint; // Unowned. If set, adjusts the bounds of all ops in this block. |
+ }; |
+ |
+ static void AdjustTextForFontMetrics(SkRect* rect, const SkPaint& paint); |
+ |
+ template <typename T> void updateCTM(const T&) {} |
+ template <typename T> void updateClipBounds(const T&) {} |
+ template <typename T> void trackBounds(const T& op) { |
+ fBounds[fCurrentOp] = this->bounds(op); |
+ this->updateSaveBounds(fBounds[fCurrentOp]); |
+ } |
+ template <typename T> Bounds bounds(const T&) const; |
+ |
+ void updateClipBoundsForClipOp(const SkIRect& devBounds); |
+ |
+ void pushSaveBlock(const SkPaint* paint); |
+ Bounds popSaveBlock(); |
+ void pushControl(); |
+ void popControl(const Bounds& bounds); |
+ void updateSaveBounds(const Bounds& bounds); |
+ |
+ bool adjustForSaveLayerPaints(SkRect* rect, int savesToIgnore = 0) const; |
+ |
+ const unsigned int fNumRecords; |
+ |
+ // The BBH being filled in |
+ SkBBoxHierarchy* fBBH; |
+ |
+ // We do not guarantee anything for operations outside of the cull rect |
+ const SkRect fCullRect; |
+ |
+ // Conservative identity-space bounds for each op in the SkRecord. |
+ SkAutoTMalloc<Bounds> fBounds; |
+ |
+ // We walk fCurrentOp through the SkRecord, as we go using updateCTM() |
+ // and updateClipBounds() to maintain the exact CTM (fCTM) and conservative |
+ // identity-space bounds of the current clip (fCurrentClipBounds). |
+ unsigned fCurrentOp; |
+ const SkMatrix* fCTM; |
+ Bounds fCurrentClipBounds; |
+ |
+ // Used to track the bounds of Save/Restore blocks and the control ops inside them. |
+ SkTDArray<SaveBounds> fSaveStack; |
+ SkTDArray<unsigned> fControlIndices; |
+}; |
+ |
+// Only Restore and SetMatrix change the CTM. |
+template <> inline void FillBounds::updateCTM(const Restore& op) { fCTM = &op.matrix; } |
+template <> inline void FillBounds::updateCTM(const SetMatrix& op) { fCTM = &op.matrix; } |
+ |
+// Clip{Path,RRect,Rect,Region} obviously change the clip. They all know their bounds already. |
+template <> inline void FillBounds::updateClipBounds(const ClipPath& op) { this->updateClipBoundsForClipOp(op.devBounds); } |
+template <> inline void FillBounds::updateClipBounds(const ClipRRect& op) { this->updateClipBoundsForClipOp(op.devBounds); } |
+template <> inline void FillBounds::updateClipBounds(const ClipRect& op) { this->updateClipBoundsForClipOp(op.devBounds); } |
+template <> inline void FillBounds::updateClipBounds(const ClipRegion& op) { this->updateClipBoundsForClipOp(op.devBounds); } |
+ |
+// Restore holds the devBounds for the clip after the {save,saveLayer}/restore block completes. |
+template <> inline void FillBounds::updateClipBounds(const Restore& op) { |
+ // This is just like the clip ops above, but we need to skip the effects (if any) of our |
+ // paired saveLayer (if it is one); it has not yet been popped off the save stack. Our |
+ // devBounds reflect the state of the world after the saveLayer/restore block is done, |
+ // so they are not affected by the saveLayer's paint. |
+ const int kSavesToIgnore = 1; |
+ Bounds clip = SkRect::Make(op.devBounds); |
+ fCurrentClipBounds = |
+ this->adjustForSaveLayerPaints(&clip, kSavesToIgnore) ? clip : fCullRect; |
+} |
+ |
+// We also take advantage of SaveLayer bounds when present to further cut the clip down. |
+template <> inline void FillBounds::updateClipBounds(const SaveLayer& op) { |
+ if (op.bounds) { |
+ // adjustAndMap() intersects these layer bounds with the previous clip for us. |
+ fCurrentClipBounds = this->adjustAndMap(*op.bounds, op.paint); |
+ } |
+} |
+ |
+template <> inline void FillBounds::trackBounds(const Save&) { this->pushSaveBlock(NULL); } |
+template <> inline void FillBounds::trackBounds(const SaveLayer& op) { this->pushSaveBlock(op.paint); } |
+template <> inline void FillBounds::trackBounds(const Restore&) { fBounds[fCurrentOp] = this->popSaveBlock(); } |
+ |
+template <> inline void FillBounds::trackBounds(const SetMatrix&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const ClipRect&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const ClipRRect&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const ClipPath&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const ClipRegion&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const PushCull&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const PopCull&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const BeginCommentGroup&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const AddComment&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const EndCommentGroup&) { this->pushControl(); } |
+template <> inline void FillBounds::trackBounds(const DrawData&) { this->pushControl(); } |
+ |
+// FIXME: this method could use better bounds |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawText&) const { return fCurrentClipBounds; } |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const Clear&) const { return fCullRect; } // Ignores the clip. |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPaint&) const { return fCurrentClipBounds; } |
+template <> inline FillBounds::Bounds FillBounds::bounds(const NoOp&) const { return Bounds::MakeEmpty(); } // NoOps don't draw. |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawSprite& op) const { |
+ // Ignores the matrix. |
+ const SkBitmap& bm = op.bitmap; |
+ return Bounds::MakeXYWH(SkIntToScalar(op.left), SkIntToScalar(op.top), |
+ SkIntToScalar(bm.width()), SkIntToScalar(bm.height())); |
+} |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawRect& op) const { return this->adjustAndMap(op.rect, &op.paint); } |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawOval& op) const { return this->adjustAndMap(op.oval, &op.paint); } |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawRRect& op) const { |
+ return this->adjustAndMap(op.rrect.rect(), &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawDRRect& op) const { |
+ return this->adjustAndMap(op.outer.rect(), &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawImage& op) const { |
+ const SkImage* image = op.image; |
+ SkRect rect = SkRect::MakeXYWH(SkIntToScalar(op.left), SkIntToScalar(op.top), |
+ SkIntToScalar(image->width()), SkIntToScalar(image->height())); |
+ |
+ return this->adjustAndMap(rect, op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawImageRect& op) const { |
+ return this->adjustAndMap(op.dst, op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawBitmapRectToRect& op) const { |
+ return this->adjustAndMap(op.dst, op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawBitmapNine& op) const { |
+ return this->adjustAndMap(op.dst, op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawBitmap& op) const { |
+ const SkBitmap& bm = op.bitmap; |
+ return this->adjustAndMap(SkRect::MakeXYWH(SkIntToScalar(op.left), |
+ SkIntToScalar(op.top), |
+ SkIntToScalar(bm.width()), |
+ SkIntToScalar(bm.height())), |
+ op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawBitmapMatrix& op) const { |
+ const SkBitmap& bm = op.bitmap; |
+ SkRect dst = SkRect::MakeWH(SkIntToScalar(bm.width()), |
+ SkIntToScalar(bm.height())); |
+ op.matrix.mapRect(&dst); |
+ return this->adjustAndMap(dst, op.paint); |
+} |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPath& op) const { |
+ return op.path.isInverseFillType() ? fCurrentClipBounds |
+ : this->adjustAndMap(op.path.getBounds(), &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPoints& op) const { |
+ SkRect dst; |
+ dst.set(op.pts, op.count); |
+ |
+ // Pad the bounding box a little to make sure hairline points' bounds aren't empty. |
+ SkScalar stroke = SkMaxScalar(op.paint.getStrokeWidth(), 0.01f); |
+ dst.outset(stroke/2, stroke/2); |
+ |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPatch& op) const { |
+ SkRect dst; |
+ dst.set(op.cubics, SkPatchUtils::kNumCtrlPts); |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawVertices& op) const { |
+ SkRect dst; |
+ dst.set(op.vertices, op.vertexCount); |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPicture& op) const { |
+ SkRect dst = op.picture->cullRect(); |
+ if (op.matrix) { |
+ op.matrix->mapRect(&dst); |
+ } |
+ return this->adjustAndMap(dst, op.paint); |
+} |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPosText& op) const { |
+ const int N = op.paint.countText(op.text, op.byteLength); |
+ if (N == 0) { |
+ return Bounds::MakeEmpty(); |
+ } |
+ |
+ SkRect dst; |
+ dst.set(op.pos, N); |
+ AdjustTextForFontMetrics(&dst, op.paint); |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawPosTextH& op) const { |
+ const int N = op.paint.countText(op.text, op.byteLength); |
+ if (N == 0) { |
+ return Bounds::MakeEmpty(); |
+ } |
+ |
+ SkScalar left = op.xpos[0], right = op.xpos[0]; |
+ for (int i = 1; i < N; i++) { |
+ left = SkMinScalar(left, op.xpos[i]); |
+ right = SkMaxScalar(right, op.xpos[i]); |
+ } |
+ SkRect dst = { left, op.y, right, op.y }; |
+ AdjustTextForFontMetrics(&dst, op.paint); |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawTextOnPath& op) const { |
+ SkRect dst = op.path.getBounds(); |
+ |
+ // Pad all sides by the maximum padding in any direction we'd normally apply. |
+ SkRect pad = { 0, 0, 0, 0}; |
+ AdjustTextForFontMetrics(&pad, op.paint); |
+ |
+ // That maximum padding happens to always be the right pad today. |
+ SkASSERT(pad.fLeft == -pad.fRight); |
+ SkASSERT(pad.fTop == -pad.fBottom); |
+ SkASSERT(pad.fRight > pad.fBottom); |
+ dst.outset(pad.fRight, pad.fRight); |
+ |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+ |
+template <> inline FillBounds::Bounds FillBounds::bounds(const DrawTextBlob& op) const { |
+ SkRect dst = op.blob->bounds(); |
+ dst.offset(op.x, op.y); |
+ return this->adjustAndMap(dst, &op.paint); |
+} |
+ |
} // namespace SkRecords |
#endif//SkRecordDraw_DEFINED |