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