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

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