PlatformContextCairo.cpp [plain text]
#include "config.h"
#include "PlatformContextCairo.h"
#if USE(CAIRO)
#include "CairoUtilities.h"
#include "Gradient.h"
#include "GraphicsContext.h"
#include "Pattern.h"
#include <cairo.h>
namespace WebCore {
class ImageMaskInformation {
public:
void update(cairo_surface_t* maskSurface, const FloatRect& maskRect)
{
m_maskSurface = maskSurface;
m_maskRect = maskRect;
}
bool isValid() const { return m_maskSurface; }
cairo_surface_t* maskSurface() const { return m_maskSurface.get(); }
const FloatRect& maskRect() const { return m_maskRect; }
private:
RefPtr<cairo_surface_t> m_maskSurface;
FloatRect m_maskRect;
};
class PlatformContextCairo::State {
public:
State()
: m_globalAlpha(1)
, m_imageInterpolationQuality(InterpolationDefault)
{
}
State(const State& state)
: m_globalAlpha(state.m_globalAlpha)
, m_imageInterpolationQuality(state.m_imageInterpolationQuality)
{
}
ImageMaskInformation m_imageMaskInformation;
float m_globalAlpha;
InterpolationQuality m_imageInterpolationQuality;
};
PlatformContextCairo::PlatformContextCairo(cairo_t* cr)
: m_cr(cr)
{
m_stateStack.append(State());
m_state = &m_stateStack.last();
}
void PlatformContextCairo::restore()
{
const ImageMaskInformation& maskInformation = m_state->m_imageMaskInformation;
if (maskInformation.isValid()) {
const FloatRect& maskRect = maskInformation.maskRect();
cairo_pop_group_to_source(m_cr.get());
cairo_mask_surface(m_cr.get(), maskInformation.maskSurface(), maskRect.x(), maskRect.y());
}
m_stateStack.removeLast();
ASSERT(!m_stateStack.isEmpty());
m_state = &m_stateStack.last();
cairo_restore(m_cr.get());
}
PlatformContextCairo::~PlatformContextCairo()
{
}
void PlatformContextCairo::save()
{
m_stateStack.append(State(*m_state));
m_state = &m_stateStack.last();
cairo_save(m_cr.get());
}
void PlatformContextCairo::pushImageMask(cairo_surface_t* surface, const FloatRect& rect)
{
ASSERT(!m_stateStack.isEmpty());
m_state->m_imageMaskInformation.update(surface, rect);
cairo_surface_t* currentTarget = cairo_get_target(m_cr.get());
cairo_surface_flush(currentTarget);
cairo_push_group(m_cr.get());
cairo_set_operator(m_cr.get(), CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(m_cr.get(), currentTarget, rect.x(), rect.y());
cairo_translate(m_cr.get(), rect.x(), rect.y());
cairo_rectangle(m_cr.get(), 0, 0, rect.width(), rect.height());
cairo_fill(m_cr.get());
cairo_translate(m_cr.get(), -rect.x(), -rect.y());
}
static void drawPatternToCairoContext(cairo_t* cr, cairo_pattern_t* pattern, const FloatRect& destRect, float alpha)
{
cairo_translate(cr, destRect.x(), destRect.y());
cairo_set_source(cr, pattern);
cairo_rectangle(cr, 0, 0, destRect.width(), destRect.height());
if (alpha < 1) {
cairo_clip(cr);
cairo_paint_with_alpha(cr, alpha);
} else
cairo_fill(cr);
}
void PlatformContextCairo::drawSurfaceToContext(cairo_surface_t* surface, const FloatRect& destRect, const FloatRect& originalSrcRect, GraphicsContext& context)
{
if (std::fabs(destRect.width()) < 0.5f || std::fabs(destRect.height()) < 0.5f)
return;
FloatRect srcRect = originalSrcRect;
if (originalSrcRect.width() < 0) {
srcRect.setX(originalSrcRect.x() + originalSrcRect.width());
srcRect.setWidth(std::fabs(originalSrcRect.width()));
}
if (originalSrcRect.height() < 0) {
srcRect.setY(originalSrcRect.y() + originalSrcRect.height());
srcRect.setHeight(std::fabs(originalSrcRect.height()));
}
RefPtr<cairo_surface_t> patternSurface = surface;
float leftPadding = 0;
float topPadding = 0;
if (srcRect.x() || srcRect.y() || srcRect.size() != cairoSurfaceSize(surface)) {
IntRect expandedSrcRect(enclosingIntRect(srcRect));
patternSurface = adoptRef(cairo_surface_create_for_rectangle(surface, expandedSrcRect.x(),
expandedSrcRect.y(), expandedSrcRect.width(), expandedSrcRect.height()));
leftPadding = static_cast<float>(expandedSrcRect.x()) - floorf(srcRect.x());
topPadding = static_cast<float>(expandedSrcRect.y()) - floorf(srcRect.y());
}
RefPtr<cairo_pattern_t> pattern = adoptRef(cairo_pattern_create_for_surface(patternSurface.get()));
ASSERT(m_state);
switch (m_state->m_imageInterpolationQuality) {
case InterpolationNone:
case InterpolationLow:
cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_FAST);
break;
case InterpolationMedium:
case InterpolationDefault:
cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_GOOD);
break;
case InterpolationHigh:
cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BEST);
break;
}
cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_PAD);
float scaleX = std::fabs(srcRect.width() / destRect.width());
float scaleY = std::fabs(srcRect.height() / destRect.height());
cairo_matrix_t matrix = { scaleX, 0, 0, scaleY, leftPadding, topPadding };
cairo_pattern_set_matrix(pattern.get(), &matrix);
ShadowBlur& shadow = context.platformContext()->shadowBlur();
if (shadow.type() != ShadowBlur::NoShadow) {
if (GraphicsContext* shadowContext = shadow.beginShadowLayer(context, destRect)) {
drawPatternToCairoContext(shadowContext->platformContext()->cr(), pattern.get(), destRect, 1);
shadow.endShadowLayer(context);
}
}
cairo_save(m_cr.get());
drawPatternToCairoContext(m_cr.get(), pattern.get(), destRect, globalAlpha());
cairo_restore(m_cr.get());
}
void PlatformContextCairo::setImageInterpolationQuality(InterpolationQuality quality)
{
ASSERT(m_state);
m_state->m_imageInterpolationQuality = quality;
}
InterpolationQuality PlatformContextCairo::imageInterpolationQuality() const
{
ASSERT(m_state);
return m_state->m_imageInterpolationQuality;
}
float PlatformContextCairo::globalAlpha() const
{
return m_state->m_globalAlpha;
}
void PlatformContextCairo::setGlobalAlpha(float globalAlpha)
{
m_state->m_globalAlpha = globalAlpha;
}
static inline void reduceSourceByAlpha(cairo_t* cr, float alpha)
{
if (alpha >= 1)
return;
cairo_push_group(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint_with_alpha(cr, alpha);
cairo_pop_group_to_source(cr);
}
static void prepareCairoContextSource(cairo_t* cr, Pattern* pattern, Gradient* gradient, const Color& color, float globalAlpha)
{
if (pattern) {
RefPtr<cairo_pattern_t> cairoPattern(adoptRef(pattern->createPlatformPattern(AffineTransform())));
cairo_set_source(cr, cairoPattern.get());
reduceSourceByAlpha(cr, globalAlpha);
} else if (gradient)
cairo_set_source(cr, gradient->platformGradient(globalAlpha));
else { if (globalAlpha < 1)
setSourceRGBAFromColor(cr, colorWithOverrideAlpha(color.rgb(), color.alpha() / 255.f * globalAlpha));
else
setSourceRGBAFromColor(cr, color);
}
}
void PlatformContextCairo::prepareForFilling(const GraphicsContextState& state, PatternAdjustment patternAdjustment)
{
cairo_set_fill_rule(m_cr.get(), state.fillRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
prepareCairoContextSource(m_cr.get(),
state.fillPattern.get(),
state.fillGradient.get(),
state.fillColor,
patternAdjustment == AdjustPatternForGlobalAlpha ? globalAlpha() : 1);
if (state.fillPattern)
clipForPatternFilling(state);
}
void PlatformContextCairo::prepareForStroking(const GraphicsContextState& state, AlphaPreservation alphaPreservation)
{
prepareCairoContextSource(m_cr.get(),
state.strokePattern.get(),
state.strokeGradient.get(),
state.strokeColor,
alphaPreservation == PreserveAlpha ? globalAlpha() : 1);
}
void PlatformContextCairo::clipForPatternFilling(const GraphicsContextState& state)
{
ASSERT(state.fillPattern);
auto currentPath = cairo_copy_path(m_cr.get());
cairo_new_path(m_cr.get());
double x1, y1, x2, y2;
cairo_clip_extents(m_cr.get(), &x1, &y1, &x2, &y2);
FloatRect clipRect(x1, y1, x2 - x1, y2 - y1);
auto& patternImage = state.fillPattern->tileImage();
const AffineTransform& patternTransform = state.fillPattern->getPatternSpaceTransform();
FloatRect patternRect = patternTransform.mapRect(FloatRect(0, 0, patternImage.width(), patternImage.height()));
bool repeatX = state.fillPattern->repeatX();
bool repeatY = state.fillPattern->repeatY();
if (!repeatX) {
clipRect.setX(patternRect.x());
clipRect.setWidth(patternRect.width());
}
if (!repeatY) {
clipRect.setY(patternRect.y());
clipRect.setHeight(patternRect.height());
}
if (!repeatX || !repeatY) {
cairo_rectangle(m_cr.get(), clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
cairo_clip(m_cr.get());
}
cairo_append_path(m_cr.get(), currentPath);
cairo_path_destroy(currentPath);
}
}
#endif // USE(CAIRO)