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 COMPUTER, 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 COMPUTER, 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 FloatRect srcRect = originalSrcRect; 160 161 // We need to account for negative source dimensions by flipping the rectangle. 162 if (originalSrcRect.width() < 0) { 163 srcRect.setX(originalSrcRect.x() + originalSrcRect.width()); 164 srcRect.setWidth(std::fabs(originalSrcRect.width())); 165 } 166 if (originalSrcRect.height() < 0) { 167 srcRect.setY(originalSrcRect.y() + originalSrcRect.height()); 168 srcRect.setHeight(std::fabs(originalSrcRect.height())); 169 } 170 171 // Cairo subsurfaces don't support floating point boundaries well, so we expand the rectangle. 172 IntRect expandedSrcRect(enclosingIntRect(srcRect)); 173 174 // We use a subsurface here so that we don't end up sampling outside the originalSrcRect rectangle. 175 // See https://bugs.webkit.org/show_bug.cgi?id=58309 176 RefPtr<cairo_surface_t> subsurface = adoptRef(cairo_surface_create_for_rectangle( 177 surface, expandedSrcRect.x(), expandedSrcRect.y(), expandedSrcRect.width(), expandedSrcRect.height())); 178 RefPtr<cairo_pattern_t> pattern = adoptRef(cairo_pattern_create_for_surface(subsurface.get())); 179 180 ASSERT(m_state); 181 switch (m_state->m_imageInterpolationQuality) { 182 case InterpolationNone: 183 case InterpolationLow: 184 cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_FAST); 185 break; 186 case InterpolationMedium: 187 case InterpolationHigh: 188 cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BILINEAR); 189 break; 190 case InterpolationDefault: 191 cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BILINEAR); 192 break; 193 } 194 cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_PAD); 195 196 // The pattern transformation properly scales the pattern for when the source rectangle is a 197 // different size than the destination rectangle. We also account for any offset we introduced 198 // by expanding floating point source rectangle sizes. It's important to take the absolute value 199 // of the scale since the original width and height might be negative. 200 float scaleX = std::fabs(srcRect.width() / destRect.width()); 201 float scaleY = std::fabs(srcRect.height() / destRect.height()); 202 float leftPadding = static_cast<float>(expandedSrcRect.x()) - floorf(srcRect.x()); 203 float topPadding = static_cast<float>(expandedSrcRect.y()) - floorf(srcRect.y()); 204 cairo_matrix_t matrix = { scaleX, 0, 0, scaleY, leftPadding, topPadding }; 205 cairo_pattern_set_matrix(pattern.get(), &matrix); 206 207 ShadowBlur& shadow = context->platformContext()->shadowBlur(); 208 if (shadow.type() != ShadowBlur::NoShadow) { 209 if (GraphicsContext* shadowContext = shadow.beginShadowLayer(context, destRect)) { 210 drawPatternToCairoContext(shadowContext->platformContext()->cr(), pattern.get(), destRect, 1); 211 shadow.endShadowLayer(context); 212 } 213 } 214 215 cairo_save(m_cr.get()); 216 drawPatternToCairoContext(m_cr.get(), pattern.get(), destRect, globalAlpha()); 217 cairo_restore(m_cr.get()); 218} 219 220void PlatformContextCairo::setImageInterpolationQuality(InterpolationQuality quality) 221{ 222 ASSERT(m_state); 223 m_state->m_imageInterpolationQuality = quality; 224} 225 226InterpolationQuality PlatformContextCairo::imageInterpolationQuality() const 227{ 228 ASSERT(m_state); 229 return m_state->m_imageInterpolationQuality; 230} 231 232 233float PlatformContextCairo::globalAlpha() const 234{ 235 return m_state->m_globalAlpha; 236} 237 238void PlatformContextCairo::setGlobalAlpha(float globalAlpha) 239{ 240 m_state->m_globalAlpha = globalAlpha; 241} 242 243static inline void reduceSourceByAlpha(cairo_t* cr, float alpha) 244{ 245 if (alpha >= 1) 246 return; 247 cairo_push_group(cr); 248 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 249 cairo_paint_with_alpha(cr, alpha); 250 cairo_pop_group_to_source(cr); 251} 252 253static void prepareCairoContextSource(cairo_t* cr, Pattern* pattern, Gradient* gradient, const Color& color, float globalAlpha) 254{ 255 if (pattern) { 256 RefPtr<cairo_pattern_t> cairoPattern(adoptRef(pattern->createPlatformPattern(AffineTransform()))); 257 cairo_set_source(cr, cairoPattern.get()); 258 reduceSourceByAlpha(cr, globalAlpha); 259 } else if (gradient) 260 cairo_set_source(cr, gradient->platformGradient(globalAlpha)); 261 else { // Solid color source. 262 if (globalAlpha < 1) 263 setSourceRGBAFromColor(cr, colorWithOverrideAlpha(color.rgb(), color.alpha() / 255.f * globalAlpha)); 264 else 265 setSourceRGBAFromColor(cr, color); 266 } 267} 268 269void PlatformContextCairo::prepareForFilling(const GraphicsContextState& state, PatternAdjustment patternAdjustment) 270{ 271 cairo_set_fill_rule(m_cr.get(), state.fillRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 272 prepareCairoContextSource(m_cr.get(), 273 state.fillPattern.get(), 274 state.fillGradient.get(), 275 state.fillColor, 276 patternAdjustment == AdjustPatternForGlobalAlpha ? globalAlpha() : 1); 277 278 if (state.fillPattern) 279 clipForPatternFilling(state); 280} 281 282void PlatformContextCairo::prepareForStroking(const GraphicsContextState& state, AlphaPreservation alphaPreservation) 283{ 284 prepareCairoContextSource(m_cr.get(), 285 state.strokePattern.get(), 286 state.strokeGradient.get(), 287 state.strokeColor, 288 alphaPreservation == PreserveAlpha ? globalAlpha() : 1); 289} 290 291void PlatformContextCairo::clipForPatternFilling(const GraphicsContextState& state) 292{ 293 ASSERT(state.fillPattern); 294 295 // Hold current cairo path in a variable for restoring it after configuring the pattern clip rectangle. 296 OwnPtr<cairo_path_t> currentPath = adoptPtr(cairo_copy_path(m_cr.get())); 297 cairo_new_path(m_cr.get()); 298 299 // Initialize clipping extent from current cairo clip extents, then shrink if needed according to pattern. 300 // Inspired by GraphicsContextQt::drawRepeatPattern. 301 double x1, y1, x2, y2; 302 cairo_clip_extents(m_cr.get(), &x1, &y1, &x2, &y2); 303 FloatRect clipRect(x1, y1, x2 - x1, y2 - y1); 304 305 Image* patternImage = state.fillPattern->tileImage(); 306 ASSERT(patternImage); 307 const AffineTransform& patternTransform = state.fillPattern->getPatternSpaceTransform(); 308 FloatRect patternRect = patternTransform.mapRect(FloatRect(0, 0, patternImage->width(), patternImage->height())); 309 310 bool repeatX = state.fillPattern->repeatX(); 311 bool repeatY = state.fillPattern->repeatY(); 312 313 if (!repeatX) { 314 clipRect.setX(patternRect.x()); 315 clipRect.setWidth(patternRect.width()); 316 } 317 if (!repeatY) { 318 clipRect.setY(patternRect.y()); 319 clipRect.setHeight(patternRect.height()); 320 } 321 if (!repeatX || !repeatY) { 322 cairo_rectangle(m_cr.get(), clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height()); 323 cairo_clip(m_cr.get()); 324 } 325 326 // Restoring cairo path. 327 cairo_append_path(m_cr.get(), currentPath.get()); 328} 329 330} // namespace WebCore 331