1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtOpenGL module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "private/qpixmapfilter_p.h" |
43 | #include "private/qpixmapdata_gl_p.h" |
44 | #include "private/qpaintengineex_opengl2_p.h" |
45 | #include "private/qglengineshadermanager_p.h" |
46 | #include "private/qpixmapdata_p.h" |
47 | #include "private/qimagepixmapcleanuphooks_p.h" |
48 | #include "qglpixmapfilter_p.h" |
49 | #include "qgraphicssystem_gl_p.h" |
50 | #include "qpaintengine_opengl_p.h" |
51 | #include "qcache.h" |
52 | |
53 | #include "qglframebufferobject.h" |
54 | #include "qglshaderprogram.h" |
55 | #include "qgl_p.h" |
56 | |
57 | #include "private/qapplication_p.h" |
58 | #include "private/qdrawhelper_p.h" |
59 | #include "private/qmemrotate_p.h" |
60 | #include "private/qmath_p.h" |
61 | #include "qmath.h" |
62 | |
63 | QT_BEGIN_NAMESPACE |
64 | |
65 | // qpixmapfilter.cpp |
66 | Q_GUI_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0); |
67 | Q_GUI_EXPORT QImage qt_halfScaled(const QImage &source); |
68 | |
69 | void QGLPixmapFilterBase::bindTexture(const QPixmap &src) const |
70 | { |
71 | const_cast<QGLContext *>(QGLContext::currentContext())->d_func()->bindTexture(src, GL_TEXTURE_2D, GL_RGBA, QGLContext::BindOptions(QGLContext::DefaultBindOption | QGLContext::MemoryManagedBindOption)); |
72 | } |
73 | |
74 | void QGLPixmapFilterBase::drawImpl(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF& source) const |
75 | { |
76 | processGL(painter, pos, src, source); |
77 | } |
78 | |
79 | class QGLPixmapColorizeFilter: public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapColorizeFilter> |
80 | { |
81 | public: |
82 | QGLPixmapColorizeFilter(); |
83 | |
84 | void setUniforms(QGLShaderProgram *program); |
85 | |
86 | protected: |
87 | bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &pixmap, const QRectF &srcRect) const; |
88 | }; |
89 | |
90 | class QGLPixmapConvolutionFilter: public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapConvolutionFilter> |
91 | { |
92 | public: |
93 | QGLPixmapConvolutionFilter(); |
94 | ~QGLPixmapConvolutionFilter(); |
95 | |
96 | void setUniforms(QGLShaderProgram *program); |
97 | |
98 | protected: |
99 | bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; |
100 | |
101 | private: |
102 | QByteArray generateConvolutionShader() const; |
103 | |
104 | mutable QSize m_srcSize; |
105 | mutable int m_prevKernelSize; |
106 | }; |
107 | |
108 | class QGLPixmapBlurFilter : public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapBlurFilter> |
109 | { |
110 | public: |
111 | QGLPixmapBlurFilter(); |
112 | |
113 | protected: |
114 | bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; |
115 | }; |
116 | |
117 | class QGLPixmapDropShadowFilter : public QGLCustomShaderStage, public QGLPixmapFilter<QPixmapDropShadowFilter> |
118 | { |
119 | public: |
120 | QGLPixmapDropShadowFilter(); |
121 | |
122 | void setUniforms(QGLShaderProgram *program); |
123 | |
124 | protected: |
125 | bool processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const; |
126 | }; |
127 | |
128 | extern const QGLContext *qt_gl_share_context(); |
129 | |
130 | QPixmapFilter *QGL2PaintEngineEx::pixmapFilter(int type, const QPixmapFilter *prototype) |
131 | { |
132 | Q_D(QGL2PaintEngineEx); |
133 | switch (type) { |
134 | case QPixmapFilter::ColorizeFilter: |
135 | if (!d->colorizeFilter) |
136 | d->colorizeFilter.reset(new QGLPixmapColorizeFilter); |
137 | return d->colorizeFilter.data(); |
138 | |
139 | case QPixmapFilter::BlurFilter: { |
140 | if (!d->blurFilter) |
141 | d->blurFilter.reset(new QGLPixmapBlurFilter()); |
142 | return d->blurFilter.data(); |
143 | } |
144 | |
145 | case QPixmapFilter::DropShadowFilter: { |
146 | if (!d->dropShadowFilter) |
147 | d->dropShadowFilter.reset(new QGLPixmapDropShadowFilter()); |
148 | return d->dropShadowFilter.data(); |
149 | } |
150 | |
151 | case QPixmapFilter::ConvolutionFilter: |
152 | if (!d->convolutionFilter) |
153 | d->convolutionFilter.reset(new QGLPixmapConvolutionFilter); |
154 | return d->convolutionFilter.data(); |
155 | |
156 | default: break; |
157 | } |
158 | return QPaintEngineEx::pixmapFilter(type, prototype); |
159 | } |
160 | |
161 | static const char *qt_gl_colorize_filter = |
162 | "uniform lowp vec4 colorizeColor;" |
163 | "uniform lowp float colorizeStrength;" |
164 | "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords)" |
165 | "{" |
166 | " lowp vec4 srcPixel = texture2D(src, srcCoords);" |
167 | " lowp float gray = dot(srcPixel.rgb, vec3(0.212671, 0.715160, 0.072169));" |
168 | " lowp vec3 colorized = 1.0-((1.0-gray)*(1.0-colorizeColor.rgb));" |
169 | " return vec4(mix(srcPixel.rgb, colorized * srcPixel.a, colorizeStrength), srcPixel.a);" |
170 | "}" ; |
171 | |
172 | QGLPixmapColorizeFilter::QGLPixmapColorizeFilter() |
173 | { |
174 | setSource(qt_gl_colorize_filter); |
175 | } |
176 | |
177 | bool QGLPixmapColorizeFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const |
178 | { |
179 | QGLPixmapColorizeFilter *filter = const_cast<QGLPixmapColorizeFilter *>(this); |
180 | |
181 | filter->setOnPainter(painter); |
182 | painter->drawPixmap(pos, src); |
183 | filter->removeFromPainter(painter); |
184 | |
185 | return true; |
186 | } |
187 | |
188 | void QGLPixmapColorizeFilter::setUniforms(QGLShaderProgram *program) |
189 | { |
190 | program->setUniformValue("colorizeColor" , color()); |
191 | program->setUniformValue("colorizeStrength" , float(strength())); |
192 | } |
193 | |
194 | void QGLPixmapConvolutionFilter::setUniforms(QGLShaderProgram *program) |
195 | { |
196 | const qreal *kernel = convolutionKernel(); |
197 | int kernelWidth = columns(); |
198 | int kernelHeight = rows(); |
199 | int kernelSize = kernelWidth * kernelHeight; |
200 | |
201 | QVarLengthArray<GLfloat> matrix(kernelSize); |
202 | QVarLengthArray<GLfloat> offset(kernelSize * 2); |
203 | |
204 | for(int i = 0; i < kernelSize; ++i) |
205 | matrix[i] = kernel[i]; |
206 | |
207 | for(int y = 0; y < kernelHeight; ++y) { |
208 | for(int x = 0; x < kernelWidth; ++x) { |
209 | offset[(y * kernelWidth + x) * 2] = x - (kernelWidth / 2); |
210 | offset[(y * kernelWidth + x) * 2 + 1] = (kernelHeight / 2) - y; |
211 | } |
212 | } |
213 | |
214 | const qreal iw = 1.0 / m_srcSize.width(); |
215 | const qreal ih = 1.0 / m_srcSize.height(); |
216 | program->setUniformValue("inv_texture_size" , iw, ih); |
217 | program->setUniformValueArray("matrix" , matrix.constData(), kernelSize, 1); |
218 | program->setUniformValueArray("offset" , offset.constData(), kernelSize, 2); |
219 | } |
220 | |
221 | // generates convolution filter code for arbitrary sized kernel |
222 | QByteArray QGLPixmapConvolutionFilter::generateConvolutionShader() const { |
223 | QByteArray code; |
224 | int kernelWidth = columns(); |
225 | int kernelHeight = rows(); |
226 | int kernelSize = kernelWidth * kernelHeight; |
227 | code.append("uniform highp vec2 inv_texture_size;\n" |
228 | "uniform mediump float matrix[" ); |
229 | code.append(QByteArray::number(kernelSize)); |
230 | code.append("];\n" |
231 | "uniform highp vec2 offset[" ); |
232 | code.append(QByteArray::number(kernelSize)); |
233 | code.append("];\n" ); |
234 | code.append("lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords) {\n" ); |
235 | |
236 | code.append(" int i = 0;\n" |
237 | " lowp vec4 sum = vec4(0.0);\n" |
238 | " for (i = 0; i < " ); |
239 | code.append(QByteArray::number(kernelSize)); |
240 | code.append("; i++) {\n" |
241 | " sum += matrix[i] * texture2D(src,srcCoords+inv_texture_size*offset[i]);\n" |
242 | " }\n" |
243 | " return sum;\n" |
244 | "}" ); |
245 | return code; |
246 | } |
247 | |
248 | QGLPixmapConvolutionFilter::QGLPixmapConvolutionFilter() |
249 | : m_prevKernelSize(-1) |
250 | { |
251 | } |
252 | |
253 | QGLPixmapConvolutionFilter::~QGLPixmapConvolutionFilter() |
254 | { |
255 | } |
256 | |
257 | bool QGLPixmapConvolutionFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const |
258 | { |
259 | QGLPixmapConvolutionFilter *filter = const_cast<QGLPixmapConvolutionFilter *>(this); |
260 | |
261 | m_srcSize = src.size(); |
262 | |
263 | int kernelSize = rows() * columns(); |
264 | if (m_prevKernelSize == -1 || m_prevKernelSize != kernelSize) { |
265 | filter->setSource(generateConvolutionShader()); |
266 | m_prevKernelSize = kernelSize; |
267 | } |
268 | |
269 | filter->setOnPainter(painter); |
270 | painter->drawPixmap(pos, src, srcRect); |
271 | filter->removeFromPainter(painter); |
272 | |
273 | return true; |
274 | } |
275 | |
276 | QGLPixmapBlurFilter::QGLPixmapBlurFilter() |
277 | { |
278 | } |
279 | |
280 | class QGLBlurTextureInfo |
281 | { |
282 | public: |
283 | QGLBlurTextureInfo(const QImage &image, GLuint tex, qreal r) |
284 | : m_texture(tex) |
285 | , m_radius(r) |
286 | { |
287 | m_paddedImage << image; |
288 | } |
289 | |
290 | ~QGLBlurTextureInfo() |
291 | { |
292 | glDeleteTextures(1, &m_texture); |
293 | } |
294 | |
295 | QImage paddedImage(int scaleLevel = 0) const; |
296 | GLuint texture() const { return m_texture; } |
297 | qreal radius() const { return m_radius; } |
298 | |
299 | private: |
300 | mutable QList<QImage> m_paddedImage; |
301 | GLuint m_texture; |
302 | qreal m_radius; |
303 | }; |
304 | |
305 | QImage QGLBlurTextureInfo::paddedImage(int scaleLevel) const |
306 | { |
307 | for (int i = m_paddedImage.size() - 1; i <= scaleLevel; ++i) |
308 | m_paddedImage << qt_halfScaled(m_paddedImage.at(i)); |
309 | |
310 | return m_paddedImage.at(scaleLevel); |
311 | } |
312 | |
313 | class QGLBlurTextureCache : public QObject |
314 | { |
315 | public: |
316 | static QGLBlurTextureCache *cacheForContext(const QGLContext *context); |
317 | |
318 | QGLBlurTextureCache(const QGLContext *); |
319 | ~QGLBlurTextureCache(); |
320 | |
321 | QGLBlurTextureInfo *takeBlurTextureInfo(const QPixmap &pixmap); |
322 | bool hasBlurTextureInfo(quint64 cacheKey) const; |
323 | void insertBlurTextureInfo(const QPixmap &pixmap, QGLBlurTextureInfo *info); |
324 | void clearBlurTextureInfo(quint64 cacheKey); |
325 | |
326 | void timerEvent(QTimerEvent *event); |
327 | |
328 | private: |
329 | static void pixmapDestroyed(QPixmapData *pixmap); |
330 | |
331 | QCache<quint64, QGLBlurTextureInfo > cache; |
332 | |
333 | static QList<QGLBlurTextureCache *> blurTextureCaches; |
334 | |
335 | int timerId; |
336 | }; |
337 | |
338 | QList<QGLBlurTextureCache *> QGLBlurTextureCache::blurTextureCaches; |
339 | Q_GLOBAL_STATIC(QGLContextGroupResource<QGLBlurTextureCache>, qt_blur_texture_caches) |
340 | |
341 | QGLBlurTextureCache::QGLBlurTextureCache(const QGLContext *) |
342 | : timerId(0) |
343 | { |
344 | cache.setMaxCost(4 * 1024 * 1024); |
345 | blurTextureCaches.append(this); |
346 | } |
347 | |
348 | QGLBlurTextureCache::~QGLBlurTextureCache() |
349 | { |
350 | blurTextureCaches.removeAt(blurTextureCaches.indexOf(this)); |
351 | } |
352 | |
353 | void QGLBlurTextureCache::timerEvent(QTimerEvent *) |
354 | { |
355 | killTimer(timerId); |
356 | timerId = 0; |
357 | |
358 | cache.clear(); |
359 | } |
360 | |
361 | QGLBlurTextureCache *QGLBlurTextureCache::cacheForContext(const QGLContext *context) |
362 | { |
363 | return qt_blur_texture_caches()->value(context); |
364 | } |
365 | |
366 | QGLBlurTextureInfo *QGLBlurTextureCache::takeBlurTextureInfo(const QPixmap &pixmap) |
367 | { |
368 | return cache.take(pixmap.cacheKey()); |
369 | } |
370 | |
371 | void QGLBlurTextureCache::clearBlurTextureInfo(quint64 cacheKey) |
372 | { |
373 | cache.remove(cacheKey); |
374 | } |
375 | |
376 | bool QGLBlurTextureCache::hasBlurTextureInfo(quint64 cacheKey) const |
377 | { |
378 | return cache.contains(cacheKey); |
379 | } |
380 | |
381 | void QGLBlurTextureCache::insertBlurTextureInfo(const QPixmap &pixmap, QGLBlurTextureInfo *info) |
382 | { |
383 | static bool hookAdded = false; |
384 | if (!hookAdded) { |
385 | QImagePixmapCleanupHooks::instance()->addPixmapDataDestructionHook(pixmapDestroyed); |
386 | QImagePixmapCleanupHooks::instance()->addPixmapDataModificationHook(pixmapDestroyed); |
387 | hookAdded = true; |
388 | } |
389 | |
390 | QImagePixmapCleanupHooks::enableCleanupHooks(pixmap); |
391 | cache.insert(pixmap.cacheKey(), info, pixmap.width() * pixmap.height()); |
392 | |
393 | if (timerId) |
394 | killTimer(timerId); |
395 | |
396 | timerId = startTimer(8000); |
397 | } |
398 | |
399 | void QGLBlurTextureCache::pixmapDestroyed(QPixmapData *pmd) |
400 | { |
401 | foreach (QGLBlurTextureCache *cache, blurTextureCaches) { |
402 | if (cache->hasBlurTextureInfo(pmd->cacheKey())) |
403 | cache->clearBlurTextureInfo(pmd->cacheKey()); |
404 | } |
405 | } |
406 | |
407 | static const int qAnimatedBlurLevelIncrement = 16; |
408 | static const int qMaxBlurHalfScaleLevel = 1; |
409 | |
410 | static GLuint generateBlurTexture(const QSize &size, GLenum format = GL_RGBA) |
411 | { |
412 | GLuint texture; |
413 | glGenTextures(1, &texture); |
414 | glBindTexture(GL_TEXTURE_2D, texture); |
415 | glTexImage2D(GL_TEXTURE_2D, 0, format, size.width(), size.height(), 0, format, |
416 | GL_UNSIGNED_BYTE, 0); |
417 | return texture; |
418 | } |
419 | |
420 | static inline uint nextMultiple(uint x, uint multiplier) |
421 | { |
422 | uint mod = x % multiplier; |
423 | if (mod == 0) |
424 | return x; |
425 | return x + multiplier - mod; |
426 | } |
427 | |
428 | Q_GUI_EXPORT void qt_memrotate90_gl(const quint32 *src, int srcWidth, int srcHeight, int srcStride, |
429 | quint32 *dest, int dstStride); |
430 | |
431 | bool QGLPixmapBlurFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &) const |
432 | { |
433 | if (radius() < 1) { |
434 | painter->drawPixmap(pos, src); |
435 | return true; |
436 | } |
437 | |
438 | qreal actualRadius = radius(); |
439 | |
440 | QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); |
441 | |
442 | QGLBlurTextureCache *blurTextureCache = QGLBlurTextureCache::cacheForContext(ctx); |
443 | QGLBlurTextureInfo *info = 0; |
444 | int padding = nextMultiple(qCeil(actualRadius), qAnimatedBlurLevelIncrement); |
445 | QRect targetRect = src.rect().adjusted(-padding, -padding, padding, padding); |
446 | |
447 | // pad so that we'll be able to half-scale qMaxBlurHalfScaleLevel times |
448 | targetRect.setWidth((targetRect.width() + (qMaxBlurHalfScaleLevel-1)) & ~(qMaxBlurHalfScaleLevel-1)); |
449 | targetRect.setHeight((targetRect.height() + (qMaxBlurHalfScaleLevel-1)) & ~(qMaxBlurHalfScaleLevel-1)); |
450 | |
451 | QSize textureSize; |
452 | |
453 | info = blurTextureCache->takeBlurTextureInfo(src); |
454 | if (!info || info->radius() < actualRadius) { |
455 | QSize paddedSize = targetRect.size() / 2; |
456 | |
457 | QImage padded(paddedSize.height(), paddedSize.width(), QImage::Format_ARGB32_Premultiplied); |
458 | padded.fill(0); |
459 | |
460 | if (info) { |
461 | int oldPadding = qRound(info->radius()); |
462 | |
463 | QPainter p(&padded); |
464 | p.setCompositionMode(QPainter::CompositionMode_Source); |
465 | p.drawImage((padding - oldPadding) / 2, (padding - oldPadding) / 2, info->paddedImage()); |
466 | p.end(); |
467 | } else { |
468 | // TODO: combine byteswapping and memrotating into one by declaring |
469 | // custom GL_RGBA pixel type and qt_colorConvert template for it |
470 | QImage prepadded = qt_halfScaled(src.toImage()).convertToFormat(QImage::Format_ARGB32_Premultiplied); |
471 | |
472 | // byte-swap and memrotates in one go |
473 | qt_memrotate90_gl(reinterpret_cast<const quint32*>(prepadded.bits()), |
474 | prepadded.width(), prepadded.height(), prepadded.bytesPerLine(), |
475 | reinterpret_cast<quint32*>(padded.scanLine(padding / 2)) + padding / 2, |
476 | padded.bytesPerLine()); |
477 | } |
478 | |
479 | delete info; |
480 | info = new QGLBlurTextureInfo(padded, generateBlurTexture(paddedSize), padding); |
481 | |
482 | textureSize = paddedSize; |
483 | } else { |
484 | textureSize = QSize(info->paddedImage().height(), info->paddedImage().width()); |
485 | } |
486 | |
487 | actualRadius *= qreal(0.5); |
488 | int level = 1; |
489 | for (; level < qMaxBlurHalfScaleLevel; ++level) { |
490 | if (actualRadius <= 16) |
491 | break; |
492 | actualRadius *= qreal(0.5); |
493 | } |
494 | |
495 | const int s = (1 << level); |
496 | |
497 | int prepadding = qRound(info->radius()); |
498 | padding = qMin(prepadding, qCeil(actualRadius) << level); |
499 | targetRect = src.rect().adjusted(-padding, -padding, padding, padding); |
500 | |
501 | targetRect.setWidth(targetRect.width() & ~(s-1)); |
502 | targetRect.setHeight(targetRect.height() & ~(s-1)); |
503 | |
504 | int paddingDelta = (prepadding - padding) >> level; |
505 | |
506 | QRect subRect(paddingDelta, paddingDelta, targetRect.width() >> level, targetRect.height() >> level); |
507 | QImage sourceImage = info->paddedImage(level-1); |
508 | |
509 | QImage subImage(subRect.height(), subRect.width(), QImage::Format_ARGB32_Premultiplied); |
510 | qt_rectcopy((QRgb *)subImage.bits(), ((QRgb *)sourceImage.scanLine(paddingDelta)) + paddingDelta, |
511 | 0, 0, subRect.height(), subRect.width(), subImage.bytesPerLine(), sourceImage.bytesPerLine()); |
512 | |
513 | GLuint texture = info->texture(); |
514 | |
515 | qt_blurImage(subImage, actualRadius, blurHints() & QGraphicsBlurEffect::QualityHint, 1); |
516 | |
517 | // subtract one pixel off the end to prevent the bilinear sampling from sampling uninitialized data |
518 | QRect textureSubRect = subImage.rect().adjusted(0, 0, -1, -1); |
519 | QRectF targetRectF = QRectF(targetRect).adjusted(0, 0, -targetRect.width() / qreal(textureSize.width()), -targetRect.height() / qreal(textureSize.height())); |
520 | |
521 | glBindTexture(GL_TEXTURE_2D, texture); |
522 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, subImage.width(), subImage.height(), GL_RGBA, |
523 | GL_UNSIGNED_BYTE, const_cast<const QImage &>(subImage).bits()); |
524 | |
525 | QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); |
526 | painter->setRenderHint(QPainter::SmoothPixmapTransform); |
527 | |
528 | // texture is flipped on the y-axis |
529 | targetRectF = QRectF(targetRectF.x(), targetRectF.bottom(), targetRectF.width(), -targetRectF.height()); |
530 | engine->drawTexture(targetRectF.translated(pos), texture, textureSize, textureSubRect); |
531 | |
532 | blurTextureCache->insertBlurTextureInfo(src, info); |
533 | |
534 | return true; |
535 | } |
536 | |
537 | static const char *qt_gl_drop_shadow_filter = |
538 | "uniform lowp vec4 shadowColor;" |
539 | "lowp vec4 customShader(lowp sampler2D src, highp vec2 srcCoords)" |
540 | "{" |
541 | " return shadowColor * texture2D(src, srcCoords.yx).a;" |
542 | "}" ; |
543 | |
544 | |
545 | QGLPixmapDropShadowFilter::QGLPixmapDropShadowFilter() |
546 | { |
547 | setSource(qt_gl_drop_shadow_filter); |
548 | } |
549 | |
550 | bool QGLPixmapDropShadowFilter::processGL(QPainter *painter, const QPointF &pos, const QPixmap &src, const QRectF &srcRect) const |
551 | { |
552 | QGLPixmapDropShadowFilter *filter = const_cast<QGLPixmapDropShadowFilter *>(this); |
553 | |
554 | qreal r = blurRadius(); |
555 | QRectF targetRectUnaligned = QRectF(src.rect()).translated(pos + offset()).adjusted(-r, -r, r, r); |
556 | QRect targetRect = targetRectUnaligned.toAlignedRect(); |
557 | |
558 | // ensure even dimensions (going to divide by two) |
559 | targetRect.setWidth((targetRect.width() + 1) & ~1); |
560 | targetRect.setHeight((targetRect.height() + 1) & ~1); |
561 | |
562 | QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext()); |
563 | QGLBlurTextureCache *blurTextureCache = QGLBlurTextureCache::cacheForContext(ctx); |
564 | |
565 | QGLBlurTextureInfo *info = blurTextureCache->takeBlurTextureInfo(src); |
566 | if (!info || info->radius() != r) { |
567 | QImage half = qt_halfScaled(src.toImage().alphaChannel()); |
568 | |
569 | qreal rx = r + targetRect.left() - targetRectUnaligned.left(); |
570 | qreal ry = r + targetRect.top() - targetRectUnaligned.top(); |
571 | |
572 | QImage image = QImage(targetRect.size() / 2, QImage::Format_Indexed8); |
573 | image.setColorTable(half.colorTable()); |
574 | image.fill(0); |
575 | int dx = qRound(rx * qreal(0.5)); |
576 | int dy = qRound(ry * qreal(0.5)); |
577 | qt_rectcopy(image.bits(), half.bits(), dx, dy, |
578 | half.width(), half.height(), |
579 | image.bytesPerLine(), half.bytesPerLine()); |
580 | |
581 | qt_blurImage(image, r * qreal(0.5), false, 1); |
582 | |
583 | GLuint texture; |
584 | glGenTextures(1, &texture); |
585 | glBindTexture(GL_TEXTURE_2D, texture); |
586 | glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, image.width(), image.height(), |
587 | 0, GL_ALPHA, GL_UNSIGNED_BYTE, image.bits()); |
588 | |
589 | info = new QGLBlurTextureInfo(image, texture, r); |
590 | } |
591 | |
592 | GLuint texture = info->texture(); |
593 | |
594 | filter->setOnPainter(painter); |
595 | |
596 | QGL2PaintEngineEx *engine = static_cast<QGL2PaintEngineEx *>(painter->paintEngine()); |
597 | painter->setRenderHint(QPainter::SmoothPixmapTransform); |
598 | |
599 | engine->drawTexture(targetRect, texture, info->paddedImage().size(), info->paddedImage().rect()); |
600 | |
601 | filter->removeFromPainter(painter); |
602 | |
603 | // Now draw the actual pixmap over the top. |
604 | painter->drawPixmap(pos, src, srcRect); |
605 | |
606 | blurTextureCache->insertBlurTextureInfo(src, info); |
607 | |
608 | return true; |
609 | } |
610 | |
611 | void QGLPixmapDropShadowFilter::setUniforms(QGLShaderProgram *program) |
612 | { |
613 | QColor col = color(); |
614 | qreal alpha = col.alphaF(); |
615 | program->setUniformValue("shadowColor" , col.redF() * alpha, |
616 | col.greenF() * alpha, |
617 | col.blueF() * alpha, |
618 | alpha); |
619 | } |
620 | |
621 | QT_END_NAMESPACE |
622 | |