Home | History | Annotate | Download | only in cairo
      1 /*
      2  * Copyright (C) 2010 Sencha, Inc.
      3  * Copyright (C) 2010 Igalia S.L.
      4  *
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include "config.h"
     30 #include "ContextShadow.h"
     31 
     32 #include "AffineTransform.h"
     33 #include "CairoUtilities.h"
     34 #include "GraphicsContext.h"
     35 #include "OwnPtrCairo.h"
     36 #include "Path.h"
     37 #include "PlatformContextCairo.h"
     38 #include "Timer.h"
     39 #include <cairo.h>
     40 
     41 using WTF::max;
     42 
     43 namespace WebCore {
     44 
     45 static RefPtr<cairo_surface_t> gScratchBuffer;
     46 static void purgeScratchBuffer()
     47 {
     48     gScratchBuffer.clear();
     49 }
     50 
     51 // ContextShadow needs a scratch image as the buffer for the blur filter.
     52 // Instead of creating and destroying the buffer for every operation,
     53 // we create a buffer which will be automatically purged via a timer.
     54 class PurgeScratchBufferTimer : public TimerBase {
     55 private:
     56     virtual void fired() { purgeScratchBuffer(); }
     57 };
     58 static PurgeScratchBufferTimer purgeScratchBufferTimer;
     59 static void scheduleScratchBufferPurge()
     60 {
     61     if (purgeScratchBufferTimer.isActive())
     62         purgeScratchBufferTimer.stop();
     63     purgeScratchBufferTimer.startOneShot(2);
     64 }
     65 
     66 static cairo_surface_t* getScratchBuffer(const IntSize& size)
     67 {
     68     int width = size.width();
     69     int height = size.height();
     70     int scratchWidth = gScratchBuffer.get() ? cairo_image_surface_get_width(gScratchBuffer.get()) : 0;
     71     int scratchHeight = gScratchBuffer.get() ? cairo_image_surface_get_height(gScratchBuffer.get()) : 0;
     72 
     73     // We do not need to recreate the buffer if the current buffer is large enough.
     74     if (gScratchBuffer.get() && scratchWidth >= width && scratchHeight >= height)
     75         return gScratchBuffer.get();
     76 
     77     purgeScratchBuffer();
     78 
     79     // Round to the nearest 32 pixels so we do not grow the buffer for similar sized requests.
     80     width = (1 + (width >> 5)) << 5;
     81     height = (1 + (height >> 5)) << 5;
     82     gScratchBuffer = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height));
     83     return gScratchBuffer.get();
     84 }
     85 
     86 PlatformContext ContextShadow::beginShadowLayer(GraphicsContext* context, const FloatRect& layerArea)
     87 {
     88     adjustBlurDistance(context);
     89 
     90     double x1, x2, y1, y2;
     91     cairo_clip_extents(context->platformContext()->cr(), &x1, &y1, &x2, &y2);
     92     IntRect layerRect = calculateLayerBoundingRect(context, layerArea, IntRect(x1, y1, x2 - x1, y2 - y1));
     93 
     94     // Don't paint if we are totally outside the clip region.
     95     if (layerRect.isEmpty())
     96         return 0;
     97 
     98     m_layerImage = getScratchBuffer(layerRect.size());
     99     m_layerContext = cairo_create(m_layerImage);
    100 
    101     // Always clear the surface first.
    102     cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR);
    103     cairo_paint(m_layerContext);
    104     cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER);
    105 
    106     cairo_translate(m_layerContext, m_layerContextTranslation.x(), m_layerContextTranslation.y());
    107     return m_layerContext;
    108 }
    109 
    110 void ContextShadow::endShadowLayer(GraphicsContext* context)
    111 {
    112     cairo_destroy(m_layerContext);
    113     m_layerContext = 0;
    114 
    115     if (m_type == BlurShadow) {
    116         cairo_surface_flush(m_layerImage);
    117         blurLayerImage(cairo_image_surface_get_data(m_layerImage),
    118                        IntSize(cairo_image_surface_get_width(m_layerImage), cairo_image_surface_get_height(m_layerImage)),
    119                        cairo_image_surface_get_stride(m_layerImage));
    120         cairo_surface_mark_dirty(m_layerImage);
    121     }
    122 
    123     cairo_t* cr = context->platformContext()->cr();
    124     cairo_save(cr);
    125     setSourceRGBAFromColor(cr, m_color);
    126     cairo_mask_surface(cr, m_layerImage, m_layerOrigin.x(), m_layerOrigin.y());
    127     cairo_restore(cr);
    128 
    129     // Schedule a purge of the scratch buffer. We do not need to destroy the surface.
    130     scheduleScratchBufferPurge();
    131 }
    132 
    133 void ContextShadow::drawRectShadowWithoutTiling(GraphicsContext* context, const IntRect& shadowRect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius, float alpha)
    134 {
    135     beginShadowLayer(context, shadowRect);
    136 
    137     if (!m_layerContext)
    138         return;
    139 
    140     Path path;
    141     path.addRoundedRect(shadowRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
    142 
    143     appendWebCorePathToCairoContext(m_layerContext, path);
    144     cairo_set_source_rgba(m_layerContext, 0, 0, 0, alpha);
    145     cairo_fill(m_layerContext);
    146 
    147     endShadowLayer(context);
    148 }
    149 
    150 static inline FloatPoint getPhase(const FloatRect& dest, const FloatRect& tile)
    151 {
    152     FloatPoint phase = dest.location();
    153     phase.move(-tile.x(), -tile.y());
    154 
    155     return phase;
    156 }
    157 
    158 /*
    159   This function uses tiling to improve the performance of the shadow
    160   drawing of rounded rectangles. The code basically does the following
    161   steps:
    162 
    163      1. Calculate the size of the shadow template, a rectangle that
    164      contains all the necessary tiles to draw the complete shadow.
    165 
    166      2. If that size is smaller than the real rectangle render the new
    167      template rectangle and its shadow in a new surface, in other case
    168      render the shadow of the real rectangle in the destination
    169      surface.
    170 
    171      3. Calculate the sizes and positions of the tiles and their
    172      destinations and use drawPattern to render the final shadow. The
    173      code divides the rendering in 8 tiles:
    174 
    175         1 | 2 | 3
    176        -----------
    177         4 |   | 5
    178        -----------
    179         6 | 7 | 8
    180 
    181      The corners are directly copied from the template rectangle to the
    182      real one and the side tiles are 1 pixel width, we use them as
    183 
    184      tiles to cover the destination side. The corner tiles are bigger
    185      than just the side of the rounded corner, we need to increase it
    186      because the modifications caused by the corner over the blur
    187      effect. We fill the central part with solid color to complete the
    188      shadow.
    189  */
    190 void ContextShadow::drawRectShadow(GraphicsContext* context, const IntRect& rect, const IntSize& topLeftRadius, const IntSize& topRightRadius, const IntSize& bottomLeftRadius, const IntSize& bottomRightRadius)
    191 {
    192 
    193     float radiusTwice = m_blurDistance * 2;
    194 
    195     // Find the space the corners need inside the rect for its shadows.
    196     int internalShadowWidth = radiusTwice + max(topLeftRadius.width(), bottomLeftRadius.width()) +
    197         max(topRightRadius.width(), bottomRightRadius.width());
    198     int internalShadowHeight = radiusTwice + max(topLeftRadius.height(), topRightRadius.height()) +
    199         max(bottomLeftRadius.height(), bottomRightRadius.height());
    200 
    201     cairo_t* cr = context->platformContext()->cr();
    202 
    203     // drawShadowedRect still does not work with rotations.
    204     // https://bugs.webkit.org/show_bug.cgi?id=45042
    205     if ((!context->getCTM().isIdentityOrTranslationOrFlipped()) || (internalShadowWidth > rect.width())
    206         || (internalShadowHeight > rect.height()) || (m_type != BlurShadow)) {
    207         drawRectShadowWithoutTiling(context, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha());
    208         return;
    209     }
    210 
    211     // Calculate size of the template shadow buffer.
    212     IntSize shadowBufferSize = IntSize(rect.width() + radiusTwice, rect.height() + radiusTwice);
    213 
    214     // Determine dimensions of shadow rect.
    215     FloatRect shadowRect = FloatRect(rect.location(), shadowBufferSize);
    216     shadowRect.move(- m_blurDistance, - m_blurDistance);
    217 
    218     // Size of the tiling side.
    219     int sideTileWidth = 1;
    220 
    221     // The length of a side of the buffer is the enough space for four blur radii,
    222     // the radii of the corners, and then 1 pixel to draw the side tiles.
    223     IntSize shadowTemplateSize = IntSize(sideTileWidth + radiusTwice + internalShadowWidth,
    224                                          sideTileWidth + radiusTwice + internalShadowHeight);
    225 
    226     // Reduce the size of what we have to draw with the clip area.
    227     double x1, x2, y1, y2;
    228     cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
    229     calculateLayerBoundingRect(context, shadowRect, IntRect(x1, y1, x2 - x1, y2 - y1));
    230 
    231     if ((shadowTemplateSize.width() * shadowTemplateSize.height() > m_sourceRect.width() * m_sourceRect.height())) {
    232         drawRectShadowWithoutTiling(context, rect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, context->getAlpha());
    233         return;
    234     }
    235 
    236     shadowRect.move(m_offset.width(), m_offset.height());
    237 
    238     m_layerImage = getScratchBuffer(shadowTemplateSize);
    239 
    240     // Draw shadow into a new ImageBuffer.
    241     m_layerContext = cairo_create(m_layerImage);
    242 
    243     // Clear the surface first.
    244     cairo_set_operator(m_layerContext, CAIRO_OPERATOR_CLEAR);
    245     cairo_paint(m_layerContext);
    246     cairo_set_operator(m_layerContext, CAIRO_OPERATOR_OVER);
    247 
    248     // Draw the rectangle.
    249     IntRect templateRect = IntRect(m_blurDistance, m_blurDistance, shadowTemplateSize.width() - radiusTwice, shadowTemplateSize.height() - radiusTwice);
    250     Path path;
    251     path.addRoundedRect(templateRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
    252     appendWebCorePathToCairoContext(m_layerContext, path);
    253 
    254     cairo_set_source_rgba(m_layerContext, 0, 0, 0, context->getAlpha());
    255     cairo_fill(m_layerContext);
    256 
    257     // Blur the image.
    258     cairo_surface_flush(m_layerImage);
    259     blurLayerImage(cairo_image_surface_get_data(m_layerImage), shadowTemplateSize, cairo_image_surface_get_stride(m_layerImage));
    260     cairo_surface_mark_dirty(m_layerImage);
    261 
    262     // Mask the image with the shadow color.
    263     cairo_set_operator(m_layerContext, CAIRO_OPERATOR_IN);
    264     setSourceRGBAFromColor(m_layerContext, m_color);
    265     cairo_paint(m_layerContext);
    266 
    267     cairo_destroy(m_layerContext);
    268     m_layerContext = 0;
    269 
    270     // Fill the internal part of the shadow.
    271     shadowRect.inflate(-radiusTwice);
    272     if (!shadowRect.isEmpty()) {
    273         cairo_save(cr);
    274         path.clear();
    275         path.addRoundedRect(shadowRect, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
    276         appendWebCorePathToCairoContext(cr, path);
    277         setSourceRGBAFromColor(cr, m_color);
    278         cairo_fill(cr);
    279         cairo_restore(cr);
    280     }
    281     shadowRect.inflate(radiusTwice);
    282 
    283     // Draw top side.
    284     FloatRect tileRect = FloatRect(radiusTwice + topLeftRadius.width(), 0, sideTileWidth, radiusTwice);
    285     FloatRect destRect = tileRect;
    286     destRect.move(shadowRect.x(), shadowRect.y());
    287     destRect.setWidth(shadowRect.width() - topLeftRadius.width() - topRightRadius.width() - m_blurDistance * 4);
    288     FloatPoint phase = getPhase(destRect, tileRect);
    289     AffineTransform patternTransform;
    290     patternTransform.makeIdentity();
    291     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    292 
    293     // Draw the bottom side.
    294     tileRect = FloatRect(radiusTwice + bottomLeftRadius.width(), shadowTemplateSize.height() - radiusTwice, sideTileWidth, radiusTwice);
    295     destRect = tileRect;
    296     destRect.move(shadowRect.x(), shadowRect.y() + radiusTwice + rect.height() - shadowTemplateSize.height());
    297     destRect.setWidth(shadowRect.width() - bottomLeftRadius.width() - bottomRightRadius.width() - m_blurDistance * 4);
    298     phase = getPhase(destRect, tileRect);
    299     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    300 
    301     // Draw the right side.
    302     tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice, radiusTwice + topRightRadius.height(), radiusTwice, sideTileWidth);
    303     destRect = tileRect;
    304     destRect.move(shadowRect.x() + radiusTwice + rect.width() - shadowTemplateSize.width(), shadowRect.y());
    305     destRect.setHeight(shadowRect.height() - topRightRadius.height() - bottomRightRadius.height() - m_blurDistance * 4);
    306     phase = getPhase(destRect, tileRect);
    307     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    308 
    309     // Draw the left side.
    310     tileRect = FloatRect(0, radiusTwice + topLeftRadius.height(), radiusTwice, sideTileWidth);
    311     destRect = tileRect;
    312     destRect.move(shadowRect.x(), shadowRect.y());
    313     destRect.setHeight(shadowRect.height() - topLeftRadius.height() - bottomLeftRadius.height() - m_blurDistance * 4);
    314     phase = FloatPoint(destRect.x() - tileRect.x(), destRect.y() - tileRect.y());
    315     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    316 
    317     // Draw the top left corner.
    318     tileRect = FloatRect(0, 0, radiusTwice + topLeftRadius.width(), radiusTwice + topLeftRadius.height());
    319     destRect = tileRect;
    320     destRect.move(shadowRect.x(), shadowRect.y());
    321     phase = getPhase(destRect, tileRect);
    322     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    323 
    324     // Draw the top right corner.
    325     tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - topRightRadius.width(), 0, radiusTwice + topRightRadius.width(),
    326                          radiusTwice + topRightRadius.height());
    327     destRect = tileRect;
    328     destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice, shadowRect.y());
    329     phase = getPhase(destRect, tileRect);
    330     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    331 
    332     // Draw the bottom right corner.
    333     tileRect = FloatRect(shadowTemplateSize.width() - radiusTwice - bottomRightRadius.width(),
    334                          shadowTemplateSize.height() - radiusTwice - bottomRightRadius.height(),
    335                          radiusTwice + bottomRightRadius.width(), radiusTwice + bottomRightRadius.height());
    336     destRect = tileRect;
    337     destRect.move(shadowRect.x() + rect.width() - shadowTemplateSize.width() + radiusTwice,
    338                   shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice);
    339     phase = getPhase(destRect, tileRect);
    340     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    341 
    342     // Draw the bottom left corner.
    343     tileRect = FloatRect(0, shadowTemplateSize.height() - radiusTwice - bottomLeftRadius.height(),
    344                          radiusTwice + bottomLeftRadius.width(), radiusTwice + bottomLeftRadius.height());
    345     destRect = tileRect;
    346     destRect.move(shadowRect.x(), shadowRect.y() + rect.height() - shadowTemplateSize.height() + radiusTwice);
    347     phase = getPhase(destRect, tileRect);
    348     drawPatternToCairoContext(cr, m_layerImage, shadowTemplateSize, tileRect, patternTransform, phase, CAIRO_OPERATOR_OVER, destRect);
    349 
    350     // Schedule a purge of the scratch buffer.
    351     scheduleScratchBufferPurge();
    352 }
    353 
    354 }
    355