Bug 1029705 - allow clipping to a list of device-space rectangles (a region) in DrawTarget via PushDeviceSpaceClipRects. r=bas

MozReview-Commit-ID: 8zM116zB0NB
This commit is contained in:
Lee Salzman 2016-09-21 17:03:20 -04:00
Родитель 1e0c767fd6
Коммит 8869fd86cf
7 изменённых файлов: 152 добавлений и 60 удалений

Просмотреть файл

@ -1019,6 +1019,16 @@ public:
*/
virtual void PushClipRect(const Rect &aRect) = 0;
/**
* Push a clip region specifed by the union of axis-aligned rectangular
* clips to the DrawTarget. These rectangles are specified in device space.
* This must be balanced by a corresponding call to PopClip within a layer.
*
* @param aRects The rects to clip to
* @param aCount The number of rectangles
*/
virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount);
/** Pop a clip from the DrawTarget. A pop without a corresponding push will
* be ignored.
*/

Просмотреть файл

@ -5,6 +5,7 @@
#include "2D.h"
#include "Logging.h"
#include "PathHelpers.h"
#include "DrawTargetCapture.h"
@ -35,5 +36,21 @@ DrawTarget::DrawCapturedDT(DrawTargetCapture *aCaptureDT,
static_cast<DrawTargetCaptureImpl*>(aCaptureDT)->ReplayToDrawTarget(this, aTransform);
}
void
DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount)
{
Matrix oldTransform = GetTransform();
SetTransform(Matrix());
RefPtr<PathBuilder> pathBuilder = CreatePathBuilder();
for (uint32_t i = 0; i < aCount; i++) {
AppendRectToPath(pathBuilder, Rect(aRects[i]));
}
RefPtr<Path> path = pathBuilder->Finish();
PushClip(path);
SetTransform(oldTransform);
}
} // namespace gfx
} // namespace mozilla

Просмотреть файл

@ -679,22 +679,18 @@ DrawTargetD2D1::Mask(const Pattern &aSource,
}
void
DrawTargetD2D1::PushClip(const Path *aPath)
DrawTargetD2D1::PushClipGeometry(ID2D1Geometry* aGeometry,
const D2D1_MATRIX_3X2_F& aTransform,
bool aPixelAligned)
{
if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
gfxDebug() << *this << ": Ignoring clipping call for incompatible path.";
return;
}
mCurrentClippedGeometry = nullptr;
RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(aPath));
PushedClip clip;
clip.mTransform = D2DMatrix(mTransform);
clip.mPath = pathD2D;
pathD2D->mGeometry->GetBounds(clip.mTransform, &clip.mBounds);
clip.mGeometry = aGeometry;
clip.mTransform = aTransform;
clip.mIsPixelAligned = aPixelAligned;
aGeometry->GetBounds(aTransform, &clip.mBounds);
CurrentLayer().mPushedClips.push_back(clip);
@ -704,10 +700,23 @@ DrawTargetD2D1::PushClip(const Path *aPath)
mTransformDirty = true;
if (CurrentLayer().mClipsArePushed) {
PushD2DLayer(mDC, pathD2D->mGeometry, clip.mTransform);
PushD2DLayer(mDC, clip.mGeometry, clip.mTransform, clip.mIsPixelAligned);
}
}
void
DrawTargetD2D1::PushClip(const Path *aPath)
{
if (aPath->GetBackendType() != BackendType::DIRECT2D1_1) {
gfxDebug() << *this << ": Ignoring clipping call for incompatible path.";
return;
}
RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(aPath));
PushClipGeometry(pathD2D->GetGeometry(), D2DMatrix(mTransform));
}
void
DrawTargetD2D1::PushClipRect(const Rect &aRect)
{
@ -715,15 +724,8 @@ DrawTargetD2D1::PushClipRect(const Rect &aRect)
// Whoops, this isn't a rectangle in device space, Direct2D will not deal
// with this transform the way we want it to.
// See remarks: http://msdn.microsoft.com/en-us/library/dd316860%28VS.85%29.aspx
RefPtr<PathBuilder> pathBuilder = CreatePathBuilder();
pathBuilder->MoveTo(aRect.TopLeft());
pathBuilder->LineTo(aRect.TopRight());
pathBuilder->LineTo(aRect.BottomRight());
pathBuilder->LineTo(aRect.BottomLeft());
pathBuilder->Close();
RefPtr<Path> path = pathBuilder->Finish();
return PushClip(path);
RefPtr<ID2D1Geometry> geom = ConvertRectToGeometry(D2DRect(aRect));
return PushClipGeometry(geom, D2DMatrix(mTransform));
}
mCurrentClippedGeometry = nullptr;
@ -746,6 +748,29 @@ DrawTargetD2D1::PushClipRect(const Rect &aRect)
}
}
void
DrawTargetD2D1::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount)
{
// Build a path for the union of the rects.
RefPtr<ID2D1PathGeometry> path;
factory()->CreatePathGeometry(getter_AddRefs(path));
RefPtr<ID2D1GeometrySink> sink;
path->Open(getter_AddRefs(sink));
sink->SetFillMode(D2D1_FILL_MODE_WINDING);
for (uint32_t i = 0; i < aCount; i++) {
const IntRect& rect = aRects[i];
sink->BeginFigure(D2DPoint(rect.TopLeft()), D2D1_FIGURE_BEGIN_FILLED);
D2D1_POINT_2F lines[3] = { D2DPoint(rect.TopRight()), D2DPoint(rect.BottomRight()), D2DPoint(rect.BottomLeft()) };
sink->AddLines(lines, 3);
sink->EndFigure(D2D1_FIGURE_END_CLOSED);
}
sink->Close();
// The path is in device-space, so there is no transform needed,
// and all rects are pixel aligned.
PushClipGeometry(path, D2D1::IdentityMatrix(), true);
}
void
DrawTargetD2D1::PopClip()
{
@ -756,7 +781,7 @@ DrawTargetD2D1::PopClip()
}
if (CurrentLayer().mClipsArePushed) {
if (CurrentLayer().mPushedClips.back().mPath) {
if (CurrentLayer().mPushedClips.back().mGeometry) {
mDC->PopLayer();
} else {
mDC->PopAxisAlignedClip();
@ -1395,7 +1420,7 @@ DrawTargetD2D1::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAli
aClipRect = D2D1::RectF(0, 0, mSize.width, mSize.height);
for (auto iter = CurrentLayer().mPushedClips.begin();iter != CurrentLayer().mPushedClips.end(); iter++) {
if (iter->mPath) {
if (iter->mGeometry) {
return false;
}
aClipRect = IntersectRect(aClipRect, iter->mBounds);
@ -1475,8 +1500,8 @@ DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
bool pathRectIsAxisAligned = false;
auto iter = CurrentLayer().mPushedClips.begin();
if (iter->mPath) {
pathGeom = GetTransformedGeometry(iter->mPath->GetGeometry(), iter->mTransform);
if (iter->mGeometry) {
pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform);
} else {
pathRect = iter->mBounds;
pathRectIsAxisAligned = iter->mIsPixelAligned;
@ -1485,7 +1510,7 @@ DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
iter++;
for (;iter != CurrentLayer().mPushedClips.end(); iter++) {
// Do nothing but add it to the current clip bounds.
if (!iter->mPath && iter->mIsPixelAligned) {
if (!iter->mGeometry && iter->mIsPixelAligned) {
mCurrentClipBounds.IntersectRect(mCurrentClipBounds,
IntRect(int32_t(iter->mBounds.left), int32_t(iter->mBounds.top),
int32_t(iter->mBounds.right - iter->mBounds.left),
@ -1500,12 +1525,12 @@ DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
int32_t(pathRect.right - pathRect.left),
int32_t(pathRect.bottom - pathRect.top)));
}
if (iter->mPath) {
if (iter->mGeometry) {
// See if pathRect needs to go into the path geometry.
if (!pathRectIsAxisAligned) {
pathGeom = ConvertRectToGeometry(pathRect);
} else {
pathGeom = GetTransformedGeometry(iter->mPath->GetGeometry(), iter->mTransform);
pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform);
}
} else {
pathRect = IntersectRect(pathRect, iter->mBounds);
@ -1520,8 +1545,8 @@ DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
RefPtr<ID2D1GeometrySink> currentSink;
newGeom->Open(getter_AddRefs(currentSink));
if (iter->mPath) {
pathGeom->CombineWithGeometry(iter->mPath->GetGeometry(), D2D1_COMBINE_MODE_INTERSECT,
if (iter->mGeometry) {
pathGeom->CombineWithGeometry(iter->mGeometry, D2D1_COMBINE_MODE_INTERSECT,
iter->mTransform, currentSink);
} else {
RefPtr<ID2D1Geometry> rectGeom = ConvertRectToGeometry(iter->mBounds);
@ -1592,8 +1617,8 @@ DrawTargetD2D1::PushClipsToDC(ID2D1DeviceContext *aDC, bool aForceIgnoreAlpha, c
mTransformDirty = true;
for (auto iter = CurrentLayer().mPushedClips.begin(); iter != CurrentLayer().mPushedClips.end(); iter++) {
if (iter->mPath) {
PushD2DLayer(aDC, iter->mPath->mGeometry, iter->mTransform, aForceIgnoreAlpha, aMaxRect);
if (iter->mGeometry) {
PushD2DLayer(aDC, iter->mGeometry, iter->mTransform, iter->mIsPixelAligned, aForceIgnoreAlpha, aMaxRect);
} else {
mDC->PushAxisAlignedClip(iter->mBounds, iter->mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
}
@ -1604,7 +1629,7 @@ void
DrawTargetD2D1::PopClipsFromDC(ID2D1DeviceContext *aDC)
{
for (int i = CurrentLayer().mPushedClips.size() - 1; i >= 0; i--) {
if (CurrentLayer().mPushedClips[i].mPath) {
if (CurrentLayer().mPushedClips[i].mGeometry) {
aDC->PopLayer();
} else {
aDC->PopAxisAlignedClip();
@ -1866,7 +1891,7 @@ DrawTargetD2D1::OptimizeSourceSurface(SourceSurface* aSurface) const
void
DrawTargetD2D1::PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform,
bool aForceIgnoreAlpha, const D2D1_RECT_F& aMaxRect)
bool aPixelAligned, bool aForceIgnoreAlpha, const D2D1_RECT_F& aMaxRect)
{
D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE;
@ -1874,8 +1899,10 @@ DrawTargetD2D1::PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry,
options = D2D1_LAYER_OPTIONS1_IGNORE_ALPHA | D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND;
}
mDC->PushLayer(D2D1::LayerParameters1(aMaxRect, aGeometry,
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, aTransform,
D2D1_ANTIALIAS_MODE antialias =
aPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
mDC->PushLayer(D2D1::LayerParameters1(aMaxRect, aGeometry, antialias, aTransform,
1.0, nullptr, options), nullptr);
}

Просмотреть файл

@ -93,6 +93,8 @@ public:
const DrawOptions &aOptions = DrawOptions()) override;
virtual void PushClip(const Path *aPath) override;
virtual void PushClipRect(const Rect &aRect) override;
virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override;
virtual void PopClip() override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,
@ -215,8 +217,11 @@ private:
already_AddRefed<ID2D1SolidColorBrush> GetSolidColorBrush(const D2D_COLOR_F& aColor);
already_AddRefed<ID2D1Brush> CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f);
void PushClipGeometry(ID2D1Geometry* aGeometry, const D2D1_MATRIX_3X2_F& aTransform, bool aPixelAligned = false);
void PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform,
bool aForceIgnoreAlpha = false, const D2D1_RECT_F& aLayerRect = D2D1::InfiniteRect());
bool aPixelAligned = false, bool aForceIgnoreAlpha = false,
const D2D1_RECT_F& aLayerRect = D2D1::InfiniteRect());
IntSize mSize;
@ -240,13 +245,12 @@ private:
struct PushedClip
{
D2D1_RECT_F mBounds;
union {
// If mPath is non-null, the mTransform member will be used, otherwise
// the mIsPixelAligned member is valid.
D2D1_MATRIX_3X2_F mTransform;
bool mIsPixelAligned;
};
RefPtr<PathD2D> mPath;
// If mGeometry is non-null, the mTransform member will be used.
D2D1_MATRIX_3X2_F mTransform;
RefPtr<ID2D1Geometry> mGeometry;
// Indicates if mBounds, and when non-null, mGeometry with mTransform
// applied, are pixel-aligned.
bool mIsPixelAligned;
};
// List of pushed layers.

Просмотреть файл

@ -1816,6 +1816,22 @@ DrawTargetSkia::PushClip(const Path *aPath)
mCanvas->clipPath(skiaPath->GetPath(), SkRegion::kIntersect_Op, true);
}
void
DrawTargetSkia::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount)
{
// Build a region by unioning all the rects together.
SkRegion region;
for (uint32_t i = 0; i < aCount; i++) {
region.op(IntRectToSkIRect(aRects[i]), SkRegion::kUnion_Op);
}
// Clip with the resulting region. clipRegion does not transform
// this region by the current transform, unlike the other SkCanvas
// clip methods, so it is just passed through in device-space.
mCanvas->save();
mCanvas->clipRegion(region, SkRegion::kIntersect_Op);
}
void
DrawTargetSkia::PushClipRect(const Rect& aRect)
{

Просмотреть файл

@ -104,6 +104,7 @@ public:
const Matrix4x4& aMatrix) override;
virtual void PushClip(const Path *aPath) override;
virtual void PushClipRect(const Rect& aRect) override;
virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override;
virtual void PopClip() override;
virtual void PushLayer(bool aOpaque, Float aOpacity,
SourceSurface* aMask,

Просмотреть файл

@ -20,6 +20,7 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtrExtensions.h"
@ -719,25 +720,41 @@ gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion)
/*static*/ void
gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion)
{
if (!aRegion.IsComplex()) {
IntRect rect = aRegion.GetBounds();
aTarget->PushClipRect(Rect(rect.x, rect.y, rect.width, rect.height));
uint32_t numRects = aRegion.GetNumRects();
// If there is only one rect, then the region bounds are equivalent to the
// contents. So just use push a single clip rect with the bounds.
if (numRects == 1) {
aTarget->PushClipRect(Rect(aRegion.GetBounds()));
return;
}
RefPtr<PathBuilder> pb = aTarget->CreatePathBuilder();
for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
const IntRect& r = iter.Get();
pb->MoveTo(Point(r.x, r.y));
pb->LineTo(Point(r.XMost(), r.y));
pb->LineTo(Point(r.XMost(), r.YMost()));
pb->LineTo(Point(r.x, r.YMost()));
pb->Close();
// Check if the target's transform will preserve axis-alignment and
// pixel-alignment for each rect. For now, just handle the common case
// of integer translations.
Matrix transform = aTarget->GetTransform();
if (transform.IsIntegerTranslation()) {
IntPoint translation = RoundedToInt(transform.GetTranslation());
AutoTArray<IntRect, 16> rects;
rects.SetLength(numRects);
uint32_t i = 0;
// Build the list of transformed rects by adding in the translation.
for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
IntRect rect = iter.Get();
rect.MoveBy(translation);
rects[i++] = rect;
}
aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
} else {
// The transform does not produce axis-aligned rects or a rect was not
// pixel-aligned. So just build a path with all the rects and clip to it
// instead.
RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();
for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
AppendRectToPath(pathBuilder, Rect(iter.Get()));
}
RefPtr<Path> path = pathBuilder->Finish();
aTarget->PushClip(path);
}
RefPtr<Path> path = pb->Finish();
aTarget->PushClip(path);
}
/*static*/ gfxFloat