gfxBlur.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2. * This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "gfxBlur.h"
  6. #include "gfx2DGlue.h"
  7. #include "gfxContext.h"
  8. #include "gfxPlatform.h"
  9. #include "mozilla/gfx/2D.h"
  10. #include "mozilla/gfx/Blur.h"
  11. #include "mozilla/gfx/PathHelpers.h"
  12. #include "mozilla/UniquePtr.h"
  13. #include "mozilla/UniquePtrExtensions.h"
  14. #include "nsExpirationTracker.h"
  15. #include "nsClassHashtable.h"
  16. #include "gfxUtils.h"
  17. using namespace mozilla;
  18. using namespace mozilla::gfx;
  19. gfxAlphaBoxBlur::gfxAlphaBoxBlur()
  20. {
  21. }
  22. gfxAlphaBoxBlur::~gfxAlphaBoxBlur()
  23. {
  24. mContext = nullptr;
  25. }
  26. gfxContext*
  27. gfxAlphaBoxBlur::Init(const gfxRect& aRect,
  28. const IntSize& aSpreadRadius,
  29. const IntSize& aBlurRadius,
  30. const gfxRect* aDirtyRect,
  31. const gfxRect* aSkipRect)
  32. {
  33. mozilla::gfx::Rect rect(Float(aRect.x), Float(aRect.y),
  34. Float(aRect.width), Float(aRect.height));
  35. IntSize spreadRadius(aSpreadRadius.width, aSpreadRadius.height);
  36. IntSize blurRadius(aBlurRadius.width, aBlurRadius.height);
  37. UniquePtr<Rect> dirtyRect;
  38. if (aDirtyRect) {
  39. dirtyRect = MakeUnique<Rect>(Float(aDirtyRect->x),
  40. Float(aDirtyRect->y),
  41. Float(aDirtyRect->width),
  42. Float(aDirtyRect->height));
  43. }
  44. UniquePtr<Rect> skipRect;
  45. if (aSkipRect) {
  46. skipRect = MakeUnique<Rect>(Float(aSkipRect->x),
  47. Float(aSkipRect->y),
  48. Float(aSkipRect->width),
  49. Float(aSkipRect->height));
  50. }
  51. mBlur = MakeUnique<AlphaBoxBlur>(rect, spreadRadius, blurRadius, dirtyRect.get(), skipRect.get());
  52. size_t blurDataSize = mBlur->GetSurfaceAllocationSize();
  53. if (blurDataSize == 0)
  54. return nullptr;
  55. IntSize size = mBlur->GetSize();
  56. // Make an alpha-only surface to draw on. We will play with the data after
  57. // everything is drawn to create a blur effect.
  58. mData = MakeUniqueFallible<unsigned char[]>(blurDataSize);
  59. if (!mData) {
  60. return nullptr;
  61. }
  62. memset(mData.get(), 0, blurDataSize);
  63. RefPtr<DrawTarget> dt =
  64. gfxPlatform::CreateDrawTargetForData(mData.get(), size,
  65. mBlur->GetStride(),
  66. SurfaceFormat::A8);
  67. if (!dt || !dt->IsValid()) {
  68. return nullptr;
  69. }
  70. IntRect irect = mBlur->GetRect();
  71. gfxPoint topleft(irect.TopLeft().x, irect.TopLeft().y);
  72. mContext = gfxContext::CreateOrNull(dt);
  73. MOZ_ASSERT(mContext); // already checked for target above
  74. mContext->SetMatrix(gfxMatrix::Translation(-topleft));
  75. return mContext;
  76. }
  77. void
  78. DrawBlur(gfxContext* aDestinationCtx,
  79. SourceSurface* aBlur,
  80. const IntPoint& aTopLeft,
  81. const Rect* aDirtyRect)
  82. {
  83. DrawTarget *dest = aDestinationCtx->GetDrawTarget();
  84. RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
  85. Pattern* pat = thebesPat->GetPattern(dest, nullptr);
  86. Matrix oldTransform = dest->GetTransform();
  87. Matrix newTransform = oldTransform;
  88. newTransform.PreTranslate(aTopLeft.x, aTopLeft.y);
  89. // Avoid a semi-expensive clip operation if we can, otherwise
  90. // clip to the dirty rect
  91. if (aDirtyRect) {
  92. dest->PushClipRect(*aDirtyRect);
  93. }
  94. dest->SetTransform(newTransform);
  95. dest->MaskSurface(*pat, aBlur, Point(0, 0));
  96. dest->SetTransform(oldTransform);
  97. if (aDirtyRect) {
  98. dest->PopClip();
  99. }
  100. }
  101. already_AddRefed<SourceSurface>
  102. gfxAlphaBoxBlur::DoBlur(DrawTarget* aDT, IntPoint* aTopLeft)
  103. {
  104. mBlur->Blur(mData.get());
  105. *aTopLeft = mBlur->GetRect().TopLeft();
  106. return aDT->CreateSourceSurfaceFromData(mData.get(),
  107. mBlur->GetSize(),
  108. mBlur->GetStride(),
  109. SurfaceFormat::A8);
  110. }
  111. void
  112. gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx)
  113. {
  114. if (!mContext)
  115. return;
  116. DrawTarget *dest = aDestinationCtx->GetDrawTarget();
  117. if (!dest) {
  118. NS_WARNING("Blurring not supported for Thebes contexts!");
  119. return;
  120. }
  121. Rect* dirtyRect = mBlur->GetDirtyRect();
  122. IntPoint topLeft;
  123. RefPtr<SourceSurface> mask = DoBlur(dest, &topLeft);
  124. if (!mask) {
  125. NS_ERROR("Failed to create mask!");
  126. return;
  127. }
  128. DrawBlur(aDestinationCtx, mask, topLeft, dirtyRect);
  129. }
  130. IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd)
  131. {
  132. mozilla::gfx::Point std(Float(aStd.x), Float(aStd.y));
  133. IntSize size = AlphaBoxBlur::CalculateBlurRadius(std);
  134. return IntSize(size.width, size.height);
  135. }
  136. struct BlurCacheKey : public PLDHashEntryHdr {
  137. typedef const BlurCacheKey& KeyType;
  138. typedef const BlurCacheKey* KeyTypePointer;
  139. enum { ALLOW_MEMMOVE = true };
  140. IntSize mMinSize;
  141. IntSize mBlurRadius;
  142. Color mShadowColor;
  143. BackendType mBackend;
  144. RectCornerRadii mCornerRadii;
  145. bool mIsInset;
  146. // Only used for inset blurs
  147. bool mHasBorderRadius;
  148. IntSize mInnerMinSize;
  149. BlurCacheKey(IntSize aMinSize, IntSize aBlurRadius,
  150. RectCornerRadii* aCornerRadii, const Color& aShadowColor,
  151. BackendType aBackendType)
  152. : BlurCacheKey(aMinSize, IntSize(0, 0),
  153. aBlurRadius, aCornerRadii,
  154. aShadowColor, false,
  155. false, aBackendType)
  156. {}
  157. explicit BlurCacheKey(const BlurCacheKey* aOther)
  158. : mMinSize(aOther->mMinSize)
  159. , mBlurRadius(aOther->mBlurRadius)
  160. , mShadowColor(aOther->mShadowColor)
  161. , mBackend(aOther->mBackend)
  162. , mCornerRadii(aOther->mCornerRadii)
  163. , mIsInset(aOther->mIsInset)
  164. , mHasBorderRadius(aOther->mHasBorderRadius)
  165. , mInnerMinSize(aOther->mInnerMinSize)
  166. { }
  167. explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize,
  168. IntSize aBlurRadius,
  169. const RectCornerRadii* aCornerRadii,
  170. const Color& aShadowColor, bool aIsInset,
  171. bool aHasBorderRadius, BackendType aBackendType)
  172. : mMinSize(aOuterMinSize)
  173. , mBlurRadius(aBlurRadius)
  174. , mShadowColor(aShadowColor)
  175. , mBackend(aBackendType)
  176. , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
  177. , mIsInset(aIsInset)
  178. , mHasBorderRadius(aHasBorderRadius)
  179. , mInnerMinSize(aInnerMinSize)
  180. { }
  181. static PLDHashNumber
  182. HashKey(const KeyTypePointer aKey)
  183. {
  184. PLDHashNumber hash = 0;
  185. hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height);
  186. hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
  187. hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.r,
  188. sizeof(aKey->mShadowColor.r)));
  189. hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.g,
  190. sizeof(aKey->mShadowColor.g)));
  191. hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.b,
  192. sizeof(aKey->mShadowColor.b)));
  193. hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.a,
  194. sizeof(aKey->mShadowColor.a)));
  195. for (int i = 0; i < 4; i++) {
  196. hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
  197. }
  198. hash = AddToHash(hash, (uint32_t)aKey->mBackend);
  199. if (aKey->mIsInset) {
  200. hash = AddToHash(hash, aKey->mInnerMinSize.width, aKey->mInnerMinSize.height);
  201. hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool)));
  202. }
  203. return hash;
  204. }
  205. bool
  206. KeyEquals(KeyTypePointer aKey) const
  207. {
  208. if (aKey->mMinSize == mMinSize &&
  209. aKey->mBlurRadius == mBlurRadius &&
  210. aKey->mCornerRadii == mCornerRadii &&
  211. aKey->mShadowColor == mShadowColor &&
  212. aKey->mBackend == mBackend) {
  213. if (mIsInset) {
  214. return (mHasBorderRadius == aKey->mHasBorderRadius) &&
  215. (mInnerMinSize == aKey->mInnerMinSize);
  216. }
  217. return true;
  218. }
  219. return false;
  220. }
  221. static KeyTypePointer
  222. KeyToPointer(KeyType aKey)
  223. {
  224. return &aKey;
  225. }
  226. };
  227. /**
  228. * This class is what is cached. It need to be allocated in an object separated
  229. * to the cache entry to be able to be tracked by the nsExpirationTracker.
  230. * */
  231. struct BlurCacheData {
  232. BlurCacheData(SourceSurface* aBlur, IntMargin aExtendDestBy, const BlurCacheKey& aKey)
  233. : mBlur(aBlur)
  234. , mExtendDest(aExtendDestBy)
  235. , mKey(aKey)
  236. {}
  237. BlurCacheData(const BlurCacheData& aOther)
  238. : mBlur(aOther.mBlur)
  239. , mExtendDest(aOther.mExtendDest)
  240. , mKey(aOther.mKey)
  241. { }
  242. nsExpirationState *GetExpirationState() {
  243. return &mExpirationState;
  244. }
  245. nsExpirationState mExpirationState;
  246. RefPtr<SourceSurface> mBlur;
  247. IntMargin mExtendDest;
  248. BlurCacheKey mKey;
  249. };
  250. /**
  251. * This class implements a cache with no maximum size, that retains the
  252. * SourceSurfaces used to draw the blurs.
  253. *
  254. * An entry stays in the cache as long as it is used often.
  255. */
  256. class BlurCache final : public nsExpirationTracker<BlurCacheData,4>
  257. {
  258. public:
  259. BlurCache()
  260. : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache")
  261. {
  262. }
  263. virtual void NotifyExpired(BlurCacheData* aObject)
  264. {
  265. RemoveObject(aObject);
  266. mHashEntries.Remove(aObject->mKey);
  267. }
  268. BlurCacheData* Lookup(const IntSize aMinSize,
  269. const IntSize& aBlurRadius,
  270. RectCornerRadii* aCornerRadii,
  271. const Color& aShadowColor,
  272. BackendType aBackendType)
  273. {
  274. BlurCacheData* blur =
  275. mHashEntries.Get(BlurCacheKey(aMinSize, aBlurRadius,
  276. aCornerRadii, aShadowColor,
  277. aBackendType));
  278. if (blur) {
  279. MarkUsed(blur);
  280. }
  281. return blur;
  282. }
  283. BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize,
  284. const IntSize aInnerMinSize,
  285. const IntSize& aBlurRadius,
  286. const RectCornerRadii* aCornerRadii,
  287. const Color& aShadowColor,
  288. const bool& aHasBorderRadius,
  289. BackendType aBackendType)
  290. {
  291. bool insetBoxShadow = true;
  292. BlurCacheKey key(aOuterMinSize, aInnerMinSize,
  293. aBlurRadius, aCornerRadii,
  294. aShadowColor, insetBoxShadow,
  295. aHasBorderRadius, aBackendType);
  296. BlurCacheData* blur = mHashEntries.Get(key);
  297. if (blur) {
  298. MarkUsed(blur);
  299. }
  300. return blur;
  301. }
  302. // Returns true if we successfully register the blur in the cache, false
  303. // otherwise.
  304. bool RegisterEntry(BlurCacheData* aValue)
  305. {
  306. nsresult rv = AddObject(aValue);
  307. if (NS_FAILED(rv)) {
  308. // We are OOM, and we cannot track this object. We don't want stall
  309. // entries in the hash table (since the expiration tracker is responsible
  310. // for removing the cache entries), so we avoid putting that entry in the
  311. // table, which is a good things considering we are short on memory
  312. // anyway, we probably don't want to retain things.
  313. return false;
  314. }
  315. mHashEntries.Put(aValue->mKey, aValue);
  316. return true;
  317. }
  318. protected:
  319. static const uint32_t GENERATION_MS = 1000;
  320. /**
  321. * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey.
  322. * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
  323. */
  324. nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries;
  325. };
  326. static BlurCache* gBlurCache = nullptr;
  327. static IntSize
  328. ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii,
  329. IntSize aBlurRadius,
  330. IntMargin& aSlice,
  331. const IntSize& aRectSize)
  332. {
  333. float cornerWidth = 0;
  334. float cornerHeight = 0;
  335. if (aCornerRadii) {
  336. RectCornerRadii corners = *aCornerRadii;
  337. for (size_t i = 0; i < 4; i++) {
  338. cornerWidth = std::max(cornerWidth, corners[i].width);
  339. cornerHeight = std::max(cornerHeight, corners[i].height);
  340. }
  341. }
  342. aSlice = IntMargin(ceil(cornerHeight) + aBlurRadius.height,
  343. ceil(cornerWidth) + aBlurRadius.width,
  344. ceil(cornerHeight) + aBlurRadius.height,
  345. ceil(cornerWidth) + aBlurRadius.width);
  346. IntSize minSize(aSlice.LeftRight() + 1,
  347. aSlice.TopBottom() + 1);
  348. // If aRectSize is smaller than minSize, the border-image approach won't
  349. // work; there's no way to squeeze parts of the min box-shadow source
  350. // image such that the result looks correct. So we need to adjust minSize
  351. // in such a way that we can later draw it without stretching in the affected
  352. // dimension. We also need to adjust "slice" to ensure that we're not trying
  353. // to slice away more than we have.
  354. if (aRectSize.width < minSize.width) {
  355. minSize.width = aRectSize.width;
  356. aSlice.left = 0;
  357. aSlice.right = 0;
  358. }
  359. if (aRectSize.height < minSize.height) {
  360. minSize.height = aRectSize.height;
  361. aSlice.top = 0;
  362. aSlice.bottom = 0;
  363. }
  364. MOZ_ASSERT(aSlice.LeftRight() <= minSize.width);
  365. MOZ_ASSERT(aSlice.TopBottom() <= minSize.height);
  366. return minSize;
  367. }
  368. void
  369. CacheBlur(DrawTarget& aDT,
  370. const IntSize& aMinSize,
  371. const IntSize& aBlurRadius,
  372. RectCornerRadii* aCornerRadii,
  373. const Color& aShadowColor,
  374. IntMargin aExtendDest,
  375. SourceSurface* aBoxShadow)
  376. {
  377. BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT.GetBackendType());
  378. BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendDest, key);
  379. if (!gBlurCache->RegisterEntry(data)) {
  380. delete data;
  381. }
  382. }
  383. // Blurs a small surface and creates the mask.
  384. static already_AddRefed<SourceSurface>
  385. CreateBlurMask(const IntSize& aMinSize,
  386. RectCornerRadii* aCornerRadii,
  387. IntSize aBlurRadius,
  388. IntMargin& aExtendDestBy,
  389. IntMargin& aSliceBorder,
  390. DrawTarget& aDestDrawTarget)
  391. {
  392. gfxAlphaBoxBlur blur;
  393. IntRect minRect(IntPoint(), aMinSize);
  394. gfxContext* blurCtx = blur.Init(ThebesRect(Rect(minRect)), IntSize(),
  395. aBlurRadius, nullptr, nullptr);
  396. if (!blurCtx) {
  397. return nullptr;
  398. }
  399. DrawTarget* blurDT = blurCtx->GetDrawTarget();
  400. ColorPattern black(Color(0.f, 0.f, 0.f, 1.f));
  401. if (aCornerRadii) {
  402. RefPtr<Path> roundedRect =
  403. MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii);
  404. blurDT->Fill(roundedRect, black);
  405. } else {
  406. blurDT->FillRect(Rect(minRect), black);
  407. }
  408. IntPoint topLeft;
  409. RefPtr<SourceSurface> result = blur.DoBlur(&aDestDrawTarget, &topLeft);
  410. if (!result) {
  411. return nullptr;
  412. }
  413. IntRect expandedMinRect(topLeft, result->GetSize());
  414. aExtendDestBy = expandedMinRect - minRect;
  415. aSliceBorder += aExtendDestBy;
  416. MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width);
  417. MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height);
  418. return result.forget();
  419. }
  420. static already_AddRefed<SourceSurface>
  421. CreateBoxShadow(DrawTarget& aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor)
  422. {
  423. IntSize blurredSize = aBlurMask->GetSize();
  424. RefPtr<DrawTarget> boxShadowDT =
  425. Factory::CreateDrawTarget(aDestDT.GetBackendType(), blurredSize, SurfaceFormat::B8G8R8A8);
  426. if (!boxShadowDT) {
  427. return nullptr;
  428. }
  429. ColorPattern shadowColor(ToDeviceColor(aShadowColor));
  430. boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0));
  431. return boxShadowDT->Snapshot();
  432. }
  433. static already_AddRefed<SourceSurface>
  434. GetBlur(gfxContext* aDestinationCtx,
  435. const IntSize& aRectSize,
  436. const IntSize& aBlurRadius,
  437. RectCornerRadii* aCornerRadii,
  438. const Color& aShadowColor,
  439. IntMargin& aExtendDestBy,
  440. IntMargin& aSlice)
  441. {
  442. if (!gBlurCache) {
  443. gBlurCache = new BlurCache();
  444. }
  445. IntSize minSize =
  446. ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aSlice, aRectSize);
  447. // We can get seams using the min size rect when drawing to the destination rect
  448. // if we have a non-pixel aligned destination transformation. In those cases,
  449. // fallback to just rendering the destination rect.
  450. Matrix destMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
  451. bool useDestRect = !destMatrix.IsRectilinear() || destMatrix.HasNonIntegerTranslation();
  452. if (useDestRect) {
  453. minSize = aRectSize;
  454. }
  455. DrawTarget& destDT = *aDestinationCtx->GetDrawTarget();
  456. BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius,
  457. aCornerRadii, aShadowColor,
  458. destDT.GetBackendType());
  459. if (cached && !useDestRect) {
  460. // See CreateBlurMask() for these values
  461. aExtendDestBy = cached->mExtendDest;
  462. aSlice = aSlice + aExtendDestBy;
  463. RefPtr<SourceSurface> blur = cached->mBlur;
  464. return blur.forget();
  465. }
  466. RefPtr<SourceSurface> blurMask =
  467. CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice,
  468. destDT);
  469. if (!blurMask) {
  470. return nullptr;
  471. }
  472. RefPtr<SourceSurface> boxShadow = CreateBoxShadow(destDT, blurMask, aShadowColor);
  473. if (!boxShadow) {
  474. return nullptr;
  475. }
  476. if (useDestRect) {
  477. // Since we're just going to paint the actual rect to the destination
  478. aSlice.SizeTo(0, 0, 0, 0);
  479. } else {
  480. CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor,
  481. aExtendDestBy, boxShadow);
  482. }
  483. return boxShadow.forget();
  484. }
  485. void
  486. gfxAlphaBoxBlur::ShutdownBlurCache()
  487. {
  488. delete gBlurCache;
  489. gBlurCache = nullptr;
  490. }
  491. static Rect
  492. RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, Float aLeft)
  493. {
  494. return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop);
  495. }
  496. static void
  497. RepeatOrStretchSurface(DrawTarget& aDT, SourceSurface* aSurface,
  498. const Rect& aDest, const Rect& aSrc, Rect& aSkipRect)
  499. {
  500. if (aSkipRect.Contains(aDest)) {
  501. return;
  502. }
  503. if ((!aDT.GetTransform().IsRectilinear() &&
  504. aDT.GetBackendType() != BackendType::CAIRO) ||
  505. (aDT.GetBackendType() == BackendType::DIRECT2D1_1)) {
  506. // Use stretching if possible, since it leads to less seams when the
  507. // destination is transformed. However, don't do this if we're using cairo,
  508. // because if cairo is using pixman it won't render anything for large
  509. // stretch factors because pixman's internal fixed point precision is not
  510. // high enough to handle those scale factors.
  511. // Calling FillRect on a D2D backend with a repeating pattern is much slower
  512. // than DrawSurface, so special case the D2D backend here.
  513. aDT.DrawSurface(aSurface, aDest, aSrc);
  514. return;
  515. }
  516. SurfacePattern pattern(aSurface, ExtendMode::REPEAT,
  517. Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()),
  518. SamplingFilter::GOOD, RoundedToInt(aSrc));
  519. aDT.FillRect(aDest, pattern);
  520. }
  521. static void
  522. DrawCorner(DrawTarget& aDT, SourceSurface* aSurface,
  523. const Rect& aDest, const Rect& aSrc, Rect& aSkipRect)
  524. {
  525. if (aSkipRect.Contains(aDest)) {
  526. return;
  527. }
  528. aDT.DrawSurface(aSurface, aDest, aSrc);
  529. }
  530. static void
  531. DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur,
  532. Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner,
  533. Rect aSkipRect)
  534. {
  535. // Corners: top left, top right, bottom left, bottom right
  536. DrawCorner(aDestDrawTarget, aSourceBlur,
  537. RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(),
  538. aDstInner.Y(), aDstOuter.X()),
  539. RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(),
  540. aSrcInner.Y(), aSrcOuter.X()),
  541. aSkipRect);
  542. DrawCorner(aDestDrawTarget, aSourceBlur,
  543. RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(),
  544. aDstInner.Y(), aDstInner.XMost()),
  545. RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(),
  546. aSrcInner.Y(), aSrcInner.XMost()),
  547. aSkipRect);
  548. DrawCorner(aDestDrawTarget, aSourceBlur,
  549. RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
  550. aDstOuter.YMost(), aDstOuter.X()),
  551. RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
  552. aSrcOuter.YMost(), aSrcOuter.X()),
  553. aSkipRect);
  554. DrawCorner(aDestDrawTarget, aSourceBlur,
  555. RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
  556. aDstOuter.YMost(), aDstInner.XMost()),
  557. RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
  558. aSrcOuter.YMost(), aSrcInner.XMost()),
  559. aSkipRect);
  560. // Edges: top, left, right, bottom
  561. RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
  562. RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
  563. aDstInner.Y(), aDstInner.X()),
  564. RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
  565. aSrcInner.Y(), aSrcInner.X()),
  566. aSkipRect);
  567. RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
  568. RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
  569. aDstInner.YMost(), aDstOuter.X()),
  570. RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
  571. aSrcInner.YMost(), aSrcOuter.X()),
  572. aSkipRect);
  573. RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
  574. RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
  575. aDstInner.YMost(), aDstInner.XMost()),
  576. RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(),
  577. aSrcInner.YMost(), aSrcInner.XMost()),
  578. aSkipRect);
  579. RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
  580. RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
  581. aDstOuter.YMost(), aDstInner.X()),
  582. RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
  583. aSrcOuter.YMost(), aSrcInner.X()),
  584. aSkipRect);
  585. }
  586. /***
  587. * We draw a blurred a rectangle by only blurring a smaller rectangle and
  588. * splitting the rectangle into 9 parts.
  589. * First, a small minimum source rect is calculated and used to create a blur
  590. * mask since the actual blurring itself is expensive. Next, we use the mask
  591. * with the given shadow color to create a minimally-sized box shadow of the
  592. * right color. Finally, we cut out the 9 parts from the box-shadow source and
  593. * paint each part in the right place, stretching the non-corner parts to fill
  594. * the space between the corners.
  595. */
  596. /* static */ void
  597. gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
  598. const gfxRect& aRect,
  599. RectCornerRadii* aCornerRadii,
  600. const gfxPoint& aBlurStdDev,
  601. const Color& aShadowColor,
  602. const gfxRect& aDirtyRect,
  603. const gfxRect& aSkipRect)
  604. {
  605. IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
  606. IntRect rect = RoundedToInt(ToRect(aRect));
  607. IntMargin extendDestBy;
  608. IntMargin slice;
  609. RefPtr<SourceSurface> boxShadow = GetBlur(aDestinationCtx,
  610. rect.Size(), blurRadius,
  611. aCornerRadii, aShadowColor,
  612. extendDestBy, slice);
  613. if (!boxShadow) {
  614. return;
  615. }
  616. DrawTarget& destDrawTarget = *aDestinationCtx->GetDrawTarget();
  617. destDrawTarget.PushClipRect(ToRect(aDirtyRect));
  618. // Copy the right parts from boxShadow into destDrawTarget. The middle parts
  619. // will be stretched, border-image style.
  620. Rect srcOuter(Point(), Size(boxShadow->GetSize()));
  621. Rect srcInner = srcOuter;
  622. srcInner.Deflate(Margin(slice));
  623. rect.Inflate(extendDestBy);
  624. Rect dstOuter(rect);
  625. Rect dstInner(rect);
  626. dstInner.Deflate(Margin(slice));
  627. Rect skipRect = ToRect(aSkipRect);
  628. if (srcInner.IsEqualInterior(srcOuter)) {
  629. MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter));
  630. // The target rect is smaller than the minimal size so just draw the surface
  631. destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner);
  632. } else {
  633. DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner,
  634. srcOuter, srcInner, skipRect);
  635. // Middle part
  636. RepeatOrStretchSurface(destDrawTarget, boxShadow,
  637. RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(),
  638. dstInner.YMost(), dstInner.X()),
  639. RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(),
  640. srcInner.YMost(), srcInner.X()),
  641. skipRect);
  642. }
  643. // A note about anti-aliasing and seems between adjacent parts:
  644. // We don't explicitly disable anti-aliasing in the DrawSurface calls above,
  645. // so if there's a transform on destDrawTarget that is not pixel-aligned,
  646. // there will be seams between adjacent parts of the box-shadow. It's hard to
  647. // avoid those without the use of an intermediate surface.
  648. // You might think that we could avoid those by just turning of AA, but there
  649. // is a problem with that: Box-shadow rendering needs to clip out the
  650. // element's border box, and we'd like that clip to have anti-aliasing -
  651. // especially if the element has rounded corners! So we can't do that unless
  652. // we have a way to say "Please anti-alias the clip, but don't antialias the
  653. // destination rect of the DrawSurface call".
  654. // On OS X there is an additional problem with turning off AA: CoreGraphics
  655. // will not just fill the pixels that have their pixel center inside the
  656. // filled shape. Instead, it will fill all the pixels which are partially
  657. // covered by the shape. So for pixels on the edge between two adjacent parts,
  658. // all those pixels will be painted to by both parts, which looks very bad.
  659. destDrawTarget.PopClip();
  660. }
  661. static already_AddRefed<Path>
  662. GetBoxShadowInsetPath(DrawTarget* aDrawTarget,
  663. const Rect aOuterRect, const Rect aInnerRect,
  664. const bool aHasBorderRadius, const RectCornerRadii& aInnerClipRadii)
  665. {
  666. /***
  667. * We create an inset path by having two rects.
  668. *
  669. * -----------------------
  670. * | ________________ |
  671. * | | | |
  672. * | | | |
  673. * | ------------------ |
  674. * |_____________________|
  675. *
  676. * The outer rect and the inside rect. The path
  677. * creates a frame around the content where we draw the inset shadow.
  678. */
  679. RefPtr<PathBuilder> builder =
  680. aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
  681. AppendRectToPath(builder, aOuterRect, true);
  682. if (aHasBorderRadius) {
  683. AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false);
  684. } else {
  685. AppendRectToPath(builder, aInnerRect, false);
  686. }
  687. return builder->Finish();
  688. }
  689. static void
  690. FillDestinationPath(gfxContext* aDestinationCtx,
  691. const Rect aDestinationRect,
  692. const Rect aShadowClipRect,
  693. const Color& aShadowColor,
  694. const bool aHasBorderRadius,
  695. const RectCornerRadii& aInnerClipRadii)
  696. {
  697. // When there is no blur radius, fill the path onto the destination
  698. // surface.
  699. aDestinationCtx->SetColor(aShadowColor);
  700. DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
  701. RefPtr<Path> shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect,
  702. aShadowClipRect, aHasBorderRadius,
  703. aInnerClipRadii);
  704. aDestinationCtx->SetPath(shadowPath);
  705. aDestinationCtx->Fill();
  706. }
  707. static void
  708. CacheInsetBlur(const IntSize aMinOuterSize,
  709. const IntSize aMinInnerSize,
  710. const IntSize& aBlurRadius,
  711. const RectCornerRadii* aCornerRadii,
  712. const Color& aShadowColor,
  713. const bool& aHasBorderRadius,
  714. BackendType aBackendType,
  715. SourceSurface* aBoxShadow)
  716. {
  717. bool isInsetBlur = true;
  718. BlurCacheKey key(aMinOuterSize, aMinInnerSize,
  719. aBlurRadius, aCornerRadii,
  720. aShadowColor, isInsetBlur,
  721. aHasBorderRadius, aBackendType);
  722. IntMargin extendDestBy(0, 0, 0, 0);
  723. BlurCacheData* data = new BlurCacheData(aBoxShadow, extendDestBy, key);
  724. if (!gBlurCache->RegisterEntry(data)) {
  725. delete data;
  726. }
  727. }
  728. already_AddRefed<mozilla::gfx::SourceSurface>
  729. gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect,
  730. const mozilla::gfx::Rect aWhitespaceRect,
  731. const bool aIsDestRect,
  732. const mozilla::gfx::Color& aShadowColor,
  733. const mozilla::gfx::IntSize& aBlurRadius,
  734. const bool aHasBorderRadius,
  735. const RectCornerRadii& aInnerClipRadii,
  736. DrawTarget* aDestDrawTarget)
  737. {
  738. if (!gBlurCache) {
  739. gBlurCache = new BlurCache();
  740. }
  741. IntSize outerSize((int)aOuterRect.width, (int)aOuterRect.height);
  742. IntSize whitespaceSize((int)aWhitespaceRect.width, (int)aWhitespaceRect.height);
  743. BlurCacheData* cached =
  744. gBlurCache->LookupInsetBoxShadow(outerSize, whitespaceSize,
  745. aBlurRadius, &aInnerClipRadii,
  746. aShadowColor, aHasBorderRadius,
  747. aDestDrawTarget->GetBackendType());
  748. if (cached && !aIsDestRect) {
  749. // So we don't forget the actual cached blur
  750. RefPtr<SourceSurface> cachedBlur = cached->mBlur;
  751. return cachedBlur.forget();
  752. }
  753. // If we can do a min rect, the whitespace rect will be expanded in Init to
  754. // aOuterRect.
  755. Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect;
  756. IntSize zeroSpread(0, 0);
  757. gfxContext* minGfxContext = Init(ThebesRect(blurRect),
  758. zeroSpread, aBlurRadius,
  759. nullptr, nullptr);
  760. if (!minGfxContext) {
  761. return nullptr;
  762. }
  763. // This is really annoying. When we create the AlphaBoxBlur, the gfxContext
  764. // has a translation applied to it that is the topLeft point. This is actually
  765. // the rect we gave it plus the blur radius. The rects we give this for the outer
  766. // and whitespace rects are based at (0, 0). We could either translate those rects
  767. // when we don't have a destination rect or ignore the translation when using
  768. // the dest rect. The dest rects layout gives us expect this translation.
  769. if (!aIsDestRect) {
  770. minGfxContext->SetMatrix(gfxMatrix());
  771. }
  772. DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
  773. // Fill in the path between the inside white space / outer rects
  774. // NOT the inner frame
  775. RefPtr<Path> maskPath =
  776. GetBoxShadowInsetPath(minDrawTarget, aOuterRect,
  777. aWhitespaceRect, aHasBorderRadius,
  778. aInnerClipRadii);
  779. Color black(0.f, 0.f, 0.f, 1.f);
  780. minGfxContext->SetColor(black);
  781. minGfxContext->SetPath(maskPath);
  782. minGfxContext->Fill();
  783. // Create the A8 mask
  784. IntPoint topLeft;
  785. RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &topLeft);
  786. if (!minMask) {
  787. return nullptr;
  788. }
  789. // Fill in with the color we actually wanted
  790. RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(*aDestDrawTarget, minMask, aShadowColor);
  791. if (!minInsetBlur) {
  792. return nullptr;
  793. }
  794. if (!aIsDestRect) {
  795. CacheInsetBlur(outerSize, whitespaceSize,
  796. aBlurRadius, &aInnerClipRadii,
  797. aShadowColor, aHasBorderRadius,
  798. aDestDrawTarget->GetBackendType(),
  799. minInsetBlur);
  800. }
  801. return minInsetBlur.forget();
  802. }
  803. /***
  804. * We create our minimal rect with 2 rects.
  805. * The first is the inside whitespace rect, that is "cut out"
  806. * from the box. This is (1). This must be the size
  807. * of the blur radius + corner radius so we can have a big enough
  808. * inside cut.
  809. *
  810. * The second (2) is one blur radius surrounding the inner
  811. * frame of (1). This is the amount of blur space required
  812. * to get a proper blend.
  813. *
  814. * B = one blur size
  815. * W = one blur + corner radii - known as inner margin
  816. * ___________________________________
  817. * | |
  818. * | | | |
  819. * | (2) | (1) | (2) |
  820. * | B | W | B |
  821. * | | | |
  822. * | | | |
  823. * | | |
  824. * |________________________________|
  825. */
  826. static void GetBlurMargins(const bool aHasBorderRadius,
  827. const RectCornerRadii& aInnerClipRadii,
  828. const IntSize aBlurRadius,
  829. Margin& aOutBlurMargin,
  830. Margin& aOutInnerMargin)
  831. {
  832. float cornerWidth = 0;
  833. float cornerHeight = 0;
  834. if (aHasBorderRadius) {
  835. for (size_t i = 0; i < 4; i++) {
  836. cornerWidth = std::max(cornerWidth, aInnerClipRadii[i].width);
  837. cornerHeight = std::max(cornerHeight, aInnerClipRadii[i].height);
  838. }
  839. }
  840. // Only the inside whitespace size cares about the border radius size.
  841. // Outer sizes only care about blur.
  842. int width = cornerWidth + aBlurRadius.width;
  843. int height = cornerHeight + aBlurRadius.height;
  844. aOutInnerMargin.SizeTo(height, width, height, width);
  845. aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width,
  846. aBlurRadius.height, aBlurRadius.width);
  847. }
  848. static bool
  849. GetInsetBoxShadowRects(const Margin aBlurMargin,
  850. const Margin aInnerMargin,
  851. const Rect aShadowClipRect,
  852. const Rect aDestinationRect,
  853. Rect& aOutWhitespaceRect,
  854. Rect& aOutOuterRect)
  855. {
  856. // We always copy (2 * blur radius) + corner radius worth of data to the destination rect
  857. // This covers the blend of the path + the actual blur
  858. // Need +1 so that we copy the edges correctly as we'll copy
  859. // over the min box shadow corners then the +1 for the edges between
  860. // Note, the (x,y) coordinates are from the blur margin
  861. // since the frame outside the whitespace rect is 1 blur radius extra space.
  862. Rect insideWhiteSpace(aBlurMargin.left,
  863. aBlurMargin.top,
  864. aInnerMargin.LeftRight() + 1,
  865. aInnerMargin.TopBottom() + 1);
  866. // If the inner white space rect is larger than the shadow clip rect
  867. // our approach does not work as we'll just copy one corner
  868. // and cover the destination. In those cases, fallback to the destination rect
  869. bool useDestRect = (aShadowClipRect.width <= aInnerMargin.LeftRight()) ||
  870. (aShadowClipRect.height <= aInnerMargin.TopBottom());
  871. if (useDestRect) {
  872. aOutWhitespaceRect = aShadowClipRect;
  873. aOutOuterRect = aDestinationRect;
  874. } else {
  875. aOutWhitespaceRect = insideWhiteSpace;
  876. aOutOuterRect = aOutWhitespaceRect;
  877. aOutOuterRect.Inflate(aBlurMargin);
  878. }
  879. return useDestRect;
  880. }
  881. void
  882. gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx,
  883. const Rect aDestinationRect,
  884. const Rect aShadowClipRect,
  885. const IntSize aBlurRadius,
  886. const IntSize aSpreadRadius,
  887. const Color& aShadowColor,
  888. bool aHasBorderRadius,
  889. const RectCornerRadii& aInnerClipRadii,
  890. const Rect aSkipRect,
  891. const Point aShadowOffset)
  892. {
  893. if ((aBlurRadius.width == 0 && aBlurRadius.height == 0)
  894. || aShadowClipRect.IsEmpty()) {
  895. FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
  896. aShadowColor, aHasBorderRadius, aInnerClipRadii);
  897. return;
  898. }
  899. DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
  900. Margin innerMargin;
  901. Margin blurMargin;
  902. GetBlurMargins(aHasBorderRadius, aInnerClipRadii, aBlurRadius,
  903. blurMargin, innerMargin);
  904. Rect whitespaceRect;
  905. Rect outerRect;
  906. bool useDestRect = GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
  907. aDestinationRect, whitespaceRect, outerRect);
  908. RefPtr<SourceSurface> minBlur = GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor,
  909. aBlurRadius, aHasBorderRadius, aInnerClipRadii,
  910. destDrawTarget);
  911. if (!minBlur) {
  912. return;
  913. }
  914. if (useDestRect) {
  915. IntSize blurSize = minBlur->GetSize();
  916. Rect srcBlur(0, 0, blurSize.width, blurSize.height);
  917. Rect destBlur = aDestinationRect;
  918. // The blur itself expands the rect by the blur margin, so we
  919. // have to mimic that here.
  920. destBlur.Inflate(blurMargin);
  921. MOZ_ASSERT(srcBlur.Size() == destBlur.Size());
  922. destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur);
  923. } else {
  924. Rect srcOuter(outerRect);
  925. Rect srcInner(srcOuter);
  926. srcInner.Deflate(blurMargin); // The outer color fill
  927. srcInner.Deflate(innerMargin); // The inner whitespace
  928. // The shadow clip rect already takes into account the spread radius
  929. Rect outerFillRect(aShadowClipRect);
  930. outerFillRect.Inflate(blurMargin);
  931. FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, aShadowColor, false, RectCornerRadii());
  932. // Inflate once for the frame around the whitespace
  933. Rect destRect(aShadowClipRect);
  934. destRect.Inflate(blurMargin);
  935. // Deflate for the blurred in white space
  936. Rect destInnerRect(aShadowClipRect);
  937. destInnerRect.Deflate(innerMargin);
  938. DrawBoxShadows(*destDrawTarget, minBlur,
  939. destRect, destInnerRect,
  940. srcOuter, srcInner,
  941. aSkipRect);
  942. }
  943. }