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 ℑ 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 ℑ 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