#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)
{
m_blurDistance = min(m_blurDistance, 128);
if (!m_color.isValid() || !color.alpha()) {
m_type = NoShadow;
} else if (radius > 0) {
m_type = BlurShadow;
} else if (!m_offset.width() && !m_offset.height()) {
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)
{
if (m_type == ContextShadow::BlurShadow)
return true;
if (!shadowsIgnoreTransforms())
return false;
if (context->getCTM().isIdentity())
return false;
return true;
}
static const int BlurSumShift = 15;
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;
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 (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();
if (transform.isIdentity())
return;
const FloatQuad unitQuad(FloatPoint(0, 0), FloatPoint(1, 0),
FloatPoint(0, 1), FloatPoint(1, 1));
const FloatQuad transformedUnitQuad = transform.mapQuad(unitQuad);
const FloatSize xUnitChange = transformedUnitQuad.p2() - transformedUnitQuad.p1();
const float xAxisScale = sqrtf(xUnitChange.width() * xUnitChange.width()
+ xUnitChange.height() * xUnitChange.height());
const FloatSize yUnitChange = transformedUnitQuad.p3() - transformedUnitQuad.p1();
const float yAxisScale = sqrtf(yUnitChange.width() * yUnitChange.width()
+ yUnitChange.height() * yUnitChange.height());
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)
{
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);
}
if (m_type == BlurShadow) {
layerFloatRect.inflate(m_blurDistance);
inflation += m_blurDistance;
}
FloatRect unclippedLayerRect = layerFloatRect;
if (!clipRect.contains(enclosingIntRect(layerFloatRect))) {
layerFloatRect.intersect(clipRect);
if (layerFloatRect.isEmpty())
return IntRect(0, 0, 0, 0);
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;
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);
}
}