1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets 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 The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <qglobal.h>
41
42#include <QDebug>
43
44#include "qpainter.h"
45#include "qpixmap.h"
46#include "qpixmapfilter_p.h"
47#include "qvarlengtharray.h"
48
49#include "private/qguiapplication_p.h"
50#include "private/qpaintengineex_p.h"
51#include "private/qpaintengine_raster_p.h"
52#include "qmath.h"
53#include "private/qmath_p.h"
54#include "private/qmemrotate_p.h"
55#include "private/qdrawhelper_p.h"
56
57#include <memory>
58
59QT_BEGIN_NAMESPACE
60
61class QPixmapFilterPrivate : public QObjectPrivate
62{
63 Q_DECLARE_PUBLIC(QPixmapFilter)
64public:
65 QPixmapFilter::FilterType type;
66};
67
68/*!
69 \class QPixmapFilter
70 \since 4.5
71 \ingroup painting
72
73 \brief The QPixmapFilter class provides the basic functionality for
74 pixmap filter classes. Pixmap filter can be for example colorize or blur.
75
76 QPixmapFilter is the base class for every pixmap filter. QPixmapFilter is
77 an abstract class and cannot itself be instantiated. It provides a standard
78 interface for filter processing.
79
80 \internal
81*/
82
83/*!
84 \enum QPixmapFilter::FilterType
85
86 \internal
87
88 This enum describes the types of filter that can be applied to pixmaps.
89
90 \value ConvolutionFilter A filter that is used to calculate the convolution
91 of the image with a kernel. See
92 QPixmapConvolutionFilter for more information.
93 \value ColorizeFilter A filter that is used to change the overall color
94 of an image. See QPixmapColorizeFilter for more
95 information.
96 \value DropShadowFilter A filter that is used to add a drop shadow to an
97 image. See QPixmapDropShadowFilter for more
98 information.
99 \value BlurFilter A filter that is used to blur an image using
100 a simple blur radius. See QPixmapBlurFilter
101 for more information.
102
103 \value UserFilter The first filter type that can be used for
104 application-specific purposes.
105*/
106
107
108/*!
109 Constructs a default QPixmapFilter with the given \a type.
110
111 This constructor should be used when subclassing QPixmapFilter to
112 create custom user filters.
113
114 \internal
115*/
116QPixmapFilter::QPixmapFilter(FilterType type, QObject *parent)
117 : QObject(*new QPixmapFilterPrivate, parent)
118{
119 d_func()->type = type;
120}
121
122
123
124/*!
125 \internal
126*/
127QPixmapFilter::QPixmapFilter(QPixmapFilterPrivate&d, QPixmapFilter::FilterType type, QObject *parent)
128 : QObject(d, parent)
129{
130 d_func()->type = type;
131}
132
133
134/*!
135 Destroys the pixmap filter.
136
137 \internal
138*/
139QPixmapFilter::~QPixmapFilter()
140{
141}
142
143/*!
144 Returns the type of the filter. All standard pixmap filter classes
145 are associated with a unique value.
146
147 \internal
148*/
149QPixmapFilter::FilterType QPixmapFilter::type() const
150{
151 Q_D(const QPixmapFilter);
152 return d->type;
153}
154
155/*!
156 Returns the bounding rectangle that is affected by the pixmap
157 filter if the filter is applied to the specified \a rect.
158
159 \internal
160*/
161QRectF QPixmapFilter::boundingRectFor(const QRectF &rect) const
162{
163 return rect;
164}
165
166/*!
167 \fn void QPixmapFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF& srcRect) const
168
169 Uses \a painter to draw filtered result of \a src at the point
170 specified by \a p. If \a srcRect is specified the it will
171 be used as a source rectangle to only draw a part of the source.
172
173 draw() will affect the area which boundingRectFor() returns.
174
175 \internal
176*/
177
178/*!
179 \class QPixmapConvolutionFilter
180 \since 4.5
181 \ingroup painting
182
183 \brief The QPixmapConvolutionFilter class provides convolution
184 filtering for pixmaps.
185
186 QPixmapConvolutionFilter implements a convolution pixmap filter,
187 which is applied when \l{QPixmapFilter::}{draw()} is called. A
188 convolution filter lets you distort an image by setting the values
189 of a matrix of qreal values called its
190 \l{setConvolutionKernel()}{kernel}. The matrix's values are
191 usually between -1.0 and 1.0.
192
193 \omit
194 In convolution filtering, the pixel value is calculated from the
195 neighboring pixels based on the weighting convolution kernel.
196 This needs explaining to be useful.
197 \endomit
198
199 Example:
200 \snippet code/src_gui_image_qpixmapfilter.cpp 1
201
202 \sa {Pixmap Filters Example}, QPixmapColorizeFilter, QPixmapDropShadowFilter
203
204
205 \internal
206*/
207
208class QPixmapConvolutionFilterPrivate : public QPixmapFilterPrivate
209{
210public:
211 QPixmapConvolutionFilterPrivate(): convolutionKernel(nullptr), kernelWidth(0), kernelHeight(0), convoluteAlpha(false) {}
212 ~QPixmapConvolutionFilterPrivate() {
213 delete[] convolutionKernel;
214 }
215
216 qreal *convolutionKernel;
217 int kernelWidth;
218 int kernelHeight;
219 bool convoluteAlpha;
220};
221
222
223/*!
224 Constructs a pixmap convolution filter.
225
226 By default there is no convolution kernel.
227
228 \internal
229*/
230QPixmapConvolutionFilter::QPixmapConvolutionFilter(QObject *parent)
231 : QPixmapFilter(*new QPixmapConvolutionFilterPrivate, ConvolutionFilter, parent)
232{
233 Q_D(QPixmapConvolutionFilter);
234 d->convoluteAlpha = true;
235}
236
237/*!
238 Destructor of pixmap convolution filter.
239
240 \internal
241*/
242QPixmapConvolutionFilter::~QPixmapConvolutionFilter()
243{
244}
245
246/*!
247 Sets convolution kernel with the given number of \a rows and \a columns.
248 Values from \a kernel are copied to internal data structure.
249
250 To preserve the intensity of the pixmap, the sum of all the
251 values in the convolution kernel should add up to 1.0. A sum
252 greater than 1.0 produces a lighter result and a sum less than 1.0
253 produces a darker and transparent result.
254
255 \internal
256*/
257void QPixmapConvolutionFilter::setConvolutionKernel(const qreal *kernel, int rows, int columns)
258{
259 Q_D(QPixmapConvolutionFilter);
260 delete [] d->convolutionKernel;
261 d->convolutionKernel = new qreal[rows * columns];
262 memcpy(dest: d->convolutionKernel, src: kernel, n: sizeof(qreal) * rows * columns);
263 d->kernelWidth = columns;
264 d->kernelHeight = rows;
265}
266
267/*!
268 Gets the convolution kernel data.
269
270 \internal
271*/
272const qreal *QPixmapConvolutionFilter::convolutionKernel() const
273{
274 Q_D(const QPixmapConvolutionFilter);
275 return d->convolutionKernel;
276}
277
278/*!
279 Gets the number of rows in the convolution kernel.
280
281 \internal
282*/
283int QPixmapConvolutionFilter::rows() const
284{
285 Q_D(const QPixmapConvolutionFilter);
286 return d->kernelHeight;
287}
288
289/*!
290 Gets the number of columns in the convolution kernel.
291
292 \internal
293*/
294int QPixmapConvolutionFilter::columns() const
295{
296 Q_D(const QPixmapConvolutionFilter);
297 return d->kernelWidth;
298}
299
300
301/*!
302 \internal
303*/
304QRectF QPixmapConvolutionFilter::boundingRectFor(const QRectF &rect) const
305{
306 Q_D(const QPixmapConvolutionFilter);
307 return rect.adjusted(xp1: -d->kernelWidth / 2, yp1: -d->kernelHeight / 2, xp2: (d->kernelWidth - 1) / 2, yp2: (d->kernelHeight - 1) / 2);
308}
309
310// Convolutes the image
311static void convolute(
312 QImage *destImage,
313 const QPointF &pos,
314 const QImage &srcImage,
315 const QRectF &srcRect,
316 QPainter::CompositionMode mode,
317 qreal *kernel,
318 int kernelWidth,
319 int kernelHeight )
320{
321 const QImage processImage = (srcImage.format() != QImage::Format_ARGB32_Premultiplied ) ? srcImage.convertToFormat(f: QImage::Format_ARGB32_Premultiplied) : srcImage;
322 // TODO: support also other formats directly without copying
323
324 std::unique_ptr<int[]> fixedKernel(new int[kernelWidth * kernelHeight]);
325 for(int i = 0; i < kernelWidth*kernelHeight; i++)
326 {
327 fixedKernel[i] = (int)(65536 * kernel[i]);
328 }
329 QRectF trect = srcRect.isNull() ? processImage.rect() : srcRect;
330 trect.moveTo(p: pos);
331 QRectF bounded = trect.adjusted(xp1: -kernelWidth / 2, yp1: -kernelHeight / 2, xp2: (kernelWidth - 1) / 2, yp2: (kernelHeight - 1) / 2);
332 QRect rect = bounded.toAlignedRect();
333 QRect targetRect = rect.intersected(other: destImage->rect());
334
335 QRectF srect = srcRect.isNull() ? processImage.rect() : srcRect;
336 QRectF sbounded = srect.adjusted(xp1: -kernelWidth / 2, yp1: -kernelHeight / 2, xp2: (kernelWidth - 1) / 2, yp2: (kernelHeight - 1) / 2);
337 QPoint srcStartPoint = sbounded.toAlignedRect().topLeft()+(targetRect.topLeft()-rect.topLeft());
338
339 const uint *sourceStart = (const uint*)processImage.scanLine(0);
340 uint *outputStart = (uint*)destImage->scanLine(0);
341
342 int yk = srcStartPoint.y();
343 for (int y = targetRect.top(); y <= targetRect.bottom(); y++) {
344 uint* output = outputStart + (destImage->bytesPerLine()/sizeof(uint))*y+targetRect.left();
345 int xk = srcStartPoint.x();
346 for(int x = targetRect.left(); x <= targetRect.right(); x++) {
347 int r = 0;
348 int g = 0;
349 int b = 0;
350 int a = 0;
351
352 // some out of bounds pre-checking to avoid inner-loop ifs
353 int kernely = -kernelHeight/2;
354 int starty = 0;
355 int endy = kernelHeight;
356 if(yk+kernely+endy >= srcImage.height())
357 endy = kernelHeight-((yk+kernely+endy)-srcImage.height())-1;
358 if(yk+kernely < 0)
359 starty = -(yk+kernely);
360
361 int kernelx = -kernelWidth/2;
362 int startx = 0;
363 int endx = kernelWidth;
364 if(xk+kernelx+endx >= srcImage.width())
365 endx = kernelWidth-((xk+kernelx+endx)-srcImage.width())-1;
366 if(xk+kernelx < 0)
367 startx = -(xk+kernelx);
368
369 for (int ys = starty; ys < endy; ys ++) {
370 const uint *pix = sourceStart + (processImage.bytesPerLine()/sizeof(uint))*(yk+kernely+ys) + ((xk+kernelx+startx));
371 const uint *endPix = pix+endx-startx;
372 int kernelPos = ys*kernelWidth+startx;
373 while (pix < endPix) {
374 int factor = fixedKernel[kernelPos++];
375 a += (((*pix) & 0xff000000)>>24) * factor;
376 r += (((*pix) & 0x00ff0000)>>16) * factor;
377 g += (((*pix) & 0x0000ff00)>>8 ) * factor;
378 b += (((*pix) & 0x000000ff) ) * factor;
379 pix++;
380 }
381 }
382
383 r = qBound(min: (int)0, val: r >> 16, max: (int)255);
384 g = qBound(min: (int)0, val: g >> 16, max: (int)255);
385 b = qBound(min: (int)0, val: b >> 16, max: (int)255);
386 a = qBound(min: (int)0, val: a >> 16, max: (int)255);
387 // composition mode checking could be moved outside of loop
388 if(mode == QPainter::CompositionMode_Source) {
389 uint color = (a<<24)+(r<<16)+(g<<8)+b;
390 *output++ = color;
391 } else {
392 uint current = *output;
393 uchar ca = (current&0xff000000)>>24;
394 uchar cr = (current&0x00ff0000)>>16;
395 uchar cg = (current&0x0000ff00)>>8;
396 uchar cb = (current&0x000000ff);
397 uint color =
398 (((ca*(255-a) >> 8)+a) << 24)+
399 (((cr*(255-a) >> 8)+r) << 16)+
400 (((cg*(255-a) >> 8)+g) << 8)+
401 (((cb*(255-a) >> 8)+b));
402 *output++ = color;;
403 }
404 xk++;
405 }
406 yk++;
407 }
408}
409
410/*!
411 \internal
412*/
413void QPixmapConvolutionFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF& srcRect) const
414{
415 Q_D(const QPixmapConvolutionFilter);
416 if (!painter->isActive())
417 return;
418
419 if(d->kernelWidth<=0 || d->kernelHeight <= 0)
420 return;
421
422 if (src.isNull())
423 return;
424
425 // raster implementation
426
427 QImage *target = nullptr;
428 if (painter->paintEngine()->paintDevice()->devType() == QInternal::Image) {
429 target = static_cast<QImage *>(painter->paintEngine()->paintDevice());
430
431 QTransform mat = painter->combinedTransform();
432
433 if (mat.type() > QTransform::TxTranslate) {
434 // Disabled because of transformation...
435 target = nullptr;
436 } else {
437 QRasterPaintEngine *pe = static_cast<QRasterPaintEngine *>(painter->paintEngine());
438 if (pe->clipType() == QRasterPaintEngine::ComplexClip)
439 // disabled because of complex clipping...
440 target = nullptr;
441 else {
442 QRectF clip = pe->clipBoundingRect();
443 QRectF rect = boundingRectFor(rect: srcRect.isEmpty() ? src.rect() : srcRect);
444 QTransform x = painter->deviceTransform();
445 if (!clip.contains(r: rect.translated(dx: x.dx() + p.x(), dy: x.dy() + p.y()))) {
446 target = nullptr;
447 }
448
449 }
450 }
451 }
452
453 if (target) {
454 QTransform x = painter->deviceTransform();
455 QPointF offset(x.dx(), x.dy());
456
457 convolute(destImage: target, pos: p+offset, srcImage: src.toImage(), srcRect, mode: QPainter::CompositionMode_SourceOver, kernel: d->convolutionKernel, kernelWidth: d->kernelWidth, kernelHeight: d->kernelHeight);
458 } else {
459 QRect srect = srcRect.isNull() ? src.rect() : srcRect.toRect();
460 QRect rect = boundingRectFor(rect: srect).toRect();
461 QImage result = QImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
462 QPoint offset = srect.topLeft() - rect.topLeft();
463 convolute(destImage: &result,
464 pos: offset,
465 srcImage: src.toImage(),
466 srcRect: srect,
467 mode: QPainter::CompositionMode_Source,
468 kernel: d->convolutionKernel,
469 kernelWidth: d->kernelWidth,
470 kernelHeight: d->kernelHeight);
471 painter->drawImage(p: p - offset, image: result);
472 }
473}
474
475/*!
476 \class QPixmapBlurFilter
477 \since 4.6
478 \ingroup multimedia
479
480 \brief The QPixmapBlurFilter class provides blur filtering
481 for pixmaps.
482
483 QPixmapBlurFilter implements a blur pixmap filter,
484 which is applied when \l{QPixmapFilter::}{draw()} is called.
485
486 The filter lets you specialize the radius of the blur as well
487 as hints as to whether to prefer performance or quality.
488
489 By default, the blur effect is produced by applying an exponential
490 filter generated from the specified blurRadius(). Paint engines
491 may override this with a custom blur that is faster on the
492 underlying hardware.
493
494 \sa {Pixmap Filters Example}, QPixmapConvolutionFilter, QPixmapDropShadowFilter
495
496 \internal
497*/
498
499class QPixmapBlurFilterPrivate : public QPixmapFilterPrivate
500{
501public:
502 QPixmapBlurFilterPrivate() : radius(5), hints(QGraphicsBlurEffect::PerformanceHint) {}
503
504 qreal radius;
505 QGraphicsBlurEffect::BlurHints hints;
506};
507
508
509/*!
510 Constructs a pixmap blur filter.
511
512 \internal
513*/
514QPixmapBlurFilter::QPixmapBlurFilter(QObject *parent)
515 : QPixmapFilter(*new QPixmapBlurFilterPrivate, BlurFilter, parent)
516{
517}
518
519/*!
520 Destructor of pixmap blur filter.
521
522 \internal
523*/
524QPixmapBlurFilter::~QPixmapBlurFilter()
525{
526}
527
528/*!
529 Sets the radius of the blur filter. Higher radius produces increased blurriness.
530
531 \internal
532*/
533void QPixmapBlurFilter::setRadius(qreal radius)
534{
535 Q_D(QPixmapBlurFilter);
536 d->radius = radius;
537}
538
539/*!
540 Gets the radius of the blur filter.
541
542 \internal
543*/
544qreal QPixmapBlurFilter::radius() const
545{
546 Q_D(const QPixmapBlurFilter);
547 return d->radius;
548}
549
550/*!
551 Setting the blur hints to PerformanceHint causes the implementation
552 to trade off visual quality to blur the image faster. Setting the
553 blur hints to QualityHint causes the implementation to improve
554 visual quality at the expense of speed.
555
556 AnimationHint causes the implementation to optimize for animating
557 the blur radius, possibly by caching blurred versions of the source
558 pixmap.
559
560 The implementation is free to ignore this value if it only has a single
561 blur algorithm.
562
563 \internal
564*/
565void QPixmapBlurFilter::setBlurHints(QGraphicsBlurEffect::BlurHints hints)
566{
567 Q_D(QPixmapBlurFilter);
568 d->hints = hints;
569}
570
571/*!
572 Gets the blur hints of the blur filter.
573
574 \internal
575*/
576QGraphicsBlurEffect::BlurHints QPixmapBlurFilter::blurHints() const
577{
578 Q_D(const QPixmapBlurFilter);
579 return d->hints;
580}
581
582const qreal radiusScale = qreal(2.5);
583
584/*!
585 \internal
586*/
587QRectF QPixmapBlurFilter::boundingRectFor(const QRectF &rect) const
588{
589 Q_D(const QPixmapBlurFilter);
590 const qreal delta = radiusScale * d->radius + 1;
591 return rect.adjusted(xp1: -delta, yp1: -delta, xp2: delta, yp2: delta);
592}
593
594template <int shift>
595inline int qt_static_shift(int value)
596{
597 if (shift == 0)
598 return value;
599 else if (shift > 0)
600 return value << (uint(shift) & 0x1f);
601 else
602 return value >> (uint(-shift) & 0x1f);
603}
604
605template<int aprec, int zprec>
606inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha)
607{
608 QRgb *pixel = (QRgb *)bptr;
609
610#define Z_MASK (0xff << zprec)
611 const int A_zprec = qt_static_shift<zprec - 24>(*pixel) & Z_MASK;
612 const int R_zprec = qt_static_shift<zprec - 16>(*pixel) & Z_MASK;
613 const int G_zprec = qt_static_shift<zprec - 8>(*pixel) & Z_MASK;
614 const int B_zprec = qt_static_shift<zprec>(*pixel) & Z_MASK;
615#undef Z_MASK
616
617 const int zR_zprec = zR >> aprec;
618 const int zG_zprec = zG >> aprec;
619 const int zB_zprec = zB >> aprec;
620 const int zA_zprec = zA >> aprec;
621
622 zR += alpha * (R_zprec - zR_zprec);
623 zG += alpha * (G_zprec - zG_zprec);
624 zB += alpha * (B_zprec - zB_zprec);
625 zA += alpha * (A_zprec - zA_zprec);
626
627#define ZA_MASK (0xff << (zprec + aprec))
628 *pixel =
629 qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK)
630 | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK)
631 | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK)
632 | qt_static_shift<-zprec - aprec>(zB & ZA_MASK);
633#undef ZA_MASK
634}
635
636const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
637
638template<int aprec, int zprec>
639inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha)
640{
641 const int A_zprec = int(*(bptr)) << zprec;
642 const int z_zprec = z >> aprec;
643 z += alpha * (A_zprec - z_zprec);
644 *(bptr) = z >> (zprec + aprec);
645}
646
647template<int aprec, int zprec, bool alphaOnly>
648inline void qt_blurrow(QImage & im, int line, int alpha)
649{
650 uchar *bptr = im.scanLine(line);
651
652 int zR = 0, zG = 0, zB = 0, zA = 0;
653
654 if (alphaOnly && im.format() != QImage::Format_Indexed8)
655 bptr += alphaIndex;
656
657 const int stride = im.depth() >> 3;
658 const int im_width = im.width();
659 for (int index = 0; index < im_width; ++index) {
660 if (alphaOnly)
661 qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
662 else
663 qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
664 bptr += stride;
665 }
666
667 bptr -= stride;
668
669 for (int index = im_width - 2; index >= 0; --index) {
670 bptr -= stride;
671 if (alphaOnly)
672 qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
673 else
674 qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
675 }
676}
677
678/*
679* expblur(QImage &img, int radius)
680*
681* Based on exponential blur algorithm by Jani Huhtanen
682*
683* In-place blur of image 'img' with kernel
684* of approximate radius 'radius'.
685*
686* Blurs with two sided exponential impulse
687* response.
688*
689* aprec = precision of alpha parameter
690* in fixed-point format 0.aprec
691*
692* zprec = precision of state parameters
693* zR,zG,zB and zA in fp format 8.zprec
694*/
695template <int aprec, int zprec, bool alphaOnly>
696void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0)
697{
698 // halve the radius if we're using two passes
699 if (improvedQuality)
700 radius *= qreal(0.5);
701
702 Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied
703 || img.format() == QImage::Format_RGB32
704 || img.format() == QImage::Format_Indexed8
705 || img.format() == QImage::Format_Grayscale8);
706
707 // choose the alpha such that pixels at radius distance from a fully
708 // saturated pixel will have an alpha component of no greater than
709 // the cutOffIntensity
710 const qreal cutOffIntensity = 2;
711 int alpha = radius <= qreal(1e-5)
712 ? ((1 << aprec)-1)
713 : qRound(d: (1<<aprec)*(1 - qPow(x: cutOffIntensity * (1 / qreal(255)), y: 1 / radius)));
714
715 int img_height = img.height();
716 for (int row = 0; row < img_height; ++row) {
717 for (int i = 0; i <= int(improvedQuality); ++i)
718 qt_blurrow<aprec, zprec, alphaOnly>(img, row, alpha);
719 }
720
721 QImage temp(img.height(), img.width(), img.format());
722 temp.setDevicePixelRatio(img.devicePixelRatioF());
723 if (transposed >= 0) {
724 if (img.depth() == 8) {
725 qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()),
726 img.width(), img.height(), img.bytesPerLine(),
727 reinterpret_cast<quint8*>(temp.bits()),
728 temp.bytesPerLine());
729 } else {
730 qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()),
731 img.width(), img.height(), img.bytesPerLine(),
732 reinterpret_cast<quint32*>(temp.bits()),
733 temp.bytesPerLine());
734 }
735 } else {
736 if (img.depth() == 8) {
737 qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()),
738 img.width(), img.height(), img.bytesPerLine(),
739 reinterpret_cast<quint8*>(temp.bits()),
740 temp.bytesPerLine());
741 } else {
742 qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()),
743 img.width(), img.height(), img.bytesPerLine(),
744 reinterpret_cast<quint32*>(temp.bits()),
745 temp.bytesPerLine());
746 }
747 }
748
749 img_height = temp.height();
750 for (int row = 0; row < img_height; ++row) {
751 for (int i = 0; i <= int(improvedQuality); ++i)
752 qt_blurrow<aprec, zprec, alphaOnly>(temp, row, alpha);
753 }
754
755 if (transposed == 0) {
756 if (img.depth() == 8) {
757 qt_memrotate90(reinterpret_cast<const quint8*>(temp.bits()),
758 temp.width(), temp.height(), temp.bytesPerLine(),
759 reinterpret_cast<quint8*>(img.bits()),
760 img.bytesPerLine());
761 } else {
762 qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()),
763 temp.width(), temp.height(), temp.bytesPerLine(),
764 reinterpret_cast<quint32*>(img.bits()),
765 img.bytesPerLine());
766 }
767 } else {
768 img = temp;
769 }
770}
771#define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) )
772#define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) )
773
774Q_WIDGETS_EXPORT QImage qt_halfScaled(const QImage &source)
775{
776 if (source.width() < 2 || source.height() < 2)
777 return QImage();
778
779 QImage srcImage = source;
780
781 if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) {
782 // assumes grayscale
783 QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
784 dest.setDevicePixelRatio(source.devicePixelRatioF());
785
786 const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
787 int sx = srcImage.bytesPerLine();
788 int sx2 = sx << 1;
789
790 uchar *dst = reinterpret_cast<uchar*>(dest.bits());
791 int dx = dest.bytesPerLine();
792 int ww = dest.width();
793 int hh = dest.height();
794
795 for (int y = hh; y; --y, dst += dx, src += sx2) {
796 const uchar *p1 = src;
797 const uchar *p2 = src + sx;
798 uchar *q = dst;
799 for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2)
800 *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2;
801 }
802
803 return dest;
804 } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) {
805 QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
806 dest.setDevicePixelRatio(source.devicePixelRatioF());
807
808 const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
809 int sx = srcImage.bytesPerLine();
810 int sx2 = sx << 1;
811
812 uchar *dst = reinterpret_cast<uchar*>(dest.bits());
813 int dx = dest.bytesPerLine();
814 int ww = dest.width();
815 int hh = dest.height();
816
817 for (int y = hh; y; --y, dst += dx, src += sx2) {
818 const uchar *p1 = src;
819 const uchar *p2 = src + sx;
820 uchar *q = dst;
821 for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) {
822 // alpha
823 q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3]));
824 // rgb
825 const quint16 p16_1 = (p1[2] << 8) | p1[1];
826 const quint16 p16_2 = (p1[5] << 8) | p1[4];
827 const quint16 p16_3 = (p2[2] << 8) | p2[1];
828 const quint16 p16_4 = (p2[5] << 8) | p2[4];
829 const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4));
830 q[1] = result & 0xff;
831 q[2] = result >> 8;
832 }
833 }
834
835 return dest;
836 } else if (source.format() != QImage::Format_ARGB32_Premultiplied
837 && source.format() != QImage::Format_RGB32)
838 {
839 srcImage = source.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
840 }
841
842 QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
843 dest.setDevicePixelRatio(source.devicePixelRatioF());
844
845 const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits());
846 int sx = srcImage.bytesPerLine() >> 2;
847 int sx2 = sx << 1;
848
849 quint32 *dst = reinterpret_cast<quint32*>(dest.bits());
850 int dx = dest.bytesPerLine() >> 2;
851 int ww = dest.width();
852 int hh = dest.height();
853
854 for (int y = hh; y; --y, dst += dx, src += sx2) {
855 const quint32 *p1 = src;
856 const quint32 *p2 = src + sx;
857 quint32 *q = dst;
858 for (int x = ww; x; --x, q++, p1 += 2, p2 += 2)
859 *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1]));
860 }
861
862 return dest;
863}
864
865Q_WIDGETS_EXPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0)
866{
867 if (blurImage.format() != QImage::Format_ARGB32_Premultiplied
868 && blurImage.format() != QImage::Format_RGB32)
869 {
870 blurImage = blurImage.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
871 }
872
873 qreal scale = 1;
874 if (radius >= 4 && blurImage.width() >= 2 && blurImage.height() >= 2) {
875 blurImage = qt_halfScaled(source: blurImage);
876 scale = 2;
877 radius *= qreal(0.5);
878 }
879
880 if (alphaOnly)
881 expblur<12, 10, true>(img&: blurImage, radius, improvedQuality: quality, transposed);
882 else
883 expblur<12, 10, false>(img&: blurImage, radius, improvedQuality: quality, transposed);
884
885 if (p) {
886 p->scale(sx: scale, sy: scale);
887 p->setRenderHint(hint: QPainter::SmoothPixmapTransform);
888 p->drawImage(r: QRect(QPoint(0, 0), blurImage.size() / blurImage.devicePixelRatioF()), image: blurImage);
889 }
890}
891
892Q_WIDGETS_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0)
893{
894 if (blurImage.format() == QImage::Format_Indexed8 || blurImage.format() == QImage::Format_Grayscale8)
895 expblur<12, 10, true>(img&: blurImage, radius, improvedQuality: quality, transposed);
896 else
897 expblur<12, 10, false>(img&: blurImage, radius, improvedQuality: quality, transposed);
898}
899
900Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale);
901
902/*!
903 \internal
904*/
905void QPixmapBlurFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &rect) const
906{
907 Q_D(const QPixmapBlurFilter);
908 if (!painter->isActive())
909 return;
910
911 if (src.isNull())
912 return;
913
914 QRectF srcRect = rect;
915 if (srcRect.isNull())
916 srcRect = src.rect();
917
918 if (d->radius <= 1) {
919 painter->drawPixmap(targetRect: srcRect.translated(p), pixmap: src, sourceRect: srcRect);
920 return;
921 }
922
923 qreal scaledRadius = radiusScale * d->radius;
924 qreal scale;
925 if (qt_scaleForTransform(transform: painter->transform(), scale: &scale))
926 scaledRadius /= scale;
927
928 QImage srcImage;
929
930 if (srcRect == src.rect()) {
931 srcImage = src.toImage();
932 } else {
933 QRect rect = srcRect.toAlignedRect().intersected(other: src.rect());
934 srcImage = src.copy(rect).toImage();
935 }
936
937 QTransform transform = painter->worldTransform();
938 painter->translate(offset: p);
939 qt_blurImage(p: painter, blurImage&: srcImage, radius: scaledRadius, quality: (d->hints & QGraphicsBlurEffect::QualityHint), alphaOnly: false);
940 painter->setWorldTransform(matrix: transform);
941}
942
943// grayscales the image to dest (could be same). If rect isn't defined
944// destination image size is used to determine the dimension of grayscaling
945// process.
946static void grayscale(const QImage &image, QImage &dest, const QRect& rect = QRect())
947{
948 QRect destRect = rect;
949 QRect srcRect = rect;
950 if (rect.isNull()) {
951 srcRect = dest.rect();
952 destRect = dest.rect();
953 }
954 if (&image != &dest) {
955 destRect.moveTo(p: QPoint(0, 0));
956 }
957
958 const unsigned int *data = (const unsigned int *)image.bits();
959 unsigned int *outData = (unsigned int *)dest.bits();
960
961 if (dest.size() == image.size() && image.rect() == srcRect) {
962 // a bit faster loop for grayscaling everything
963 int pixels = dest.width() * dest.height();
964 for (int i = 0; i < pixels; ++i) {
965 int val = qGray(rgb: data[i]);
966 outData[i] = qRgba(r: val, g: val, b: val, a: qAlpha(rgb: data[i]));
967 }
968 } else {
969 int yd = destRect.top();
970 for (int y = srcRect.top(); y <= srcRect.bottom() && y < image.height(); y++) {
971 data = (const unsigned int*)image.scanLine(y);
972 outData = (unsigned int*)dest.scanLine(yd++);
973 int xd = destRect.left();
974 for (int x = srcRect.left(); x <= srcRect.right() && x < image.width(); x++) {
975 int val = qGray(rgb: data[x]);
976 outData[xd++] = qRgba(r: val, g: val, b: val, a: qAlpha(rgb: data[x]));
977 }
978 }
979 }
980}
981
982/*!
983 \class QPixmapColorizeFilter
984 \since 4.5
985 \ingroup painting
986
987 \brief The QPixmapColorizeFilter class provides colorizing
988 filtering for pixmaps.
989
990 A colorize filter gives the pixmap a tint of its color(). The
991 filter first grayscales the pixmap and then converts those to
992 colorized values using QPainter::CompositionMode_Screen with the
993 chosen color. The alpha-channel is not changed.
994
995 Example:
996 \snippet code/src_gui_image_qpixmapfilter.cpp 0
997
998 \sa QPainter::CompositionMode
999
1000 \internal
1001*/
1002class QPixmapColorizeFilterPrivate : public QPixmapFilterPrivate
1003{
1004 Q_DECLARE_PUBLIC(QPixmapColorizeFilter)
1005public:
1006 QColor color;
1007 qreal strength;
1008 quint32 opaque : 1;
1009 quint32 alphaBlend : 1;
1010 quint32 padding : 30;
1011};
1012
1013/*!
1014 Constructs an pixmap colorize filter.
1015
1016 Default color value for colorizing is QColor(0, 0, 192).
1017
1018 \internal
1019*/
1020QPixmapColorizeFilter::QPixmapColorizeFilter(QObject *parent)
1021 : QPixmapFilter(*new QPixmapColorizeFilterPrivate, ColorizeFilter, parent)
1022{
1023 Q_D(QPixmapColorizeFilter);
1024 d->color = QColor(0, 0, 192);
1025 d->strength = qreal(1);
1026 d->opaque = true;
1027 d->alphaBlend = false;
1028}
1029
1030/*!
1031 \internal
1032*/
1033QPixmapColorizeFilter::~QPixmapColorizeFilter()
1034{
1035 // was inline until Qt 5.6, so essentially
1036 // must stay empty until ### Qt 6
1037}
1038
1039/*!
1040 Gets the color of the colorize filter.
1041
1042 \internal
1043*/
1044QColor QPixmapColorizeFilter::color() const
1045{
1046 Q_D(const QPixmapColorizeFilter);
1047 return d->color;
1048}
1049
1050/*!
1051 Sets the color of the colorize filter to the \a color specified.
1052
1053 \internal
1054*/
1055void QPixmapColorizeFilter::setColor(const QColor &color)
1056{
1057 Q_D(QPixmapColorizeFilter);
1058 d->color = color;
1059}
1060
1061/*!
1062 Gets the strength of the colorize filter, 1.0 means full colorized while
1063 0.0 equals to no filtering at all.
1064
1065 \internal
1066*/
1067qreal QPixmapColorizeFilter::strength() const
1068{
1069 Q_D(const QPixmapColorizeFilter);
1070 return d->strength;
1071}
1072
1073/*!
1074 Sets the strength of the colorize filter to \a strength.
1075
1076 \internal
1077*/
1078void QPixmapColorizeFilter::setStrength(qreal strength)
1079{
1080 Q_D(QPixmapColorizeFilter);
1081 d->strength = qBound(min: qreal(0), val: strength, max: qreal(1));
1082 d->opaque = !qFuzzyIsNull(d: d->strength);
1083 d->alphaBlend = !qFuzzyIsNull(d: d->strength - 1);
1084}
1085
1086/*!
1087 \internal
1088*/
1089void QPixmapColorizeFilter::draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect) const
1090{
1091 Q_D(const QPixmapColorizeFilter);
1092
1093 if (src.isNull())
1094 return;
1095
1096 // raster implementation
1097
1098 if (!d->opaque) {
1099 painter->drawPixmap(p: dest, pm: src, sr: srcRect);
1100 return;
1101 }
1102
1103 QImage srcImage;
1104 QImage destImage;
1105
1106 if (srcRect.isNull()) {
1107 srcImage = src.toImage();
1108 const auto format = srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
1109 srcImage = std::move(srcImage).convertToFormat(f: format);
1110 destImage = QImage(srcImage.size(), srcImage.format());
1111 } else {
1112 QRect rect = srcRect.toAlignedRect().intersected(other: src.rect());
1113
1114 srcImage = src.copy(rect).toImage();
1115 const auto format = srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
1116 srcImage = std::move(srcImage).convertToFormat(f: format);
1117 destImage = QImage(rect.size(), srcImage.format());
1118 }
1119 destImage.setDevicePixelRatio(src.devicePixelRatioF());
1120
1121 // do colorizing
1122 QPainter destPainter(&destImage);
1123 grayscale(image: srcImage, dest&: destImage, rect: srcImage.rect());
1124 destPainter.setCompositionMode(QPainter::CompositionMode_Screen);
1125 destPainter.fillRect(srcImage.rect(), color: d->color);
1126 destPainter.end();
1127
1128 if (d->alphaBlend) {
1129 // alpha blending srcImage and destImage
1130 QImage buffer = srcImage;
1131 QPainter bufPainter(&buffer);
1132 bufPainter.setOpacity(d->strength);
1133 bufPainter.drawImage(x: 0, y: 0, image: destImage);
1134 bufPainter.end();
1135 destImage = std::move(buffer);
1136 }
1137
1138 if (srcImage.hasAlphaChannel()) {
1139 Q_ASSERT(destImage.format() == QImage::Format_ARGB32_Premultiplied);
1140 QPainter maskPainter(&destImage);
1141 maskPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
1142 maskPainter.drawImage(x: 0, y: 0, image: srcImage);
1143 }
1144
1145 painter->drawImage(p: dest, image: destImage);
1146}
1147
1148class QPixmapDropShadowFilterPrivate : public QPixmapFilterPrivate
1149{
1150public:
1151 QPixmapDropShadowFilterPrivate()
1152 : offset(8, 8), color(63, 63, 63, 180), radius(1) {}
1153
1154 QPointF offset;
1155 QColor color;
1156 qreal radius;
1157};
1158
1159/*!
1160 \class QPixmapDropShadowFilter
1161 \since 4.5
1162 \ingroup painting
1163
1164 \brief The QPixmapDropShadowFilter class is a convenience class
1165 for drawing pixmaps with drop shadows.
1166
1167 The drop shadow is produced by taking a copy of the source pixmap
1168 and applying a color to the copy using a
1169 QPainter::CompositionMode_DestinationIn operation. This produces a
1170 homogeneously-colored pixmap which is then drawn using a
1171 QPixmapConvolutionFilter at an offset. The original pixmap is
1172 drawn on top.
1173
1174 The QPixmapDropShadowFilter class provides some customization
1175 options to specify how the drop shadow should appear. The color of
1176 the drop shadow can be modified using the setColor() function, the
1177 drop shadow offset can be modified using the setOffset() function,
1178 and the blur radius of the drop shadow can be changed through the
1179 setBlurRadius() function.
1180
1181 By default, the drop shadow is a dark gray shadow, blurred with a
1182 radius of 1 at an offset of 8 pixels towards the lower right.
1183
1184 Example:
1185 \snippet code/src_gui_image_qpixmapfilter.cpp 2
1186
1187 \sa QPixmapColorizeFilter, QPixmapConvolutionFilter
1188
1189 \internal
1190 */
1191
1192/*!
1193 Constructs drop shadow filter.
1194
1195 \internal
1196*/
1197QPixmapDropShadowFilter::QPixmapDropShadowFilter(QObject *parent)
1198 : QPixmapFilter(*new QPixmapDropShadowFilterPrivate, DropShadowFilter, parent)
1199{
1200}
1201
1202/*!
1203 Destroys drop shadow filter.
1204
1205 \internal
1206*/
1207QPixmapDropShadowFilter::~QPixmapDropShadowFilter()
1208{
1209}
1210
1211/*!
1212 Returns the radius in pixels of the blur on the drop shadow.
1213
1214 A smaller radius results in a sharper shadow.
1215
1216 \sa color(), offset()
1217
1218 \internal
1219*/
1220qreal QPixmapDropShadowFilter::blurRadius() const
1221{
1222 Q_D(const QPixmapDropShadowFilter);
1223 return d->radius;
1224}
1225
1226/*!
1227 Sets the radius in pixels of the blur on the drop shadow to the \a radius specified.
1228
1229 Using a smaller radius results in a sharper shadow.
1230
1231 \sa setColor(), setOffset()
1232
1233 \internal
1234*/
1235void QPixmapDropShadowFilter::setBlurRadius(qreal radius)
1236{
1237 Q_D(QPixmapDropShadowFilter);
1238 d->radius = radius;
1239}
1240
1241/*!
1242 Returns the color of the drop shadow.
1243
1244 \sa blurRadius(), offset()
1245
1246 \internal
1247*/
1248QColor QPixmapDropShadowFilter::color() const
1249{
1250 Q_D(const QPixmapDropShadowFilter);
1251 return d->color;
1252}
1253
1254/*!
1255 Sets the color of the drop shadow to the \a color specified.
1256
1257 \sa setBlurRadius(), setOffset()
1258
1259 \internal
1260*/
1261void QPixmapDropShadowFilter::setColor(const QColor &color)
1262{
1263 Q_D(QPixmapDropShadowFilter);
1264 d->color = color;
1265}
1266
1267/*!
1268 Returns the shadow offset in pixels.
1269
1270 \sa blurRadius(), color()
1271
1272 \internal
1273*/
1274QPointF QPixmapDropShadowFilter::offset() const
1275{
1276 Q_D(const QPixmapDropShadowFilter);
1277 return d->offset;
1278}
1279
1280/*!
1281 Sets the shadow offset in pixels to the \a offset specified.
1282
1283 \sa setBlurRadius(), setColor()
1284
1285 \internal
1286*/
1287void QPixmapDropShadowFilter::setOffset(const QPointF &offset)
1288{
1289 Q_D(QPixmapDropShadowFilter);
1290 d->offset = offset;
1291}
1292
1293/*!
1294 \fn void QPixmapDropShadowFilter::setOffset(qreal dx, qreal dy)
1295 \overload
1296
1297 Sets the shadow offset in pixels to be the displacement specified by the
1298 horizontal \a dx and vertical \a dy coordinates.
1299
1300 \sa setBlurRadius(), setColor()
1301
1302 \internal
1303*/
1304
1305/*!
1306 \internal
1307 */
1308QRectF QPixmapDropShadowFilter::boundingRectFor(const QRectF &rect) const
1309{
1310 Q_D(const QPixmapDropShadowFilter);
1311 return rect.united(r: rect.translated(p: d->offset).adjusted(xp1: -d->radius, yp1: -d->radius, xp2: d->radius, yp2: d->radius));
1312}
1313
1314/*!
1315 \internal
1316 */
1317void QPixmapDropShadowFilter::draw(QPainter *p,
1318 const QPointF &pos,
1319 const QPixmap &px,
1320 const QRectF &src) const
1321{
1322 Q_D(const QPixmapDropShadowFilter);
1323
1324 if (px.isNull())
1325 return;
1326
1327 QImage tmp(px.size(), QImage::Format_ARGB32_Premultiplied);
1328 tmp.setDevicePixelRatio(px.devicePixelRatioF());
1329 tmp.fill(pixel: 0);
1330 QPainter tmpPainter(&tmp);
1331 tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
1332 tmpPainter.drawPixmap(p: d->offset, pm: px);
1333 tmpPainter.end();
1334
1335 // blur the alpha channel
1336 QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied);
1337 blurred.setDevicePixelRatio(px.devicePixelRatioF());
1338 blurred.fill(pixel: 0);
1339 QPainter blurPainter(&blurred);
1340 qt_blurImage(p: &blurPainter, blurImage&: tmp, radius: d->radius, quality: false, alphaOnly: true);
1341 blurPainter.end();
1342
1343 tmp = std::move(blurred);
1344
1345 // blacken the image...
1346 tmpPainter.begin(&tmp);
1347 tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
1348 tmpPainter.fillRect(tmp.rect(), color: d->color);
1349 tmpPainter.end();
1350
1351 // draw the blurred drop shadow...
1352 p->drawImage(p: pos, image: tmp);
1353
1354 // Draw the actual pixmap...
1355 p->drawPixmap(p: pos, pm: px, sr: src);
1356}
1357
1358QT_END_NAMESPACE
1359
1360#include "moc_qpixmapfilter_p.cpp"
1361

source code of qtbase/src/widgets/effects/qpixmapfilter.cpp