diff options
Diffstat (limited to 'WebCore/platform/graphics/qt/ContextShadow.cpp')
-rw-r--r-- | WebCore/platform/graphics/qt/ContextShadow.cpp | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/WebCore/platform/graphics/qt/ContextShadow.cpp b/WebCore/platform/graphics/qt/ContextShadow.cpp new file mode 100644 index 0000000..0511218 --- /dev/null +++ b/WebCore/platform/graphics/qt/ContextShadow.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2010 Sencha, Inc. + * + * 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" + +namespace WebCore { + +ContextShadow::ContextShadow() + : type(NoShadow) + , blurRadius(0) +{ +} + +ContextShadow::ContextShadow(const QColor& c, float r, qreal dx, qreal dy) + : color(c) + , blurRadius(qRound(r)) + , offset(dx, dy) +{ + // The type of shadow is decided by the blur radius, shadow offset, and shadow color. + if (!color.isValid() || !color.alpha()) { + // Can't paint the shadow with invalid or invisible color. + type = NoShadow; + } else if (r > 0) { + // Shadow is always blurred, even the offset is zero. + type = BlurShadow; + } else if (offset.isNull()) { + // Without blur and zero offset means the shadow is fully hidden. + type = NoShadow; + } else { + if (color.alpha() > 0) + type = AlphaSolidShadow; + else + type = OpaqueSolidShadow; + } +} + +void ContextShadow::clear() +{ + type = NoShadow; + color = QColor(); + blurRadius = 0; + offset = QPointF(0, 0); +} + +// Instead of integer division, we use 18.14 for fixed-point division. +static const int BlurSumShift = 14; + +// Note: image must be RGB32 format +static void blurHorizontal(QImage& image, int radius, bool swap = false) +{ + Q_ASSERT(image.format() == QImage::Format_ARGB32_Premultiplied); + + // See comments in http://webkit.org/b/40793, it seems sensible + // to follow Skia's limit of 128 pixels of blur radius + radius = qMin(128, radius); + + int imgWidth = image.width(); + int imgHeight = image.height(); + + // Check http://www.w3.org/TR/SVG/filters.html#feGaussianBlur + // for the approaches when the box-blur radius is even vs odd. + int dmax = radius >> 1; + int dmin = qMax(0, dmax - 1 + (radius & 1)); + + for (int y = 0; y < imgHeight; ++y) { + + unsigned char* pixels = image.scanLine(y); + + int left; + int right; + int pixelCount; + int prev; + int next; + int firstAlpha; + int lastAlpha; + int totalAlpha; + unsigned char* target; + unsigned char* prevPtr; + unsigned char* nextPtr; + + int invCount; + + static const int alphaChannel = 3; + static const int blueChannel = 0; + static const int greenChannel = 1; + + // For each step, we use sliding window algorithm. This is much more + // efficient than computing the sum of each pixels covered by the box + // kernel size for each x. + + // As noted in the SVG filter specification, running box blur 3x + // approximates a real gaussian blur nicely. + + // Step 1: blur alpha channel and store the result in the blue channel. + left = swap ? dmax : dmin; + right = swap ? dmin : dmax; + pixelCount = left + 1 + right; + invCount = (1 << BlurSumShift) / pixelCount; + prev = -left; + next = 1 + right; + firstAlpha = pixels[alphaChannel]; + lastAlpha = pixels[(imgWidth - 1) * 4 + alphaChannel]; + totalAlpha = 0; + for (int i = 0; i < pixelCount; ++i) + totalAlpha += pixels[qBound(0, i - left, imgWidth - 1) * 4 + alphaChannel]; + target = pixels + blueChannel; + prevPtr = pixels + prev * 4 + alphaChannel; + nextPtr = pixels + next * 4 + alphaChannel; + for (int x = 0; x < imgWidth; ++x, ++prev, ++next, target += 4, prevPtr += 4, nextPtr += 4) { + *target = (totalAlpha * invCount) >> BlurSumShift; + int delta = ((next < imgWidth) ? *nextPtr : lastAlpha) - + ((prev > 0) ? *prevPtr : firstAlpha); + totalAlpha += delta; + } + + // Step 2: blur blue channel and store the result in the green channel. + left = swap ? dmin : dmax; + right = swap ? dmax : dmin; + pixelCount = left + 1 + right; + invCount = (1 << BlurSumShift) / pixelCount; + prev = -left; + next = 1 + right; + firstAlpha = pixels[blueChannel]; + lastAlpha = pixels[(imgWidth - 1) * 4 + blueChannel]; + totalAlpha = 0; + for (int i = 0; i < pixelCount; ++i) + totalAlpha += pixels[qBound(0, i - left, imgWidth - 1) * 4 + blueChannel]; + target = pixels + greenChannel; + prevPtr = pixels + prev * 4 + blueChannel; + nextPtr = pixels + next * 4 + blueChannel; + for (int x = 0; x < imgWidth; ++x, ++prev, ++next, target += 4, prevPtr += 4, nextPtr += 4) { + *target = (totalAlpha * invCount) >> BlurSumShift; + int delta = ((next < imgWidth) ? *nextPtr : lastAlpha) - + ((prev > 0) ? *prevPtr : firstAlpha); + totalAlpha += delta; + } + + // Step 3: blur green channel and store the result in the alpha channel. + left = dmax; + right = dmax; + pixelCount = left + 1 + right; + invCount = (1 << BlurSumShift) / pixelCount; + prev = -left; + next = 1 + right; + firstAlpha = pixels[greenChannel]; + lastAlpha = pixels[(imgWidth - 1) * 4 + greenChannel]; + totalAlpha = 0; + for (int i = 0; i < pixelCount; ++i) + totalAlpha += pixels[qBound(0, i - left, imgWidth - 1) * 4 + greenChannel]; + target = pixels + alphaChannel; + prevPtr = pixels + prev * 4 + greenChannel; + nextPtr = pixels + next * 4 + greenChannel; + for (int x = 0; x < imgWidth; ++x, ++prev, ++next, target += 4, prevPtr += 4, nextPtr += 4) { + *target = (totalAlpha * invCount) >> BlurSumShift; + int delta = ((next < imgWidth) ? *nextPtr : lastAlpha) - + ((prev > 0) ? *prevPtr : firstAlpha); + totalAlpha += delta; + } + } +} + +static void shadowBlur(QImage& image, int radius, const QColor& shadowColor) +{ + blurHorizontal(image, radius); + + QTransform transform; + transform.rotate(90); + image = image.transformed(transform); + blurHorizontal(image, radius, true); + transform.reset(); + transform.rotate(270); + image = image.transformed(transform); + + // "Colorize" with the right shadow color. + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(image.rect(), shadowColor.rgb()); + p.end(); +} + +void ContextShadow::drawShadowRect(QPainter* p, const QRectF& rect) +{ + if (type == NoShadow) + return; + + if (type == BlurShadow) { + QRectF shadowRect = rect.translated(offset); + + // We expand the area by the blur radius * 2 to give extra space + // for the blur transition. + int extra = blurRadius * 2; + QRectF bufferRect = shadowRect.adjusted(-extra, -extra, extra, extra); + QRect alignedBufferRect = bufferRect.toAlignedRect(); + + QRect clipRect; + if (p->hasClipping()) + clipRect = p->clipRegion().boundingRect(); + else + clipRect = p->transform().inverted().mapRect(p->window()); + + if (!clipRect.contains(alignedBufferRect)) { + + // No need to have the buffer larger that the clip. + alignedBufferRect = alignedBufferRect.intersected(clipRect); + if (alignedBufferRect.isEmpty()) + return; + + // We adjust again because the pixels at the borders are still + // potentially affected by the pixels outside the buffer. + alignedBufferRect.adjust(-extra, -extra, extra, extra); + } + + QImage shadowImage(alignedBufferRect.size(), QImage::Format_ARGB32_Premultiplied); + shadowImage.fill(Qt::transparent); + QPainter shadowPainter(&shadowImage); + + shadowPainter.fillRect(shadowRect.translated(-alignedBufferRect.topLeft()), color); + shadowPainter.end(); + + shadowBlur(shadowImage, blurRadius, color); + + p->drawImage(alignedBufferRect.topLeft(), shadowImage); + + return; + } + + p->fillRect(rect.translated(offset), color); +} + + +} |