Home | History | Annotate | Download | only in graphics
      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 "FloatQuad.h"
     34 #include "GraphicsContext.h"
     35 #include <cmath>
     36 #include <wtf/MathExtras.h>
     37 #include <wtf/Noncopyable.h>
     38 
     39 using WTF::min;
     40 using WTF::max;
     41 
     42 namespace WebCore {
     43 
     44 ContextShadow::ContextShadow()
     45     : m_type(NoShadow)
     46     , m_blurDistance(0)
     47     , m_layerContext(0)
     48     , m_shadowsIgnoreTransforms(false)
     49 {
     50 }
     51 
     52 ContextShadow::ContextShadow(const Color& color, float radius, const FloatSize& offset)
     53     : m_color(color)
     54     , m_blurDistance(round(radius))
     55     , m_offset(offset)
     56     , m_layerContext(0)
     57     , m_shadowsIgnoreTransforms(false)
     58 {
     59     // See comments in http://webkit.org/b/40793, it seems sensible
     60     // to follow Skia's limit of 128 pixels of blur radius
     61     m_blurDistance = min(m_blurDistance, 128);
     62 
     63     // The type of shadow is decided by the blur radius, shadow offset, and shadow color.
     64     if (!m_color.isValid() || !color.alpha()) {
     65         // Can't paint the shadow with invalid or invisible color.
     66         m_type = NoShadow;
     67     } else if (radius > 0) {
     68         // Shadow is always blurred, even the offset is zero.
     69         m_type = BlurShadow;
     70     } else if (!m_offset.width() && !m_offset.height()) {
     71         // Without blur and zero offset means the shadow is fully hidden.
     72         m_type = NoShadow;
     73     } else {
     74         m_type = SolidShadow;
     75     }
     76 }
     77 
     78 void ContextShadow::clear()
     79 {
     80     m_type = NoShadow;
     81     m_color = Color();
     82     m_blurDistance = 0;
     83     m_offset = FloatSize();
     84 }
     85 
     86 bool ContextShadow::mustUseContextShadow(GraphicsContext* context)
     87 {
     88     // We can't avoid ContextShadow, since the shadow has blur.
     89     if (m_type == ContextShadow::BlurShadow)
     90         return true;
     91     // We can avoid ContextShadow and optimize, since we're not drawing on a
     92     // canvas and box shadows are affected by the transformation matrix.
     93     if (!shadowsIgnoreTransforms())
     94         return false;
     95     // We can avoid ContextShadow, since there are no transformations to apply to the canvas.
     96     if (context->getCTM().isIdentity())
     97         return false;
     98     // Otherwise, no chance avoiding ContextShadow.
     99     return true;
    100 }
    101 
    102 // Instead of integer division, we use 17.15 for fixed-point division.
    103 static const int BlurSumShift = 15;
    104 
    105 // Check http://www.w3.org/TR/SVG/filters.html#feGaussianBlur.
    106 // As noted in the SVG filter specification, running box blur 3x
    107 // approximates a real gaussian blur nicely.
    108 
    109 void ContextShadow::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
    110 {
    111 #if CPU(BIG_ENDIAN)
    112     int channels[4] = { 0, 3, 2, 0 };
    113 #elif CPU(MIDDLE_ENDIAN)
    114     int channels[4] = { 1, 2, 3, 1 };
    115 #else
    116     int channels[4] = { 3, 0, 1, 3 };
    117 #endif
    118 
    119     int d = max(2, static_cast<int>(floorf((2 / 3.f) * m_blurDistance)));
    120     int dmax = d >> 1;
    121     int dmin = dmax - 1 + (d & 1);
    122     if (dmin < 0)
    123         dmin = 0;
    124 
    125     // Two stages: horizontal and vertical
    126     for (int k = 0; k < 2; ++k) {
    127 
    128         unsigned char* pixels = imageData;
    129         int stride = (!k) ? 4 : rowStride;
    130         int delta = (!k) ? rowStride : 4;
    131         int jfinal = (!k) ? size.height() : size.width();
    132         int dim = (!k) ? size.width() : size.height();
    133 
    134         for (int j = 0; j < jfinal; ++j, pixels += delta) {
    135 
    136             // For each step, we blur the alpha in a channel and store the result
    137             // in another channel for the subsequent step.
    138             // We use sliding window algorithm to accumulate the alpha values.
    139             // This is much more efficient than computing the sum of each pixels
    140             // covered by the box kernel size for each x.
    141 
    142             for (int step = 0; step < 3; ++step) {
    143                 int side1 = (!step) ? dmin : dmax;
    144                 int side2 = (step == 1) ? dmin : dmax;
    145                 int pixelCount = side1 + 1 + side2;
    146                 int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount;
    147                 int ofs = 1 + side2;
    148                 int alpha1 = pixels[channels[step]];
    149                 int alpha2 = pixels[(dim - 1) * stride + channels[step]];
    150                 unsigned char* ptr = pixels + channels[step + 1];
    151                 unsigned char* prev = pixels + stride + channels[step];
    152                 unsigned char* next = pixels + ofs * stride + channels[step];
    153 
    154                 int i;
    155                 int sum = side1 * alpha1 + alpha1;
    156                 int limit = (dim < side2 + 1) ? dim : side2 + 1;
    157                 for (i = 1; i < limit; ++i, prev += stride)
    158                     sum += *prev;
    159                 if (limit <= side2)
    160                     sum += (side2 - limit + 1) * alpha2;
    161 
    162                 limit = (side1 < dim) ? side1 : dim;
    163                 for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
    164                     *ptr = (sum * invCount) >> BlurSumShift;
    165                     sum += ((ofs < dim) ? *next : alpha2) - alpha1;
    166                 }
    167                 prev = pixels + channels[step];
    168                 for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
    169                     *ptr = (sum * invCount) >> BlurSumShift;
    170                     sum += (*next) - (*prev);
    171                 }
    172                 for (; i < dim; ptr += stride, prev += stride, ++i) {
    173                     *ptr = (sum * invCount) >> BlurSumShift;
    174                     sum += alpha2 - (*prev);
    175                 }
    176             }
    177         }
    178     }
    179 }
    180 
    181 void ContextShadow::adjustBlurDistance(GraphicsContext* context)
    182 {
    183     const AffineTransform transform = context->getCTM();
    184 
    185     // Adjust blur if we're scaling, since the radius must not be affected by transformations.
    186     if (transform.isIdentity())
    187         return;
    188 
    189     // Calculate transformed unit vectors.
    190     const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
    191                              FloatPoint(0, 1), FloatPoint(1, 1));
    192     const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
    193 
    194     // Calculate X axis scale factor.
    195     const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
    196     const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
    197                                    + xUnitChange.height() * xUnitChange.height());
    198 
    199     // Calculate Y axis scale factor.
    200     const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
    201     const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
    202                                    + yUnitChange.height() * yUnitChange.height());
    203 
    204     // blurLayerImage() does not support per-axis blurring, so calculate a balanced scaling.
    205     const float scale = sqrtf(xAxisScale * yAxisScale);
    206     m_blurDistance = roundf(static_cast<float>(m_blurDistance) / scale);
    207 }
    208 
    209 IntRect ContextShadow::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& layerArea, const IntRect& clipRect)
    210 {
    211     // Calculate the destination of the blurred and/or transformed layer.
    212     FloatRect layerFloatRect;
    213     float inflation = 0;
    214 
    215     const AffineTransform transform = context->getCTM();
    216     if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
    217         FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(layerArea));
    218         transformedPolygon.move(m_offset);
    219         layerFloatRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
    220     } else {
    221         layerFloatRect = layerArea;
    222         layerFloatRect.move(m_offset);
    223     }
    224 
    225     // We expand the area by the blur radius to give extra space for the blur transition.
    226     if (m_type == BlurShadow) {
    227         layerFloatRect.inflate(m_blurDistance);
    228         inflation += m_blurDistance;
    229     }
    230 
    231     FloatRect unclippedLayerRect = layerFloatRect;
    232 
    233     if (!clipRect.contains(enclosingIntRect(layerFloatRect))) {
    234         // No need to have the buffer larger than the clip.
    235         layerFloatRect.intersect(clipRect);
    236 
    237         // If we are totally outside the clip region, we aren't painting at all.
    238         if (layerFloatRect.isEmpty())
    239             return IntRect(0, 0, 0, 0);
    240 
    241         // We adjust again because the pixels at the borders are still
    242         // potentially affected by the pixels outside the buffer.
    243         if (m_type == BlurShadow) {
    244             layerFloatRect.inflate(m_blurDistance);
    245             unclippedLayerRect.inflate(m_blurDistance);
    246             inflation += m_blurDistance;
    247         }
    248     }
    249 
    250     const int frameSize = inflation * 2;
    251     m_sourceRect = IntRect(0, 0, layerArea.width() + frameSize, layerArea.height() + frameSize);
    252     m_layerOrigin = FloatPoint(layerFloatRect.x(), layerFloatRect.y());
    253 
    254     const FloatPoint m_unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
    255     const FloatSize clippedOut = m_unclippedLayerOrigin - m_layerOrigin;
    256 
    257     // Set the origin as the top left corner of the scratch image, or, in case there's a clipped
    258     // out region, set the origin accordingly to the full bounding rect's top-left corner.
    259     const float translationX = -layerArea.x() + inflation - fabsf(clippedOut.width());
    260     const float translationY = -layerArea.y() + inflation - fabsf(clippedOut.height());
    261     m_layerContextTranslation = FloatPoint(translationX, translationY);
    262 
    263     return enclosingIntRect(layerFloatRect);
    264 }
    265 
    266 } // namespace WebCore
    267