1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
/*
* Copyright (C) 2010 Sencha, Inc.
* Copyright (C) 2010 Igalia S.L.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ContextShadow.h"
#include "AffineTransform.h"
#include "FloatQuad.h"
#include "GraphicsContext.h"
#include <cmath>
#include <wtf/MathExtras.h>
#include <wtf/Noncopyable.h>
using WTF::min;
using WTF::max;
namespace WebCore {
ContextShadow::ContextShadow()
: m_type(NoShadow)
, m_blurDistance(0)
, m_layerContext(0)
, m_shadowsIgnoreTransforms(false)
{
}
ContextShadow::ContextShadow(const Color& color, float radius, const FloatSize& offset)
: m_color(color)
, m_blurDistance(round(radius))
, m_offset(offset)
, m_layerContext(0)
, m_shadowsIgnoreTransforms(false)
{
// See comments in http://webkit.org/b/40793, it seems sensible
// to follow Skia's limit of 128 pixels of blur radius
m_blurDistance = min(m_blurDistance, 128);
// The type of shadow is decided by the blur radius, shadow offset, and shadow color.
if (!m_color.isValid() || !color.alpha()) {
// Can't paint the shadow with invalid or invisible color.
m_type = NoShadow;
} else if (radius > 0) {
// Shadow is always blurred, even the offset is zero.
m_type = BlurShadow;
} else if (!m_offset.width() && !m_offset.height()) {
// Without blur and zero offset means the shadow is fully hidden.
m_type = NoShadow;
} else {
m_type = SolidShadow;
}
}
void ContextShadow::clear()
{
m_type = NoShadow;
m_color = Color();
m_blurDistance = 0;
m_offset = FloatSize();
}
bool ContextShadow::mustUseContextShadow(GraphicsContext* context)
{
// We can't avoid ContextShadow, since the shadow has blur.
if (m_type == ContextShadow::BlurShadow)
return true;
// We can avoid ContextShadow and optimize, since we're not drawing on a
// canvas and box shadows are affected by the transformation matrix.
if (!shadowsIgnoreTransforms())
return false;
// We can avoid ContextShadow, since there are no transformations to apply to the canvas.
if (context->getCTM().isIdentity())
return false;
// Otherwise, no chance avoiding ContextShadow.
return true;
}
// Instead of integer division, we use 17.15 for fixed-point division.
static const int BlurSumShift = 15;
// Check http://www.w3.org/TR/SVG/filters.html#feGaussianBlur.
// As noted in the SVG filter specification, running box blur 3x
// approximates a real gaussian blur nicely.
void ContextShadow::blurLayerImage(unsigned char* imageData, const IntSize& size, int rowStride)
{
#if CPU(BIG_ENDIAN)
int channels[4] = { 0, 3, 2, 0 };
#elif CPU(MIDDLE_ENDIAN)
int channels[4] = { 1, 2, 3, 1 };
#else
int channels[4] = { 3, 0, 1, 3 };
#endif
int d = max(2, static_cast<int>(floorf((2 / 3.f) * m_blurDistance)));
int dmax = d >> 1;
int dmin = dmax - 1 + (d & 1);
if (dmin < 0)
dmin = 0;
// Two stages: horizontal and vertical
for (int k = 0; k < 2; ++k) {
unsigned char* pixels = imageData;
int stride = (!k) ? 4 : rowStride;
int delta = (!k) ? rowStride : 4;
int jfinal = (!k) ? size.height() : size.width();
int dim = (!k) ? size.width() : size.height();
for (int j = 0; j < jfinal; ++j, pixels += delta) {
// For each step, we blur the alpha in a channel and store the result
// in another channel for the subsequent step.
// We use sliding window algorithm to accumulate the alpha values.
// This is much more efficient than computing the sum of each pixels
// covered by the box kernel size for each x.
for (int step = 0; step < 3; ++step) {
int side1 = (!step) ? dmin : dmax;
int side2 = (step == 1) ? dmin : dmax;
int pixelCount = side1 + 1 + side2;
int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount;
int ofs = 1 + side2;
int alpha1 = pixels[channels[step]];
int alpha2 = pixels[(dim - 1) * stride + channels[step]];
unsigned char* ptr = pixels + channels[step + 1];
unsigned char* prev = pixels + stride + channels[step];
unsigned char* next = pixels + ofs * stride + channels[step];
int i;
int sum = side1 * alpha1 + alpha1;
int limit = (dim < side2 + 1) ? dim : side2 + 1;
for (i = 1; i < limit; ++i, prev += stride)
sum += *prev;
if (limit <= side2)
sum += (side2 - limit + 1) * alpha2;
limit = (side1 < dim) ? side1 : dim;
for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
*ptr = (sum * invCount) >> BlurSumShift;
sum += ((ofs < dim) ? *next : alpha2) - alpha1;
}
prev = pixels + channels[step];
for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
*ptr = (sum * invCount) >> BlurSumShift;
sum += (*next) - (*prev);
}
for (; i < dim; ptr += stride, prev += stride, ++i) {
*ptr = (sum * invCount) >> BlurSumShift;
sum += alpha2 - (*prev);
}
}
}
}
}
void ContextShadow::adjustBlurDistance(GraphicsContext* context)
{
const AffineTransform transform = context->getCTM();
// Adjust blur if we're scaling, since the radius must not be affected by transformations.
if (transform.isIdentity())
return;
// Calculate transformed unit vectors.
const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
FloatPoint(0, 1), FloatPoint(1, 1));
const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
// Calculate X axis scale factor.
const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
+ xUnitChange.height() * xUnitChange.height());
// Calculate Y axis scale factor.
const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
+ yUnitChange.height() * yUnitChange.height());
// blurLayerImage() does not support per-axis blurring, so calculate a balanced scaling.
const float scale = sqrtf(xAxisScale * yAxisScale);
m_blurDistance = roundf(static_cast<float>(m_blurDistance) / scale);
}
IntRect ContextShadow::calculateLayerBoundingRect(GraphicsContext* context, const FloatRect& layerArea, const IntRect& clipRect)
{
// Calculate the destination of the blurred and/or transformed layer.
FloatRect layerFloatRect;
float inflation = 0;
const AffineTransform transform = context->getCTM();
if (m_shadowsIgnoreTransforms && !transform.isIdentity()) {
FloatQuad transformedPolygon = transform.mapQuad(FloatQuad(layerArea));
transformedPolygon.move(m_offset);
layerFloatRect = transform.inverse().mapQuad(transformedPolygon).boundingBox();
} else {
layerFloatRect = layerArea;
layerFloatRect.move(m_offset);
}
// We expand the area by the blur radius to give extra space for the blur transition.
if (m_type == BlurShadow) {
layerFloatRect.inflate(m_blurDistance);
inflation += m_blurDistance;
}
FloatRect unclippedLayerRect = layerFloatRect;
if (!clipRect.contains(enclosingIntRect(layerFloatRect))) {
// No need to have the buffer larger than the clip.
layerFloatRect.intersect(clipRect);
// If we are totally outside the clip region, we aren't painting at all.
if (layerFloatRect.isEmpty())
return IntRect(0, 0, 0, 0);
// We adjust again because the pixels at the borders are still
// potentially affected by the pixels outside the buffer.
if (m_type == BlurShadow) {
layerFloatRect.inflate(m_blurDistance);
unclippedLayerRect.inflate(m_blurDistance);
inflation += m_blurDistance;
}
}
const int frameSize = inflation * 2;
m_sourceRect = IntRect(0, 0, layerArea.width() + frameSize, layerArea.height() + frameSize);
m_layerOrigin = FloatPoint(layerFloatRect.x(), layerFloatRect.y());
const FloatPoint m_unclippedLayerOrigin = FloatPoint(unclippedLayerRect.x(), unclippedLayerRect.y());
const FloatSize clippedOut = m_unclippedLayerOrigin - m_layerOrigin;
// Set the origin as the top left corner of the scratch image, or, in case there's a clipped
// out region, set the origin accordingly to the full bounding rect's top-left corner.
const float translationX = -layerArea.x() + inflation - fabsf(clippedOut.width());
const float translationY = -layerArea.y() + inflation - fabsf(clippedOut.height());
m_layerContextTranslation = FloatPoint(translationX, translationY);
return enclosingIntRect(layerFloatRect);
}
} // namespace WebCore
|