1/*
2 * Copyright (C) 2010 Sencha, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "ContextShadow.h"
30
31#include "AffineTransform.h"
32#include "GraphicsContext.h"
33#include <QPainter>
34#include <QTimerEvent>
35
36namespace WebCore {
37
38// ContextShadow needs a scratch image as the buffer for the blur filter.
39// Instead of creating and destroying the buffer for every operation,
40// we create a buffer which will be automatically purged via a timer.
41
42class ShadowBuffer: public QObject {
43public:
44    ShadowBuffer(QObject* parent = 0);
45
46    QImage* scratchImage(const QSize& size);
47
48    void schedulePurge();
49
50protected:
51    void timerEvent(QTimerEvent* event);
52
53private:
54    QImage image;
55    int timerId;
56};
57
58ShadowBuffer::ShadowBuffer(QObject* parent)
59    : QObject(parent)
60    , timerId(-1)
61{
62}
63
64QImage* ShadowBuffer::scratchImage(const QSize& size)
65{
66    int width = size.width();
67    int height = size.height();
68
69    // We do not need to recreate the buffer if the buffer is reasonably
70    // larger than the requested size. However, if the requested size is
71    // much smaller than our buffer, reduce our buffer so that we will not
72    // keep too many allocated pixels for too long.
73    if (!image.isNull() && (image.width() > width) && (image.height() > height))
74        if (((2 * width) > image.width()) && ((2 * height) > image.height())) {
75            image.fill(0);
76            return &image;
77        }
78
79    // Round to the nearest 32 pixels so we do not grow the buffer everytime
80    // there is larger request by 1 pixel.
81    width = (1 + (width >> 5)) << 5;
82    height = (1 + (height >> 5)) << 5;
83
84    image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
85    image.fill(0);
86    return &image;
87}
88
89void ShadowBuffer::schedulePurge()
90{
91    static const double BufferPurgeDelay = 2; // seconds
92    if (timerId >= 0)
93        killTimer(timerId);
94    timerId = startTimer(BufferPurgeDelay * 1000);
95}
96
97void ShadowBuffer::timerEvent(QTimerEvent* event)
98{
99    if (event->timerId() == timerId) {
100        killTimer(timerId);
101        image = QImage();
102    }
103    QObject::timerEvent(event);
104}
105
106Q_GLOBAL_STATIC(ShadowBuffer, scratchShadowBuffer)
107
108PlatformContext ContextShadow::beginShadowLayer(GraphicsContext* context, const FloatRect& layerArea)
109{
110    // Set m_blurDistance.
111    adjustBlurDistance(context);
112
113    PlatformContext p = context->platformContext();
114
115    QRect clipRect;
116    if (p->hasClipping())
117#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
118        clipRect = p->clipBoundingRect().toAlignedRect();
119#else
120        clipRect = p->clipRegion().boundingRect();
121#endif
122    else
123        clipRect = p->transform().inverted().mapRect(p->window());
124
125    // Set m_layerOrigin, m_layerContextTranslation, m_sourceRect.
126    IntRect clip(clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
127    IntRect layerRect = calculateLayerBoundingRect(context, layerArea, clip);
128
129    // Don't paint if we are totally outside the clip region.
130    if (layerRect.isEmpty())
131        return 0;
132
133    ShadowBuffer* shadowBuffer = scratchShadowBuffer();
134    QImage* shadowImage = shadowBuffer->scratchImage(layerRect.size());
135    m_layerImage = QImage(*shadowImage);
136
137    m_layerContext = new QPainter;
138    m_layerContext->begin(&m_layerImage);
139    m_layerContext->setFont(p->font());
140    m_layerContext->translate(m_layerContextTranslation);
141    return m_layerContext;
142}
143
144void ContextShadow::endShadowLayer(GraphicsContext* context)
145{
146    m_layerContext->end();
147    delete m_layerContext;
148    m_layerContext = 0;
149
150    if (m_type == BlurShadow) {
151        blurLayerImage(m_layerImage.bits(), IntSize(m_layerImage.width(), m_layerImage.height()),
152                       m_layerImage.bytesPerLine());
153    }
154
155    if (m_type != NoShadow) {
156        // "Colorize" with the right shadow color.
157        QPainter p(&m_layerImage);
158        p.setCompositionMode(QPainter::CompositionMode_SourceIn);
159        p.fillRect(m_layerImage.rect(), m_color.rgb());
160        p.end();
161    }
162
163    context->platformContext()->drawImage(m_layerOrigin, m_layerImage, m_sourceRect);
164
165    scratchShadowBuffer()->schedulePurge();
166}
167
168}
169