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