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 
ContextShadow()44 ContextShadow::ContextShadow()
45     : m_type(NoShadow)
46     , m_blurDistance(0)
47     , m_layerContext(0)
48     , m_shadowsIgnoreTransforms(false)
49 {
50 }
51 
ContextShadow(const Color & color,float radius,const FloatSize & offset)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 
clear()78 void ContextShadow::clear()
79 {
80     m_type = NoShadow;
81     m_color = Color();
82     m_blurDistance = 0;
83     m_offset = FloatSize();
84 }
85 
mustUseContextShadow(GraphicsContext * context)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 
blurLayerImage(unsigned char * imageData,const IntSize & size,int rowStride)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 
adjustBlurDistance(GraphicsContext * context)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 
calculateLayerBoundingRect(GraphicsContext * context,const FloatRect & layerArea,const IntRect & clipRect)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