gfxContext.cpp (18262B)
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/. */ 6 7 #include <math.h> 8 9 #include "cairo.h" 10 11 #include "gfxContext.h" 12 13 #include "gfxMatrix.h" 14 #include "gfxUtils.h" 15 #include "gfxPattern.h" 16 #include "gfxPlatform.h" 17 18 #include "gfx2DGlue.h" 19 #include "mozilla/gfx/PathHelpers.h" 20 #include "mozilla/ProfilerLabels.h" 21 #include <algorithm> 22 #include "TextDrawTarget.h" 23 24 #if XP_WIN 25 # include "gfxWindowsPlatform.h" 26 # include "mozilla/gfx/DeviceManagerDx.h" 27 #endif 28 29 using namespace mozilla; 30 using namespace mozilla::gfx; 31 32 #ifdef DEBUG 33 # define CURRENTSTATE_CHANGED() mAzureState.mContentChanged = true; 34 #else 35 # define CURRENTSTATE_CHANGED() 36 #endif 37 38 PatternFromState::operator Pattern&() { 39 const gfxContext::AzureState& state = mContext->mAzureState; 40 41 if (state.pattern) { 42 return *state.pattern->GetPattern( 43 mContext->mDT, 44 state.patternTransformChanged ? &state.patternTransform : nullptr); 45 } 46 47 mPattern = new (mColorPattern.addr()) ColorPattern(state.color); 48 return *mPattern; 49 } 50 51 /* static */ 52 UniquePtr<gfxContext> gfxContext::CreateOrNull(DrawTarget* aTarget) { 53 if (!aTarget || !aTarget->IsValid()) { 54 gfxCriticalNote << "Invalid target in gfxContext::CreateOrNull " 55 << hexa(aTarget); 56 return nullptr; 57 } 58 59 return MakeUnique<gfxContext>(aTarget); 60 } 61 62 gfxContext::~gfxContext() { 63 while (!mSavedStates.IsEmpty()) { 64 Restore(); 65 } 66 for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) { 67 mDT->PopClip(); 68 } 69 } 70 71 mozilla::layout::TextDrawTarget* gfxContext::GetTextDrawer() const { 72 if (mDT->GetBackendType() == BackendType::WEBRENDER_TEXT) { 73 return static_cast<mozilla::layout::TextDrawTarget*>(&*mDT); 74 } 75 return nullptr; 76 } 77 78 void gfxContext::Save() { 79 mSavedStates.AppendElement(mAzureState); 80 mAzureState.pushedClips.Clear(); 81 #ifdef DEBUG 82 mAzureState.mContentChanged = false; 83 #endif 84 } 85 86 void gfxContext::Restore() { 87 #ifdef DEBUG 88 // gfxContext::Restore is used to restore AzureState. We need to restore it 89 // only if it was altered. The following APIs do change the content of 90 // AzureState, a user should save the state before using them and restore it 91 // after finishing painting: 92 // 1. APIs to setup how to paint, such as SetColor()/SetAntialiasMode(). All 93 // gfxContext SetXXXX public functions belong to this category, except 94 // gfxContext::SetPath & gfxContext::SetMatrix. 95 // 2. Clip functions, such as Clip() or PopClip(). You may call PopClip() 96 // directly instead of using gfxContext::Save if the clip region is the 97 // only thing that you altered in the target context. 98 // 3. Function of setup transform matrix, such as Multiply() and 99 // SetMatrix(). Using gfxContextMatrixAutoSaveRestore is more recommended 100 // if transform data is the only thing that you are going to alter. 101 // 102 // You will hit the assertion message below if there is no above functions 103 // been used between a pair of gfxContext::Save and gfxContext::Restore. 104 // Considerate to remove that pair of Save/Restore if hitting that assertion. 105 // 106 // In the other hand, the following APIs do not alter the content of the 107 // current AzureState, therefore, there is no need to save & restore 108 // AzureState: 109 // 1. constant member functions of gfxContext. 110 // 2. Paint calls, such as Line()/Rectangle()/Fill(). Those APIs change the 111 // content of drawing buffer, which is not part of AzureState. 112 // 3. Path building APIs, such as SetPath()/MoveTo()/LineTo()/NewPath(). 113 // Surprisingly, path information is not stored in AzureState either. 114 // Save current AzureState before using these type of APIs does nothing but 115 // make performance worse. 116 NS_ASSERTION( 117 mAzureState.mContentChanged || mAzureState.pushedClips.Length() > 0, 118 "The context of the current AzureState is not altered after " 119 "Save() been called. you may consider to remove this pair of " 120 "gfxContext::Save/Restore."); 121 #endif 122 123 for (unsigned int c = 0; c < mAzureState.pushedClips.Length(); c++) { 124 mDT->PopClip(); 125 } 126 127 mAzureState = mSavedStates.PopLastElement(); 128 129 ChangeTransform(mAzureState.transform, false); 130 } 131 132 // drawing 133 134 void gfxContext::Fill(const Pattern& aPattern) { 135 AUTO_PROFILER_LABEL("gfxContext::Fill", GRAPHICS); 136 137 CompositionOp op = GetOp(); 138 139 if (mPathIsRect) { 140 MOZ_ASSERT(!mTransformChanged); 141 142 if (op == CompositionOp::OP_SOURCE) { 143 // Emulate cairo operator source which is bound by mask! 144 mDT->ClearRect(mRect); 145 mDT->FillRect(mRect, aPattern, DrawOptions(1.0f)); 146 } else { 147 mDT->FillRect(mRect, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode)); 148 } 149 } else { 150 EnsurePath(); 151 mDT->Fill(mPath, aPattern, DrawOptions(1.0f, op, mAzureState.aaMode)); 152 } 153 } 154 155 // XXX snapToPixels is only valid when snapping for filled 156 // rectangles and for even-width stroked rectangles. 157 // For odd-width stroked rectangles, we need to offset x/y by 158 // 0.5... 159 void gfxContext::Rectangle(const gfxRect& rect, bool snapToPixels) { 160 Rect rec = ToRect(rect); 161 162 if (snapToPixels) { 163 gfxRect newRect(rect); 164 if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) { 165 gfxMatrix mat = CurrentMatrixDouble(); 166 if (mat.Invert()) { 167 // We need the user space rect. 168 rec = ToRect(mat.TransformBounds(newRect)); 169 } else { 170 rec = Rect(); 171 } 172 } 173 } 174 175 if (!mPathBuilder && !mPathIsRect) { 176 mPathIsRect = true; 177 mRect = rec; 178 return; 179 } 180 181 EnsurePathBuilder(); 182 183 mPathBuilder->MoveTo(rec.TopLeft()); 184 mPathBuilder->LineTo(rec.TopRight()); 185 mPathBuilder->LineTo(rec.BottomRight()); 186 mPathBuilder->LineTo(rec.BottomLeft()); 187 mPathBuilder->Close(); 188 } 189 190 void gfxContext::SnappedClip(const gfxRect& rect) { 191 Rect rec = ToRect(rect); 192 193 gfxRect newRect(rect); 194 if (UserToDevicePixelSnapped(newRect, SnapOption::IgnoreScale)) { 195 gfxMatrix mat = CurrentMatrixDouble(); 196 if (mat.Invert()) { 197 // We need the user space rect. 198 rec = ToRect(mat.TransformBounds(newRect)); 199 } else { 200 rec = Rect(); 201 } 202 } 203 204 Clip(rec); 205 } 206 207 bool gfxContext::UserToDevicePixelSnapped(gfxRect& rect, 208 SnapOptions aOptions) const { 209 if (mDT->GetUserData(&sDisablePixelSnapping)) { 210 return false; 211 } 212 213 // if we're not at 1.0 scale, don't snap, unless we're 214 // ignoring the scale. If we're not -just- a scale, 215 // never snap. 216 const gfxFloat epsilon = 0.0000001; 217 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) 218 Matrix mat = mAzureState.transform; 219 if (!aOptions.contains(SnapOption::IgnoreScale) && 220 (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) || 221 !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) { 222 return false; 223 } 224 #undef WITHIN_E 225 226 gfxPoint p1 = UserToDevice(rect.TopLeft()); 227 gfxPoint p2 = UserToDevice(rect.TopRight()); 228 gfxPoint p3 = UserToDevice(rect.BottomRight()); 229 230 // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, 231 // two opposite corners define the entire rectangle. So check if 232 // the axis-aligned rectangle with opposite corners p1 and p3 233 // define an axis-aligned rectangle whose other corners are p2 and p4. 234 // We actually only need to check one of p2 and p4, since an affine 235 // transform maps parallelograms to parallelograms. 236 if (!(p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y))) { 237 return false; 238 } 239 240 if (aOptions.contains(SnapOption::PrioritizeSize)) { 241 // Snap the dimensions of the rect, to minimize distortion; only after that 242 // will we snap its position. In particular, this guarantees that a square 243 // remains square after snapping, which may not be the case if each edge is 244 // independently snapped to device pixels. 245 246 // Use the same rounding approach as gfx::BasePoint::Round. 247 rect.SizeTo(std::floor(rect.width + 0.5), std::floor(rect.height + 0.5)); 248 249 // Find the top-left corner based on the original center and the snapped 250 // size, then snap this new corner to the grid. 251 gfxPoint center = (p1 + p3) / 2; 252 gfxPoint topLeft = center - gfxPoint(rect.width / 2.0, rect.height / 2.0); 253 topLeft.Round(); 254 rect.MoveTo(topLeft); 255 } else { 256 p1.Round(); 257 p3.Round(); 258 rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); 259 rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(), 260 std::max(p1.y, p3.y) - rect.Y())); 261 } 262 263 return true; 264 } 265 266 bool gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, 267 bool ignoreScale) const { 268 if (mDT->GetUserData(&sDisablePixelSnapping)) { 269 return false; 270 } 271 272 // if we're not at 1.0 scale, don't snap, unless we're 273 // ignoring the scale. If we're not -just- a scale, 274 // never snap. 275 const gfxFloat epsilon = 0.0000001; 276 #define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) 277 Matrix mat = mAzureState.transform; 278 if (!ignoreScale && (!WITHIN_E(mat._11, 1.0) || !WITHIN_E(mat._22, 1.0) || 279 !WITHIN_E(mat._12, 0.0) || !WITHIN_E(mat._21, 0.0))) { 280 return false; 281 } 282 #undef WITHIN_E 283 284 pt = UserToDevice(pt); 285 pt.Round(); 286 return true; 287 } 288 289 void gfxContext::SetDash(const Float* dashes, int ndash, Float offset, 290 Float devPxScale) { 291 CURRENTSTATE_CHANGED() 292 293 mAzureState.dashPattern.SetLength(ndash); 294 for (int i = 0; i < ndash; i++) { 295 mAzureState.dashPattern[i] = dashes[i] * devPxScale; 296 } 297 mAzureState.strokeOptions.mDashLength = ndash; 298 mAzureState.strokeOptions.mDashOffset = offset * devPxScale; 299 mAzureState.strokeOptions.mDashPattern = 300 ndash ? mAzureState.dashPattern.Elements() : nullptr; 301 } 302 303 bool gfxContext::CurrentDash(FallibleTArray<Float>& dashes, 304 Float* offset) const { 305 if (mAzureState.strokeOptions.mDashLength == 0 || 306 !dashes.Assign(mAzureState.dashPattern, fallible)) { 307 return false; 308 } 309 310 *offset = mAzureState.strokeOptions.mDashOffset; 311 312 return true; 313 } 314 315 // clipping 316 void gfxContext::Clip(const Rect& rect) { 317 AzureState::PushedClip clip = {nullptr, rect, mAzureState.transform}; 318 mAzureState.pushedClips.AppendElement(clip); 319 mDT->PushClipRect(rect); 320 NewPath(); 321 } 322 323 void gfxContext::Clip(Path* aPath) { 324 mDT->PushClip(aPath); 325 AzureState::PushedClip clip = {aPath, Rect(), mAzureState.transform}; 326 mAzureState.pushedClips.AppendElement(clip); 327 } 328 329 void gfxContext::Clip() { 330 if (mPathIsRect) { 331 MOZ_ASSERT(!mTransformChanged); 332 333 AzureState::PushedClip clip = {nullptr, mRect, mAzureState.transform}; 334 mAzureState.pushedClips.AppendElement(clip); 335 mDT->PushClipRect(mRect); 336 } else { 337 EnsurePath(); 338 mDT->PushClip(mPath); 339 AzureState::PushedClip clip = {mPath, Rect(), mAzureState.transform}; 340 mAzureState.pushedClips.AppendElement(clip); 341 } 342 } 343 344 gfxRect gfxContext::GetClipExtents(ClipExtentsSpace aSpace) const { 345 Rect rect = GetAzureDeviceSpaceClipBounds(); 346 347 if (rect.IsZeroArea()) { 348 return gfxRect(0, 0, 0, 0); 349 } 350 351 if (aSpace == eUserSpace) { 352 Matrix mat = mAzureState.transform; 353 mat.Invert(); 354 rect = mat.TransformBounds(rect); 355 } 356 357 return ThebesRect(rect); 358 } 359 360 bool gfxContext::ExportClip(ClipExporter& aExporter) const { 361 ForAllClips([&](const AzureState::PushedClip& aClip) -> void { 362 gfx::Matrix transform = aClip.transform; 363 transform.PostTranslate(-GetDeviceOffset()); 364 365 aExporter.BeginClip(transform); 366 if (aClip.path) { 367 aClip.path->StreamToSink(&aExporter); 368 } else { 369 aExporter.MoveTo(aClip.rect.TopLeft()); 370 aExporter.LineTo(aClip.rect.TopRight()); 371 aExporter.LineTo(aClip.rect.BottomRight()); 372 aExporter.LineTo(aClip.rect.BottomLeft()); 373 aExporter.Close(); 374 } 375 aExporter.EndClip(); 376 }); 377 378 return true; 379 } 380 381 // rendering sources 382 383 bool gfxContext::GetDeviceColor(DeviceColor& aColorOut) const { 384 if (mAzureState.pattern) { 385 return mAzureState.pattern->GetSolidColor(aColorOut); 386 } 387 388 aColorOut = mAzureState.color; 389 return true; 390 } 391 392 already_AddRefed<gfxPattern> gfxContext::GetPattern() const { 393 RefPtr<gfxPattern> pat; 394 395 if (mAzureState.pattern) { 396 pat = mAzureState.pattern; 397 } else { 398 pat = new gfxPattern(mAzureState.color); 399 } 400 return pat.forget(); 401 } 402 403 void gfxContext::Paint(Float alpha) const { 404 AUTO_PROFILER_LABEL("gfxContext::Paint", GRAPHICS); 405 406 Matrix mat = mDT->GetTransform(); 407 mat.Invert(); 408 Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize()))); 409 410 mDT->FillRect(paintRect, PatternFromState(this), DrawOptions(alpha, GetOp())); 411 } 412 413 #ifdef MOZ_DUMP_PAINTING 414 void gfxContext::WriteAsPNG(const char* aFile) { 415 gfxUtils::WriteAsPNG(mDT, aFile); 416 } 417 418 void gfxContext::DumpAsDataURI() { gfxUtils::DumpAsDataURI(mDT); } 419 420 void gfxContext::CopyAsDataURI() { gfxUtils::CopyAsDataURI(mDT); } 421 #endif 422 423 void gfxContext::EnsurePath() { 424 if (mPathBuilder) { 425 mPath = mPathBuilder->Finish(); 426 mPathBuilder = nullptr; 427 } 428 429 if (mPath) { 430 if (mTransformChanged) { 431 Matrix mat = mAzureState.transform; 432 mat.Invert(); 433 mat = mPathTransform * mat; 434 Path::Transform(mPath, mat); 435 436 mTransformChanged = false; 437 } 438 return; 439 } 440 441 EnsurePathBuilder(); 442 mPath = mPathBuilder->Finish(); 443 mPathBuilder = nullptr; 444 } 445 446 void gfxContext::EnsurePathBuilder() { 447 if (mPathBuilder && !mTransformChanged) { 448 return; 449 } 450 451 if (mPath) { 452 if (!mTransformChanged) { 453 mPathBuilder = Path::ToBuilder(mPath.forget()); 454 } else { 455 Matrix invTransform = mAzureState.transform; 456 invTransform.Invert(); 457 Matrix toNewUS = mPathTransform * invTransform; 458 mPathBuilder = Path::ToBuilder(mPath.forget(), toNewUS); 459 } 460 return; 461 } 462 463 DebugOnly<PathBuilder*> oldPath = mPathBuilder.get(); 464 465 if (!mPathBuilder) { 466 mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); 467 468 if (mPathIsRect) { 469 mPathBuilder->MoveTo(mRect.TopLeft()); 470 mPathBuilder->LineTo(mRect.TopRight()); 471 mPathBuilder->LineTo(mRect.BottomRight()); 472 mPathBuilder->LineTo(mRect.BottomLeft()); 473 mPathBuilder->Close(); 474 } 475 } 476 477 if (mTransformChanged) { 478 // This could be an else if since this should never happen when 479 // mPathBuilder is nullptr and mPath is nullptr. But this way we can 480 // assert if all the state is as expected. 481 MOZ_ASSERT(oldPath); 482 MOZ_ASSERT(!mPathIsRect); 483 484 Matrix invTransform = mAzureState.transform; 485 invTransform.Invert(); 486 Matrix toNewUS = mPathTransform * invTransform; 487 488 RefPtr<Path> path = mPathBuilder->Finish(); 489 if (!path) { 490 gfxCriticalError() 491 << "gfxContext::EnsurePathBuilder failed in PathBuilder::Finish"; 492 } 493 mPathBuilder = Path::ToBuilder(path.forget(), toNewUS); 494 } 495 496 mPathIsRect = false; 497 } 498 499 CompositionOp gfxContext::GetOp() const { 500 if (mAzureState.op != CompositionOp::OP_SOURCE) { 501 return mAzureState.op; 502 } 503 504 if (mAzureState.pattern) { 505 if (mAzureState.pattern->IsOpaque()) { 506 return CompositionOp::OP_OVER; 507 } else { 508 return CompositionOp::OP_SOURCE; 509 } 510 } else { 511 if (mAzureState.color.a > 0.999) { 512 return CompositionOp::OP_OVER; 513 } else { 514 return CompositionOp::OP_SOURCE; 515 } 516 } 517 } 518 519 /* SVG font code can change the transform after having set the pattern on the 520 * context. When the pattern is set it is in user space, if the transform is 521 * changed after doing so the pattern needs to be converted back into userspace. 522 * We just store the old pattern transform here so that we only do the work 523 * needed here if the pattern is actually used. 524 * We need to avoid doing this when this ChangeTransform comes from a restore, 525 * since the current pattern and the current transform are both part of the 526 * state we know the new mAzureState's values are valid. But if we assume 527 * a change they might become invalid since patternTransformChanged is part of 528 * the state and might be false for the restored AzureState. 529 */ 530 void gfxContext::ChangeTransform(const Matrix& aNewMatrix, 531 bool aUpdatePatternTransform) { 532 if (aUpdatePatternTransform && (mAzureState.pattern) && 533 !mAzureState.patternTransformChanged) { 534 mAzureState.patternTransform = GetDTTransform(); 535 mAzureState.patternTransformChanged = true; 536 } 537 538 if (mPathIsRect) { 539 Matrix invMatrix = aNewMatrix; 540 541 invMatrix.Invert(); 542 543 Matrix toNewUS = mAzureState.transform * invMatrix; 544 545 if (toNewUS.IsRectilinear()) { 546 mRect = toNewUS.TransformBounds(mRect); 547 mRect.NudgeToIntegers(); 548 } else { 549 mPathBuilder = mDT->CreatePathBuilder(FillRule::FILL_WINDING); 550 551 mPathBuilder->MoveTo(toNewUS.TransformPoint(mRect.TopLeft())); 552 mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.TopRight())); 553 mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomRight())); 554 mPathBuilder->LineTo(toNewUS.TransformPoint(mRect.BottomLeft())); 555 mPathBuilder->Close(); 556 557 mPathIsRect = false; 558 } 559 560 // No need to consider the transform changed now! 561 mTransformChanged = false; 562 } else if ((mPath || mPathBuilder) && !mTransformChanged) { 563 mTransformChanged = true; 564 mPathTransform = mAzureState.transform; 565 } 566 567 mAzureState.transform = aNewMatrix; 568 569 mDT->SetTransform(GetDTTransform()); 570 } 571 572 Rect gfxContext::GetAzureDeviceSpaceClipBounds() const { 573 Rect rect(mAzureState.deviceOffset.x + Float(mDT->GetRect().x), 574 mAzureState.deviceOffset.y + Float(mDT->GetRect().y), 575 Float(mDT->GetSize().width), Float(mDT->GetSize().height)); 576 ForAllClips([&](const AzureState::PushedClip& aClip) -> void { 577 if (aClip.path) { 578 rect.IntersectRect(rect, aClip.path->GetBounds(aClip.transform)); 579 } else { 580 rect.IntersectRect(rect, aClip.transform.TransformBounds(aClip.rect)); 581 } 582 }); 583 584 return rect; 585 } 586 587 template <typename F> 588 void gfxContext::ForAllClips(F&& aLambda) const { 589 for (const auto& state : mSavedStates) { 590 for (const auto& clip : state.pushedClips) { 591 aLambda(clip); 592 } 593 } 594 for (const auto& clip : mAzureState.pushedClips) { 595 aLambda(clip); 596 } 597 }