#include "config.h"
#include "ShadowBlur.h"
#include "AffineTransform.h"
#include "FloatQuad.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "Timer.h"
#include <wtf/MathExtras.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Noncopyable.h>
namespace WebCore {
enum {
LeftLobe = 0,
RightLobe = 1
};
#if USE(CG)
static inline int roundUpToMultipleOf32(int d)
{
return (1 + (d >> 5)) << 5;
}
class ScratchBuffer {
WTF_MAKE_FAST_ALLOCATED;
public:
ScratchBuffer()
: m_purgeTimer(*this, &ScratchBuffer::clearScratchBuffer)
, m_lastWasInset(false)
#if !ASSERT_DISABLED
, m_bufferInUse(false)
#endif
{
}
ImageBuffer* getScratchBuffer(const IntSize& size)
{
ASSERT(!m_bufferInUse);
#if !ASSERT_DISABLED
m_bufferInUse = true;
#endif
if (m_imageBuffer && m_imageBuffer->logicalSize().width() >= size.width() && m_imageBuffer->logicalSize().height() >= size.height())
return m_imageBuffer.get();
IntSize roundedSize(roundUpToMultipleOf32(size.width()), roundUpToMultipleOf32(size.height()));
clearScratchBuffer();
m_imageBuffer = ImageBuffer::create(roundedSize, Unaccelerated, 1);
return m_imageBuffer.get();
}
bool setCachedShadowValues(const FloatSize& radius, const Color& color, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii, const FloatSize& layerSize)
{
if (!m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastShadowRect == shadowRect && m_lastRadii == radii && m_lastLayerSize == layerSize)
return false;
m_lastWasInset = false;
m_lastRadius = radius;
m_lastColor = color;
m_lastShadowRect = shadowRect;
m_lastRadii = radii;
m_lastLayerSize = layerSize;
return true;
}
bool setCachedInsetShadowValues(const FloatSize& radius, const Color& color, const FloatRect& bounds, const FloatRect& shadowRect, const FloatRoundedRect::Radii& radii)
{
if (m_lastWasInset && m_lastRadius == radius && m_lastColor == color && m_lastInsetBounds == bounds && shadowRect == m_lastShadowRect && radii == m_lastRadii)
return false;
m_lastWasInset = true;
m_lastInsetBounds = bounds;
m_lastRadius = radius;
m_lastColor = color;
m_lastShadowRect = shadowRect;
m_lastRadii = radii;
return true;
}
void scheduleScratchBufferPurge()
{
#if !ASSERT_DISABLED
m_bufferInUse = false;
#endif
if (m_purgeTimer.isActive())
m_purgeTimer.stop();
const Seconds scratchBufferPurgeInterval { 2_s };
m_purgeTimer.startOneShot(scratchBufferPurgeInterval);
}
static ScratchBuffer& singleton();
private:
void clearScratchBuffer()
{
m_imageBuffer = nullptr;
m_lastRadius = FloatSize();
m_lastLayerSize = FloatSize();
}
std::unique_ptr<ImageBuffer> m_imageBuffer;
Timer m_purgeTimer;
FloatRect m_lastInsetBounds;
FloatRect m_lastShadowRect;
FloatRoundedRect::Radii m_lastRadii;
Color m_lastColor;
FloatSize m_lastRadius;
bool m_lastWasInset;
FloatSize m_lastLayerSize;
#if !ASSERT_DISABLED
bool m_bufferInUse;
#endif
};
ScratchBuffer& ScratchBuffer::singleton()
{
static NeverDestroyed<ScratchBuffer> scratchBuffer;
return scratchBuffer;
}
static float radiusToLegacyRadius(float radius)
{
return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius;
}
#endif
static const int templateSideLength = 1;
ShadowBlur::ShadowBlur() = default;
ShadowBlur::ShadowBlur(const FloatSize& radius, const FloatSize& offset, const Color& color, bool shadowsIgnoreTransforms)
: m_color(color)
, m_blurRadius(radius)
, m_offset(offset)
, m_shadowsIgnoreTransforms(shadowsIgnoreTransforms)
{
updateShadowBlurValues();
}
ShadowBlur::ShadowBlur(const GraphicsContextState& state)
: m_color(state.shadowColor)
, m_blurRadius(state.shadowBlur, state.shadowBlur)
, m_offset(state.shadowOffset)
, m_shadowsIgnoreTransforms(state.shadowsIgnoreTransforms)
{
#if USE(CG)
if (state.shadowsUseLegacyRadius) {
float shadowBlur = radiusToLegacyRadius(state.shadowBlur);
m_blurRadius = FloatSize(shadowBlur, shadowBlur);
}
#endif
updateShadowBlurValues();
}
void ShadowBlur::setShadowValues(const FloatSize& radius, const FloatSize& offset, const Color& color, bool ignoreTransforms)
{
m_blurRadius = radius;
m_offset = offset;
m_color = color;
m_shadowsIgnoreTransforms = ignoreTransforms;
updateShadowBlurValues();
}
void ShadowBlur::updateShadowBlurValues()
{
m_blurRadius = m_blurRadius.shrunkTo(FloatSize(128, 128));
if (!m_color.isVisible()) {
m_type = NoShadow;
} else if (m_blurRadius.width() > 0 || m_blurRadius.height() > 0) {
m_type = BlurShadow;
} else if (!m_offset.width() && !m_offset.height()) {
m_type = NoShadow;
} else
m_type = SolidShadow;
}
static const int blurSumShift = 15;
static void calculateLobes(int lobes[][2], float blurRadius, bool shadowsIgnoreTransforms)
{
int diameter;
if (shadowsIgnoreTransforms)
diameter = std::max(2, static_cast<int>(floorf((2 / 3.f) * blurRadius))); else {
float stdDev = blurRadius / 2;
const float gaussianKernelFactor = 3 / 4.f * sqrtf(2 * piFloat);
const float fudgeFactor = 0.88f;
diameter = std::max(2, static_cast<int>(floorf(stdDev * gaussianKernelFactor * fudgeFactor + 0.5f)));
}
if (diameter & 1) {
int lobeSize = (diameter - 1) / 2;
lobes[0][LeftLobe] = lobeSize;
lobes[0][RightLobe] = lobeSize;
lobes[1][LeftLobe] = lobeSize;
lobes[1][RightLobe] = lobeSize;
lobes[2][LeftLobe] = lobeSize;
lobes[2][RightLobe] = lobeSize;
} else {
int lobeSize = diameter / 2;
lobes[0][LeftLobe] = lobeSize;
lobes[0][RightLobe] = lobeSize - 1;
lobes[1][LeftLobe] = lobeSize - 1;
lobes[1][RightLobe] = lobeSize;
lobes[2][LeftLobe] = lobeSize;
lobes[2][RightLobe] = lobeSize;
}
}
void ShadowBlur::clear()
{
m_type = NoShadow;
m_color = Color();
m_blurRadius = FloatSize();
m_offset = FloatSize();
}
void ShadowBlur::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
{
const int channels[4] = { 3, 0, 1, 3 };
int lobes[3][2]; calculateLobes(lobes, m_blurRadius.width(), m_shadowsIgnoreTransforms);
int stride = 4;
int delta = rowStride;
int final = size.height();
int dim = size.width();
for (int pass = 0; pass < 2; ++pass) {
unsigned char* pixels = imageData;
if (!pass && !m_blurRadius.width())
final = 0;
for (int j = 0; j < final; ++j, pixels += delta) {
for (int step = 0; step < 3; ++step) {
int side1 = lobes[step][LeftLobe];
int side2 = lobes[step][RightLobe];
int pixelCount = side1 + 1 + side2;
int invCount = ((1 << blurSumShift) + pixelCount - 1) / pixelCount;
int ofs = 1 + side2;
int alpha1 = pixels[channels[step]];
int alpha2 = pixels[(dim - 1) * stride + channels[step]];
unsigned char* ptr = pixels + channels[step + 1];
unsigned char* prev = pixels + stride + channels[step];
unsigned char* next = pixels + ofs * stride + channels[step];
int i;
int sum = side1 * alpha1 + alpha1;
int limit = (dim < side2 + 1) ? dim : side2 + 1;
for (i = 1; i < limit; ++i, prev += stride)
sum += *prev;
if (limit <= side2)
sum += (side2 - limit + 1) * alpha2;
limit = (side1 < dim) ? side1 : dim;
for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
*ptr = (sum * invCount) >> blurSumShift;
sum += ((ofs < dim) ? *next : alpha2) - alpha1;
}
prev = pixels + channels[step];
for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
*ptr = (sum * invCount) >> blurSumShift;
sum += (*next) - (*prev);
}
for (; i < dim; ptr += stride, prev += stride, ++i) {
*ptr = (sum * invCount) >> blurSumShift;
sum += alpha2 - (*prev);
}
}
}
stride = rowStride;
delta = 4;
final = size.width();
dim = size.height();
if (!m_blurRadius.height())
break;
if (m_blurRadius.width() != m_blurRadius.height())
calculateLobes(lobes, m_blurRadius.height(), m_shadowsIgnoreTransforms);
}
}
void ShadowBlur::adjustBlurRadius(const AffineTransform& transform)
{
if (m_shadowsIgnoreTransforms)
m_blurRadius.scale(1 / static_cast<float>(transform.xScale()), 1 / static_cast<float>(transform.yScale()));
}
IntSize ShadowBlur::blurredEdgeSize() const
{
IntSize edgeSize = expandedIntSize(m_blurRadius);
if (edgeSize.width() == 1)
edgeSize.setWidth(2);
if (edgeSize.height() == 1)
edgeSize.setHeight(2);
return edgeSize;
}
Optional<ShadowBlur::LayerImageProperties> ShadowBlur::calculateLayerBoundingRect(const AffineTransform& transform, const FloatRect& shadowedRect, const IntRect& clipRect)
{
LayerImageProperties calculatedLayerImageProperties;
IntSize edgeSize = blurredEdgeSize();
FloatRect layerRect;
IntSize inflation;
if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(shadowedRect));
transformedPolygon.move(m_offset);
layerRect = transform.inverse().valueOr(AffineTransform()).mapQuad(transformedPolygon).boundingBox();
} else {
layerRect = shadowedRect;
layerRect.move(m_offset);
}
if (m_type == BlurShadow) {
layerRect.inflateX(edgeSize.width());
layerRect.inflateY(edgeSize.height());
inflation = edgeSize;
}
FloatRect unclippedLayerRect = layerRect;
if (!clipRect.contains(enclosingIntRect(layerRect))) {
if (intersection(layerRect, clipRect).isEmpty())
return WTF::nullopt;
IntRect inflatedClip = clipRect;
if (m_type == BlurShadow) {
inflatedClip.inflateX(edgeSize.width());
inflatedClip.inflateY(edgeSize.height());
} else {
inflatedClip.inflateX(1);
inflatedClip.inflateY(1);
}
layerRect.intersect(inflatedClip);
}
IntSize frameSize = inflation;
frameSize.scale(2);
calculatedLayerImageProperties.shadowedResultSize = FloatSize(shadowedRect.width() + frameSize.width(), shadowedRect.height() + frameSize.height());
calculatedLayerImageProperties.layerOrigin = FloatPoint(layerRect.x(), layerRect.y());
calculatedLayerImageProperties.layerSize = layerRect.size();
const FloatPoint unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
const FloatSize clippedOut = unclippedLayerOrigin - calculatedLayerImageProperties.layerOrigin;
float translationX = -shadowedRect.x() + inflation.width() - fabsf(clippedOut.width());
float translationY = -shadowedRect.y() + inflation.height() - fabsf(clippedOut.height());
calculatedLayerImageProperties.layerContextTranslation = FloatSize(translationX, translationY);
return calculatedLayerImageProperties;
}
void ShadowBlur::drawShadowBuffer(GraphicsContext& graphicsContext, ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize)
{
GraphicsContextStateSaver stateSaver(graphicsContext);
IntSize bufferSize = layerImage.internalSize();
if (bufferSize != layerSize) {
graphicsContext.clip(FloatRect(layerOrigin, layerSize));
}
graphicsContext.clipToImageBuffer(layerImage, FloatRect(layerOrigin, bufferSize));
graphicsContext.setFillColor(m_color);
graphicsContext.clearShadow();
graphicsContext.fillRect(FloatRect(layerOrigin, layerSize));
}
static void computeSliceSizesFromRadii(const IntSize& twiceRadius, const FloatRoundedRect::Radii& radii, int& leftSlice, int& rightSlice, int& topSlice, int& bottomSlice)
{
leftSlice = twiceRadius.width() + std::max(radii.topLeft().width(), radii.bottomLeft().width());
rightSlice = twiceRadius.width() + std::max(radii.topRight().width(), radii.bottomRight().width());
topSlice = twiceRadius.height() + std::max(radii.topLeft().height(), radii.topRight().height());
bottomSlice = twiceRadius.height() + std::max(radii.bottomLeft().height(), radii.bottomRight().height());
}
IntSize ShadowBlur::templateSize(const IntSize& radiusPadding, const FloatRoundedRect::Radii& radii) const
{
const int templateSideLength = 1;
int leftSlice;
int rightSlice;
int topSlice;
int bottomSlice;
IntSize blurExpansion = radiusPadding;
blurExpansion.scale(2);
computeSliceSizesFromRadii(blurExpansion, radii, leftSlice, rightSlice, topSlice, bottomSlice);
return IntSize(templateSideLength + leftSlice + rightSlice, templateSideLength + topSlice + bottomSlice);
}
void ShadowBlur::drawRectShadow(GraphicsContext& graphicsContext, const FloatRoundedRect& shadowedRect)
{
drawRectShadow(graphicsContext.getCTM(), graphicsContext.clipBounds(), shadowedRect,
[this, &graphicsContext](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize) {
drawShadowBuffer(graphicsContext, layerImage, layerOrigin, layerSize);
},
[&graphicsContext](ImageBuffer& image, const FloatRect& destRect, const FloatRect& srcRect) {
GraphicsContextStateSaver stateSaver(graphicsContext);
graphicsContext.clearShadow();
graphicsContext.drawImageBuffer(image, destRect, srcRect);
},
[&graphicsContext](const FloatRect& rect, const Color& color) {
GraphicsContextStateSaver stateSaver(graphicsContext);
graphicsContext.setFillColor(color);
graphicsContext.clearShadow();
graphicsContext.fillRect(rect);
});
}
void ShadowBlur::drawInsetShadow(GraphicsContext& graphicsContext, const FloatRect& fullRect, const FloatRoundedRect& holeRect)
{
drawInsetShadow(graphicsContext.getCTM(), graphicsContext.clipBounds(), fullRect, holeRect,
[this, &graphicsContext](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize) {
drawShadowBuffer(graphicsContext, layerImage, layerOrigin, layerSize);
},
[&graphicsContext](ImageBuffer& image, const FloatRect& destRect, const FloatRect& srcRect) {
GraphicsContextStateSaver stateSaver(graphicsContext);
graphicsContext.clearShadow();
graphicsContext.drawImageBuffer(image, destRect, srcRect);
},
[&graphicsContext](const FloatRect& rect, const FloatRect& holeRect, const Color& color) {
Path exteriorPath;
exteriorPath.addRect(rect);
exteriorPath.addRect(holeRect);
GraphicsContextStateSaver fillStateSaver(graphicsContext);
graphicsContext.setFillRule(WindRule::EvenOdd);
graphicsContext.setFillColor(color);
graphicsContext.clearShadow();
graphicsContext.fillPath(exteriorPath);
});
}
void ShadowBlur::drawRectShadow(const AffineTransform& transform, const IntRect& clipBounds, const FloatRoundedRect& shadowedRect, const DrawBufferCallback& drawBuffer, const DrawImageCallback& drawImage, const FillRectCallback& fillRect)
{
auto layerImageProperties = calculateLayerBoundingRect(transform, shadowedRect.rect(), clipBounds);
if (!layerImageProperties)
return;
adjustBlurRadius(transform);
bool canUseTilingTechnique = true;
if (!transform.preservesAxisAlignment() || m_type != BlurShadow)
canUseTilingTechnique = false;
IntSize edgeSize = blurredEdgeSize();
IntSize templateSize = this->templateSize(edgeSize, shadowedRect.radii());
const FloatRect& rect = shadowedRect.rect();
if (templateSize.width() > rect.width() || templateSize.height() > rect.height()
|| (templateSize.area() > layerImageProperties->shadowedResultSize.area()))
canUseTilingTechnique = false;
if (canUseTilingTechnique)
drawRectShadowWithTiling(transform, shadowedRect, templateSize, edgeSize, drawImage, fillRect, *layerImageProperties);
else
drawRectShadowWithoutTiling(transform, shadowedRect, *layerImageProperties, drawBuffer);
}
void ShadowBlur::drawInsetShadow(const AffineTransform& transform, const IntRect& clipBounds, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const DrawBufferCallback& drawBuffer, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole)
{
auto layerImageProperties = calculateLayerBoundingRect(transform, fullRect, clipBounds);
if (!layerImageProperties)
return;
adjustBlurRadius(transform);
bool canUseTilingTechnique = true;
if (!transform.preservesAxisAlignment() || m_type != BlurShadow)
canUseTilingTechnique = false;
IntSize edgeSize = blurredEdgeSize();
IntSize templateSize = this->templateSize(edgeSize, holeRect.radii());
const FloatRect& hRect = holeRect.rect();
if (templateSize.width() > hRect.width() || templateSize.height() > hRect.height()
|| (templateSize.width() * templateSize.height() > hRect.width() * hRect.height()))
canUseTilingTechnique = false;
if (canUseTilingTechnique)
drawInsetShadowWithTiling(transform, fullRect, holeRect, templateSize, edgeSize, drawImage, fillRectWithHole);
else
drawInsetShadowWithoutTiling(transform, fullRect, holeRect, *layerImageProperties, drawBuffer);
}
void ShadowBlur::drawRectShadowWithoutTiling(const AffineTransform&, const FloatRoundedRect& shadowedRect, const LayerImageProperties& layerImageProperties, const DrawBufferCallback& drawBuffer)
{
auto layerImage = ImageBuffer::create(expandedIntSize(layerImageProperties.layerSize), Unaccelerated, 1);
if (!layerImage)
return;
GraphicsContext& shadowContext = layerImage->context();
GraphicsContextStateSaver stateSaver(shadowContext);
shadowContext.setFillColor(Color::black);
{
GraphicsContext& shadowContext = layerImage->context();
GraphicsContextStateSaver stateSaver(shadowContext);
shadowContext.translate(layerImageProperties.layerContextTranslation);
shadowContext.setFillColor(Color::black);
if (shadowedRect.radii().isZero())
shadowContext.fillRect(shadowedRect.rect());
else {
Path path;
path.addRoundedRect(shadowedRect);
shadowContext.fillPath(path);
}
blurShadowBuffer(*layerImage, expandedIntSize(layerImageProperties.layerSize));
}
drawBuffer(*layerImage, layerImageProperties.layerOrigin, layerImageProperties.layerSize);
}
void ShadowBlur::drawInsetShadowWithoutTiling(const AffineTransform&, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const LayerImageProperties& layerImageProperties, const DrawBufferCallback& drawBuffer)
{
auto layerImage = ImageBuffer::create(expandedIntSize(layerImageProperties.layerSize), Unaccelerated, 1);
if (!layerImage)
return;
{
GraphicsContext& shadowContext = layerImage->context();
GraphicsContextStateSaver stateSaver(shadowContext);
shadowContext.translate(layerImageProperties.layerContextTranslation);
Path path;
path.addRect(fullRect);
if (holeRect.radii().isZero())
path.addRect(holeRect.rect());
else
path.addRoundedRect(holeRect);
shadowContext.setFillRule(WindRule::EvenOdd);
shadowContext.setFillColor(Color::black);
shadowContext.fillPath(path);
blurShadowBuffer(*layerImage, expandedIntSize(layerImageProperties.layerSize));
}
drawBuffer(*layerImage, layerImageProperties.layerOrigin, layerImageProperties.layerSize);
}
void ShadowBlur::drawRectShadowWithTiling(const AffineTransform& transform, const FloatRoundedRect& shadowedRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect, const LayerImageProperties& layerImageProperties)
{
#if USE(CG)
auto* layerImage = ScratchBuffer::singleton().getScratchBuffer(templateSize);
#else
UNUSED_PARAM(layerImageProperties);
auto layerImageBuffer = ImageBuffer::create(templateSize, Unaccelerated, 1);
auto* layerImage = layerImageBuffer.get();
#endif
if (!layerImage)
return;
FloatRect templateShadow = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
bool redrawNeeded = true;
#if USE(CG)
redrawNeeded = ScratchBuffer::singleton().setCachedShadowValues(m_blurRadius, m_color, templateShadow, shadowedRect.radii(), layerImageProperties.layerSize);
#endif
if (redrawNeeded) {
GraphicsContext& shadowContext = layerImage->context();
GraphicsContextStateSaver shadowStateSaver(shadowContext);
shadowContext.clearRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
shadowContext.setFillColor(Color::black);
if (shadowedRect.radii().isZero())
shadowContext.fillRect(templateShadow);
else {
Path path;
path.addRoundedRect(FloatRoundedRect(templateShadow, shadowedRect.radii()));
shadowContext.fillPath(path);
}
blurAndColorShadowBuffer(*layerImage, templateSize);
}
FloatSize offset = m_offset;
if (shadowsIgnoreTransforms())
offset.scale(1 / transform.xScale(), 1 / transform.yScale());
FloatRect shadowBounds = shadowedRect.rect();
shadowBounds.move(offset);
shadowBounds.inflateX(edgeSize.width());
shadowBounds.inflateY(edgeSize.height());
drawLayerPiecesAndFillCenter(*layerImage, shadowBounds, shadowedRect.radii(), edgeSize, templateSize, drawImage, fillRect);
#if USE(CG)
ScratchBuffer::singleton().scheduleScratchBufferPurge();
#endif
}
void ShadowBlur::drawInsetShadowWithTiling(const AffineTransform& transform, const FloatRect& fullRect, const FloatRoundedRect& holeRect, const IntSize& templateSize, const IntSize& edgeSize, const DrawImageCallback& drawImage, const FillRectWithHoleCallback& fillRectWithHole)
{
#if USE(CG)
auto* layerImage = ScratchBuffer::singleton().getScratchBuffer(templateSize);
#else
auto layerImageBuffer = ImageBuffer::create(templateSize, Unaccelerated, 1);
auto* layerImage = layerImageBuffer.get();
#endif
if (!layerImage)
return;
FloatRect templateBounds(0, 0, templateSize.width(), templateSize.height());
FloatRect templateHole = FloatRect(edgeSize.width(), edgeSize.height(), templateSize.width() - 2 * edgeSize.width(), templateSize.height() - 2 * edgeSize.height());
bool redrawNeeded = true;
#if USE(CG)
redrawNeeded = ScratchBuffer::singleton().setCachedInsetShadowValues(m_blurRadius, m_color, templateBounds, templateHole, holeRect.radii());
#endif
if (redrawNeeded) {
GraphicsContext& shadowContext = layerImage->context();
GraphicsContextStateSaver shadowStateSaver(shadowContext);
shadowContext.clearRect(templateBounds);
shadowContext.setFillRule(WindRule::EvenOdd);
shadowContext.setFillColor(Color::black);
Path path;
path.addRect(templateBounds);
if (holeRect.radii().isZero())
path.addRect(templateHole);
else
path.addRoundedRect(FloatRoundedRect(templateHole, holeRect.radii()));
shadowContext.fillPath(path);
blurAndColorShadowBuffer(*layerImage, templateSize);
}
FloatSize offset = m_offset;
if (shadowsIgnoreTransforms())
offset.scale(1 / transform.xScale(), 1 / transform.yScale());
FloatRect boundingRect = fullRect;
boundingRect.move(offset);
FloatRect destHoleRect = holeRect.rect();
destHoleRect.move(offset);
FloatRect destHoleBounds = destHoleRect;
destHoleBounds.inflateX(edgeSize.width());
destHoleBounds.inflateY(edgeSize.height());
fillRectWithHole(boundingRect, destHoleBounds, m_color);
drawLayerPieces(*layerImage, destHoleBounds, holeRect.radii(), edgeSize, templateSize, drawImage);
#if USE(CG)
ScratchBuffer::singleton().scheduleScratchBufferPurge();
#endif
}
void ShadowBlur::drawLayerPieces(ImageBuffer& layerImage, const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, const DrawImageCallback& drawImage)
{
const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
int leftSlice;
int rightSlice;
int topSlice;
int bottomSlice;
computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
FloatRect tileRect = FloatRect(leftSlice, 0, templateSideLength, topSlice);
FloatRect destRect = FloatRect(centerRect.x(), centerRect.y() - topSlice, centerRect.width(), topSlice);
drawImage(layerImage, destRect, tileRect);
tileRect.setY(templateSize.height() - bottomSlice);
tileRect.setHeight(bottomSlice);
destRect.setY(centerRect.maxY());
destRect.setHeight(bottomSlice);
drawImage(layerImage, destRect, tileRect);
tileRect = FloatRect(0, topSlice, leftSlice, templateSideLength);
destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y(), leftSlice, centerRect.height());
drawImage(layerImage, destRect, tileRect);
tileRect.setX(templateSize.width() - rightSlice);
tileRect.setWidth(rightSlice);
destRect.setX(centerRect.maxX());
destRect.setWidth(rightSlice);
drawImage(layerImage, destRect, tileRect);
tileRect = FloatRect(0, 0, leftSlice, topSlice);
destRect = FloatRect(centerRect.x() - leftSlice, centerRect.y() - topSlice, leftSlice, topSlice);
drawImage(layerImage, destRect, tileRect);
tileRect = FloatRect(templateSize.width() - rightSlice, 0, rightSlice, topSlice);
destRect = FloatRect(centerRect.maxX(), centerRect.y() - topSlice, rightSlice, topSlice);
drawImage(layerImage, destRect, tileRect);
tileRect = FloatRect(templateSize.width() - rightSlice, templateSize.height() - bottomSlice, rightSlice, bottomSlice);
destRect = FloatRect(centerRect.maxX(), centerRect.maxY(), rightSlice, bottomSlice);
drawImage(layerImage, destRect, tileRect);
tileRect = FloatRect(0, templateSize.height() - bottomSlice, leftSlice, bottomSlice);
destRect = FloatRect(centerRect.x() - leftSlice, centerRect.maxY(), leftSlice, bottomSlice);
drawImage(layerImage, destRect, tileRect);
}
void ShadowBlur::drawLayerPiecesAndFillCenter(ImageBuffer& layerImage, const FloatRect& shadowBounds, const FloatRoundedRect::Radii& radii, const IntSize& bufferPadding, const IntSize& templateSize, const DrawImageCallback& drawImage, const FillRectCallback& fillRect)
{
const IntSize twiceRadius = IntSize(bufferPadding.width() * 2, bufferPadding.height() * 2);
int leftSlice;
int rightSlice;
int topSlice;
int bottomSlice;
computeSliceSizesFromRadii(twiceRadius, radii, leftSlice, rightSlice, topSlice, bottomSlice);
int centerWidth = shadowBounds.width() - leftSlice - rightSlice;
int centerHeight = shadowBounds.height() - topSlice - bottomSlice;
FloatRect centerRect(shadowBounds.x() + leftSlice, shadowBounds.y() + topSlice, centerWidth, centerHeight);
if (!centerRect.isEmpty())
fillRect(centerRect, m_color);
drawLayerPieces(layerImage, shadowBounds, radii, bufferPadding, templateSize, drawImage);
}
void ShadowBlur::blurShadowBuffer(ImageBuffer& layerImage, const IntSize& templateSize)
{
if (m_type != BlurShadow)
return;
IntRect blurRect(IntPoint(), templateSize);
auto layerData = layerImage.getUnmultipliedImageData(blurRect);
if (!layerData)
return;
blurLayerImage(layerData->data(), blurRect.size(), blurRect.width() * 4);
layerImage.putByteArray(*layerData, AlphaPremultiplication::Unpremultiplied, blurRect.size(), blurRect, { });
}
void ShadowBlur::blurAndColorShadowBuffer(ImageBuffer& layerImage, const IntSize& templateSize)
{
blurShadowBuffer(layerImage, templateSize);
GraphicsContext& shadowContext = layerImage.context();
GraphicsContextStateSaver stateSaver(shadowContext);
shadowContext.setCompositeOperation(CompositeOperator::SourceIn);
shadowContext.setFillColor(m_color);
shadowContext.fillRect(FloatRect(0, 0, templateSize.width(), templateSize.height()));
}
void ShadowBlur::drawShadowLayer(const AffineTransform& transform, const IntRect& clipBounds, const FloatRect& layerArea, const DrawShadowCallback& drawShadow, const DrawBufferCallback& drawBuffer)
{
auto layerImageProperties = calculateLayerBoundingRect(transform, layerArea, clipBounds);
if (!layerImageProperties)
return;
adjustBlurRadius(transform);
auto layerImage = ImageBuffer::create(expandedIntSize(layerImageProperties->layerSize), Unaccelerated, 1);
if (!layerImage)
return;
{
GraphicsContext& shadowContext = layerImage->context();
GraphicsContextStateSaver stateSaver(shadowContext);
shadowContext.translate(layerImageProperties->layerContextTranslation);
drawShadow(shadowContext);
}
blurAndColorShadowBuffer(*layerImage, expandedIntSize(layerImageProperties->layerSize));
drawBuffer(*layerImage, layerImageProperties->layerOrigin, layerImageProperties->layerSize);
}
}