1/* 2 * Copyright (C) 2011 Igalia S.L. 3 * Copyright (c) 2008, Google Inc. All rights reserved. 4 * Copyright (c) 2012, Intel Corporation 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#include "config.h" 29#include "PlatformContextCairo.h" 30 31#include "CairoUtilities.h" 32#include "Gradient.h" 33#include "GraphicsContext.h" 34#include "OwnPtrCairo.h" 35#include "Pattern.h" 36#include <cairo.h> 37 38namespace WebCore { 39 40// In Cairo image masking is immediate, so to emulate image clipping we must save masking 41// details as part of the context state and apply them during platform restore. 42class ImageMaskInformation { 43public: 44 void update(cairo_surface_t* maskSurface, const FloatRect& maskRect) 45 { 46 m_maskSurface = maskSurface; 47 m_maskRect = maskRect; 48 } 49 50 bool isValid() const { return m_maskSurface; } 51 cairo_surface_t* maskSurface() const { return m_maskSurface.get(); } 52 const FloatRect& maskRect() const { return m_maskRect; } 53 54private: 55 RefPtr<cairo_surface_t> m_maskSurface; 56 FloatRect m_maskRect; 57}; 58 59 60// Encapsulates the additional painting state information we store for each 61// pushed graphics state. 62class PlatformContextCairo::State { 63public: 64 State() 65 : m_globalAlpha(1) 66 , m_imageInterpolationQuality(InterpolationDefault) 67 { 68 } 69 70 State(const State& state) 71 : m_globalAlpha(state.m_globalAlpha) 72 , m_imageInterpolationQuality(state.m_imageInterpolationQuality) 73 { 74 // We do not copy m_imageMaskInformation because otherwise it would be applied 75 // more than once during subsequent calls to restore(). 76 } 77 78 ImageMaskInformation m_imageMaskInformation; 79 float m_globalAlpha; 80 InterpolationQuality m_imageInterpolationQuality; 81}; 82 83PlatformContextCairo::PlatformContextCairo(cairo_t* cr) 84 : m_cr(cr) 85{ 86 m_stateStack.append(State()); 87 m_state = &m_stateStack.last(); 88} 89 90void PlatformContextCairo::restore() 91{ 92 const ImageMaskInformation& maskInformation = m_state->m_imageMaskInformation; 93 if (maskInformation.isValid()) { 94 const FloatRect& maskRect = maskInformation.maskRect(); 95 cairo_pop_group_to_source(m_cr.get()); 96 cairo_mask_surface(m_cr.get(), maskInformation.maskSurface(), maskRect.x(), maskRect.y()); 97 } 98 99 m_stateStack.removeLast(); 100 ASSERT(!m_stateStack.isEmpty()); 101 m_state = &m_stateStack.last(); 102 103 cairo_restore(m_cr.get()); 104} 105 106PlatformContextCairo::~PlatformContextCairo() 107{ 108} 109 110void PlatformContextCairo::save() 111{ 112 m_stateStack.append(State(*m_state)); 113 m_state = &m_stateStack.last(); 114 115 cairo_save(m_cr.get()); 116} 117 118void PlatformContextCairo::pushImageMask(cairo_surface_t* surface, const FloatRect& rect) 119{ 120 // We must call savePlatformState at least once before we can use image masking, 121 // since we actually apply the mask in restorePlatformState. 122 ASSERT(!m_stateStack.isEmpty()); 123 m_state->m_imageMaskInformation.update(surface, rect); 124 125 // Cairo doesn't support the notion of an image clip, so we push a group here 126 // and then paint it to the surface with an image mask (which is an immediate 127 // operation) during restorePlatformState. 128 129 // We want to allow the clipped elements to composite with the surface as it 130 // is now, but they are isolated in another group. To make this work, we're 131 // going to blit the current surface contents onto the new group once we push it. 132 cairo_surface_t* currentTarget = cairo_get_target(m_cr.get()); 133 cairo_surface_flush(currentTarget); 134 135 // Pushing a new group ensures that only things painted after this point are clipped. 136 cairo_push_group(m_cr.get()); 137 cairo_set_operator(m_cr.get(), CAIRO_OPERATOR_SOURCE); 138 139 cairo_set_source_surface(m_cr.get(), currentTarget, 0, 0); 140 cairo_rectangle(m_cr.get(), rect.x(), rect.y(), rect.width(), rect.height()); 141 cairo_fill(m_cr.get()); 142} 143 144static void drawPatternToCairoContext(cairo_t* cr, cairo_pattern_t* pattern, const FloatRect& destRect, float alpha) 145{ 146 cairo_translate(cr, destRect.x(), destRect.y()); 147 cairo_set_source(cr, pattern); 148 cairo_rectangle(cr, 0, 0, destRect.width(), destRect.height()); 149 150 if (alpha < 1) { 151 cairo_clip(cr); 152 cairo_paint_with_alpha(cr, alpha); 153 } else 154 cairo_fill(cr); 155} 156 157void PlatformContextCairo::drawSurfaceToContext(cairo_surface_t* surface, const FloatRect& destRect, const FloatRect& originalSrcRect, GraphicsContext* context) 158{ 159 // Avoid invalid cairo matrix with small values. 160 if (std::fabs(destRect.width()) < 0.5f || std::fabs(destRect.height()) < 0.5f) 161 return; 162 163 FloatRect srcRect = originalSrcRect; 164 165 // We need to account for negative source dimensions by flipping the rectangle. 166 if (originalSrcRect.width() < 0) { 167 srcRect.setX(originalSrcRect.x() + originalSrcRect.width()); 168 srcRect.setWidth(std::fabs(originalSrcRect.width())); 169 } 170 if (originalSrcRect.height() < 0) { 171 srcRect.setY(originalSrcRect.y() + originalSrcRect.height()); 172 srcRect.setHeight(std::fabs(originalSrcRect.height())); 173 } 174 175 RefPtr<cairo_surface_t> patternSurface = surface; 176 float leftPadding = 0; 177 float topPadding = 0; 178 if (srcRect.x() || srcRect.y() || srcRect.size() != cairoSurfaceSize(surface)) { 179 // Cairo subsurfaces don't support floating point boundaries well, so we expand the rectangle. 180 IntRect expandedSrcRect(enclosingIntRect(srcRect)); 181 182 // We use a subsurface here so that we don't end up sampling outside the originalSrcRect rectangle. 183 // See https://bugs.webkit.org/show_bug.cgi?id=58309 184 patternSurface = adoptRef(cairo_surface_create_for_rectangle(surface, expandedSrcRect.x(), 185 expandedSrcRect.y(), expandedSrcRect.width(), expandedSrcRect.height())); 186 187 leftPadding = static_cast<float>(expandedSrcRect.x()) - floorf(srcRect.x()); 188 topPadding = static_cast<float>(expandedSrcRect.y()) - floorf(srcRect.y()); 189 } 190 191 RefPtr<cairo_pattern_t> pattern = adoptRef(cairo_pattern_create_for_surface(patternSurface.get())); 192 193 ASSERT(m_state); 194 switch (m_state->m_imageInterpolationQuality) { 195 case InterpolationNone: 196 case InterpolationLow: 197 cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_FAST); 198 break; 199 case InterpolationMedium: 200 case InterpolationHigh: 201 cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BILINEAR); 202 break; 203 case InterpolationDefault: 204 cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BILINEAR); 205 break; 206 } 207 cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_PAD); 208 209 // The pattern transformation properly scales the pattern for when the source rectangle is a 210 // different size than the destination rectangle. We also account for any offset we introduced 211 // by expanding floating point source rectangle sizes. It's important to take the absolute value 212 // of the scale since the original width and height might be negative. 213 float scaleX = std::fabs(srcRect.width() / destRect.width()); 214 float scaleY = std::fabs(srcRect.height() / destRect.height()); 215 cairo_matrix_t matrix = { scaleX, 0, 0, scaleY, leftPadding, topPadding }; 216 cairo_pattern_set_matrix(pattern.get(), &matrix); 217 218 ShadowBlur& shadow = context->platformContext()->shadowBlur(); 219 if (shadow.type() != ShadowBlur::NoShadow) { 220 if (GraphicsContext* shadowContext = shadow.beginShadowLayer(context, destRect)) { 221 drawPatternToCairoContext(shadowContext->platformContext()->cr(), pattern.get(), destRect, 1); 222 shadow.endShadowLayer(context); 223 } 224 } 225 226 cairo_save(m_cr.get()); 227 drawPatternToCairoContext(m_cr.get(), pattern.get(), destRect, globalAlpha()); 228 cairo_restore(m_cr.get()); 229} 230 231void PlatformContextCairo::setImageInterpolationQuality(InterpolationQuality quality) 232{ 233 ASSERT(m_state); 234 m_state->m_imageInterpolationQuality = quality; 235} 236 237InterpolationQuality PlatformContextCairo::imageInterpolationQuality() const 238{ 239 ASSERT(m_state); 240 return m_state->m_imageInterpolationQuality; 241} 242 243 244float PlatformContextCairo::globalAlpha() const 245{ 246 return m_state->m_globalAlpha; 247} 248 249void PlatformContextCairo::setGlobalAlpha(float globalAlpha) 250{ 251 m_state->m_globalAlpha = globalAlpha; 252} 253 254static inline void reduceSourceByAlpha(cairo_t* cr, float alpha) 255{ 256 if (alpha >= 1) 257 return; 258 cairo_push_group(cr); 259 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 260 cairo_paint_with_alpha(cr, alpha); 261 cairo_pop_group_to_source(cr); 262} 263 264static void prepareCairoContextSource(cairo_t* cr, Pattern* pattern, Gradient* gradient, const Color& color, float globalAlpha) 265{ 266 if (pattern) { 267 RefPtr<cairo_pattern_t> cairoPattern(adoptRef(pattern->createPlatformPattern(AffineTransform()))); 268 cairo_set_source(cr, cairoPattern.get()); 269 reduceSourceByAlpha(cr, globalAlpha); 270 } else if (gradient) 271 cairo_set_source(cr, gradient->platformGradient(globalAlpha)); 272 else { // Solid color source. 273 if (globalAlpha < 1) 274 setSourceRGBAFromColor(cr, colorWithOverrideAlpha(color.rgb(), color.alpha() / 255.f * globalAlpha)); 275 else 276 setSourceRGBAFromColor(cr, color); 277 } 278} 279 280void PlatformContextCairo::prepareForFilling(const GraphicsContextState& state, PatternAdjustment patternAdjustment) 281{ 282 cairo_set_fill_rule(m_cr.get(), state.fillRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 283 prepareCairoContextSource(m_cr.get(), 284 state.fillPattern.get(), 285 state.fillGradient.get(), 286 state.fillColor, 287 patternAdjustment == AdjustPatternForGlobalAlpha ? globalAlpha() : 1); 288 289 if (state.fillPattern) 290 clipForPatternFilling(state); 291} 292 293void PlatformContextCairo::prepareForStroking(const GraphicsContextState& state, AlphaPreservation alphaPreservation) 294{ 295 prepareCairoContextSource(m_cr.get(), 296 state.strokePattern.get(), 297 state.strokeGradient.get(), 298 state.strokeColor, 299 alphaPreservation == PreserveAlpha ? globalAlpha() : 1); 300} 301 302void PlatformContextCairo::clipForPatternFilling(const GraphicsContextState& state) 303{ 304 ASSERT(state.fillPattern); 305 306 // Hold current cairo path in a variable for restoring it after configuring the pattern clip rectangle. 307 OwnPtr<cairo_path_t> currentPath = adoptPtr(cairo_copy_path(m_cr.get())); 308 cairo_new_path(m_cr.get()); 309 310 // Initialize clipping extent from current cairo clip extents, then shrink if needed according to pattern. 311 // Inspired by GraphicsContextQt::drawRepeatPattern. 312 double x1, y1, x2, y2; 313 cairo_clip_extents(m_cr.get(), &x1, &y1, &x2, &y2); 314 FloatRect clipRect(x1, y1, x2 - x1, y2 - y1); 315 316 Image* patternImage = state.fillPattern->tileImage(); 317 ASSERT(patternImage); 318 const AffineTransform& patternTransform = state.fillPattern->getPatternSpaceTransform(); 319 FloatRect patternRect = patternTransform.mapRect(FloatRect(0, 0, patternImage->width(), patternImage->height())); 320 321 bool repeatX = state.fillPattern->repeatX(); 322 bool repeatY = state.fillPattern->repeatY(); 323 324 if (!repeatX) { 325 clipRect.setX(patternRect.x()); 326 clipRect.setWidth(patternRect.width()); 327 } 328 if (!repeatY) { 329 clipRect.setY(patternRect.y()); 330 clipRect.setHeight(patternRect.height()); 331 } 332 if (!repeatX || !repeatY) { 333 cairo_rectangle(m_cr.get(), clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height()); 334 cairo_clip(m_cr.get()); 335 } 336 337 // Restoring cairo path. 338 cairo_append_path(m_cr.get(), currentPath.get()); 339} 340 341} // namespace WebCore 342