diff options
author | Moonchild <moonchild@palemoon.org> | 2023-11-02 00:27:19 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2023-11-02 00:27:19 +0000 |
commit | 2132ea4a2257ced9894b956595019b4b04c19131 (patch) | |
tree | 75f0f817f7f6c84d902f4602f49ff60877b7f3e4 | |
parent | 33bff10b1cbd4a0746b628414d2ec5e60c5397fe (diff) | |
parent | 9493e8c3dedbe92fed6391e74aa21627aee89d67 (diff) | |
download | uxp-2132ea4a2257ced9894b956595019b4b04c19131.tar.gz |
Merge pull request 'Improve boxshadow rendering' (#2367) from boxshadow-work into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2367
-rw-r--r-- | gfx/2d/BaseSize.h | 11 | ||||
-rw-r--r-- | gfx/2d/Blur.cpp | 461 | ||||
-rw-r--r-- | gfx/2d/Blur.h | 19 | ||||
-rw-r--r-- | gfx/2d/DrawTargetCairo.cpp | 33 | ||||
-rw-r--r-- | gfx/2d/DrawTargetD2D1.cpp | 20 | ||||
-rw-r--r-- | gfx/2d/DrawTargetSkia.cpp | 8 | ||||
-rw-r--r-- | gfx/2d/HelpersD2D.h | 6 | ||||
-rw-r--r-- | gfx/2d/PathHelpers.h | 9 | ||||
-rw-r--r-- | gfx/2d/Point.h | 2 | ||||
-rw-r--r-- | gfx/thebes/gfxBlur.cpp | 941 | ||||
-rw-r--r-- | gfx/thebes/gfxBlur.h | 80 | ||||
-rw-r--r-- | gfx/thebes/gfxPlatform.cpp | 11 | ||||
-rw-r--r-- | gfx/thebes/gfxUtils.cpp | 13 | ||||
-rw-r--r-- | gfx/thebes/gfxUtils.h | 5 | ||||
-rw-r--r-- | layout/base/FrameLayerBuilder.cpp | 45 | ||||
-rw-r--r-- | layout/base/nsCSSRendering.cpp | 12 | ||||
-rw-r--r-- | layout/reftests/box-shadow/reftest.list | 10 | ||||
-rw-r--r-- | layout/reftests/bugs/reftest.list | 2 | ||||
-rw-r--r-- | layout/reftests/outline/reftest.list | 2 |
19 files changed, 1046 insertions, 644 deletions
diff --git a/gfx/2d/BaseSize.h b/gfx/2d/BaseSize.h index 7bcccc6299..f9f3fa4615 100644 --- a/gfx/2d/BaseSize.h +++ b/gfx/2d/BaseSize.h @@ -6,6 +6,7 @@ #ifndef MOZILLA_GFX_BASESIZE_H_ #define MOZILLA_GFX_BASESIZE_H_ +#include <algorithm> #include "mozilla/Attributes.h" namespace mozilla { @@ -92,6 +93,16 @@ struct BaseSize { Sub operator/(const Sub& aSize) const { return Sub(width / aSize.width, height / aSize.height); } + + friend Sub Min(const Sub& aA, const Sub& aB) { + return Sub(std::min(aA.width, aB.width), + std::min(aA.height, aB.height)); + } + + friend Sub Max(const Sub& aA, const Sub& aB) { + return Sub(std::max(aA.width, aB.width), + std::max(aA.height, aB.height)); + } }; } // namespace gfx diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index 6a38b66669..73f09ba1af 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -25,159 +25,268 @@ namespace mozilla { namespace gfx { /** - * Box blur involves looking at one pixel, and setting its value to the average - * of its neighbouring pixels. - * @param aInput The input buffer. - * @param aOutput The output buffer. - * @param aLeftLobe The number of pixels to blend on the left. - * @param aRightLobe The number of pixels to blend on the right. - * @param aWidth The number of columns in the buffers. - * @param aRows The number of rows in the buffers. - * @param aSkipRect An area to skip blurring in. - * XXX shouldn't we pass stride in separately here? + * Helper function to process each row of the box blur. + * It takes care of transposing the data on input or output depending + * on whether we intend a horizontal or vertical blur, and whether we're + * reading from the initial source or writing to the final destination. + * It allows starting or ending anywhere within the row to accomodate + * a skip rect. */ -static void -BoxBlurHorizontal(unsigned char* aInput, - unsigned char* aOutput, - int32_t aLeftLobe, - int32_t aRightLobe, - int32_t aWidth, - int32_t aRows, - const IntRect& aSkipRect) +template<bool aTransposeInput, bool aTransposeOutput> +static inline void +BoxBlurRow(const uint8_t* aInput, + uint8_t* aOutput, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aWidth, + int32_t aStride, + int32_t aStart, + int32_t aEnd) { - MOZ_ASSERT(aWidth > 0); - - int32_t boxSize = aLeftLobe + aRightLobe + 1; - bool skipRectCoversWholeRow = 0 >= aSkipRect.x && - aWidth <= aSkipRect.XMost(); - if (boxSize == 1) { - memcpy(aOutput, aInput, aWidth*aRows); - return; - } - uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); - - for (int32_t y = 0; y < aRows; y++) { - // Check whether the skip rect intersects this row. If the skip - // rect covers the whole surface in this row, we can avoid - // this row entirely (and any others along the skip rect). - bool inSkipRectY = y >= aSkipRect.y && - y < aSkipRect.YMost(); - if (inSkipRectY && skipRectCoversWholeRow) { - y = aSkipRect.YMost() - 1; - continue; - } - - uint32_t alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = i - aLeftLobe; - // See assertion above; if aWidth is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aWidth - 1); - alphaSum += aInput[aWidth * y + pos]; - } - for (int32_t x = 0; x < aWidth; x++) { - // Check whether we are within the skip rect. If so, go - // to the next point outside the skip rect. - if (inSkipRectY && x >= aSkipRect.x && - x < aSkipRect.XMost()) { - x = aSkipRect.XMost(); - if (x >= aWidth) - break; + // If the input or output is transposed, then we will move down a row + // for each step, instead of moving over a column. Since these values + // only depend on a template parameter, they will more easily get + // copy-propagated in the non-transposed case, which is why they + // are not passed as parameters. + const int32_t inputStep = aTransposeInput ? aStride : 1; + const int32_t outputStep = aTransposeOutput ? aStride : 1; + + // We need to sample aLeftLobe pixels to the left and aRightLobe pixels + // to the right of the current position, then average them. So this is + // the size of the total width of this filter. + const int32_t boxSize = aLeftLobe + aRightLobe + 1; + + // Instead of dividing the pixel sum by boxSize to average, we can just + // compute a scale that will normalize the result so that it can be quickly + // shifted into the desired range. + const uint32_t reciprocal = (1 << 24) / boxSize; + + // The shift would normally truncate the result, whereas we would rather + // prefer to round the result to the closest increment. By adding 0.5 units + // to the initial sum, we bias the sum so that it will be rounded by the + // truncation instead. + uint32_t alphaSum = (boxSize + 1) / 2; + + // We process the row with a moving filter, keeping a sum (alphaSum) of + // boxSize pixels. As we move over a pixel, we need to add on a pixel + // from the right extreme of the window that moved into range, and subtract + // off a pixel from the left extreme of window that moved out of range. + // But first, we need to initialization alphaSum to the contents of + // the window before we can get going. If the window moves out of bounds + // of the row, we clamp each sample to be the closest pixel from within + // row bounds, so the 0th and aWidth-1th pixel. + int32_t initLeft = aStart - aLeftLobe; + if (initLeft < 0) { + // If the left lobe samples before the row, add in clamped samples. + alphaSum += -initLeft * aInput[0]; + initLeft = 0; + } + int32_t initRight = aStart + boxSize - aLeftLobe; + if (initRight > aWidth) { + // If the right lobe samples after the row, add in clamped samples. + alphaSum += (initRight - aWidth) * aInput[(aWidth - 1) * inputStep]; + initRight = aWidth; + } + // Finally, add in all the valid, non-clamped samples to fill up the + // rest of the window. + const uint8_t* src = &aInput[initLeft * inputStep]; + const uint8_t* iterEnd = &aInput[initRight * inputStep]; + + #define INIT_ITER \ + alphaSum += *src; \ + src += inputStep; + + // We unroll the per-pixel loop here substantially. The amount of work + // done per sample is so small that the cost of a loop condition check + // and a branch can substantially add to or even dominate the performance + // of the loop. + while (src + 16 * inputStep <= iterEnd) { + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + } + while (src < iterEnd) { + INIT_ITER; + } - // Recalculate the neighbouring alpha values for - // our new point on the surface. - alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = x + i - aLeftLobe; - // See assertion above; if aWidth is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aWidth - 1); - alphaSum += aInput[aWidth * y + pos]; - } - } - int32_t tmp = x - aLeftLobe; - int32_t last = max(tmp, 0); - int32_t next = min(tmp + boxSize, aWidth - 1); + // Now we start moving the window over the row. We will be accessing + // pixels form aStart - aLeftLobe up to aEnd + aRightLobe, which may be + // out of bounds of the row. To avoid having to check within the inner + // loops if we are in bound, we instead compute the points at which + // we will move out of bounds of the row on the left side (splitLeft) + // and right side (splitRight). + int32_t splitLeft = min(max(aLeftLobe, aStart), aEnd); + int32_t splitRight = min(max(aWidth - (boxSize - aLeftLobe), aStart), aEnd); + // If the filter window is actually large than the size of the row, + // there will be a middle area of overlap where the leftmost and rightmost + // pixel of the filter will both be outside the row. In this case, we need + // to invert the splits so that splitLeft <= splitRight. + if (boxSize > aWidth) { + swap(splitLeft, splitRight); + } - aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32; + // Process all pixels up to splitLeft that would sample before the start of the row. + // Note that because inputStep and outputStep may not be a const 1 value, it is more + // performant to increment pointers here for the source and destination rather than + // use a loop counter, since doing so would entail an expensive multiplication that + // significantly slows down the loop. + uint8_t* dst = &aOutput[aStart * outputStep]; + iterEnd = &aOutput[splitLeft * outputStep]; + src = &aInput[(aStart + boxSize - aLeftLobe) * inputStep]; + uint8_t firstVal = aInput[0]; + + #define LEFT_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += *src - firstVal; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + } + while (dst < iterEnd) { + LEFT_ITER; + } - alphaSum += aInput[aWidth * y + next] - - aInput[aWidth * y + last]; - } + // Process all pixels between splitLeft and splitRight. + iterEnd = &aOutput[splitRight * outputStep]; + if (boxSize <= aWidth) { + // The filter window is smaller than the row size, so the leftmost and rightmost + // samples are both within row bounds. + src = &aInput[(splitLeft - aLeftLobe) * inputStep]; + int32_t boxStep = boxSize * inputStep; + + #define CENTER_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += src[boxStep] - *src; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + } + while (dst < iterEnd) { + CENTER_ITER; } + } else { + // The filter window is larger than the row size, and we're in the area of split + // overlap. So the leftmost and rightmost samples are both out of bounds and need + // to be clamped. We can just precompute the difference here consequently. + int32_t firstLastDiff = aInput[(aWidth -1) * inputStep] - aInput[0]; + while (dst < iterEnd) { + *dst = (alphaSum * reciprocal) >> 24; + alphaSum += firstLastDiff; + dst += outputStep; + } + } + + // Process all remaining pixels after splitRight that would sample after the row end. + iterEnd = &aOutput[aEnd * outputStep]; + src = &aInput[(splitRight - aLeftLobe) * inputStep]; + uint8_t lastVal = aInput[(aWidth - 1) * inputStep]; + + #define RIGHT_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += lastVal - *src; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + } + while (dst < iterEnd) { + RIGHT_ITER; + } } /** - * Identical to BoxBlurHorizontal, except it blurs top and bottom instead of - * left and right. - * XXX shouldn't we pass stride in separately here? + * Box blur involves looking at one pixel, and setting its value to the average + * of its neighbouring pixels. This is meant to provide a 3-pass approximation of a + * Gaussian blur. + * @param aTranspose Whether to transpose the buffer when reading and writing to it. + * @param aData The buffer to be blurred. + * @param aLobes The number of pixels to blend on the left and right for each of 3 passes. + * @param aWidth The number of columns in the buffers. + * @param aRows The number of rows in the buffers. + * @param aStride The stride of the buffer. */ +template<bool aTranspose> static void -BoxBlurVertical(unsigned char* aInput, - unsigned char* aOutput, - int32_t aTopLobe, - int32_t aBottomLobe, - int32_t aWidth, - int32_t aRows, - const IntRect& aSkipRect) +BoxBlur(uint8_t* aData, + const int32_t aLobes[3][2], + int32_t aWidth, + int32_t aRows, + int32_t aStride, + IntRect aSkipRect) { - MOZ_ASSERT(aRows > 0); + if (aTranspose) { + swap(aWidth, aRows); + swap(aSkipRect.x, aSkipRect.y); + swap(aSkipRect.width, aSkipRect.height); + } - int32_t boxSize = aTopLobe + aBottomLobe + 1; - bool skipRectCoversWholeColumn = 0 >= aSkipRect.y && - aRows <= aSkipRect.YMost(); - if (boxSize == 1) { - memcpy(aOutput, aInput, aWidth*aRows); - return; + MOZ_ASSERT(aWidth > 0); + + // All three passes of the box blur that approximate the Gaussian are done + // on each row in turn, so we only need two temporary row buffers to process + // each row, instead of a full-sized buffer. Data moves from the source to the + // first temporary, from the first temporary to the second, then from the second + // back to the destination. This way is more cache-friendly than processing whe + // whole buffer in each pass and thus yields a nice speedup. + uint8_t* tmpRow = new (std::nothrow) uint8_t[2 * aWidth]; + if (!tmpRow) { + return; + } + uint8_t* tmpRow2 = tmpRow + aWidth; + + const int32_t stride = aTranspose ? 1 : aStride; + bool skipRectCoversWholeRow = 0 >= aSkipRect.x && + aWidth <= aSkipRect.XMost(); + + for (int32_t y = 0; y < aRows; y++) { + // Check whether the skip rect intersects this row. If the skip + // rect covers the whole surface in this row, we can avoid + // this row entirely (and any others along the skip rect). + bool inSkipRectY = y >= aSkipRect.y && + y < aSkipRect.YMost(); + if (inSkipRectY && skipRectCoversWholeRow) { + aData += stride * (aSkipRect.YMost() - y); + y = aSkipRect.YMost() - 1; + continue; } - uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); - for (int32_t x = 0; x < aWidth; x++) { - bool inSkipRectX = x >= aSkipRect.x && - x < aSkipRect.XMost(); - if (inSkipRectX && skipRectCoversWholeColumn) { - x = aSkipRect.XMost() - 1; - continue; - } + // Read in data from the source transposed if necessary. + BoxBlurRow<aTranspose, false>(aData, tmpRow, aLobes[0][0], aLobes[0][1], aWidth, aStride, 0, aWidth); - uint32_t alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = i - aTopLobe; - // See assertion above; if aRows is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aRows - 1); - alphaSum += aInput[aWidth * pos + x]; - } - for (int32_t y = 0; y < aRows; y++) { - if (inSkipRectX && y >= aSkipRect.y && - y < aSkipRect.YMost()) { - y = aSkipRect.YMost(); - if (y >= aRows) - break; + // For the middle pass, the data is already pre-transposed and does not need to be post-transposed yet. + BoxBlurRow<false, false>(tmpRow, tmpRow2, aLobes[1][0], aLobes[1][1], aWidth, aStride, 0, aWidth); - alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = y + i - aTopLobe; - // See assertion above; if aRows is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aRows - 1); - alphaSum += aInput[aWidth * pos + x]; - } - } - int32_t tmp = y - aTopLobe; - int32_t last = max(tmp, 0); - int32_t next = min(tmp + boxSize, aRows - 1); + // Write back data to the destination transposed if necessary too. + // Make sure not to overwrite the skip rect by only outputting to the + // destination before and after the skip rect, if requested. + int32_t skipStart = inSkipRectY ? min(max(aSkipRect.x, 0), aWidth) : aWidth; + int32_t skipEnd = max(skipStart, aSkipRect.XMost()); + if (skipStart > 0) { + BoxBlurRow<false, aTranspose>(tmpRow2, aData, aLobes[2][0], aLobes[2][1], aWidth, aStride, 0, skipStart); + } + if (skipEnd < aWidth) { + BoxBlurRow<false, aTranspose>(tmpRow2, aData, aLobes[2][0], aLobes[2][1], aWidth, aStride, skipEnd, aWidth); + } - aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32; + aData += stride; + } - alphaSum += aInput[aWidth * next + x] - - aInput[aWidth * last + x]; - } - } + delete[] tmpRow; } static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2]) @@ -226,8 +335,8 @@ static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2]) } static void -SpreadHorizontal(unsigned char* aInput, - unsigned char* aOutput, +SpreadHorizontal(uint8_t* aInput, + uint8_t* aOutput, int32_t aRadius, int32_t aWidth, int32_t aRows, @@ -274,8 +383,8 @@ SpreadHorizontal(unsigned char* aInput, } static void -SpreadVertical(unsigned char* aInput, - unsigned char* aOutput, +SpreadVertical(uint8_t* aInput, + uint8_t* aOutput, int32_t aRadius, int32_t aWidth, int32_t aRows, @@ -335,10 +444,26 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, const IntSize& aBlurRadius, const Rect* aDirtyRect, const Rect* aSkipRect) - : mSpreadRadius(aSpreadRadius), - mBlurRadius(aBlurRadius), - mSurfaceAllocationSize(0) + : mSurfaceAllocationSize(0) { + Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); +} + +AlphaBoxBlur::AlphaBoxBlur() + : mSurfaceAllocationSize(0) +{ +} + +void +AlphaBoxBlur::Init(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect) +{ + mSpreadRadius = aSpreadRadius; + mBlurRadius = aBlurRadius; + Rect rect(aRect); rect.Inflate(Size(aBlurRadius + aSpreadRadius)); rect.RoundOut(); @@ -355,8 +480,7 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, mHasDirtyRect = false; } - mRect = IntRect(int32_t(rect.x), int32_t(rect.y), - int32_t(rect.width), int32_t(rect.height)); + mRect = TruncatedToInt(rect); if (mRect.IsEmpty()) { return; } @@ -366,11 +490,8 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, // blurring/spreading we need to do. We convert it to IntRect to avoid // expensive int<->float conversions if we were to use Rect instead. Rect skipRect = *aSkipRect; - skipRect.RoundIn(); skipRect.Deflate(Size(aBlurRadius + aSpreadRadius)); - mSkipRect = IntRect(int32_t(skipRect.x), int32_t(skipRect.y), - int32_t(skipRect.width), int32_t(skipRect.height)); - + mSkipRect = RoundedIn(skipRect); mSkipRect = mSkipRect.Intersect(mRect); if (mSkipRect.IsEqualInterior(mRect)) return; @@ -397,8 +518,7 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, int32_t aStride, float aSigmaX, float aSigmaY) - : mRect(int32_t(aRect.x), int32_t(aRect.y), - int32_t(aRect.width), int32_t(aRect.height)), + : mRect(TruncatedToInt(aRect)), mSpreadRadius(), mBlurRadius(CalculateBlurRadius(Point(aSigmaX, aSigmaY))), mStride(aStride), @@ -469,7 +589,7 @@ AlphaBoxBlur::Blur(uint8_t* aData) if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { // No need to use CheckedInt here - we have validated it in the constructor. size_t szB = stride * size.height; - unsigned char* tmpData = new (std::nothrow) uint8_t[szB]; + uint8_t* tmpData = new (std::nothrow) uint8_t[szB]; if (!tmpData) { return; @@ -477,8 +597,8 @@ AlphaBoxBlur::Blur(uint8_t* aData) memset(tmpData, 0, szB); - SpreadHorizontal(aData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect); - SpreadVertical(tmpData, aData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect); + SpreadHorizontal(aData, tmpData, mSpreadRadius.width, size.width, size.height, stride, mSkipRect); + SpreadVertical(tmpData, aData, mSpreadRadius.height, size.width, size.height, stride, mSkipRect); delete [] tmpData; } @@ -497,39 +617,12 @@ AlphaBoxBlur::Blur(uint8_t* aData) if ((integralImageSize.width * integralImageSize.height) > (1 << 24)) { // Fallback to old blurring code when the surface is so large it may // overflow our integral image! - - // No need to use CheckedInt here - we have validated it in the constructor. - size_t szB = stride * size.height; - uint8_t* tmpData = new (std::nothrow) uint8_t[szB]; - if (!tmpData) { - return; - } - - memset(tmpData, 0, szB); - - uint8_t* a = aData; - uint8_t* b = tmpData; if (mBlurRadius.width > 0) { - BoxBlurHorizontal(a, b, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(b, a, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(a, b, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect); - } else { - a = tmpData; - b = aData; + BoxBlur<false>(aData, horizontalLobes, size.width, size.height, stride, mSkipRect); } - // The result is in 'b' here. if (mBlurRadius.height > 0) { - BoxBlurVertical(b, a, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(a, b, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(b, a, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect); - } else { - a = b; - } - // The result is in 'a' here. - if (a == tmpData) { - memcpy(aData, tmpData, szB); + BoxBlur<true>(aData, verticalLobes, size.width, size.height, stride, mSkipRect); } - delete [] tmpData; } else { size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width, 4); if (integralImageStride == 0) { @@ -765,5 +858,11 @@ AlphaBoxBlur::CalculateBlurRadius(const Point& aStd) return size; } +Float +AlphaBoxBlur::CalculateBlurSigma(int32_t aBlurRadius) +{ + return aBlurRadius / GAUSSIAN_SCALE_FACTOR; +} + } // namespace gfx } // namespace mozilla diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index 6fffeb2390..0777e1ef1c 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -68,6 +68,14 @@ public: float aSigmaX, float aSigmaY); + AlphaBoxBlur(); + + void Init(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect); + ~AlphaBoxBlur(); /** @@ -92,6 +100,16 @@ public: Rect* GetDirtyRect(); /** + * Return the spread radius, in pixels. + */ + IntSize GetSpreadRadius() const { return mSpreadRadius; } + + /** + * Return the blur radius, in pixels. + */ + IntSize GetBlurRadius() const { return mBlurRadius; } + + /** * Return the minimum buffer size that should be given to Blur() method. If * zero, the class is not properly setup for blurring. Note that this * includes the extra three bytes on top of the stride*width, where something @@ -114,6 +132,7 @@ public: * constructor, above. */ static IntSize CalculateBlurRadius(const Point& aStandardDeviation); + static Float CalculateBlurSigma(int32_t aBlurRadius); private: diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp index c0e4f0af26..ad22a9a456 100644 --- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -931,16 +931,18 @@ DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface, if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) { blursurf = cairo_tee_surface_index(sourcesurf, 0); surf = cairo_tee_surface_index(sourcesurf, 1); + } else { + blursurf = sourcesurf; + surf = sourcesurf; + } + if (aSigma != 0.0f) { MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE); Rect extents(0, 0, width, height); AlphaBoxBlur blur(extents, cairo_image_surface_get_stride(blursurf), aSigma, aSigma); blur.Blur(cairo_image_surface_get_data(blursurf)); - } else { - blursurf = sourcesurf; - surf = sourcesurf; } WillChange(); @@ -951,25 +953,24 @@ DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface, cairo_identity_matrix(mContext); cairo_translate(mContext, aDest.x, aDest.y); - if (IsOperatorBoundByMask(aOperator)){ - cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); - cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + bool needsGroup = !IsOperatorBoundByMask(aOperator); + if (needsGroup) { + cairo_push_group(mContext); + } + + cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); + cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + if (blursurf != surf || + aSurface->GetFormat() != SurfaceFormat::A8) { // Now that the shadow has been drawn, we can draw the surface on top. cairo_set_source_surface(mContext, surf, 0, 0); cairo_new_path(mContext); cairo_rectangle(mContext, 0, 0, width, height); cairo_fill(mContext); - } else { - cairo_push_group(mContext); - cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); - cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + } - // Now that the shadow has been drawn, we can draw the surface on top. - cairo_set_source_surface(mContext, surf, 0, 0); - cairo_new_path(mContext); - cairo_rectangle(mContext, 0, 0, width, height); - cairo_fill(mContext); + if (needsGroup) { cairo_pop_group_to_source(mContext); cairo_paint(mContext); } @@ -1924,7 +1925,7 @@ DrawTargetCairo::CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFor // If we don't have a blur then we can use the RGBA mask and keep all the // operations in graphics memory. - if (aSigma == 0.0F) { + if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) { RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); if (target->InitAlreadyReferenced(similar, aSize)) { return target.forget(); diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp index a2e8541078..1e1f49ba47 100644 --- a/gfx/2d/DrawTargetD2D1.cpp +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -244,21 +244,29 @@ DrawTargetD2D1::DrawSurfaceWithShadow(SourceSurface *aSurface, // Step 1, create the shadow effect. RefPtr<ID2D1Effect> shadowEffect; - HRESULT hr = mDC->CreateEffect(CLSID_D2D1Shadow, getter_AddRefs(shadowEffect)); + HRESULT hr = mDC->CreateEffect(mFormat == SurfaceFormat::A8 ? CLSID_D2D1GaussianBlur : CLSID_D2D1Shadow, + getter_AddRefs(shadowEffect)); if (FAILED(hr) || !shadowEffect) { gfxWarning() << "Failed to create shadow effect. Code: " << hexa(hr); return; } shadowEffect->SetInput(0, image); - shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, aSigma); - D2D1_VECTOR_4F color = { aColor.r, aColor.g, aColor.b, aColor.a }; - shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color); + if (mFormat == SurfaceFormat::A8) { + shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, aSigma); + shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD); + } else { + shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, aSigma); + D2D1_VECTOR_4F color = { aColor.r, aColor.g, aColor.b, aColor.a }; + shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color); + } D2D1_POINT_2F shadowPoint = D2DPoint(aDest + aOffset); mDC->DrawImage(shadowEffect, &shadowPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); - D2D1_POINT_2F imgPoint = D2DPoint(aDest); - mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); + if (aSurface->GetFormat() != SurfaceFormat::A8) { + D2D1_POINT_2F imgPoint = D2DPoint(aDest); + mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); + } } void diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp index 7e71e54ccf..130623658e 100644 --- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -713,9 +713,11 @@ DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface, mCanvas->drawImage(image, shadowDest.x, shadowDest.y, &shadowPaint); } - // Composite the original image after the shadow - auto dest = IntPoint::Round(aDest); - mCanvas->drawImage(image, dest.x, dest.y, &paint); + if (aSurface->GetFormat() != SurfaceFormat::A8) { + // Composite the original image after the shadow + auto dest = IntPoint::Round(aDest); + mCanvas->drawImage(image, dest.x, dest.y, &paint); + } mCanvas->restore(); } diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h index 48aec2cb5e..455b158671 100644 --- a/gfx/2d/HelpersD2D.h +++ b/gfx/2d/HelpersD2D.h @@ -595,6 +595,8 @@ CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestin // // + int Bpp = BytesPerPixel(aSurface->GetFormat()); + if (uploadRect.Contains(rect)) { // Extend mode is irrelevant, the displayed rect is completely contained // by the source bitmap. @@ -631,7 +633,7 @@ CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestin // A partial upload will suffice. aRT->CreateBitmap(D2D1::SizeU(uint32_t(uploadRect.width), uint32_t(uploadRect.height)), - mapping.GetData() + int(uploadRect.x) * 4 + int(uploadRect.y) * mapping.GetStride(), + mapping.GetData() + int(uploadRect.x) * Bpp + int(uploadRect.y) * mapping.GetStride(), mapping.GetStride(), D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), getter_AddRefs(bitmap)); @@ -641,8 +643,6 @@ CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestin return bitmap.forget(); } else { - int Bpp = BytesPerPixel(aSurface->GetFormat()); - if (Bpp != 4) { // This shouldn't actually happen in practice! MOZ_ASSERT(false); diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h index 553a886b93..6b7135db1b 100644 --- a/gfx/2d/PathHelpers.h +++ b/gfx/2d/PathHelpers.h @@ -224,6 +224,15 @@ struct RectCornerRadii { return true; } + bool AreRadiiSame() const { + for (size_t i = 1; i < RectCorner::Count; i++) { + if (radii[i] != radii[0]) { + return false; + } + } + return true; + } + void Scale(Float aXScale, Float aYScale) { for (int i = 0; i < RectCorner::Count; i++) { radii[i].Scale(aXScale, aYScale); diff --git a/gfx/2d/Point.h b/gfx/2d/Point.h index f66967622f..96b2d8d5cf 100644 --- a/gfx/2d/Point.h +++ b/gfx/2d/Point.h @@ -285,7 +285,7 @@ typedef IntSizeTyped<UnknownUnits> IntSize; template<class units, class F = Float> struct SizeTyped : - public BaseSize< F, SizeTyped<units> >, + public BaseSize< F, SizeTyped<units, F> >, public units { static_assert(IsPixel<units>::value, "'units' must be a coordinate system tag"); diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp index d5878e1619..63e261a9ec 100644 --- a/gfx/thebes/gfxBlur.cpp +++ b/gfx/thebes/gfxBlur.cpp @@ -11,8 +11,7 @@ #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Blur.h" #include "mozilla/gfx/PathHelpers.h" -#include "mozilla/UniquePtr.h" -#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Maybe.h" #include "nsExpirationTracker.h" #include "nsClassHashtable.h" #include "gfxUtils.h" @@ -21,138 +20,189 @@ using namespace mozilla; using namespace mozilla::gfx; gfxAlphaBoxBlur::gfxAlphaBoxBlur() + : mData(nullptr), + mAccelerated(false) { } gfxAlphaBoxBlur::~gfxAlphaBoxBlur() { - mContext = nullptr; + if (mData) { + free(mData); + } } -gfxContext* -gfxAlphaBoxBlur::Init(const gfxRect& aRect, +already_AddRefed<gfxContext> +gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, const IntSize& aSpreadRadius, const IntSize& aBlurRadius, const gfxRect* aDirtyRect, const gfxRect* aSkipRect) { - mozilla::gfx::Rect rect(Float(aRect.x), Float(aRect.y), - Float(aRect.width), Float(aRect.height)); - IntSize spreadRadius(aSpreadRadius.width, aSpreadRadius.height); - IntSize blurRadius(aBlurRadius.width, aBlurRadius.height); - UniquePtr<Rect> dirtyRect; - if (aDirtyRect) { - dirtyRect = MakeUnique<Rect>(Float(aDirtyRect->x), - Float(aDirtyRect->y), - Float(aDirtyRect->width), - Float(aDirtyRect->height)); - } - UniquePtr<Rect> skipRect; - if (aSkipRect) { - skipRect = MakeUnique<Rect>(Float(aSkipRect->x), - Float(aSkipRect->y), - Float(aSkipRect->width), - Float(aSkipRect->height)); - } + DrawTarget* refDT = aDestinationCtx->GetDrawTarget(); + Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing(); + Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing(); + RefPtr<DrawTarget> dt = + InitDrawTarget(refDT, ToRect(aRect), aSpreadRadius, aBlurRadius, + dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr)); + if (!dt) { + return nullptr; + } - mBlur = MakeUnique<AlphaBoxBlur>(rect, spreadRadius, blurRadius, dirtyRect.get(), skipRect.get()); - size_t blurDataSize = mBlur->GetSurfaceAllocationSize(); - if (blurDataSize == 0) - return nullptr; + RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); // already checked for target above + context->SetMatrix(gfxMatrix::Translation(-mBlur.GetRect().TopLeft())); + return context.forget(); +} - IntSize size = mBlur->GetSize(); +already_AddRefed<DrawTarget> +gfxAlphaBoxBlur::InitDrawTarget(const DrawTarget* aReferenceDT, + const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect) +{ + mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); + size_t blurDataSize = mBlur.GetSurfaceAllocationSize(); + if (blurDataSize == 0) { + return nullptr; + } + BackendType backend = aReferenceDT->GetBackendType(); + + // Check if the backend has an accelerated DrawSurfaceWithShadow. + // Currently, only D2D1.1 supports this. + // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread. + // When blurring small draw targets such as short spans of text, the cost of + // creating and flushing an accelerated draw target exceeds the gains from + // the faster HWA blur, so we also make sure the blurred data exceeds + // a sufficient number of pixels before HWA kicks in to offset this cost. + if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && + blurDataSize >= 8192 && + backend == BackendType::DIRECT2D1_1) { + mAccelerated = true; + mDrawTarget = + aReferenceDT->CreateShadowDrawTarget(mBlur.GetSize(), + SurfaceFormat::A8, + AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width)); + } else { // Make an alpha-only surface to draw on. We will play with the data after // everything is drawn to create a blur effect. - mData = MakeUniqueFallible<unsigned char[]>(blurDataSize); + mData = static_cast<uint8_t*>(calloc(1, blurDataSize)); if (!mData) { - return nullptr; + return nullptr; } - memset(mData.get(), 0, blurDataSize); - - RefPtr<DrawTarget> dt = - gfxPlatform::CreateDrawTargetForData(mData.get(), size, - mBlur->GetStride(), + mDrawTarget = + Factory::DoesBackendSupportDataDrawtarget(backend) ? + Factory::CreateDrawTargetForData(backend, + mData, + mBlur.GetSize(), + mBlur.GetStride(), + SurfaceFormat::A8) : + gfxPlatform::CreateDrawTargetForData(mData, + mBlur.GetSize(), + mBlur.GetStride(), SurfaceFormat::A8); - if (!dt || !dt->IsValid()) { - return nullptr; - } - - IntRect irect = mBlur->GetRect(); - gfxPoint topleft(irect.TopLeft().x, irect.TopLeft().y); - - mContext = gfxContext::CreateOrNull(dt); - MOZ_ASSERT(mContext); // already checked for target above - mContext->SetMatrix(gfxMatrix::Translation(-topleft)); + } - return mContext; + if (!mDrawTarget || !mDrawTarget->IsValid()) { + return nullptr; + } + mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return do_AddRef(mDrawTarget); } -void -DrawBlur(gfxContext* aDestinationCtx, - SourceSurface* aBlur, - const IntPoint& aTopLeft, - const Rect* aDirtyRect) +already_AddRefed<SourceSurface> +gfxAlphaBoxBlur::DoBlur(const Color* aShadowColor, IntPoint* aOutTopLeft) { - DrawTarget *dest = aDestinationCtx->GetDrawTarget(); - - RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern(); - Pattern* pat = thebesPat->GetPattern(dest, nullptr); - - Matrix oldTransform = dest->GetTransform(); - Matrix newTransform = oldTransform; - newTransform.PreTranslate(aTopLeft.x, aTopLeft.y); - - // Avoid a semi-expensive clip operation if we can, otherwise - // clip to the dirty rect - if (aDirtyRect) { - dest->PushClipRect(*aDirtyRect); - } + if (mData) { + mBlur.Blur(mData); + } - dest->SetTransform(newTransform); - dest->MaskSurface(*pat, aBlur, Point(0, 0)); - dest->SetTransform(oldTransform); + if (aOutTopLeft) { + *aOutTopLeft = mBlur.GetRect().TopLeft(); + } - if (aDirtyRect) { - dest->PopClip(); + RefPtr<SourceSurface> blurMask = mDrawTarget->Snapshot(); + if (mAccelerated) { + RefPtr<DrawTarget> blurDT = + Factory::CreateDrawTarget(mDrawTarget->GetBackendType(), + blurMask->GetSize(), + SurfaceFormat::A8); + if (!blurDT) { + return nullptr; } -} + blurDT->DrawSurfaceWithShadow(blurMask, Point(0, 0), Color(1, 1, 1), Point(0, 0), + AlphaBoxBlur::CalculateBlurSigma(mBlur.GetBlurRadius().width), + CompositionOp::OP_OVER); + blurMask = blurDT->Snapshot(); + } -already_AddRefed<SourceSurface> -gfxAlphaBoxBlur::DoBlur(DrawTarget* aDT, IntPoint* aTopLeft) -{ - mBlur->Blur(mData.get()); + if (!aShadowColor) { + return blurMask.forget(); + } - *aTopLeft = mBlur->GetRect().TopLeft(); + RefPtr<DrawTarget> shadowDT = + Factory::CreateDrawTarget(mDrawTarget->GetBackendType(), + blurMask->GetSize(), + SurfaceFormat::B8G8R8A8); + if (!shadowDT) { + return nullptr; + } + ColorPattern shadowColor(ToDeviceColor(*aShadowColor)); + shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0)); - return aDT->CreateSourceSurfaceFromData(mData.get(), - mBlur->GetSize(), - mBlur->GetStride(), - SurfaceFormat::A8); + return shadowDT->Snapshot(); } void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) { - if (!mContext) - return; + if (!mAccelerated && !mData) { + return; + } - DrawTarget *dest = aDestinationCtx->GetDrawTarget(); - if (!dest) { - NS_WARNING("Blurring not supported for Thebes contexts!"); - return; - } + DrawTarget *dest = aDestinationCtx->GetDrawTarget(); + if (!dest) { + NS_WARNING("Blurring not supported for Thebes contexts!"); + return; + } - Rect* dirtyRect = mBlur->GetDirtyRect(); + RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern(); + Pattern* pat = thebesPat->GetPattern(dest, nullptr); + if (!pat) { + NS_WARNING("Failed to get pattern for blur!"); + return; + } - IntPoint topLeft; - RefPtr<SourceSurface> mask = DoBlur(dest, &topLeft); - if (!mask) { - NS_ERROR("Failed to create mask!"); - return; - } + IntPoint topLeft; + RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft); + if (!mask) { + NS_ERROR("Failed to create mask!"); + return; + } + + // Avoid a semi-expensive clip operation if we can, otherwise + // clip to the dirty rect + Rect* dirtyRect = mBlur.GetDirtyRect(); + if (dirtyRect) { + dest->PushClipRect(*dirtyRect); + } + + Matrix oldTransform = dest->GetTransform(); + Matrix newTransform = oldTransform; + newTransform.PreTranslate(topLeft); + dest->SetTransform(newTransform); + + dest->MaskSurface(*pat, mask, Point(0, 0)); - DrawBlur(aDestinationCtx, mask, topLeft, dirtyRect); + dest->SetTransform(oldTransform); + + if (dirtyRect) { + dest->PopClip(); + } } IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd) @@ -175,16 +225,15 @@ struct BlurCacheKey : public PLDHashEntryHdr { bool mIsInset; // Only used for inset blurs - bool mHasBorderRadius; IntSize mInnerMinSize; - BlurCacheKey(IntSize aMinSize, IntSize aBlurRadius, - RectCornerRadii* aCornerRadii, const Color& aShadowColor, + BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, BackendType aBackendType) : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii, aShadowColor, false, - false, aBackendType) + aBackendType) {} explicit BlurCacheKey(const BlurCacheKey* aOther) @@ -194,22 +243,20 @@ struct BlurCacheKey : public PLDHashEntryHdr { , mBackend(aOther->mBackend) , mCornerRadii(aOther->mCornerRadii) , mIsInset(aOther->mIsInset) - , mHasBorderRadius(aOther->mHasBorderRadius) , mInnerMinSize(aOther->mInnerMinSize) { } - explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize, - IntSize aBlurRadius, + explicit BlurCacheKey(const IntSize& aOuterMinSize, const IntSize& aInnerMinSize, + const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, bool aIsInset, - bool aHasBorderRadius, BackendType aBackendType) + BackendType aBackendType) : mMinSize(aOuterMinSize) , mBlurRadius(aBlurRadius) , mShadowColor(aShadowColor) , mBackend(aBackendType) , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()) , mIsInset(aIsInset) - , mHasBorderRadius(aHasBorderRadius) , mInnerMinSize(aInnerMinSize) { } @@ -237,7 +284,6 @@ struct BlurCacheKey : public PLDHashEntryHdr { if (aKey->mIsInset) { hash = AddToHash(hash, aKey->mInnerMinSize.width, aKey->mInnerMinSize.height); - hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool))); } return hash; } @@ -252,8 +298,7 @@ struct BlurCacheKey : public PLDHashEntryHdr { aKey->mBackend == mBackend) { if (mIsInset) { - return (mHasBorderRadius == aKey->mHasBorderRadius) && - (mInnerMinSize == aKey->mInnerMinSize); + return (mInnerMinSize == aKey->mInnerMinSize); } return true; @@ -274,15 +319,15 @@ struct BlurCacheKey : public PLDHashEntryHdr { * to the cache entry to be able to be tracked by the nsExpirationTracker. * */ struct BlurCacheData { - BlurCacheData(SourceSurface* aBlur, IntMargin aExtendDestBy, const BlurCacheKey& aKey) + BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin, const BlurCacheKey& aKey) : mBlur(aBlur) - , mExtendDest(aExtendDestBy) + , mBlurMargin(aBlurMargin) , mKey(aKey) {} BlurCacheData(const BlurCacheData& aOther) : mBlur(aOther.mBlur) - , mExtendDest(aOther.mExtendDest) + , mBlurMargin(aOther.mBlurMargin) , mKey(aOther.mKey) { } @@ -292,7 +337,7 @@ struct BlurCacheData { nsExpirationState mExpirationState; RefPtr<SourceSurface> mBlur; - IntMargin mExtendDest; + IntMargin mBlurMargin; BlurCacheKey mKey; }; @@ -316,9 +361,9 @@ class BlurCache final : public nsExpirationTracker<BlurCacheData,4> mHashEntries.Remove(aObject->mKey); } - BlurCacheData* Lookup(const IntSize aMinSize, + BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, BackendType aBackendType) { @@ -333,19 +378,18 @@ class BlurCache final : public nsExpirationTracker<BlurCacheData,4> return blur; } - BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize, - const IntSize aInnerMinSize, + BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize, + const IntSize& aInnerMinSize, const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - const bool& aHasBorderRadius, BackendType aBackendType) { bool insetBoxShadow = true; BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii, aShadowColor, insetBoxShadow, - aHasBorderRadius, aBackendType); + aBackendType); BlurCacheData* blur = mHashEntries.Get(key); if (blur) { MarkUsed(blur); @@ -383,28 +427,26 @@ class BlurCache final : public nsExpirationTracker<BlurCacheData,4> static BlurCache* gBlurCache = nullptr; static IntSize -ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii, - IntSize aBlurRadius, - IntMargin& aSlice, +ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + IntMargin& aOutSlice, const IntSize& aRectSize) { - float cornerWidth = 0; - float cornerHeight = 0; + Size cornerSize(0, 0); if (aCornerRadii) { - RectCornerRadii corners = *aCornerRadii; - for (size_t i = 0; i < 4; i++) { - cornerWidth = std::max(cornerWidth, corners[i].width); - cornerHeight = std::max(cornerHeight, corners[i].height); + const RectCornerRadii& corners = *aCornerRadii; + for (size_t i = 0; i < RectCorner::Count; i++) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); } } - aSlice = IntMargin(ceil(cornerHeight) + aBlurRadius.height, - ceil(cornerWidth) + aBlurRadius.width, - ceil(cornerHeight) + aBlurRadius.height, - ceil(cornerWidth) + aBlurRadius.width); + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; + aOutSlice = IntMargin(margin.height, margin.width, + margin.height, margin.width); - IntSize minSize(aSlice.LeftRight() + 1, - aSlice.TopBottom() + 1); + IntSize minSize(aOutSlice.LeftRight() + 1, + aOutSlice.TopBottom() + 1); // If aRectSize is smaller than minSize, the border-image approach won't // work; there's no way to squeeze parts of the min box-shadow source @@ -414,113 +456,103 @@ ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii, // to slice away more than we have. if (aRectSize.width < minSize.width) { minSize.width = aRectSize.width; - aSlice.left = 0; - aSlice.right = 0; + aOutSlice.left = 0; + aOutSlice.right = 0; } if (aRectSize.height < minSize.height) { minSize.height = aRectSize.height; - aSlice.top = 0; - aSlice.bottom = 0; + aOutSlice.top = 0; + aOutSlice.bottom = 0; } - MOZ_ASSERT(aSlice.LeftRight() <= minSize.width); - MOZ_ASSERT(aSlice.TopBottom() <= minSize.height); + MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width); + MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height); return minSize; } void -CacheBlur(DrawTarget& aDT, +CacheBlur(DrawTarget* aDT, const IntSize& aMinSize, const IntSize& aBlurRadius, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - IntMargin aExtendDest, + const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) { - BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT.GetBackendType()); - BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendDest, key); + BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT->GetBackendType()); + BlurCacheData* data = new BlurCacheData(aBoxShadow, aBlurMargin, key); if (!gBlurCache->RegisterEntry(data)) { delete data; } } -// Blurs a small surface and creates the mask. +// Blurs a small surface and creates the colored box shadow. static already_AddRefed<SourceSurface> -CreateBlurMask(const IntSize& aMinSize, - RectCornerRadii* aCornerRadii, - IntSize aBlurRadius, - IntMargin& aExtendDestBy, - IntMargin& aSliceBorder, - DrawTarget& aDestDrawTarget) +CreateBoxShadow(DrawTarget* aDestDrawTarget, + const IntSize& aMinSize, + const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + const Color& aShadowColor, + bool aMirrorCorners, + IntMargin& aOutBlurMargin) { gfxAlphaBoxBlur blur; - IntRect minRect(IntPoint(), aMinSize); - - gfxContext* blurCtx = blur.Init(ThebesRect(Rect(minRect)), IntSize(), - aBlurRadius, nullptr, nullptr); - - if (!blurCtx) { + Rect minRect(Point(0, 0), Size(aMinSize)); + Rect blurRect(minRect); + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.width * 0.5f), ceil(blurRect.height * 0.5f)); + } + IntSize zeroSpread(0, 0); + RefPtr<DrawTarget> blurDT = + blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); + if (!blurDT) { return nullptr; } - DrawTarget* blurDT = blurCtx->GetDrawTarget(); ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); if (aCornerRadii) { RefPtr<Path> roundedRect = - MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii); + MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii); blurDT->Fill(roundedRect, black); } else { - blurDT->FillRect(Rect(minRect), black); + blurDT->FillRect(minRect, black); } IntPoint topLeft; - RefPtr<SourceSurface> result = blur.DoBlur(&aDestDrawTarget, &topLeft); + RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft); if (!result) { return nullptr; } - IntRect expandedMinRect(topLeft, result->GetSize()); - aExtendDestBy = expandedMinRect - minRect; - aSliceBorder += aExtendDestBy; - - MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width); - MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height); + // Since blurRect is at (0, 0), we can find the inflated margin by + // negating the new rect origin, which would have been negative if + // the rect was inflated. + aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x); return result.forget(); } static already_AddRefed<SourceSurface> -CreateBoxShadow(DrawTarget& aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor) -{ - IntSize blurredSize = aBlurMask->GetSize(); - RefPtr<DrawTarget> boxShadowDT = - Factory::CreateDrawTarget(aDestDT.GetBackendType(), blurredSize, SurfaceFormat::B8G8R8A8); - - if (!boxShadowDT) { - return nullptr; - } - - ColorPattern shadowColor(ToDeviceColor(aShadowColor)); - boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0)); - return boxShadowDT->Snapshot(); -} - -static already_AddRefed<SourceSurface> GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, const IntSize& aBlurRadius, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - IntMargin& aExtendDestBy, - IntMargin& aSlice) + bool aMirrorCorners, + IntMargin& aOutBlurMargin, + IntMargin& aOutSlice, + IntSize& aOutMinSize) { if (!gBlurCache) { gBlurCache = new BlurCache(); } IntSize minSize = - ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aSlice, aRectSize); + ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aOutSlice, aRectSize); // We can get seams using the min size rect when drawing to the destination rect // if we have a non-pixel aligned destination transformation. In those cases, @@ -530,39 +562,32 @@ GetBlur(gfxContext* aDestinationCtx, if (useDestRect) { minSize = aRectSize; } - - DrawTarget& destDT = *aDestinationCtx->GetDrawTarget(); - - BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, - aCornerRadii, aShadowColor, - destDT.GetBackendType()); - if (cached && !useDestRect) { - // See CreateBlurMask() for these values - aExtendDestBy = cached->mExtendDest; - aSlice = aSlice + aExtendDestBy; - RefPtr<SourceSurface> blur = cached->mBlur; - return blur.forget(); - } - - RefPtr<SourceSurface> blurMask = - CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, - destDT); - - if (!blurMask) { - return nullptr; + aOutMinSize = minSize; + + DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); + + if (!useDestRect) { + BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, + aCornerRadii, aShadowColor, + destDT->GetBackendType()); + if (cached) { + // See CreateBoxShadow() for these values + aOutBlurMargin = cached->mBlurMargin; + RefPtr<SourceSurface> blur = cached->mBlur; + return blur.forget(); + } } - RefPtr<SourceSurface> boxShadow = CreateBoxShadow(destDT, blurMask, aShadowColor); + RefPtr<SourceSurface> boxShadow = + CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, + aShadowColor, aMirrorCorners, aOutBlurMargin); if (!boxShadow) { return nullptr; } - if (useDestRect) { - // Since we're just going to paint the actual rect to the destination - aSlice.SizeTo(0, 0, 0, 0); - } else { + if (!useDestRect) { CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, - aExtendDestBy, boxShadow); + aOutBlurMargin, boxShadow); } return boxShadow.forget(); } @@ -580,49 +605,56 @@ RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, Float aLeft) return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); } +static bool +ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) +{ + // Use stretching if possible, since it leads to less seams when the + // destination is transformed. However, don't do this if we're using cairo, + // because if cairo is using pixman it won't render anything for large + // stretch factors because pixman's internal fixed point precision is not + // high enough to handle those scale factors. + // Calling FillRect on a D2D backend with a repeating pattern is much slower + // than DrawSurface, so special case the D2D backend here. + return (!aDT->GetTransform().IsRectilinear() && + aDT->GetBackendType() != BackendType::CAIRO) || + (aDT->GetBackendType() == BackendType::DIRECT2D1_1); +} + static void -RepeatOrStretchSurface(DrawTarget& aDT, SourceSurface* aSurface, - const Rect& aDest, const Rect& aSrc, Rect& aSkipRect) +RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, const Rect& aSkipRect) { if (aSkipRect.Contains(aDest)) { return; } - if ((!aDT.GetTransform().IsRectilinear() && - aDT.GetBackendType() != BackendType::CAIRO) || - (aDT.GetBackendType() == BackendType::DIRECT2D1_1)) { - // Use stretching if possible, since it leads to less seams when the - // destination is transformed. However, don't do this if we're using cairo, - // because if cairo is using pixman it won't render anything for large - // stretch factors because pixman's internal fixed point precision is not - // high enough to handle those scale factors. - // Calling FillRect on a D2D backend with a repeating pattern is much slower - // than DrawSurface, so special case the D2D backend here. - aDT.DrawSurface(aSurface, aDest, aSrc); + if (ShouldStretchSurface(aDT, aSurface)) { + aDT->DrawSurface(aSurface, aDest, aSrc); return; } SurfacePattern pattern(aSurface, ExtendMode::REPEAT, Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()), SamplingFilter::GOOD, RoundedToInt(aSrc)); - aDT.FillRect(aDest, pattern); + aDT->FillRect(aDest, pattern); } static void -DrawCorner(DrawTarget& aDT, SourceSurface* aSurface, - const Rect& aDest, const Rect& aSrc, Rect& aSkipRect) +DrawCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, const Rect& aSkipRect) { if (aSkipRect.Contains(aDest)) { return; } - aDT.DrawSurface(aSurface, aDest, aSrc); + aDT->DrawSurface(aSurface, aDest, aSrc); } static void -DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur, - Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner, - Rect aSkipRect) +DrawMinBoxShadow(DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, + const Rect& aDstOuter, const Rect& aDstInner, + const Rect& aSrcOuter, const Rect& aSrcInner, + const Rect& aSkipRect, bool aMiddle = false) { // Corners: top left, top right, bottom left, bottom right DrawCorner(aDestDrawTarget, aSourceBlur, @@ -679,6 +711,192 @@ DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur, RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(), aSrcOuter.YMost(), aSrcInner.X()), aSkipRect); + + // Middle part + if (aMiddle) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } +} + +static void +DrawMirroredRect(DrawTarget* aDT, + SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + Float aScaleX, Float aScaleY) +{ + SurfacePattern pattern(aSurface, ExtendMode::CLAMP, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc) + .PostTranslate( + aScaleX < 0 ? aDest.XMost() : aDest.x, + aScaleY < 0 ? aDest.YMost() : aDest.y)); + aDT->FillRect(aDest, pattern); +} + +static void +DrawMirroredBoxShadow(DrawTarget* aDT, + SourceSurface* aSurface, + const Rect& aDestRect) +{ + Point center(ceil(aDestRect.x + aDestRect.width / 2), + ceil(aDestRect.y + aDestRect.height / 2)); + Rect topLeft(aDestRect.x, aDestRect.y, + center.x - aDestRect.x, + center.y - aDestRect.y); + Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size()); + Rect topRight(bottomRight.x, topLeft.y, bottomRight.width, topLeft.height); + Rect bottomLeft(topLeft.x, bottomRight.y, topLeft.width, bottomRight.height); + DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1); + DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1); + DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1); + DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1); +} + +static void +DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + const Rect& aSkipRect, Float aScaleX, Float aScaleY) +{ + if (aSkipRect.Contains(aDest)) { + return; + } + + DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY); +} + +static void +RepeatOrStretchMirroredSurface(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect, Float aScaleX, Float aScaleY) +{ + if (aSkipRect.Contains(aDest)) { + return; + } + + if (ShouldStretchSurface(aDT, aSurface)) { + aScaleX *= aDest.width / aSrc.width; + aScaleY *= aDest.height / aSrc.height; + DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY); + return; + } + + SurfacePattern pattern(aSurface, ExtendMode::REPEAT, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc.TopLeft()) + .PostTranslate( + aScaleX < 0 ? aDest.XMost() : aDest.x, + aScaleY < 0 ? aDest.YMost() : aDest.y), + SamplingFilter::GOOD, RoundedToInt(aSrc)); + aDT->FillRect(aDest, pattern); +} + +static void +DrawMirroredMinBoxShadow(DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, + const Rect& aDstOuter, const Rect& aDstInner, + const Rect& aSrcOuter, const Rect& aSrcInner, + const Rect& aSkipRect, bool aMiddle = false) +{ + // Corners: top left, top right, bottom left, bottom right + // Compute quadrant bounds and then clip them to corners along + // dimensions where we need to stretch from min size. + Point center(ceil(aDstOuter.x + aDstOuter.width / 2), + ceil(aDstOuter.y + aDstOuter.height / 2)); + Rect topLeft(aDstOuter.x, aDstOuter.y, + center.x - aDstOuter.x, + center.y - aDstOuter.y); + Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size()); + Rect topRight(bottomRight.x, topLeft.y, bottomRight.width, topLeft.height); + Rect bottomLeft(topLeft.x, bottomRight.y, topLeft.width, bottomRight.height); + + // Check if the middle part has been minimized along each dimension. + // If so, those will be strecthed/drawn separately and need to be clipped out. + if (aSrcInner.width == 1) { + topLeft.SetRightEdge(aDstInner.x); + topRight.SetLeftEdge(aDstInner.XMost()); + bottomLeft.SetRightEdge(aDstInner.x); + bottomRight.SetLeftEdge(aDstInner.XMost()); + } + if (aSrcInner.height == 1) { + topLeft.SetBottomEdge(aDstInner.y); + topRight.SetBottomEdge(aDstInner.y); + bottomLeft.SetTopEdge(aDstInner.YMost()); + bottomRight.SetTopEdge(aDstInner.YMost()); + } + + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, + aSrcOuter.TopLeft(), aSkipRect, 1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight, + aSrcOuter.TopLeft(), aSkipRect, -1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft, + aSrcOuter.TopLeft(), aSkipRect, 1, -1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight, + aSrcOuter.TopLeft(), aSkipRect, -1, -1); + + // Edges: top, bottom, left, right + // Draw middle edges where they need to be stretched. The top and left + // sections that are part of the top-left quadrant will be mirrored to + // the bottom and right sections, respectively. + if (aSrcInner.width == 1) { + Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), + aDstInner.Y(), aDstInner.X()); + Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), + aDstOuter.YMost(), aDstInner.X()); + Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + // If we only need to stretch along the X axis and we're drawing + // the middle section, just sample all the way to the center of the + // source on the Y axis to avoid extra draw calls. + if (aMiddle && aSrcInner.height != 1) { + dstTop.SetBottomEdge(center.y); + srcTop.height = dstTop.height; + dstBottom.SetTopEdge(dstTop.YMost()); + srcBottom.height = dstBottom.height; + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstTop, srcTop, aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstBottom, srcBottom, aSkipRect, 1, -1); + } + + if (aSrcInner.height == 1) { + Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), + aDstInner.YMost(), aDstOuter.X()); + Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), + aDstInner.YMost(), aDstInner.XMost()); + Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + // Only stretching on Y axis, so sample source to the center of the X axis. + if (aMiddle && aSrcInner.width != 1) { + dstLeft.SetRightEdge(center.x); + srcLeft.width = dstLeft.width; + dstRight.SetLeftEdge(dstLeft.XMost()); + srcRight.width = dstRight.width; + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstLeft, srcLeft, aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstRight, srcRight, aSkipRect, -1, 1); + } + + // If we need to stretch along both dimensions, then the middle part + // must be drawn separately. + if (aMiddle && aSrcInner.width == 1 && aSrcInner.height == 1) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } } /*** @@ -695,66 +913,69 @@ DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur, /* static */ void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, const gfxRect& aRect, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const gfxPoint& aBlurStdDev, const Color& aShadowColor, const gfxRect& aDirtyRect, const gfxRect& aSkipRect) { IntSize blurRadius = CalculateBlurRadius(aBlurStdDev); + bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame(); IntRect rect = RoundedToInt(ToRect(aRect)); - IntMargin extendDestBy; + IntMargin blurMargin; IntMargin slice; + IntSize minSize; RefPtr<SourceSurface> boxShadow = GetBlur(aDestinationCtx, rect.Size(), blurRadius, - aCornerRadii, aShadowColor, - extendDestBy, slice); + aCornerRadii, aShadowColor, mirrorCorners, + blurMargin, slice, minSize); if (!boxShadow) { return; } - DrawTarget& destDrawTarget = *aDestinationCtx->GetDrawTarget(); - destDrawTarget.PushClipRect(ToRect(aDirtyRect)); + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + destDrawTarget->PushClipRect(ToRect(aDirtyRect)); // Copy the right parts from boxShadow into destDrawTarget. The middle parts // will be stretched, border-image style. - Rect srcOuter(Point(), Size(boxShadow->GetSize())); - Rect srcInner = srcOuter; + Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize)); + Rect srcInner(srcOuter); + srcOuter.Inflate(Margin(blurMargin)); srcInner.Deflate(Margin(slice)); - rect.Inflate(extendDestBy); Rect dstOuter(rect); Rect dstInner(rect); + dstOuter.Inflate(Margin(blurMargin)); dstInner.Deflate(Margin(slice)); Rect skipRect = ToRect(aSkipRect); - if (srcInner.IsEqualInterior(srcOuter)) { - MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter)); + if (minSize == rect.Size()) { // The target rect is smaller than the minimal size so just draw the surface - destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner); + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter); + } else { + destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter); + } } else { - DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner, - srcOuter, srcInner, skipRect); - - // Middle part - RepeatOrStretchSurface(destDrawTarget, boxShadow, - RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(), - dstInner.YMost(), dstInner.X()), - RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(), - srcInner.YMost(), srcInner.X()), - skipRect); + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, + srcOuter, srcInner, skipRect, true); + } else { + DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, + srcOuter, srcInner, skipRect, true); + } } - // A note about anti-aliasing and seems between adjacent parts: + // A note about anti-aliasing and seams between adjacent parts: // We don't explicitly disable anti-aliasing in the DrawSurface calls above, // so if there's a transform on destDrawTarget that is not pixel-aligned, // there will be seams between adjacent parts of the box-shadow. It's hard to // avoid those without the use of an intermediate surface. - // You might think that we could avoid those by just turning of AA, but there + // You might think that we could avoid those by just turning off AA, but there // is a problem with that: Box-shadow rendering needs to clip out the // element's border box, and we'd like that clip to have anti-aliasing - // especially if the element has rounded corners! So we can't do that unless @@ -766,13 +987,13 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, // covered by the shape. So for pixels on the edge between two adjacent parts, // all those pixels will be painted to by both parts, which looks very bad. - destDrawTarget.PopClip(); + destDrawTarget->PopClip(); } static already_AddRefed<Path> GetBoxShadowInsetPath(DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect, - const bool aHasBorderRadius, const RectCornerRadii& aInnerClipRadii) + const RectCornerRadii* aInnerClipRadii) { /*** * We create an inset path by having two rects. @@ -791,8 +1012,8 @@ GetBoxShadowInsetPath(DrawTarget* aDrawTarget, aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD); AppendRectToPath(builder, aOuterRect, true); - if (aHasBorderRadius) { - AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false); + if (aInnerClipRadii) { + AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false); } else { AppendRectToPath(builder, aInnerRect, false); } @@ -801,31 +1022,28 @@ GetBoxShadowInsetPath(DrawTarget* aDrawTarget, static void FillDestinationPath(gfxContext* aDestinationCtx, - const Rect aDestinationRect, - const Rect aShadowClipRect, + const Rect& aDestinationRect, + const Rect& aShadowClipRect, const Color& aShadowColor, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii) + const RectCornerRadii* aInnerClipRadii = nullptr) { // When there is no blur radius, fill the path onto the destination // surface. aDestinationCtx->SetColor(aShadowColor); DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); RefPtr<Path> shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect, - aShadowClipRect, aHasBorderRadius, - aInnerClipRadii); + aShadowClipRect, aInnerClipRadii); aDestinationCtx->SetPath(shadowPath); aDestinationCtx->Fill(); } static void -CacheInsetBlur(const IntSize aMinOuterSize, - const IntSize aMinInnerSize, +CacheInsetBlur(const IntSize& aMinOuterSize, + const IntSize& aMinInnerSize, const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - const bool& aHasBorderRadius, BackendType aBackendType, SourceSurface* aBoxShadow) { @@ -833,95 +1051,87 @@ CacheInsetBlur(const IntSize aMinOuterSize, BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii, aShadowColor, isInsetBlur, - aHasBorderRadius, aBackendType); - IntMargin extendDestBy(0, 0, 0, 0); - BlurCacheData* data = new BlurCacheData(aBoxShadow, extendDestBy, key); + aBackendType); + IntMargin blurMargin(0, 0, 0, 0); + BlurCacheData* data = new BlurCacheData(aBoxShadow, blurMargin, key); if (!gBlurCache->RegisterEntry(data)) { delete data; } } -already_AddRefed<mozilla::gfx::SourceSurface> -gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect, - const mozilla::gfx::Rect aWhitespaceRect, - const bool aIsDestRect, - const mozilla::gfx::Color& aShadowColor, - const mozilla::gfx::IntSize& aBlurRadius, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - DrawTarget* aDestDrawTarget) +already_AddRefed<SourceSurface> +gfxAlphaBoxBlur::GetInsetBlur(const Rect& aOuterRect, + const Rect& aWhitespaceRect, + bool aIsDestRect, + const Color& aShadowColor, + const IntSize& aBlurRadius, + const RectCornerRadii* aInnerClipRadii, + DrawTarget* aDestDrawTarget, + bool aMirrorCorners) { if (!gBlurCache) { gBlurCache = new BlurCache(); } - IntSize outerSize((int)aOuterRect.width, (int)aOuterRect.height); - IntSize whitespaceSize((int)aWhitespaceRect.width, (int)aWhitespaceRect.height); - BlurCacheData* cached = + IntSize outerSize = IntSize::Truncate(aOuterRect.Size()); + IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size()); + if (!aIsDestRect) { + BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(outerSize, whitespaceSize, - aBlurRadius, &aInnerClipRadii, - aShadowColor, aHasBorderRadius, - aDestDrawTarget->GetBackendType()); - - if (cached && !aIsDestRect) { - // So we don't forget the actual cached blur - RefPtr<SourceSurface> cachedBlur = cached->mBlur; - return cachedBlur.forget(); + aBlurRadius, aInnerClipRadii, + aShadowColor, aDestDrawTarget->GetBackendType()); + if (cached) { + // So we don't forget the actual cached blur + RefPtr<SourceSurface> cachedBlur = cached->mBlur; + return cachedBlur.forget(); + } } // If we can do a min rect, the whitespace rect will be expanded in Init to // aOuterRect. Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect; + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.width * 0.5f), ceil(blurRect.height * 0.5f)); + } IntSize zeroSpread(0, 0); - gfxContext* minGfxContext = Init(ThebesRect(blurRect), - zeroSpread, aBlurRadius, - nullptr, nullptr); - if (!minGfxContext) { + RefPtr<DrawTarget> minDrawTarget = + InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); + if (!minDrawTarget) { return nullptr; } - // This is really annoying. When we create the AlphaBoxBlur, the gfxContext + // This is really annoying. When we create the AlphaBoxBlur, the DrawTarget // has a translation applied to it that is the topLeft point. This is actually // the rect we gave it plus the blur radius. The rects we give this for the outer // and whitespace rects are based at (0, 0). We could either translate those rects // when we don't have a destination rect or ignore the translation when using // the dest rect. The dest rects layout gives us expect this translation. if (!aIsDestRect) { - minGfxContext->SetMatrix(gfxMatrix()); + minDrawTarget->SetTransform(Matrix()); } - DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget(); - // Fill in the path between the inside white space / outer rects // NOT the inner frame RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, aOuterRect, - aWhitespaceRect, aHasBorderRadius, - aInnerClipRadii); + aWhitespaceRect, aInnerClipRadii); - Color black(0.f, 0.f, 0.f, 1.f); - minGfxContext->SetColor(black); - minGfxContext->SetPath(maskPath); - minGfxContext->Fill(); - - // Create the A8 mask - IntPoint topLeft; - RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &topLeft); - if (!minMask) { - return nullptr; - } + ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); + minDrawTarget->Fill(maskPath, black); - // Fill in with the color we actually wanted - RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(*aDestDrawTarget, minMask, aShadowColor); + // Blur and fill in with the color we actually wanted + RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor); if (!minInsetBlur) { return nullptr; } if (!aIsDestRect) { CacheInsetBlur(outerSize, whitespaceSize, - aBlurRadius, &aInnerClipRadii, - aShadowColor, aHasBorderRadius, - aDestDrawTarget->GetBackendType(), + aBlurRadius, aInnerClipRadii, + aShadowColor, aDestDrawTarget->GetBackendType(), minInsetBlur); } @@ -951,36 +1161,35 @@ gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect, * | | | * |________________________________| */ -static void GetBlurMargins(const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - const IntSize aBlurRadius, +static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii, + const IntSize& aBlurRadius, Margin& aOutBlurMargin, Margin& aOutInnerMargin) { - float cornerWidth = 0; - float cornerHeight = 0; - if (aHasBorderRadius) { - for (size_t i = 0; i < 4; i++) { - cornerWidth = std::max(cornerWidth, aInnerClipRadii[i].width); - cornerHeight = std::max(cornerHeight, aInnerClipRadii[i].height); + Size cornerSize(0, 0); + if (aInnerClipRadii) { + const RectCornerRadii& corners = *aInnerClipRadii; + for (size_t i = 0; i < RectCorner::Count; i++) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); } } // Only the inside whitespace size cares about the border radius size. // Outer sizes only care about blur. - int width = cornerWidth + aBlurRadius.width; - int height = cornerHeight + aBlurRadius.height; + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; - aOutInnerMargin.SizeTo(height, width, height, width); + aOutInnerMargin.SizeTo(margin.height, margin.width, + margin.height, margin.width); aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width, aBlurRadius.height, aBlurRadius.width); } static bool -GetInsetBoxShadowRects(const Margin aBlurMargin, - const Margin aInnerMargin, - const Rect aShadowClipRect, - const Rect aDestinationRect, +GetInsetBoxShadowRects(const Margin& aBlurMargin, + const Margin& aInnerMargin, + const Rect& aShadowClipRect, + const Rect& aDestinationRect, Rect& aOutWhitespaceRect, Rect& aOutOuterRect) { @@ -1015,20 +1224,18 @@ GetInsetBoxShadowRects(const Margin aBlurMargin, void gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, - const Rect aDestinationRect, - const Rect aShadowClipRect, - const IntSize aBlurRadius, - const IntSize aSpreadRadius, + const Rect& aDestinationRect, + const Rect& aShadowClipRect, + const IntSize& aBlurRadius, const Color& aShadowColor, - bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - const Rect aSkipRect, - const Point aShadowOffset) + const RectCornerRadii* aInnerClipRadii, + const Rect& aSkipRect, + const Point& aShadowOffset) { if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) || aShadowClipRect.IsEmpty()) { FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect, - aShadowColor, aHasBorderRadius, aInnerClipRadii); + aShadowColor, aInnerClipRadii); return; } @@ -1036,31 +1243,32 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, Margin innerMargin; Margin blurMargin; - GetBlurMargins(aHasBorderRadius, aInnerClipRadii, aBlurRadius, - blurMargin, innerMargin); + GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin); Rect whitespaceRect; Rect outerRect; - bool useDestRect = GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, - aDestinationRect, whitespaceRect, outerRect); - - RefPtr<SourceSurface> minBlur = GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, - aBlurRadius, aHasBorderRadius, aInnerClipRadii, - destDrawTarget); + bool useDestRect = + GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, + aDestinationRect, whitespaceRect, outerRect); + + bool mirrorCorners = !aInnerClipRadii || aInnerClipRadii->AreRadiiSame(); + RefPtr<SourceSurface> minBlur = + GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, + aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners); if (!minBlur) { return; } if (useDestRect) { - IntSize blurSize = minBlur->GetSize(); - Rect srcBlur(0, 0, blurSize.width, blurSize.height); Rect destBlur = aDestinationRect; - - // The blur itself expands the rect by the blur margin, so we - // have to mimic that here. destBlur.Inflate(blurMargin); - MOZ_ASSERT(srcBlur.Size() == destBlur.Size()); - destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur); + } else { + Rect srcBlur(Point(0, 0), Size(minBlur->GetSize())); + MOZ_ASSERT(srcBlur.Size() == destBlur.Size()); + destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); + } } else { Rect srcOuter(outerRect); Rect srcInner(srcOuter); @@ -1070,7 +1278,7 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, // The shadow clip rect already takes into account the spread radius Rect outerFillRect(aShadowClipRect); outerFillRect.Inflate(blurMargin); - FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, aShadowColor, false, RectCornerRadii()); + FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, aShadowColor); // Inflate once for the frame around the whitespace Rect destRect(aShadowClipRect); @@ -1080,9 +1288,16 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, Rect destInnerRect(aShadowClipRect); destInnerRect.Deflate(innerMargin); - DrawBoxShadows(*destDrawTarget, minBlur, - destRect, destInnerRect, - srcOuter, srcInner, - aSkipRect); + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, minBlur, + destRect, destInnerRect, + srcOuter, srcInner, + aSkipRect); + } else { + DrawMinBoxShadow(destDrawTarget, minBlur, + destRect, destInnerRect, + srcOuter, srcInner, + aSkipRect); + } } } diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h index 9cc5f37166..9ff5d14a34 100644 --- a/gfx/thebes/gfxBlur.h +++ b/gfx/thebes/gfxBlur.h @@ -10,14 +10,13 @@ #include "nsSize.h" #include "gfxPoint.h" #include "mozilla/RefPtr.h" -#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Blur.h" class gfxContext; struct gfxRect; namespace mozilla { namespace gfx { - class AlphaBoxBlur; struct Color; struct RectCornerRadii; class SourceSurface; @@ -57,6 +56,9 @@ public: /** * Constructs a box blur and initializes the temporary surface. + * + * @param aDestinationCtx The destination to blur to. + * * @param aRect The coordinates of the surface to create in device units. * * @param aBlurRadius The blur radius in pixels. This is the radius of @@ -73,24 +75,28 @@ public: * represents an area where blurring is unnecessary and shouldn't be done * for speed reasons. It is safe to pass nullptr here. */ - gfxContext* Init(const gfxRect& aRect, - const mozilla::gfx::IntSize& aSpreadRadius, - const mozilla::gfx::IntSize& aBlurRadius, - const gfxRect* aDirtyRect, - const gfxRect* aSkipRect); + already_AddRefed<gfxContext> + Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const gfxRect* aDirtyRect, + const gfxRect* aSkipRect); + + already_AddRefed<DrawTarget> + InitDrawTarget(const mozilla::gfx::DrawTarget* aReferenceDT, + const mozilla::gfx::Rect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const mozilla::gfx::Rect* aDirtyRect = nullptr, + const mozilla::gfx::Rect* aSkipRect = nullptr); /** - * Returns the context that should be drawn to supply the alpha mask to be - * blurred. If the returned surface is null, then there was an error in - * its creation. + * Performs the blur and optionally colors the result if aShadowColor is not null. */ - gfxContext* GetContext() - { - return mContext; - } - already_AddRefed<mozilla::gfx::SourceSurface> - DoBlur(DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft); + DoBlur(const mozilla::gfx::Color* aShadowColor = nullptr, + mozilla::gfx::IntPoint* aOutTopLeft = nullptr); /** * Does the actual blurring/spreading and mask applying. Users of this @@ -129,7 +135,7 @@ public: */ static void BlurRectangle(gfxContext *aDestinationCtx, const gfxRect& aRect, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const gfxPoint& aBlurStdDev, const Color& aShadowColor, const gfxRect& aDirtyRect, @@ -154,41 +160,45 @@ public: * @param aSKipRect An area in device pixels we don't have to paint in. */ void BlurInsetBox(gfxContext* aDestinationCtx, - const mozilla::gfx::Rect aDestinationRect, - const mozilla::gfx::Rect aShadowClipRect, - const mozilla::gfx::IntSize aBlurRadius, - const mozilla::gfx::IntSize aSpreadRadius, + const mozilla::gfx::Rect& aDestinationRect, + const mozilla::gfx::Rect& aShadowClipRect, + const mozilla::gfx::IntSize& aBlurRadius, const mozilla::gfx::Color& aShadowColor, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - const mozilla::gfx::Rect aSkipRect, - const mozilla::gfx::Point aShadowOffset); + const RectCornerRadii* aInnerClipRadii, + const mozilla::gfx::Rect& aSkipRect, + const mozilla::gfx::Point& aShadowOffset); protected: already_AddRefed<mozilla::gfx::SourceSurface> - GetInsetBlur(const mozilla::gfx::Rect aOuterRect, - const mozilla::gfx::Rect aWhitespaceRect, - const bool aIsDestRect, + GetInsetBlur(const mozilla::gfx::Rect& aOuterRect, + const mozilla::gfx::Rect& aWhitespaceRect, + bool aIsDestRect, const mozilla::gfx::Color& aShadowColor, const mozilla::gfx::IntSize& aBlurRadius, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - DrawTarget* aDestDrawTarget); + const RectCornerRadii* aInnerClipRadii, + DrawTarget* aDestDrawTarget, + bool aMirrorCorners); + /** - * The context of the temporary alpha surface. + * The DrawTarget of the temporary alpha surface. */ - RefPtr<gfxContext> mContext; + RefPtr<DrawTarget> mDrawTarget; /** * The temporary alpha surface. */ - mozilla::UniquePtr<unsigned char[]> mData; + uint8_t* mData; /** * The object that actually does the blurring for us. */ - mozilla::UniquePtr<mozilla::gfx::AlphaBoxBlur> mBlur; + mozilla::gfx::AlphaBoxBlur mBlur; + + /** + * Indicates using DrawTarget-accelerated blurs. + */ + bool mAccelerated; }; #endif /* GFX_BLUR_H */ diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 848f810094..9a29c2a1e3 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -1304,7 +1304,12 @@ gfxPlatform::CreateSimilarSoftwareDrawTarget(DrawTarget* aDT, if (Factory::DoesBackendSupportDataDrawtarget(aDT->GetBackendType())) { dt = aDT->CreateSimilarDrawTarget(aSize, aFormat); } else { - dt = Factory::CreateDrawTarget(BackendType::SKIA, aSize, aFormat); +#ifdef USE_SKIA + BackendType backendType = BackendType::SKIA; +#else + BackendType backendType = BackendType::CAIRO; +#endif + dt = Factory::CreateDrawTarget(backendType, aSize, aFormat); } return dt.forget(); @@ -1317,7 +1322,11 @@ gfxPlatform::CreateDrawTargetForData(unsigned char* aData, const IntSize& aSize, NS_ASSERTION(backendType != BackendType::NONE, "No backend."); if (!Factory::DoesBackendSupportDataDrawtarget(backendType)) { +#ifdef USE_SKIA + backendType = BackendType::SKIA; +#else backendType = BackendType::CAIRO; +#endif } RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(backendType, diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index 1ec73fbde0..3fc1f64639 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -692,7 +692,7 @@ gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) } /*static*/ gfxFloat -gfxUtils::ClampToScaleFactor(gfxFloat aVal) +gfxUtils::ClampToScaleFactor(gfxFloat aVal, bool aRoundDown) { // Arbitary scale factor limitation. We can increase this // for better scaling performance at the cost of worse @@ -713,14 +713,17 @@ gfxUtils::ClampToScaleFactor(gfxFloat aVal) gfxFloat power = log(aVal)/log(kScaleResolution); - // If power is within 1e-5 of an integer, round to nearest to - // prevent floating point errors, otherwise round up to the - // next integer value. if (fabs(power - NS_round(power)) < 1e-5) { + // If power is within 1e-5 of an integer, round to nearest to + // prevent floating point errors. power = NS_round(power); - } else if (inverse) { + } else if (inverse != aRoundDown) { + // Use floor when we are either inverted or rounding down, but + // not both. power = floor(power); } else { + // Otherwise, ceil when we are not inverted and not rounding + // down, or we are inverted and rounding down. power = ceil(power); } diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 80094a762f..e2b5d476f5 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -128,9 +128,10 @@ public: /** * Return the smallest power of kScaleResolution (2) greater than or equal to - * aVal. + * aVal. If aRoundDown is specified, the power of 2 will be less than + * or equal to aVal. */ - static gfxFloat ClampToScaleFactor(gfxFloat aVal); + static gfxFloat ClampToScaleFactor(gfxFloat aVal, bool aRoundDown = false); /** * Clears surface to aColor (which defaults to transparent black). diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index d38b17adc4..d23ca436c9 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -5245,6 +5245,23 @@ static void RestrictScaleToMaxLayerSize(gfxSize& aScale, aScale.height /= scale; } } + +static nsSize +ComputeDesiredDisplaySizeForAnimation(nsIFrame* aContainerFrame) +{ + // Use the size of the nearest widget as the maximum size. This + // is important since it might be a popup that is bigger than the + // pres context's size. + nsPresContext* presContext = aContainerFrame->PresContext(); + nsIWidget* widget = aContainerFrame->GetNearestWidget(); + if (widget) { + return LayoutDevicePixel::ToAppUnits(widget->GetClientSize(), + presContext->AppUnitsPerDevPixel()); + } else { + return presContext->GetVisibleArea().Size(); + } +} + static bool ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, nsDisplayListBuilder* aDisplayListBuilder, @@ -5309,20 +5326,7 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, aContainerItem->GetType() == nsDisplayItem::TYPE_TRANSFORM && EffectCompositor::HasAnimationsForCompositor( aContainerFrame, eCSSProperty_transform)) { - // Use the size of the nearest widget as the maximum size. This - // is important since it might be a popup that is bigger than the - // pres context's size. - nsPresContext* presContext = aContainerFrame->PresContext(); - nsIWidget* widget = aContainerFrame->GetNearestWidget(); - nsSize displaySize; - if (widget) { - LayoutDeviceIntSize widgetSize = widget->GetClientSize(); - int32_t p2a = presContext->AppUnitsPerDevPixel(); - displaySize.width = NSIntPixelsToAppUnits(widgetSize.width, p2a); - displaySize.height = NSIntPixelsToAppUnits(widgetSize.height, p2a); - } else { - displaySize = presContext->GetVisibleArea().Size(); - } + nsSize displaySize = ComputeDesiredDisplaySizeForAnimation(aContainerFrame); // compute scale using the animation on the container (ignoring // its ancestors) scale = nsLayoutUtils::ComputeSuitableScaleForAnimation( @@ -5359,6 +5363,19 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, if (clamp) { scale.width = gfxUtils::ClampToScaleFactor(scale.width); scale.height = gfxUtils::ClampToScaleFactor(scale.height); + + // Limit animated scale factors to not grow excessively beyond the display size. + nsSize maxScale(4, 4); + if (!aVisibleRect.IsEmpty()) { + nsSize displaySize = ComputeDesiredDisplaySizeForAnimation(aContainerFrame); + maxScale = Max(maxScale, displaySize / aVisibleRect.Size()); + } + if (scale.width > maxScale.width) { + scale.width = gfxUtils::ClampToScaleFactor(maxScale.width, true); + } + if (scale.height > maxScale.height) { + scale.height = gfxUtils::ClampToScaleFactor(maxScale.height, true); + } } } else { // XXX Do we need to move nearly-integer values to integers here? diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 5e1ce69abc..3084cf9085 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -1625,7 +1625,6 @@ nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext, // transparent in the shadow, so drawing them changes nothing. gfxContext* renderContext = aRenderingContext.ThebesContext(); DrawTarget* drawTarget = renderContext->GetDrawTarget(); - nsContextBoxBlur blurringArea; // Clip the context to the area of the frame's padding rect, so no part of the // shadow is painted outside. Also cut out anything beyond where the inset shadow @@ -6024,10 +6023,10 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius, dirtyRect = transform.TransformBounds(dirtyRect); if (aSkipRect) { gfxRect skipRect = transform.TransformBounds(*aSkipRect); - mContext = mAlphaBoxBlur.Init(rect, spreadRadius, + mContext = mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, &dirtyRect, &skipRect); } else { - mContext = mAlphaBoxBlur.Init(rect, spreadRadius, + mContext = mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, &dirtyRect, nullptr); } @@ -6237,9 +6236,8 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, transformedShadowClipRect, - blurRadius, spreadRadius, - aShadowColor, aHasBorderRadius, - aInnerClipRectRadii, transformedSkipRect, - aShadowOffset); + blurRadius, aShadowColor, + aHasBorderRadius ? &aInnerClipRectRadii : nullptr, + transformedSkipRect, aShadowOffset); return true; } diff --git a/layout/reftests/box-shadow/reftest.list b/layout/reftests/box-shadow/reftest.list index 8f62299432..a195dcf430 100644 --- a/layout/reftests/box-shadow/reftest.list +++ b/layout/reftests/box-shadow/reftest.list @@ -18,19 +18,19 @@ fuzzy-if(skiaContent,1,50) HTTP(..) == boxshadow-dynamic.xul boxshadow-dynamic-r random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html random-if(d2d) == boxshadow-twocorners.html boxshadow-twocorners-ref.html random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html -== boxshadow-skiprect.html boxshadow-skiprect-ref.html +fuzzy(2,440) == boxshadow-skiprect.html boxshadow-skiprect-ref.html == boxshadow-opacity.html boxshadow-opacity-ref.html == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html == boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html -fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,568) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649 -fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html +fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,908) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649 +fuzzy(3,500) fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html == boxshadow-inset-neg-spread.html about:blank == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html -fuzzy(26,3610) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264 +fuzzy(26,3610) fuzzy-if(d2d,26,5910) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264 == boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html # fuzzy due to blur going inside, but as long as it's essentially black instead of a light gray its ok. -fuzzy(12,9445) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html +fuzzy(12,9445) fuzzy-if(d2d,13,10926) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index e6cc895d94..3591f38c0c 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1818,7 +1818,7 @@ HTTP(..) == 983985-2.html 983985-2-ref.html == 985303-1a.html 985303-1-ref.html == 985303-1b.html 985303-1-ref.html == 987680-1.html 987680-1-ref.html -fuzzy-if(d2d,1,36) == 991046-1.html 991046-1-ref.html +fuzzy-if(d2d,1,601) == 991046-1.html 991046-1-ref.html pref(layout.css.overflow-clip-box.enabled,true) fuzzy-if(skiaContent,2,845) == 992447.html 992447-ref.html == 1003425-1.html 1003425-1-ref.html == 1003425-2.html 1003425-2-ref.html diff --git a/layout/reftests/outline/reftest.list b/layout/reftests/outline/reftest.list index a729d892e9..df7e11564a 100644 --- a/layout/reftests/outline/reftest.list +++ b/layout/reftests/outline/reftest.list @@ -1,4 +1,4 @@ -== outline-and-box-shadow.html outline-and-box-shadow-ref.html +fuzzy(2,18) == outline-and-box-shadow.html outline-and-box-shadow-ref.html == outline-and-3d-transform-1a.html outline-and-3d-transform-1-ref.html == outline-and-3d-transform-1b.html outline-and-3d-transform-1-ref.html fuzzy-if(gtkWidget,136,120) fuzzy-if(d2d,16,96) fuzzy-if(cocoaWidget,255,120) fuzzy-if(winWidget,255,216) == outline-and-3d-transform-2.html outline-and-3d-transform-2-ref.html |