diff options
Diffstat (limited to 'gfx/thebes/gfxContext.cpp')
-rw-r--r-- | gfx/thebes/gfxContext.cpp | 1257 |
1 files changed, 1257 insertions, 0 deletions
diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp new file mode 100644 index 000000000..fd66d5394 --- /dev/null +++ b/gfx/thebes/gfxContext.cpp @@ -0,0 +1,1257 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <math.h> + +#include "mozilla/Alignment.h" + +#include "cairo.h" + +#include "gfxContext.h" + +#include "gfxMatrix.h" +#include "gfxUtils.h" +#include "gfxASurface.h" +#include "gfxPattern.h" +#include "gfxPlatform.h" +#include "gfxPrefs.h" +#include "GeckoProfiler.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/DrawTargetTiled.h" +#include <algorithm> + +#if XP_WIN +#include "gfxWindowsPlatform.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +UserDataKey gfxContext::sDontUseAsSourceKey; + + +PatternFromState::operator mozilla::gfx::Pattern&() +{ + gfxContext::AzureState &state = mContext->CurrentState(); + + if (state.pattern) { + return *state.pattern->GetPattern(mContext->mDT, state.patternTransformChanged ? &state.patternTransform : nullptr); + } + + if (state.sourceSurface) { + Matrix transform = state.surfTransform; + + if (state.patternTransformChanged) { + Matrix mat = mContext->GetDTTransform(); + if (!mat.Invert()) { + mPattern = new (mColorPattern.addr()) + ColorPattern(Color()); // transparent black to paint nothing + return *mPattern; + } + transform = transform * state.patternTransform * mat; + } + + mPattern = new (mSurfacePattern.addr()) + SurfacePattern(state.sourceSurface, ExtendMode::CLAMP, transform); + return *mPattern; + } + + mPattern = new (mColorPattern.addr()) + ColorPattern(state.color); + return *mPattern; +} + + +gfxContext::gfxContext(DrawTarget *aTarget, const Point& aDeviceOffset) + : mPathIsRect(false) + , mTransformChanged(false) + , mDT(aTarget) +{ + if (!aTarget) { + gfxCriticalError() << "Don't create a gfxContext without a DrawTarget"; + } + + MOZ_COUNT_CTOR(gfxContext); + + mStateStack.SetLength(1); + CurrentState().drawTarget = mDT; + CurrentState().deviceOffset = aDeviceOffset; + mDT->SetTransform(GetDTTransform()); +} + +/* static */ already_AddRefed<gfxContext> +gfxContext::CreateOrNull(DrawTarget* aTarget, + const mozilla::gfx::Point& aDeviceOffset) +{ + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " << hexa(aTarget); + return nullptr; + } + + RefPtr<gfxContext> result = new gfxContext(aTarget, aDeviceOffset); + return result.forget(); +} + +/* static */ already_AddRefed<gfxContext> +gfxContext::CreatePreservingTransformOrNull(DrawTarget* aTarget) +{ + if (!aTarget || !aTarget->IsValid()) { + gfxCriticalNote << "Invalid target in gfxContext::CreatePreservingTransformOrNull " << hexa(aTarget); + return nullptr; + } + + Matrix transform = aTarget->GetTransform(); + RefPtr<gfxContext> result = new gfxContext(aTarget); + result->SetMatrix(ThebesMatrix(transform)); + return result.forget(); +} + +gfxContext::~gfxContext() +{ + for (int i = mStateStack.Length() - 1; i >= 0; i--) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + mStateStack[i].drawTarget->PopClip(); + } + } + MOZ_COUNT_DTOR(gfxContext); +} + +void +gfxContext::Save() +{ + CurrentState().transform = mTransform; + mStateStack.AppendElement(AzureState(CurrentState())); + CurrentState().pushedClips.Clear(); +} + +void +gfxContext::Restore() +{ + for (unsigned int c = 0; c < CurrentState().pushedClips.Length(); c++) { + mDT->PopClip(); + } + + mStateStack.RemoveElementAt(mStateStack.Length() - 1); + + mDT = CurrentState().drawTarget; + + ChangeTransform(CurrentState().transform, false); +} + +// drawing +void +gfxContext::NewPath() +{ + mPath = nullptr; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; +} + +void +gfxContext::ClosePath() +{ + EnsurePathBuilder(); + mPathBuilder->Close(); +} + +already_AddRefed<Path> gfxContext::GetPath() +{ + EnsurePath(); + RefPtr<Path> path(mPath); + return path.forget(); +} + +void gfxContext::SetPath(Path* path) +{ + MOZ_ASSERT(path->GetBackendType() == mDT->GetBackendType() || + path->GetBackendType() == BackendType::RECORDING || + (mDT->GetBackendType() == BackendType::DIRECT2D1_1 && path->GetBackendType() == BackendType::DIRECT2D)); + mPath = path; + mPathBuilder = nullptr; + mPathIsRect = false; + mTransformChanged = false; +} + +gfxPoint +gfxContext::CurrentPoint() +{ + EnsurePathBuilder(); + return ThebesPoint(mPathBuilder->CurrentPoint()); +} + +void +gfxContext::Fill() +{ + Fill(PatternFromState(this)); +} + +void +gfxContext::Fill(const Pattern& aPattern) +{ + PROFILER_LABEL("gfxContext", "Fill", + js::ProfileEntry::Category::GRAPHICS); + FillAzure(aPattern, 1.0f); +} + +void +gfxContext::MoveTo(const gfxPoint& pt) +{ + EnsurePathBuilder(); + mPathBuilder->MoveTo(ToPoint(pt)); +} + +void +gfxContext::LineTo(const gfxPoint& pt) +{ + EnsurePathBuilder(); + mPathBuilder->LineTo(ToPoint(pt)); +} + +void +gfxContext::Line(const gfxPoint& start, const gfxPoint& end) +{ + EnsurePathBuilder(); + mPathBuilder->MoveTo(ToPoint(start)); + mPathBuilder->LineTo(ToPoint(end)); +} + +// XXX snapToPixels is only valid when snapping for filled +// rectangles and for even-width stroked rectangles. +// For odd-width stroked rectangles, we need to offset x/y by +// 0.5... +void +gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) +{ + Rect rec = ToRect(rect); + + if (snapToPixels) { + gfxRect newRect(rect); + if (UserToDevicePixelSnapped(newRect, true)) { + gfxMatrix mat = ThebesMatrix(mTransform); + if (mat.Invert()) { + // We need the user space rect. + rec = ToRect(mat.TransformBounds(newRect)); + } else { + rec = Rect(); + } + } + } + + if (!mPathBuilder && !mPathIsRect) { + mPathIsRect = true; + mRect = rec; + return; + } + + EnsurePathBuilder(); + + mPathBuilder->MoveTo(rec.TopLeft()); + mPathBuilder->LineTo(rec.TopRight()); + mPathBuilder->LineTo(rec.BottomRight()); + mPathBuilder->LineTo(rec.BottomLeft()); + mPathBuilder->Close(); +} + +// transform stuff +void +gfxContext::Multiply(const gfxMatrix& matrix) +{ + ChangeTransform(ToMatrix(matrix) * mTransform); +} + +void +gfxContext::SetMatrix(const gfxMatrix& matrix) +{ + ChangeTransform(ToMatrix(matrix)); +} + +gfxMatrix +gfxContext::CurrentMatrix() const +{ + return ThebesMatrix(mTransform); +} + +gfxPoint +gfxContext::DeviceToUser(const gfxPoint& point) const +{ + return ThebesPoint(mTransform.Inverse().TransformPoint(ToPoint(point))); +} + +Size +gfxContext::DeviceToUser(const Size& size) const +{ + return mTransform.Inverse().TransformSize(size); +} + +gfxRect +gfxContext::DeviceToUser(const gfxRect& rect) const +{ + return ThebesRect(mTransform.Inverse().TransformBounds(ToRect(rect))); +} + +gfxPoint +gfxContext::UserToDevice(const gfxPoint& point) const +{ + return ThebesPoint(mTransform.TransformPoint(ToPoint(point))); +} + +Size +gfxContext::UserToDevice(const Size& size) const +{ + const Matrix &matrix = mTransform; + + Size newSize; + newSize.width = size.width * matrix._11 + size.height * matrix._12; + newSize.height = size.width * matrix._21 + size.height * matrix._22; + return newSize; +} + +gfxRect +gfxContext::UserToDevice(const gfxRect& rect) const +{ + const Matrix &matrix = mTransform; + return ThebesRect(matrix.TransformBounds(ToRect(rect))); +} + +bool +gfxContext::UserToDevicePixelSnapped(gfxRect& rect, bool ignoreScale) const +{ + if (mDT->GetUserData(&sDisablePixelSnapping)) + return false; + + // if we're not at 1.0 scale, don't snap, unless we're + // ignoring the scale. If we're not -just- a scale, + // never snap. + const gfxFloat epsilon = 0.0000001; +#define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) + Matrix mat = mTransform; + if (!ignoreScale && + (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || + !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) + return false; +#undef WITHIN_E + + gfxPoint p1 = UserToDevice(rect.TopLeft()); + gfxPoint p2 = UserToDevice(rect.TopRight()); + gfxPoint p3 = UserToDevice(rect.BottomRight()); + + // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, + // two opposite corners define the entire rectangle. So check if + // the axis-aligned rectangle with opposite corners p1 and p3 + // define an axis-aligned rectangle whose other corners are p2 and p4. + // We actually only need to check one of p2 and p4, since an affine + // transform maps parallelograms to parallelograms. + if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) { + p1.Round(); + p3.Round(); + + rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); + rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), + std::max(p1.y, p3.y) - rect.Y())); + return true; + } + + return false; +} + +bool +gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, bool ignoreScale) const +{ + if (mDT->GetUserData(&sDisablePixelSnapping)) + return false; + + // if we're not at 1.0 scale, don't snap, unless we're + // ignoring the scale. If we're not -just- a scale, + // never snap. + const gfxFloat epsilon = 0.0000001; +#define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) + Matrix mat = mTransform; + if (!ignoreScale && + (!WITHIN_E(mat._11,1.0) || !WITHIN_E(mat._22,1.0) || + !WITHIN_E(mat._12,0.0) || !WITHIN_E(mat._21,0.0))) + return false; +#undef WITHIN_E + + pt = UserToDevice(pt); + pt.Round(); + return true; +} + +void +gfxContext::SetAntialiasMode(AntialiasMode mode) +{ + CurrentState().aaMode = mode; +} + +AntialiasMode +gfxContext::CurrentAntialiasMode() const +{ + return CurrentState().aaMode; +} + +void +gfxContext::SetDash(gfxFloat *dashes, int ndash, gfxFloat offset) +{ + AzureState &state = CurrentState(); + + state.dashPattern.SetLength(ndash); + for (int i = 0; i < ndash; i++) { + state.dashPattern[i] = Float(dashes[i]); + } + state.strokeOptions.mDashLength = ndash; + state.strokeOptions.mDashOffset = Float(offset); + state.strokeOptions.mDashPattern = ndash ? state.dashPattern.Elements() + : nullptr; +} + +bool +gfxContext::CurrentDash(FallibleTArray<gfxFloat>& dashes, gfxFloat* offset) const +{ + const AzureState &state = CurrentState(); + int count = state.strokeOptions.mDashLength; + + if (count <= 0 || !dashes.SetLength(count, fallible)) { + return false; + } + + for (int i = 0; i < count; i++) { + dashes[i] = state.dashPattern[i]; + } + + *offset = state.strokeOptions.mDashOffset; + + return true; +} + +gfxFloat +gfxContext::CurrentDashOffset() const +{ + return CurrentState().strokeOptions.mDashOffset; +} + +void +gfxContext::SetLineWidth(gfxFloat width) +{ + CurrentState().strokeOptions.mLineWidth = Float(width); +} + +gfxFloat +gfxContext::CurrentLineWidth() const +{ + return CurrentState().strokeOptions.mLineWidth; +} + +void +gfxContext::SetOp(CompositionOp aOp) +{ + CurrentState().op = aOp; +} + +CompositionOp +gfxContext::CurrentOp() const +{ + return CurrentState().op; +} + +void +gfxContext::SetLineCap(CapStyle cap) +{ + CurrentState().strokeOptions.mLineCap = cap; +} + +CapStyle +gfxContext::CurrentLineCap() const +{ + return CurrentState().strokeOptions.mLineCap; +} + +void +gfxContext::SetLineJoin(JoinStyle join) +{ + CurrentState().strokeOptions.mLineJoin = join; +} + +JoinStyle +gfxContext::CurrentLineJoin() const +{ + return CurrentState().strokeOptions.mLineJoin; +} + +void +gfxContext::SetMiterLimit(gfxFloat limit) +{ + CurrentState().strokeOptions.mMiterLimit = Float(limit); +} + +gfxFloat +gfxContext::CurrentMiterLimit() const +{ + return CurrentState().strokeOptions.mMiterLimit; +} + +// clipping +void +gfxContext::Clip(const Rect& rect) +{ + AzureState::PushedClip clip = { nullptr, rect, mTransform }; + CurrentState().pushedClips.AppendElement(clip); + mDT->PushClipRect(rect); + NewPath(); +} + +void +gfxContext::Clip(const gfxRect& rect) +{ + Clip(ToRect(rect)); +} + +void +gfxContext::Clip(Path* aPath) +{ + mDT->PushClip(aPath); + AzureState::PushedClip clip = { aPath, Rect(), mTransform }; + CurrentState().pushedClips.AppendElement(clip); +} + +void +gfxContext::Clip() +{ + if (mPathIsRect) { + MOZ_ASSERT(!mTransformChanged); + + AzureState::PushedClip clip = { nullptr, mRect, mTransform }; + CurrentState().pushedClips.AppendElement(clip); + mDT->PushClipRect(mRect); + } else { + EnsurePath(); + mDT->PushClip(mPath); + AzureState::PushedClip clip = { mPath, Rect(), mTransform }; + CurrentState().pushedClips.AppendElement(clip); + } +} + +void +gfxContext::PopClip() +{ + MOZ_ASSERT(CurrentState().pushedClips.Length() > 0); + + CurrentState().pushedClips.RemoveElementAt(CurrentState().pushedClips.Length() - 1); + mDT->PopClip(); +} + +gfxRect +gfxContext::GetClipExtents() +{ + Rect rect = GetAzureDeviceSpaceClipBounds(); + + if (rect.width == 0 || rect.height == 0) { + return gfxRect(0, 0, 0, 0); + } + + Matrix mat = mTransform; + mat.Invert(); + rect = mat.TransformBounds(rect); + + return ThebesRect(rect); +} + +bool +gfxContext::HasComplexClip() const +{ + for (int i = mStateStack.Length() - 1; i >= 0; i--) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + const AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; + if (clip.path || !clip.transform.IsRectilinear()) { + return true; + } + } + } + return false; +} + +bool +gfxContext::ExportClip(ClipExporter& aExporter) +{ + for (unsigned int i = 0; i < mStateStack.Length(); i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; + gfx::Matrix transform = clip.transform; + transform.PostTranslate(-GetDeviceOffset()); + + aExporter.BeginClip(transform); + if (clip.path) { + clip.path->StreamToSink(&aExporter); + } else { + aExporter.MoveTo(clip.rect.TopLeft()); + aExporter.LineTo(clip.rect.TopRight()); + aExporter.LineTo(clip.rect.BottomRight()); + aExporter.LineTo(clip.rect.BottomLeft()); + aExporter.Close(); + } + aExporter.EndClip(); + } + } + + return true; +} + +bool +gfxContext::ClipContainsRect(const gfxRect& aRect) +{ + // Since we always return false when the clip list contains a + // non-rectangular clip or a non-rectilinear transform, our 'total' clip + // is always a rectangle if we hit the end of this function. + Rect clipBounds(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); + + for (unsigned int i = 0; i < mStateStack.Length(); i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; + if (clip.path || !clip.transform.IsRectilinear()) { + // Cairo behavior is we return false if the clip contains a non- + // rectangle. + return false; + } else { + Rect clipRect = mTransform.TransformBounds(clip.rect); + + clipBounds.IntersectRect(clipBounds, clipRect); + } + } + } + + return clipBounds.Contains(ToRect(aRect)); +} + +// rendering sources + +void +gfxContext::SetColor(const Color& aColor) +{ + CurrentState().pattern = nullptr; + CurrentState().sourceSurfCairo = nullptr; + CurrentState().sourceSurface = nullptr; + CurrentState().color = ToDeviceColor(aColor); +} + +void +gfxContext::SetDeviceColor(const Color& aColor) +{ + CurrentState().pattern = nullptr; + CurrentState().sourceSurfCairo = nullptr; + CurrentState().sourceSurface = nullptr; + CurrentState().color = aColor; +} + +bool +gfxContext::GetDeviceColor(Color& aColorOut) +{ + if (CurrentState().sourceSurface) { + return false; + } + if (CurrentState().pattern) { + return CurrentState().pattern->GetSolidColor(aColorOut); + } + + aColorOut = CurrentState().color; + return true; +} + +void +gfxContext::SetSource(gfxASurface *surface, const gfxPoint& offset) +{ + CurrentState().surfTransform = Matrix(1.0f, 0, 0, 1.0f, Float(offset.x), Float(offset.y)); + CurrentState().pattern = nullptr; + CurrentState().patternTransformChanged = false; + // Keep the underlying cairo surface around while we keep the + // sourceSurface. + CurrentState().sourceSurfCairo = surface; + CurrentState().sourceSurface = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mDT, surface); + CurrentState().color = Color(0, 0, 0, 0); +} + +void +gfxContext::SetPattern(gfxPattern *pattern) +{ + CurrentState().sourceSurfCairo = nullptr; + CurrentState().sourceSurface = nullptr; + CurrentState().patternTransformChanged = false; + CurrentState().pattern = pattern; +} + +already_AddRefed<gfxPattern> +gfxContext::GetPattern() +{ + RefPtr<gfxPattern> pat; + + AzureState &state = CurrentState(); + if (state.pattern) { + pat = state.pattern; + } else if (state.sourceSurface) { + NS_ASSERTION(false, "Ugh, this isn't good."); + } else { + pat = new gfxPattern(state.color); + } + return pat.forget(); +} + +void +gfxContext::SetFontSmoothingBackgroundColor(const Color& aColor) +{ + CurrentState().fontSmoothingBackgroundColor = aColor; +} + +Color +gfxContext::GetFontSmoothingBackgroundColor() +{ + return CurrentState().fontSmoothingBackgroundColor; +} + +// masking +void +gfxContext::Mask(SourceSurface* aSurface, Float aAlpha, const Matrix& aTransform) +{ + Matrix old = mTransform; + Matrix mat = aTransform * mTransform; + + ChangeTransform(mat); + mDT->MaskSurface(PatternFromState(this), aSurface, Point(), + DrawOptions(aAlpha, CurrentState().op, CurrentState().aaMode)); + ChangeTransform(old); +} + +void +gfxContext::Mask(SourceSurface *surface, float alpha, const Point& offset) +{ + // We clip here to bind to the mask surface bounds, see above. + mDT->MaskSurface(PatternFromState(this), + surface, + offset, + DrawOptions(alpha, CurrentState().op, CurrentState().aaMode)); +} + +void +gfxContext::Paint(gfxFloat alpha) +{ + PROFILER_LABEL("gfxContext", "Paint", + js::ProfileEntry::Category::GRAPHICS); + + AzureState &state = CurrentState(); + + if (state.sourceSurface && !state.sourceSurfCairo && + !state.patternTransformChanged) + { + // This is the case where a PopGroupToSource has been done and this + // paint is executed without changing the transform or the source. + Matrix oldMat = mDT->GetTransform(); + + IntSize surfSize = state.sourceSurface->GetSize(); + + mDT->SetTransform(Matrix::Translation(-state.deviceOffset.x, + -state.deviceOffset.y)); + + mDT->DrawSurface(state.sourceSurface, + Rect(state.sourceSurfaceDeviceOffset, Size(surfSize.width, surfSize.height)), + Rect(Point(), Size(surfSize.width, surfSize.height)), + DrawSurfaceOptions(), DrawOptions(alpha, GetOp())); + mDT->SetTransform(oldMat); + return; + } + + Matrix mat = mDT->GetTransform(); + mat.Invert(); + Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); + + mDT->FillRect(paintRect, PatternFromState(this), + DrawOptions(Float(alpha), GetOp())); +} + +void +gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform) +{ + if (gfxPrefs::UseNativePushLayer()) { + Save(); + mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform); + } else { + DrawTarget* oldDT = mDT; + + PushNewDT(content); + + if (oldDT != mDT) { + PushClipsToDT(mDT); + } + mDT->SetTransform(GetDTTransform()); + + CurrentState().mBlendOpacity = aOpacity; + CurrentState().mBlendMask = aMask; +#ifdef DEBUG + CurrentState().mWasPushedForBlendBack = true; +#endif + CurrentState().mBlendMaskTransform = aMaskTransform; + } +} + +static gfxRect +GetRoundOutDeviceClipExtents(gfxContext* aCtx) +{ + gfxContextMatrixAutoSaveRestore save(aCtx); + aCtx->SetMatrix(gfxMatrix()); + gfxRect r = aCtx->GetClipExtents(); + r.RoundOut(); + return r; +} + +void +gfxContext::PushGroupAndCopyBackground(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform) +{ + IntRect clipExtents; + if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) { + gfxRect clipRect = GetRoundOutDeviceClipExtents(this); + clipExtents = IntRect::Truncate(clipRect.x, clipRect.y, clipRect.width, clipRect.height); + } + bool pushOpaqueWithCopiedBG = (mDT->GetFormat() == SurfaceFormat::B8G8R8X8 || + mDT->GetOpaqueRect().Contains(clipExtents)) && + !mDT->GetUserData(&sDontUseAsSourceKey); + + if (gfxPrefs::UseNativePushLayer()) { + Save(); + + if (pushOpaqueWithCopiedBG) { + mDT->PushLayer(true, aOpacity, aMask, aMaskTransform, IntRect(), true); + } else { + mDT->PushLayer(content == gfxContentType::COLOR, aOpacity, aMask, aMaskTransform, IntRect(), false); + } + } else { + RefPtr<SourceSurface> source; + // This snapshot can be nullptr if the DrawTarget is a cairo target that is currently + // in an error state. + if (pushOpaqueWithCopiedBG && (source = mDT->Snapshot())) { + DrawTarget *oldDT = mDT; + Point oldDeviceOffset = CurrentState().deviceOffset; + + PushNewDT(gfxContentType::COLOR); + + if (oldDT == mDT) { + // Creating new DT failed. + return; + } + + CurrentState().mBlendOpacity = aOpacity; + CurrentState().mBlendMask = aMask; +#ifdef DEBUG + CurrentState().mWasPushedForBlendBack = true; +#endif + CurrentState().mBlendMaskTransform = aMaskTransform; + + Point offset = CurrentState().deviceOffset - oldDeviceOffset; + Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height)); + Rect sourceRect = surfRect + offset; + + mDT->SetTransform(Matrix()); + + // XXX: It's really sad that we have to do this (for performance). + // Once DrawTarget gets a PushLayer API we can implement this within + // DrawTargetTiled. + if (source->GetType() == SurfaceType::TILED) { + SnapshotTiled *sourceTiled = static_cast<SnapshotTiled*>(source.get()); + for (uint32_t i = 0; i < sourceTiled->mSnapshots.size(); i++) { + Rect tileSourceRect = sourceRect.Intersect(Rect(sourceTiled->mOrigins[i].x, + sourceTiled->mOrigins[i].y, + sourceTiled->mSnapshots[i]->GetSize().width, + sourceTiled->mSnapshots[i]->GetSize().height)); + + if (tileSourceRect.IsEmpty()) { + continue; + } + Rect tileDestRect = tileSourceRect - offset; + tileSourceRect -= sourceTiled->mOrigins[i]; + + mDT->DrawSurface(sourceTiled->mSnapshots[i], tileDestRect, tileSourceRect); + } + } else { + mDT->DrawSurface(source, surfRect, sourceRect); + } + mDT->SetOpaqueRect(oldDT->GetOpaqueRect()); + + PushClipsToDT(mDT); + mDT->SetTransform(GetDTTransform()); + return; + } + DrawTarget* oldDT = mDT; + + PushNewDT(content); + + if (oldDT != mDT) { + PushClipsToDT(mDT); + } + + mDT->SetTransform(GetDTTransform()); + CurrentState().mBlendOpacity = aOpacity; + CurrentState().mBlendMask = aMask; +#ifdef DEBUG + CurrentState().mWasPushedForBlendBack = true; +#endif + CurrentState().mBlendMaskTransform = aMaskTransform; + } +} + +void +gfxContext::PopGroupAndBlend() +{ + if (gfxPrefs::UseNativePushLayer()) { + mDT->PopLayer(); + Restore(); + } else { + MOZ_ASSERT(CurrentState().mWasPushedForBlendBack); + Float opacity = CurrentState().mBlendOpacity; + RefPtr<SourceSurface> mask = CurrentState().mBlendMask; + Matrix maskTransform = CurrentState().mBlendMaskTransform; + + RefPtr<SourceSurface> src = mDT->Snapshot(); + Point deviceOffset = CurrentState().deviceOffset; + Restore(); + CurrentState().sourceSurfCairo = nullptr; + CurrentState().sourceSurface = src; + CurrentState().sourceSurfaceDeviceOffset = deviceOffset; + CurrentState().pattern = nullptr; + CurrentState().patternTransformChanged = false; + + Matrix mat = mTransform; + mat.Invert(); + mat.PreTranslate(deviceOffset.x, deviceOffset.y); // device offset translation + + CurrentState().surfTransform = mat; + + CompositionOp oldOp = GetOp(); + SetOp(CompositionOp::OP_OVER); + + if (mask) { + if (!maskTransform.HasNonTranslation()) { + Mask(mask, opacity, Point(maskTransform._31, maskTransform._32)); + } else { + Mask(mask, opacity, maskTransform); + } + } else { + Paint(opacity); + } + + SetOp(oldOp); + } +} + +#ifdef MOZ_DUMP_PAINTING +void +gfxContext::WriteAsPNG(const char* aFile) +{ + gfxUtils::WriteAsPNG(mDT, aFile); +} + +void +gfxContext::DumpAsDataURI() +{ + gfxUtils::DumpAsDataURI(mDT); +} + +void +gfxContext::CopyAsDataURI() +{ + gfxUtils::CopyAsDataURI(mDT); +} +#endif + +void +gfxContext::EnsurePath() +{ + if (mPathBuilder) { + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + } + + if (mPath) { + if (mTransformChanged) { + Matrix mat = mTransform; + mat.Invert(); + mat = mPathTransform * mat; + mPathBuilder = mPath->TransformedCopyToBuilder(mat); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; + + mTransformChanged = false; + } + return; + } + + EnsurePathBuilder(); + mPath = mPathBuilder->Finish(); + mPathBuilder = nullptr; +} + +void +gfxContext::EnsurePathBuilder() +{ + if (mPathBuilder && !mTransformChanged) { + return; + } + + if (mPath) { + if (!mTransformChanged) { + mPathBuilder = mPath->CopyToBuilder(); + mPath = nullptr; + } else { + Matrix invTransform = mTransform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + mPathBuilder = mPath->TransformedCopyToBuilder(toNewUS); + } + return; + } + + DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); + + if (!mPathBuilder) { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + if (mPathIsRect) { + mPathBuilder->MoveTo(mRect.TopLeft()); + mPathBuilder->LineTo(mRect.TopRight()); + mPathBuilder->LineTo(mRect.BottomRight()); + mPathBuilder->LineTo(mRect.BottomLeft()); + mPathBuilder->Close(); + } + } + + if (mTransformChanged) { + // This could be an else if since this should never happen when + // mPathBuilder is nullptr and mPath is nullptr. But this way we can + // assert if all the state is as expected. + MOZ_ASSERT(oldPath); + MOZ_ASSERT(!mPathIsRect); + + Matrix invTransform = mTransform; + invTransform.Invert(); + Matrix toNewUS = mPathTransform * invTransform; + + RefPtr<Path> path = mPathBuilder->Finish(); + if (!path) { + gfxCriticalError() << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish"; + } + mPathBuilder = path->TransformedCopyToBuilder(toNewUS); + } + + mPathIsRect = false; +} + +void +gfxContext::FillAzure(const Pattern& aPattern, Float aOpacity) +{ + AzureState &state = CurrentState(); + + CompositionOp op = GetOp(); + + if (mPathIsRect) { + MOZ_ASSERT(!mTransformChanged); + + if (op == CompositionOp::OP_SOURCE) { + // Emulate cairo operator source which is bound by mask! + mDT->ClearRect(mRect); + mDT->FillRect(mRect, aPattern, DrawOptions(aOpacity)); + } else { + mDT->FillRect(mRect, aPattern, DrawOptions(aOpacity, op, state.aaMode)); + } + } else { + EnsurePath(); + mDT->Fill(mPath, aPattern, DrawOptions(aOpacity, op, state.aaMode)); + } +} + +void +gfxContext::PushClipsToDT(DrawTarget *aDT) +{ + // Don't need to save the old transform, we'll be setting a new one soon! + + // Push all clips from the bottom of the stack to the clip before ours. + for (unsigned int i = 0; i < mStateStack.Length() - 1; i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + aDT->SetTransform(mStateStack[i].pushedClips[c].transform * GetDeviceTransform()); + if (mStateStack[i].pushedClips[c].path) { + aDT->PushClip(mStateStack[i].pushedClips[c].path); + } else { + aDT->PushClipRect(mStateStack[i].pushedClips[c].rect); + } + } + } +} + +CompositionOp +gfxContext::GetOp() +{ + if (CurrentState().op != CompositionOp::OP_SOURCE) { + return CurrentState().op; + } + + AzureState &state = CurrentState(); + if (state.pattern) { + if (state.pattern->IsOpaque()) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } else if (state.sourceSurface) { + if (state.sourceSurface->GetFormat() == SurfaceFormat::B8G8R8X8) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } else { + if (state.color.a > 0.999) { + return CompositionOp::OP_OVER; + } else { + return CompositionOp::OP_SOURCE; + } + } +} + +/* SVG font code can change the transform after having set the pattern on the + * context. When the pattern is set it is in user space, if the transform is + * changed after doing so the pattern needs to be converted back into userspace. + * We just store the old pattern transform here so that we only do the work + * needed here if the pattern is actually used. + * We need to avoid doing this when this ChangeTransform comes from a restore, + * since the current pattern and the current transform are both part of the + * state we know the new CurrentState()'s values are valid. But if we assume + * a change they might become invalid since patternTransformChanged is part of + * the state and might be false for the restored AzureState. + */ +void +gfxContext::ChangeTransform(const Matrix &aNewMatrix, bool aUpdatePatternTransform) +{ + AzureState &state = CurrentState(); + + if (aUpdatePatternTransform && (state.pattern || state.sourceSurface) + && !state.patternTransformChanged) { + state.patternTransform = GetDTTransform(); + state.patternTransformChanged = true; + } + + if (mPathIsRect) { + Matrix invMatrix = aNewMatrix; + + invMatrix.Invert(); + + Matrix toNewUS = mTransform * invMatrix; + + if (toNewUS.IsRectilinear()) { + mRect = toNewUS.TransformBounds(mRect); + mRect.NudgeToIntegers(); + } else { + mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); + + mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight())); + mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft())); + mPathBuilder->Close(); + + mPathIsRect = false; + } + + // No need to consider the transform changed now! + mTransformChanged = false; + } else if ((mPath || mPathBuilder) && !mTransformChanged) { + mTransformChanged = true; + mPathTransform = mTransform; + } + + mTransform = aNewMatrix; + + mDT->SetTransform(GetDTTransform()); +} + +Rect +gfxContext::GetAzureDeviceSpaceClipBounds() +{ + Rect rect(CurrentState().deviceOffset.x, CurrentState().deviceOffset.y, + Float(mDT->GetSize().width), Float(mDT->GetSize().height)); + for (unsigned int i = 0; i < mStateStack.Length(); i++) { + for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) { + AzureState::PushedClip &clip = mStateStack[i].pushedClips[c]; + if (clip.path) { + Rect bounds = clip.path->GetBounds(clip.transform); + rect.IntersectRect(rect, bounds); + } else { + rect.IntersectRect(rect, clip.transform.TransformBounds(clip.rect)); + } + } + } + + return rect; +} + +Point +gfxContext::GetDeviceOffset() const +{ + return CurrentState().deviceOffset; +} + +Matrix +gfxContext::GetDeviceTransform() const +{ + return Matrix::Translation(-CurrentState().deviceOffset.x, + -CurrentState().deviceOffset.y); +} + +Matrix +gfxContext::GetDTTransform() const +{ + Matrix mat = mTransform; + mat._31 -= CurrentState().deviceOffset.x; + mat._32 -= CurrentState().deviceOffset.y; + return mat; +} + +void +gfxContext::PushNewDT(gfxContentType content) +{ + Rect clipBounds = GetAzureDeviceSpaceClipBounds(); + clipBounds.RoundOut(); + + clipBounds.width = std::max(1.0f, clipBounds.width); + clipBounds.height = std::max(1.0f, clipBounds.height); + + SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content); + + RefPtr<DrawTarget> newDT = + mDT->CreateSimilarDrawTarget(IntSize(int32_t(clipBounds.width), int32_t(clipBounds.height)), + format); + + if (!newDT) { + NS_WARNING("Failed to create DrawTarget of sufficient size."); + newDT = mDT->CreateSimilarDrawTarget(IntSize(64, 64), format); + + if (!newDT) { + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset() +#ifdef XP_WIN + && !(mDT->GetBackendType() == BackendType::DIRECT2D1_1 && + !DeviceManagerDx::Get()->GetContentDevice()) +#endif + ) { + // If even this fails.. we're most likely just out of memory! + NS_ABORT_OOM(BytesPerPixel(format) * 64 * 64); + } + newDT = CurrentState().drawTarget; + } + } + + Save(); + + CurrentState().drawTarget = newDT; + CurrentState().deviceOffset = clipBounds.TopLeft(); + + mDT = newDT; +} + |