ContextShadowCairo.cpp [plain text]
#include "config.h"
#include "ContextShadow.h"
#include "AffineTransform.h"
#include "CairoUtilities.h"
#include "GraphicsContext.h"
#include "OwnPtrCairo.h"
#include "Path.h"
#include "PlatformContextCairo.h"
#include "Timer.h"
#include <cairo.h>
using WTF::max;
namespace WebCore {
static RefPtr<cairo_surface_t> gScratchBuffer;
static void purgeScratchBuffer()
{
gScratchBuffer.clear();
}
class PurgeScratchBufferTimer : public TimerBase {
private:
virtual void fired() { purgeScratchBuffer(); }
};
static PurgeScratchBufferTimer purgeScratchBufferTimer;
static void scheduleScratchBufferPurge()
{
if (purgeScratchBufferTimer.isActive())
purgeScratchBufferTimer.stop();
purgeScratchBufferTimer.startOneShot(2);
}
static cairo_surface_t* getScratchBuffer(const IntSize& size)
{
int width = size.width();
int height = size.height();
int scratchWidth = gScratchBuffer.get() ? cairo_image_surface_get_width(gScratchBuffer.get()) : 0;
int scratchHeight = gScratchBuffer.get() ? cairo_image_surface_get_height(gScratchBuffer.get()) : 0;
if (gScratchBuffer.get() && scratchWidth >= width && scratchHeight >= height)
return gScratchBuffer.get();
purgeScratchBuffer();
width = (1 + (width >> 5)) << 5;
height = (1 + (height >> 5)) << 5;
gScratchBuffer = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height));
return gScratchBuffer.get();
}
PlatformContext ContextShadow::beginShadowLayer(GraphicsContext* context, const FloatRect& layerArea)
{
adjustBlurDistance(context);
double x1, x2, y1, y2;
cairo_clip_extents(context->platformContext()->cr(), &x1, &y1, &x2, &y2);
IntRect layerRect = calculateLayerBoundingRect(context, layerArea, IntRect(x1, y1, x2 - x1, y2 - y1));
if (layerRect.isEmpty())
return 0;
m_layerImage = getScratchBuffer(layerRect.size());
m_layerContext = cairo_create(m_layerImage);
cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR);
cairo_paint(m_layerContext);
cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER);
cairo_translate(m_layerContext, m_layerContextTranslation.x(), m_layerContextTranslation.y());
return m_layerContext;
}
void ContextShadow::endShadowLayer(GraphicsContext* context)
{
cairo_destroy(m_layerContext);
m_layerContext = 0;
if (m_type == BlurShadow) {
cairo_surface_flush(m_layerImage);
blurLayerImage(cairo_image_surface_get_data(m_layerImage),
IntSize(cairo_image_surface_get_width(m_layerImage), cairo_image_surface_get_height(m_layerImage)),
cairo_image_surface_get_stride(m_layerImage));
cairo_surface_mark_dirty(m_layerImage);
}
cairo_t* cr = context->platformContext()->cr();
cairo_save(cr);
setSourceRGBAFromColor(cr, m_color);
cairo_mask_surface(cr, m_layerImage, m_layerOrigin.x(), m_layerOrigin.y());
cairo_restore(cr);
scheduleScratchBufferPurge();
}
void ContextShadow::drawRectShadowWithoutTiling(GraphicsContext* context, const IntRect& shadowRect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius, float alpha)
{
beginShadowLayer(context, shadowRect);
if (!m_layerContext)
return;
Path path;
path.addRoundedRect(shadowRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
appendWebCorePathToCairoContext(m_layerContext, path);
cairo_set_source_rgba(m_layerContext, 0, 0, 0, alpha);
cairo_fill(m_layerContext);
endShadowLayer(context);
}
static inline FloatPoint getPhase(const FloatRect& dest, const FloatRect& tile)
{
FloatPoint phase = dest.location();
phase.move(-tile.x(), -tile.y());
return phase;
}
void ContextShadow::drawRectShadow(GraphicsContext* context, const IntRect& rect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius)
{
float radiusTwice = m_blurDistance * 2;
int internalShadowWidth = radiusTwice + max(topLeftRadius.width(), bottomLeftRadius.width()) +
max(topRightRadius.width(), bottomRightRadius.width());
int internalShadowHeight = radiusTwice + max(topLeftRadius.height(), topRightRadius.height()) +
max(bottomLeftRadius.height(), bottomRightRadius.height());
cairo_t* cr = context->platformContext()->cr();
if ((!context->getCTM().isIdentityOrTranslationOrFlipped()) || (internalShadowWidth > rect.width())
|| (internalShadowHeight > rect.height()) || (m_type != BlurShadow)) {
drawRectShadowWithoutTiling(context, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha());
return;
}
IntSize shadowBufferSize = IntSize(rect.width() + radiusTwice, rect.height() + radiusTwice);
FloatRect shadowRect = FloatRect(rect.location(), shadowBufferSize);
shadowRect.move(- m_blurDistance, - m_blurDistance);
int sideTileWidth = 1;
IntSize shadowTemplateSize = IntSize(sideTileWidth + radiusTwice + internalShadowWidth,
sideTileWidth + radiusTwice + internalShadowHeight);
double x1, x2, y1, y2;
cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
calculateLayerBoundingRect(context, shadowRect, IntRect(x1, y1, x2 - x1, y2 - y1));
if ((shadowTemplateSize.width() * shadowTemplateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
drawRectShadowWithoutTiling(context, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha());
return;
}
shadowRect.move(m_offset.width(), m_offset.height());
m_layerImage = getScratchBuffer(shadowTemplateSize);
m_layerContext = cairo_create(m_layerImage);
cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR);
cairo_paint(m_layerContext);
cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER);
IntRect templateRect = IntRect(m_blurDistance, m_blurDistance, shadowTemplateSize.width() - radiusTwice, shadowTemplateSize.height() - radiusTwice);
Path path;
path.addRoundedRect(templateRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
appendWebCorePathToCairoContext(m_layerContext, path);
cairo_set_source_rgba(m_layerContext, 0, 0, 0, context->getAlpha());
cairo_fill(m_layerContext);
cairo_surface_flush(m_layerImage);
blurLayerImage(cairo_image_surface_get_data(m_layerImage), shadowTemplateSize, cairo_image_surface_get_stride(m_layerImage));
cairo_surface_mark_dirty(m_layerImage);
cairo_set_operator(m_layerContext, CAIRO_OPERATOR_IN);
setSourceRGBAFromColor(m_layerContext, m_color);
cairo_paint(m_layerContext);
cairo_destroy(m_layerContext);
m_layerContext = 0;
shadowRect.inflate(-radiusTwice);
if (!shadowRect.isEmpty()) {
cairo_save(cr);
path.clear();
path.addRoundedRect(shadowRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
appendWebCorePathToCairoContext(cr, path);
setSourceRGBAFromColor(cr, m_color);
cairo_fill(cr);
cairo_restore(cr);
}
shadowRect.inflate(radiusTwice);
FloatRect tileRect = FloatRect(radiusTwice + topLeftRadius.width(), 0, sideTileWidth, radiusTwice);
FloatRect destRect = tileRect;
destRect.move(shadowRect.x(), shadowRect.y());
destRect.setWidth(shadowRect.width() - topLeftRadius.width() - topRightRadius.width() - m_blurDistance * 4);
FloatPoint phase = getPhase(destRect, tileRect);
AffineTransform patternTransform;
patternTransform.makeIdentity();
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(radiusTwice + bottomLeftRadius.width(), shadowTemplateSize.height() - radiusTwice, sideTileWidth, radiusTwice);
destRect = tileRect;
destRect.move(shadowRect.x(), shadowRect.y() + radiusTwice + rect.height() - shadowTemplateSize.height());
destRect.setWidth(shadowRect.width() - bottomLeftRadius.width() - bottomRightRadius.width() - m_blurDistance * 4);
phase = getPhase(destRect, tileRect);
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice, radiusTwice + topRightRadius.height(), radiusTwice, sideTileWidth);
destRect = tileRect;
destRect.move(shadowRect.x() + radiusTwice + rect.width() - shadowTemplateSize.width(), shadowRect.y());
destRect.setHeight(shadowRect.height() - topRightRadius.height() - bottomRightRadius.height() - m_blurDistance * 4);
phase = getPhase(destRect, tileRect);
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(0, radiusTwice + topLeftRadius.height(), radiusTwice, sideTileWidth);
destRect = tileRect;
destRect.move(shadowRect.x(), shadowRect.y());
destRect.setHeight(shadowRect.height() - topLeftRadius.height() - bottomLeftRadius.height() - m_blurDistance * 4);
phase = FloatPoint(destRect.x() - tileRect.x(), destRect.y() - tileRect.y());
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(0, 0, radiusTwice + topLeftRadius.width(), radiusTwice + topLeftRadius.height());
destRect = tileRect;
destRect.move(shadowRect.x(), shadowRect.y());
phase = getPhase(destRect, tileRect);
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - topRightRadius.width(), 0, radiusTwice + topRightRadius.width(),
radiusTwice + topRightRadius.height());
destRect = tileRect;
destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice, shadowRect.y());
phase = getPhase(destRect, tileRect);
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - bottomRightRadius.width(),
shadowTemplateSize.height() - radiusTwice - bottomRightRadius.height(),
radiusTwice + bottomRightRadius.width(), radiusTwice + bottomRightRadius.height());
destRect = tileRect;
destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice,
shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice);
phase = getPhase(destRect, tileRect);
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
tileRect = FloatRect(0, shadowTemplateSize.height() - radiusTwice - bottomLeftRadius.height(),
radiusTwice + bottomLeftRadius.width(), radiusTwice + bottomLeftRadius.height());
destRect = tileRect;
destRect.move(shadowRect.x(), shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice);
phase = getPhase(destRect, tileRect);
drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
scheduleScratchBufferPurge();
}
}