1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "mozilla/Alignment.h"
13 #include "gfxContext.h"
15 #include "gfxMatrix.h"
17 #include "gfxPattern.h"
18 #include "gfxPlatform.h"
20 #include "gfx2DGlue.h"
21 #include "mozilla/gfx/PathHelpers.h"
22 #include "mozilla/ProfilerLabels.h"
24 #include "TextDrawTarget.h"
27 # include "gfxWindowsPlatform.h"
28 # include "mozilla/gfx/DeviceManagerDx.h"
31 using namespace mozilla
;
32 using namespace mozilla::gfx
;
35 # define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true;
37 # define CURRENTSTATE_CHANGED()
40 PatternFromState::operator Pattern
&() {
41 const gfxContext::AzureState
& state
= mContext
->mAzureState
;
44 return *state
.pattern
->GetPattern(
46 state
.patternTransformChanged
? &state
.patternTransform
: nullptr);
49 mPattern
= new (mColorPattern
.addr()) ColorPattern(state
.color
);
54 UniquePtr
<gfxContext
> gfxContext::CreateOrNull(DrawTarget
* aTarget
) {
55 if (!aTarget
|| !aTarget
->IsValid()) {
56 gfxCriticalNote
<< "Invalid target in gfxContext::CreateOrNull "
61 return MakeUnique
<gfxContext
>(aTarget
);
64 gfxContext::~gfxContext() {
65 while (!mSavedStates
.IsEmpty()) {
68 for (unsigned int c
= 0; c
< mAzureState
.pushedClips
.Length(); c
++) {
73 mozilla::layout::TextDrawTarget
* gfxContext::GetTextDrawer() const {
74 if (mDT
->GetBackendType() == BackendType::WEBRENDER_TEXT
) {
75 return static_cast<mozilla::layout::TextDrawTarget
*>(&*mDT
);
80 void gfxContext::Save() {
81 mSavedStates
.AppendElement(mAzureState
);
82 mAzureState
.pushedClips
.Clear();
84 mAzureState
.mContentChanged
= false;
88 void gfxContext::Restore() {
90 // gfxContext::Restore is used to restore AzureState. We need to restore it
91 // only if it was altered. The following APIs do change the content of
92 // AzureState, a user should save the state before using them and restore it
93 // after finishing painting:
94 // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All
95 // gfxContext SetXXXX public functions belong to this category, except
96 // gfxContext::SetPath & gfxContext::SetMatrix.
97 // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip()
98 // directly instead of using gfxContext::Save if the clip region is the
99 // only thing that you altered in the target context.
100 // 3. Function of setup transform matrix, such as Multiply() and
101 // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended
102 // if transform data is the only thing that you are going to alter.
104 // You will hit the assertion message below if there is no above functions
105 // been used between a pair of gfxContext::Save and gfxContext::Restore.
106 // Considerate to remove that pair of Save/Restore if hitting that assertion.
108 // In the other hand, the following APIs do not alter the content of the
109 // current AzureState, therefore, there is no need to save & restore
111 // 1. constant member functions of gfxContext.
112 // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the
113 // content of drawing buffer, which is not part of AzureState.
114 // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath().
115 // Surprisingly, path information is not stored in AzureState either.
116 // Save current AzureState before using these type of APIs does nothing but
117 // make performance worse.
119 mAzureState
.mContentChanged
|| mAzureState
.pushedClips
.Length() > 0,
120 "The context of the current AzureState is not altered after "
121 "Save() been called. you may consider to remove this pair of "
122 "gfxContext::Save/Restore.");
125 for (unsigned int c
= 0; c
< mAzureState
.pushedClips
.Length(); c
++) {
129 mAzureState
= mSavedStates
.PopLastElement();
131 ChangeTransform(mAzureState
.transform
, false);
136 void gfxContext::Fill(const Pattern
& aPattern
) {
137 AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS
);
139 CompositionOp op
= GetOp();
142 MOZ_ASSERT(!mTransformChanged
);
144 if (op
== CompositionOp::OP_SOURCE
) {
145 // Emulate cairo operator source which is bound by mask!
146 mDT
->ClearRect(mRect
);
147 mDT
->FillRect(mRect
, aPattern
, DrawOptions(1.0f
));
149 mDT
->FillRect(mRect
, aPattern
, DrawOptions(1.0f
, op
, mAzureState
.aaMode
));
153 mDT
->Fill(mPath
, aPattern
, DrawOptions(1.0f
, op
, mAzureState
.aaMode
));
157 // XXX snapToPixels is only valid when snapping for filled
158 // rectangles and for even-width stroked rectangles.
159 // For odd-width stroked rectangles, we need to offset x/y by
161 void gfxContext::Rectangle(const gfxRect
& rect
, bool snapToPixels
) {
162 Rect rec
= ToRect(rect
);
165 gfxRect
newRect(rect
);
166 if (UserToDevicePixelSnapped(newRect
, SnapOption::IgnoreScale
)) {
167 gfxMatrix mat
= CurrentMatrixDouble();
169 // We need the user space rect.
170 rec
= ToRect(mat
.TransformBounds(newRect
));
177 if (!mPathBuilder
&& !mPathIsRect
) {
185 mPathBuilder
->MoveTo(rec
.TopLeft());
186 mPathBuilder
->LineTo(rec
.TopRight());
187 mPathBuilder
->LineTo(rec
.BottomRight());
188 mPathBuilder
->LineTo(rec
.BottomLeft());
189 mPathBuilder
->Close();
192 void gfxContext::SnappedClip(const gfxRect
& rect
) {
193 Rect rec
= ToRect(rect
);
195 gfxRect
newRect(rect
);
196 if (UserToDevicePixelSnapped(newRect
, SnapOption::IgnoreScale
)) {
197 gfxMatrix mat
= CurrentMatrixDouble();
199 // We need the user space rect.
200 rec
= ToRect(mat
.TransformBounds(newRect
));
209 bool gfxContext::UserToDevicePixelSnapped(gfxRect
& rect
,
210 SnapOptions aOptions
) const {
211 if (mDT
->GetUserData(&sDisablePixelSnapping
)) {
215 // if we're not at 1.0 scale, don't snap, unless we're
216 // ignoring the scale. If we're not -just- a scale,
218 const gfxFloat epsilon
= 0.0000001;
219 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
220 Matrix mat
= mAzureState
.transform
;
221 if (!aOptions
.contains(SnapOption::IgnoreScale
) &&
222 (!WITHIN_E(mat
._11
, 1.0) || !WITHIN_E(mat
._22
, 1.0) ||
223 !WITHIN_E(mat
._12
, 0.0) || !WITHIN_E(mat
._21
, 0.0))) {
228 gfxPoint p1
= UserToDevice(rect
.TopLeft());
229 gfxPoint p2
= UserToDevice(rect
.TopRight());
230 gfxPoint p3
= UserToDevice(rect
.BottomRight());
232 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle,
233 // two opposite corners define the entire rectangle. So check if
234 // the axis-aligned rectangle with opposite corners p1 and p3
235 // define an axis-aligned rectangle whose other corners are p2 and p4.
236 // We actually only need to check one of p2 and p4, since an affine
237 // transform maps parallelograms to parallelograms.
238 if (!(p2
== gfxPoint(p1
.x
, p3
.y
) || p2
== gfxPoint(p3
.x
, p1
.y
))) {
242 if (aOptions
.contains(SnapOption::PrioritizeSize
)) {
243 // Snap the dimensions of the rect, to minimize distortion; only after that
244 // will we snap its position. In particular, this guarantees that a square
245 // remains square after snapping, which may not be the case if each edge is
246 // independently snapped to device pixels.
248 // Use the same rounding approach as gfx::BasePoint::Round.
249 rect
.SizeTo(std::floor(rect
.width
+ 0.5), std::floor(rect
.height
+ 0.5));
251 // Find the top-left corner based on the original center and the snapped
252 // size, then snap this new corner to the grid.
253 gfxPoint center
= (p1
+ p3
) / 2;
254 gfxPoint topLeft
= center
- gfxPoint(rect
.width
/ 2.0, rect
.height
/ 2.0);
256 rect
.MoveTo(topLeft
);
260 rect
.MoveTo(gfxPoint(std::min(p1
.x
, p3
.x
), std::min(p1
.y
, p3
.y
)));
261 rect
.SizeTo(gfxSize(std::max(p1
.x
, p3
.x
) - rect
.X(),
262 std::max(p1
.y
, p3
.y
) - rect
.Y()));
268 bool gfxContext::UserToDevicePixelSnapped(gfxPoint
& pt
,
269 bool ignoreScale
) const {
270 if (mDT
->GetUserData(&sDisablePixelSnapping
)) {
274 // if we're not at 1.0 scale, don't snap, unless we're
275 // ignoring the scale. If we're not -just- a scale,
277 const gfxFloat epsilon
= 0.0000001;
278 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon)
279 Matrix mat
= mAzureState
.transform
;
280 if (!ignoreScale
&& (!WITHIN_E(mat
._11
, 1.0) || !WITHIN_E(mat
._22
, 1.0) ||
281 !WITHIN_E(mat
._12
, 0.0) || !WITHIN_E(mat
._21
, 0.0))) {
286 pt
= UserToDevice(pt
);
291 void gfxContext::SetDash(const Float
* dashes
, int ndash
, Float offset
,
293 CURRENTSTATE_CHANGED()
295 mAzureState
.dashPattern
.SetLength(ndash
);
296 for (int i
= 0; i
< ndash
; i
++) {
297 mAzureState
.dashPattern
[i
] = dashes
[i
] * devPxScale
;
299 mAzureState
.strokeOptions
.mDashLength
= ndash
;
300 mAzureState
.strokeOptions
.mDashOffset
= offset
* devPxScale
;
301 mAzureState
.strokeOptions
.mDashPattern
=
302 ndash
? mAzureState
.dashPattern
.Elements() : nullptr;
305 bool gfxContext::CurrentDash(FallibleTArray
<Float
>& dashes
,
306 Float
* offset
) const {
307 if (mAzureState
.strokeOptions
.mDashLength
== 0 ||
308 !dashes
.Assign(mAzureState
.dashPattern
, fallible
)) {
312 *offset
= mAzureState
.strokeOptions
.mDashOffset
;
318 void gfxContext::Clip(const Rect
& rect
) {
319 AzureState::PushedClip clip
= {nullptr, rect
, mAzureState
.transform
};
320 mAzureState
.pushedClips
.AppendElement(clip
);
321 mDT
->PushClipRect(rect
);
325 void gfxContext::Clip(Path
* aPath
) {
326 mDT
->PushClip(aPath
);
327 AzureState::PushedClip clip
= {aPath
, Rect(), mAzureState
.transform
};
328 mAzureState
.pushedClips
.AppendElement(clip
);
331 void gfxContext::Clip() {
333 MOZ_ASSERT(!mTransformChanged
);
335 AzureState::PushedClip clip
= {nullptr, mRect
, mAzureState
.transform
};
336 mAzureState
.pushedClips
.AppendElement(clip
);
337 mDT
->PushClipRect(mRect
);
340 mDT
->PushClip(mPath
);
341 AzureState::PushedClip clip
= {mPath
, Rect(), mAzureState
.transform
};
342 mAzureState
.pushedClips
.AppendElement(clip
);
346 gfxRect
gfxContext::GetClipExtents(ClipExtentsSpace aSpace
) const {
347 Rect rect
= GetAzureDeviceSpaceClipBounds();
349 if (rect
.IsZeroArea()) {
350 return gfxRect(0, 0, 0, 0);
353 if (aSpace
== eUserSpace
) {
354 Matrix mat
= mAzureState
.transform
;
356 rect
= mat
.TransformBounds(rect
);
359 return ThebesRect(rect
);
362 bool gfxContext::ExportClip(ClipExporter
& aExporter
) const {
363 ForAllClips([&](const AzureState::PushedClip
& aClip
) -> void {
364 gfx::Matrix transform
= aClip
.transform
;
365 transform
.PostTranslate(-GetDeviceOffset());
367 aExporter
.BeginClip(transform
);
369 aClip
.path
->StreamToSink(&aExporter
);
371 aExporter
.MoveTo(aClip
.rect
.TopLeft());
372 aExporter
.LineTo(aClip
.rect
.TopRight());
373 aExporter
.LineTo(aClip
.rect
.BottomRight());
374 aExporter
.LineTo(aClip
.rect
.BottomLeft());
385 bool gfxContext::GetDeviceColor(DeviceColor
& aColorOut
) const {
386 if (mAzureState
.pattern
) {
387 return mAzureState
.pattern
->GetSolidColor(aColorOut
);
390 aColorOut
= mAzureState
.color
;
394 already_AddRefed
<gfxPattern
> gfxContext::GetPattern() const {
395 RefPtr
<gfxPattern
> pat
;
397 if (mAzureState
.pattern
) {
398 pat
= mAzureState
.pattern
;
400 pat
= new gfxPattern(mAzureState
.color
);
405 void gfxContext::Paint(Float alpha
) const {
406 AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS
);
408 Matrix mat
= mDT
->GetTransform();
410 Rect paintRect
= mat
.TransformBounds(Rect(Point(0, 0), Size(mDT
->GetSize())));
412 mDT
->FillRect(paintRect
, PatternFromState(this), DrawOptions(alpha
, GetOp()));
415 #ifdef MOZ_DUMP_PAINTING
416 void gfxContext::WriteAsPNG(const char* aFile
) {
417 gfxUtils::WriteAsPNG(mDT
, aFile
);
420 void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT
); }
422 void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT
); }
425 void gfxContext::EnsurePath() {
427 mPath
= mPathBuilder
->Finish();
428 mPathBuilder
= nullptr;
432 if (mTransformChanged
) {
433 Matrix mat
= mAzureState
.transform
;
435 mat
= mPathTransform
* mat
;
436 mPathBuilder
= mPath
->TransformedCopyToBuilder(mat
);
437 mPath
= mPathBuilder
->Finish();
438 mPathBuilder
= nullptr;
440 mTransformChanged
= false;
446 mPath
= mPathBuilder
->Finish();
447 mPathBuilder
= nullptr;
450 void gfxContext::EnsurePathBuilder() {
451 if (mPathBuilder
&& !mTransformChanged
) {
456 if (!mTransformChanged
) {
457 mPathBuilder
= mPath
->CopyToBuilder();
460 Matrix invTransform
= mAzureState
.transform
;
461 invTransform
.Invert();
462 Matrix toNewUS
= mPathTransform
* invTransform
;
463 mPathBuilder
= mPath
->TransformedCopyToBuilder(toNewUS
);
468 DebugOnly
<PathBuilder
*> oldPath
= mPathBuilder
.get();
471 mPathBuilder
= mDT
->CreatePathBuilder(FillRule::FILL_WINDING
);
474 mPathBuilder
->MoveTo(mRect
.TopLeft());
475 mPathBuilder
->LineTo(mRect
.TopRight());
476 mPathBuilder
->LineTo(mRect
.BottomRight());
477 mPathBuilder
->LineTo(mRect
.BottomLeft());
478 mPathBuilder
->Close();
482 if (mTransformChanged
) {
483 // This could be an else if since this should never happen when
484 // mPathBuilder is nullptr and mPath is nullptr. But this way we can
485 // assert if all the state is as expected.
487 MOZ_ASSERT(!mPathIsRect
);
489 Matrix invTransform
= mAzureState
.transform
;
490 invTransform
.Invert();
491 Matrix toNewUS
= mPathTransform
* invTransform
;
493 RefPtr
<Path
> path
= mPathBuilder
->Finish();
496 << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish";
498 mPathBuilder
= path
->TransformedCopyToBuilder(toNewUS
);
504 CompositionOp
gfxContext::GetOp() const {
505 if (mAzureState
.op
!= CompositionOp::OP_SOURCE
) {
506 return mAzureState
.op
;
509 if (mAzureState
.pattern
) {
510 if (mAzureState
.pattern
->IsOpaque()) {
511 return CompositionOp::OP_OVER
;
513 return CompositionOp::OP_SOURCE
;
516 if (mAzureState
.color
.a
> 0.999) {
517 return CompositionOp::OP_OVER
;
519 return CompositionOp::OP_SOURCE
;
524 /* SVG font code can change the transform after having set the pattern on the
525 * context. When the pattern is set it is in user space, if the transform is
526 * changed after doing so the pattern needs to be converted back into userspace.
527 * We just store the old pattern transform here so that we only do the work
528 * needed here if the pattern is actually used.
529 * We need to avoid doing this when this ChangeTransform comes from a restore,
530 * since the current pattern and the current transform are both part of the
531 * state we know the new mAzureState's values are valid. But if we assume
532 * a change they might become invalid since patternTransformChanged is part of
533 * the state and might be false for the restored AzureState.
535 void gfxContext::ChangeTransform(const Matrix
& aNewMatrix
,
536 bool aUpdatePatternTransform
) {
537 if (aUpdatePatternTransform
&& (mAzureState
.pattern
) &&
538 !mAzureState
.patternTransformChanged
) {
539 mAzureState
.patternTransform
= GetDTTransform();
540 mAzureState
.patternTransformChanged
= true;
544 Matrix invMatrix
= aNewMatrix
;
548 Matrix toNewUS
= mAzureState
.transform
* invMatrix
;
550 if (toNewUS
.IsRectilinear()) {
551 mRect
= toNewUS
.TransformBounds(mRect
);
552 mRect
.NudgeToIntegers();
554 mPathBuilder
= mDT
->CreatePathBuilder(FillRule::FILL_WINDING
);
556 mPathBuilder
->MoveTo(toNewUS
.TransformPoint(mRect
.TopLeft()));
557 mPathBuilder
->LineTo(toNewUS
.TransformPoint(mRect
.TopRight()));
558 mPathBuilder
->LineTo(toNewUS
.TransformPoint(mRect
.BottomRight()));
559 mPathBuilder
->LineTo(toNewUS
.TransformPoint(mRect
.BottomLeft()));
560 mPathBuilder
->Close();
565 // No need to consider the transform changed now!
566 mTransformChanged
= false;
567 } else if ((mPath
|| mPathBuilder
) && !mTransformChanged
) {
568 mTransformChanged
= true;
569 mPathTransform
= mAzureState
.transform
;
572 mAzureState
.transform
= aNewMatrix
;
574 mDT
->SetTransform(GetDTTransform());
577 Rect
gfxContext::GetAzureDeviceSpaceClipBounds() const {
578 Rect
rect(mAzureState
.deviceOffset
.x
+ Float(mDT
->GetRect().x
),
579 mAzureState
.deviceOffset
.y
+ Float(mDT
->GetRect().y
),
580 Float(mDT
->GetSize().width
), Float(mDT
->GetSize().height
));
581 ForAllClips([&](const AzureState::PushedClip
& aClip
) -> void {
583 rect
.IntersectRect(rect
, aClip
.path
->GetBounds(aClip
.transform
));
585 rect
.IntersectRect(rect
, aClip
.transform
.TransformBounds(aClip
.rect
));
592 template <typename F
>
593 void gfxContext::ForAllClips(F
&& aLambda
) const {
594 for (const auto& state
: mSavedStates
) {
595 for (const auto& clip
: state
.pushedClips
) {
599 for (const auto& clip
: mAzureState
.pushedClips
) {