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/qdrawhelper_p.h" |
19 | |
20 | #include <memory> |
21 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | class QPixmapFilterPrivate : public QObjectPrivate |
25 | { |
26 | Q_DECLARE_PUBLIC(QPixmapFilter) |
27 | public: |
28 | QPixmapFilter::FilterType type; |
29 | }; |
30 | |
31 | /*! |
32 | \class QPixmapFilter |
33 | \since 4.5 |
34 | \ingroup painting |
35 | |
36 | \brief The QPixmapFilter class provides the basic functionality for |
37 | pixmap filter classes. Pixmap filter can be for example colorize or blur. |
38 | |
39 | QPixmapFilter is the base class for every pixmap filter. QPixmapFilter is |
40 | an abstract class and cannot itself be instantiated. It provides a standard |
41 | interface for filter processing. |
42 | |
43 | \internal |
44 | */ |
45 | |
46 | /*! |
47 | \enum QPixmapFilter::FilterType |
48 | |
49 | \internal |
50 | |
51 | This enum describes the types of filter that can be applied to pixmaps. |
52 | |
53 | \value ConvolutionFilter A filter that is used to calculate the convolution |
54 | of the image with a kernel. See |
55 | QPixmapConvolutionFilter for more information. |
56 | \value ColorizeFilter A filter that is used to change the overall color |
57 | of an image. See QPixmapColorizeFilter for more |
58 | information. |
59 | \value DropShadowFilter A filter that is used to add a drop shadow to an |
60 | image. See QPixmapDropShadowFilter for more |
61 | information. |
62 | \value BlurFilter A filter that is used to blur an image using |
63 | a simple blur radius. See QPixmapBlurFilter |
64 | for more information. |
65 | |
66 | \value UserFilter The first filter type that can be used for |
67 | application-specific purposes. |
68 | */ |
69 | |
70 | |
71 | /*! |
72 | Constructs a default QPixmapFilter with the given \a type. |
73 | |
74 | This constructor should be used when subclassing QPixmapFilter to |
75 | create custom user filters. |
76 | |
77 | \internal |
78 | */ |
79 | QPixmapFilter::QPixmapFilter(FilterType type, QObject *parent) |
80 | : QObject(*new QPixmapFilterPrivate, parent) |
81 | { |
82 | d_func()->type = type; |
83 | } |
84 | |
85 | |
86 | |
87 | /*! |
88 | \internal |
89 | */ |
90 | QPixmapFilter::QPixmapFilter(QPixmapFilterPrivate&d, QPixmapFilter::FilterType type, QObject *parent) |
91 | : QObject(d, parent) |
92 | { |
93 | d_func()->type = type; |
94 | } |
95 | |
96 | |
97 | /*! |
98 | Destroys the pixmap filter. |
99 | |
100 | \internal |
101 | */ |
102 | QPixmapFilter::~QPixmapFilter() |
103 | { |
104 | } |
105 | |
106 | /*! |
107 | Returns the type of the filter. All standard pixmap filter classes |
108 | are associated with a unique value. |
109 | |
110 | \internal |
111 | */ |
112 | QPixmapFilter::FilterType QPixmapFilter::type() const |
113 | { |
114 | Q_D(const QPixmapFilter); |
115 | return d->type; |
116 | } |
117 | |
118 | /*! |
119 | Returns the bounding rectangle that is affected by the pixmap |
120 | filter if the filter is applied to the specified \a rect. |
121 | |
122 | \internal |
123 | */ |
124 | QRectF QPixmapFilter::boundingRectFor(const QRectF &rect) const |
125 | { |
126 | return rect; |
127 | } |
128 | |
129 | /*! |
130 | \fn void QPixmapFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF& srcRect) const |
131 | |
132 | Uses \a painter to draw filtered result of \a src at the point |
133 | specified by \a p. If \a srcRect is specified the it will |
134 | be used as a source rectangle to only draw a part of the source. |
135 | |
136 | draw() will affect the area which boundingRectFor() returns. |
137 | |
138 | \internal |
139 | */ |
140 | |
141 | /*! |
142 | \class QPixmapConvolutionFilter |
143 | \since 4.5 |
144 | \ingroup painting |
145 | |
146 | \brief The QPixmapConvolutionFilter class provides convolution |
147 | filtering for pixmaps. |
148 | |
149 | QPixmapConvolutionFilter implements a convolution pixmap filter, |
150 | which is applied when \l{QPixmapFilter::}{draw()} is called. A |
151 | convolution filter lets you distort an image by setting the values |
152 | of a matrix of qreal values called its |
153 | \l{setConvolutionKernel()}{kernel}. The matrix's values are |
154 | usually between -1.0 and 1.0. |
155 | |
156 | \omit |
157 | In convolution filtering, the pixel value is calculated from the |
158 | neighboring pixels based on the weighting convolution kernel. |
159 | This needs explaining to be useful. |
160 | \endomit |
161 | |
162 | Example: |
163 | \snippet code/src_gui_image_qpixmapfilter.cpp 1 |
164 | |
165 | \sa {Pixmap Filters Example}, QPixmapColorizeFilter, QPixmapDropShadowFilter |
166 | |
167 | |
168 | \internal |
169 | */ |
170 | |
171 | class QPixmapConvolutionFilterPrivate : public QPixmapFilterPrivate |
172 | { |
173 | public: |
174 | QPixmapConvolutionFilterPrivate(): convolutionKernel(nullptr), kernelWidth(0), kernelHeight(0), convoluteAlpha(false) {} |
175 | ~QPixmapConvolutionFilterPrivate() { |
176 | delete[] convolutionKernel; |
177 | } |
178 | |
179 | qreal *convolutionKernel; |
180 | int kernelWidth; |
181 | int kernelHeight; |
182 | bool convoluteAlpha; |
183 | }; |
184 | |
185 | |
186 | /*! |
187 | Constructs a pixmap convolution filter. |
188 | |
189 | By default there is no convolution kernel. |
190 | |
191 | \internal |
192 | */ |
193 | QPixmapConvolutionFilter::QPixmapConvolutionFilter(QObject *parent) |
194 | : QPixmapFilter(*new QPixmapConvolutionFilterPrivate, ConvolutionFilter, parent) |
195 | { |
196 | Q_D(QPixmapConvolutionFilter); |
197 | d->convoluteAlpha = true; |
198 | } |
199 | |
200 | /*! |
201 | Destructor of pixmap convolution filter. |
202 | |
203 | \internal |
204 | */ |
205 | QPixmapConvolutionFilter::~QPixmapConvolutionFilter() |
206 | { |
207 | } |
208 | |
209 | /*! |
210 | Sets convolution kernel with the given number of \a rows and \a columns. |
211 | Values from \a kernel are copied to internal data structure. |
212 | |
213 | To preserve the intensity of the pixmap, the sum of all the |
214 | values in the convolution kernel should add up to 1.0. A sum |
215 | greater than 1.0 produces a lighter result and a sum less than 1.0 |
216 | produces a darker and transparent result. |
217 | |
218 | \internal |
219 | */ |
220 | void QPixmapConvolutionFilter::setConvolutionKernel(const qreal *kernel, int rows, int columns) |
221 | { |
222 | Q_D(QPixmapConvolutionFilter); |
223 | delete [] d->convolutionKernel; |
224 | d->convolutionKernel = new qreal[rows * columns]; |
225 | memcpy(dest: d->convolutionKernel, src: kernel, n: sizeof(qreal) * rows * columns); |
226 | d->kernelWidth = columns; |
227 | d->kernelHeight = rows; |
228 | } |
229 | |
230 | /*! |
231 | Gets the convolution kernel data. |
232 | |
233 | \internal |
234 | */ |
235 | const qreal *QPixmapConvolutionFilter::convolutionKernel() const |
236 | { |
237 | Q_D(const QPixmapConvolutionFilter); |
238 | return d->convolutionKernel; |
239 | } |
240 | |
241 | /*! |
242 | Gets the number of rows in the convolution kernel. |
243 | |
244 | \internal |
245 | */ |
246 | int QPixmapConvolutionFilter::rows() const |
247 | { |
248 | Q_D(const QPixmapConvolutionFilter); |
249 | return d->kernelHeight; |
250 | } |
251 | |
252 | /*! |
253 | Gets the number of columns in the convolution kernel. |
254 | |
255 | \internal |
256 | */ |
257 | int QPixmapConvolutionFilter::columns() const |
258 | { |
259 | Q_D(const QPixmapConvolutionFilter); |
260 | return d->kernelWidth; |
261 | } |
262 | |
263 | |
264 | /*! |
265 | \internal |
266 | */ |
267 | QRectF QPixmapConvolutionFilter::boundingRectFor(const QRectF &rect) const |
268 | { |
269 | Q_D(const QPixmapConvolutionFilter); |
270 | return rect.adjusted(xp1: -d->kernelWidth / 2, yp1: -d->kernelHeight / 2, xp2: (d->kernelWidth - 1) / 2, yp2: (d->kernelHeight - 1) / 2); |
271 | } |
272 | |
273 | // Convolutes the image |
274 | static void convolute( |
275 | QImage *destImage, |
276 | const QPointF &pos, |
277 | const QImage &srcImage, |
278 | const QRectF &srcRect, |
279 | QPainter::CompositionMode mode, |
280 | qreal *kernel, |
281 | int kernelWidth, |
282 | int kernelHeight ) |
283 | { |
284 | const QImage processImage = (srcImage.format() != QImage::Format_ARGB32_Premultiplied ) ? srcImage.convertToFormat(f: QImage::Format_ARGB32_Premultiplied) : srcImage; |
285 | // TODO: support also other formats directly without copying |
286 | |
287 | std::unique_ptr<int[]> fixedKernel(new int[kernelWidth * kernelHeight]); |
288 | for(int i = 0; i < kernelWidth*kernelHeight; i++) |
289 | { |
290 | fixedKernel[i] = (int)(65536 * kernel[i]); |
291 | } |
292 | QRectF trect = srcRect.isNull() ? processImage.rect() : srcRect; |
293 | trect.moveTo(p: pos); |
294 | QRectF bounded = trect.adjusted(xp1: -kernelWidth / 2, yp1: -kernelHeight / 2, xp2: (kernelWidth - 1) / 2, yp2: (kernelHeight - 1) / 2); |
295 | QRect rect = bounded.toAlignedRect(); |
296 | QRect targetRect = rect.intersected(other: destImage->rect()); |
297 | |
298 | QRectF srect = srcRect.isNull() ? processImage.rect() : srcRect; |
299 | QRectF sbounded = srect.adjusted(xp1: -kernelWidth / 2, yp1: -kernelHeight / 2, xp2: (kernelWidth - 1) / 2, yp2: (kernelHeight - 1) / 2); |
300 | QPoint srcStartPoint = sbounded.toAlignedRect().topLeft()+(targetRect.topLeft()-rect.topLeft()); |
301 | |
302 | const uint *sourceStart = (const uint*)processImage.scanLine(0); |
303 | uint *outputStart = (uint*)destImage->scanLine(0); |
304 | |
305 | int yk = srcStartPoint.y(); |
306 | for (int y = targetRect.top(); y <= targetRect.bottom(); y++) { |
307 | uint* output = outputStart + (destImage->bytesPerLine()/sizeof(uint))*y+targetRect.left(); |
308 | int xk = srcStartPoint.x(); |
309 | for(int x = targetRect.left(); x <= targetRect.right(); x++) { |
310 | int r = 0; |
311 | int g = 0; |
312 | int b = 0; |
313 | int a = 0; |
314 | |
315 | // some out of bounds pre-checking to avoid inner-loop ifs |
316 | int kernely = -kernelHeight/2; |
317 | int starty = 0; |
318 | int endy = kernelHeight; |
319 | if (yk+kernely+endy >= srcImage.height()) |
320 | endy = kernelHeight-((yk+kernely+endy)-srcImage.height())-1; |
321 | if (yk+kernely < 0) |
322 | starty = -(yk+kernely); |
323 | |
324 | int kernelx = -kernelWidth/2; |
325 | int startx = 0; |
326 | int endx = kernelWidth; |
327 | if (xk+kernelx+endx >= srcImage.width()) |
328 | endx = kernelWidth-((xk+kernelx+endx)-srcImage.width())-1; |
329 | if (xk+kernelx < 0) |
330 | startx = -(xk+kernelx); |
331 | |
332 | for (int ys = starty; ys < endy; ys ++) { |
333 | const uint *pix = sourceStart + (processImage.bytesPerLine()/sizeof(uint))*(yk+kernely+ys) + ((xk+kernelx+startx)); |
334 | const uint *endPix = pix+endx-startx; |
335 | int kernelPos = ys*kernelWidth+startx; |
336 | while (pix < endPix) { |
337 | int factor = fixedKernel[kernelPos++]; |
338 | a += (((*pix) & 0xff000000)>>24) * factor; |
339 | r += (((*pix) & 0x00ff0000)>>16) * factor; |
340 | g += (((*pix) & 0x0000ff00)>>8 ) * factor; |
341 | b += (((*pix) & 0x000000ff) ) * factor; |
342 | pix++; |
343 | } |
344 | } |
345 | |
346 | r = qBound(min: (int)0, val: r >> 16, max: (int)255); |
347 | g = qBound(min: (int)0, val: g >> 16, max: (int)255); |
348 | b = qBound(min: (int)0, val: b >> 16, max: (int)255); |
349 | a = qBound(min: (int)0, val: a >> 16, max: (int)255); |
350 | // composition mode checking could be moved outside of loop |
351 | if (mode == QPainter::CompositionMode_Source) { |
352 | uint color = (a<<24)+(r<<16)+(g<<8)+b; |
353 | *output++ = color; |
354 | } else { |
355 | uint current = *output; |
356 | uchar ca = (current&0xff000000)>>24; |
357 | uchar cr = (current&0x00ff0000)>>16; |
358 | uchar cg = (current&0x0000ff00)>>8; |
359 | uchar cb = (current&0x000000ff); |
360 | uint color = |
361 | (((ca*(255-a) >> 8)+a) << 24)+ |
362 | (((cr*(255-a) >> 8)+r) << 16)+ |
363 | (((cg*(255-a) >> 8)+g) << 8)+ |
364 | (((cb*(255-a) >> 8)+b)); |
365 | *output++ = color; |
366 | } |
367 | xk++; |
368 | } |
369 | yk++; |
370 | } |
371 | } |
372 | |
373 | /*! |
374 | \internal |
375 | */ |
376 | void QPixmapConvolutionFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF& srcRect) const |
377 | { |
378 | Q_D(const QPixmapConvolutionFilter); |
379 | if (!painter->isActive()) |
380 | return; |
381 | |
382 | if (d->kernelWidth<=0 || d->kernelHeight <= 0) |
383 | return; |
384 | |
385 | if (src.isNull()) |
386 | return; |
387 | |
388 | // raster implementation |
389 | |
390 | QImage *target = nullptr; |
391 | if (painter->paintEngine()->paintDevice()->devType() == QInternal::Image) { |
392 | target = static_cast<QImage *>(painter->paintEngine()->paintDevice()); |
393 | |
394 | QTransform mat = painter->combinedTransform(); |
395 | |
396 | if (mat.type() > QTransform::TxTranslate) { |
397 | // Disabled because of transformation... |
398 | target = nullptr; |
399 | } else { |
400 | QRasterPaintEngine *pe = static_cast<QRasterPaintEngine *>(painter->paintEngine()); |
401 | if (pe->clipType() == QRasterPaintEngine::ComplexClip) |
402 | // disabled because of complex clipping... |
403 | target = nullptr; |
404 | else { |
405 | QRectF clip = pe->clipBoundingRect(); |
406 | QRectF rect = boundingRectFor(rect: srcRect.isEmpty() ? src.rect() : srcRect); |
407 | QTransform x = painter->deviceTransform(); |
408 | if (!clip.contains(r: rect.translated(dx: x.dx() + p.x(), dy: x.dy() + p.y()))) { |
409 | target = nullptr; |
410 | } |
411 | |
412 | } |
413 | } |
414 | } |
415 | |
416 | if (target) { |
417 | QTransform x = painter->deviceTransform(); |
418 | QPointF offset(x.dx(), x.dy()); |
419 | |
420 | convolute(destImage: target, pos: p+offset, srcImage: src.toImage(), srcRect, mode: QPainter::CompositionMode_SourceOver, kernel: d->convolutionKernel, kernelWidth: d->kernelWidth, kernelHeight: d->kernelHeight); |
421 | } else { |
422 | QRect srect = srcRect.isNull() ? src.rect() : srcRect.toRect(); |
423 | QRect rect = boundingRectFor(rect: srect).toRect(); |
424 | QImage result = QImage(rect.size(), QImage::Format_ARGB32_Premultiplied); |
425 | QPoint offset = srect.topLeft() - rect.topLeft(); |
426 | convolute(destImage: &result, |
427 | pos: offset, |
428 | srcImage: src.toImage(), |
429 | srcRect: srect, |
430 | mode: QPainter::CompositionMode_Source, |
431 | kernel: d->convolutionKernel, |
432 | kernelWidth: d->kernelWidth, |
433 | kernelHeight: d->kernelHeight); |
434 | painter->drawImage(p: p - offset, image: result); |
435 | } |
436 | } |
437 | |
438 | /*! |
439 | \class QPixmapBlurFilter |
440 | \since 4.6 |
441 | \ingroup multimedia |
442 | |
443 | \brief The QPixmapBlurFilter class provides blur filtering |
444 | for pixmaps. |
445 | |
446 | QPixmapBlurFilter implements a blur pixmap filter, |
447 | which is applied when \l{QPixmapFilter::}{draw()} is called. |
448 | |
449 | The filter lets you specialize the radius of the blur as well |
450 | as hints as to whether to prefer performance or quality. |
451 | |
452 | By default, the blur effect is produced by applying an exponential |
453 | filter generated from the specified blurRadius(). Paint engines |
454 | may override this with a custom blur that is faster on the |
455 | underlying hardware. |
456 | |
457 | \sa {Pixmap Filters Example}, QPixmapConvolutionFilter, QPixmapDropShadowFilter |
458 | |
459 | \internal |
460 | */ |
461 | |
462 | class QPixmapBlurFilterPrivate : public QPixmapFilterPrivate |
463 | { |
464 | public: |
465 | QPixmapBlurFilterPrivate() : radius(5), hints(QGraphicsBlurEffect::PerformanceHint) {} |
466 | |
467 | qreal radius; |
468 | QGraphicsBlurEffect::BlurHints hints; |
469 | }; |
470 | |
471 | |
472 | /*! |
473 | Constructs a pixmap blur filter. |
474 | |
475 | \internal |
476 | */ |
477 | QPixmapBlurFilter::QPixmapBlurFilter(QObject *parent) |
478 | : QPixmapFilter(*new QPixmapBlurFilterPrivate, BlurFilter, parent) |
479 | { |
480 | } |
481 | |
482 | /*! |
483 | Destructor of pixmap blur filter. |
484 | |
485 | \internal |
486 | */ |
487 | QPixmapBlurFilter::~QPixmapBlurFilter() |
488 | { |
489 | } |
490 | |
491 | /*! |
492 | Sets the radius of the blur filter. Higher radius produces increased blurriness. |
493 | |
494 | \internal |
495 | */ |
496 | void QPixmapBlurFilter::setRadius(qreal radius) |
497 | { |
498 | Q_D(QPixmapBlurFilter); |
499 | d->radius = radius; |
500 | } |
501 | |
502 | /*! |
503 | Gets the radius of the blur filter. |
504 | |
505 | \internal |
506 | */ |
507 | qreal QPixmapBlurFilter::radius() const |
508 | { |
509 | Q_D(const QPixmapBlurFilter); |
510 | return d->radius; |
511 | } |
512 | |
513 | /*! |
514 | Setting the blur hints to PerformanceHint causes the implementation |
515 | to trade off visual quality to blur the image faster. Setting the |
516 | blur hints to QualityHint causes the implementation to improve |
517 | visual quality at the expense of speed. |
518 | |
519 | AnimationHint causes the implementation to optimize for animating |
520 | the blur radius, possibly by caching blurred versions of the source |
521 | pixmap. |
522 | |
523 | The implementation is free to ignore this value if it only has a single |
524 | blur algorithm. |
525 | |
526 | \internal |
527 | */ |
528 | void QPixmapBlurFilter::setBlurHints(QGraphicsBlurEffect::BlurHints hints) |
529 | { |
530 | Q_D(QPixmapBlurFilter); |
531 | d->hints = hints; |
532 | } |
533 | |
534 | /*! |
535 | Gets the blur hints of the blur filter. |
536 | |
537 | \internal |
538 | */ |
539 | QGraphicsBlurEffect::BlurHints QPixmapBlurFilter::blurHints() const |
540 | { |
541 | Q_D(const QPixmapBlurFilter); |
542 | return d->hints; |
543 | } |
544 | |
545 | const qreal radiusScale = qreal(2.5); |
546 | |
547 | /*! |
548 | \internal |
549 | */ |
550 | QRectF QPixmapBlurFilter::boundingRectFor(const QRectF &rect) const |
551 | { |
552 | Q_D(const QPixmapBlurFilter); |
553 | const qreal delta = radiusScale * d->radius + 1; |
554 | return rect.adjusted(xp1: -delta, yp1: -delta, xp2: delta, yp2: delta); |
555 | } |
556 | |
557 | Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); |
558 | |
559 | Q_GUI_EXPORT extern void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); |
560 | |
561 | /*! |
562 | \internal |
563 | */ |
564 | void QPixmapBlurFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &rect) const |
565 | { |
566 | Q_D(const QPixmapBlurFilter); |
567 | if (!painter->isActive()) |
568 | return; |
569 | |
570 | if (src.isNull()) |
571 | return; |
572 | |
573 | QRectF srcRect = rect; |
574 | if (srcRect.isNull()) |
575 | srcRect = src.rect(); |
576 | |
577 | if (d->radius <= 1) { |
578 | painter->drawPixmap(targetRect: srcRect.translated(p), pixmap: src, sourceRect: srcRect); |
579 | return; |
580 | } |
581 | |
582 | qreal scaledRadius = radiusScale * d->radius; |
583 | qreal scale; |
584 | if (qt_scaleForTransform(transform: painter->transform(), scale: &scale)) |
585 | scaledRadius /= scale; |
586 | |
587 | QImage srcImage; |
588 | |
589 | if (srcRect == src.rect()) { |
590 | srcImage = src.toImage(); |
591 | } else { |
592 | QRect rect = srcRect.toAlignedRect().intersected(other: src.rect()); |
593 | srcImage = src.copy(rect).toImage(); |
594 | } |
595 | |
596 | QTransform transform = painter->worldTransform(); |
597 | painter->translate(offset: p); |
598 | qt_blurImage(p: painter, blurImage&: srcImage, radius: scaledRadius, quality: (d->hints & QGraphicsBlurEffect::QualityHint), alphaOnly: false); |
599 | painter->setWorldTransform(matrix: transform); |
600 | } |
601 | |
602 | // grayscales the image to dest (could be same). If rect isn't defined |
603 | // destination image size is used to determine the dimension of grayscaling |
604 | // process. |
605 | static void grayscale(const QImage &image, QImage &dest, const QRect& rect = QRect()) |
606 | { |
607 | QRect destRect = rect; |
608 | QRect srcRect = rect; |
609 | if (rect.isNull()) { |
610 | srcRect = dest.rect(); |
611 | destRect = dest.rect(); |
612 | } |
613 | if (&image != &dest) { |
614 | destRect.moveTo(p: QPoint(0, 0)); |
615 | } |
616 | |
617 | const unsigned int *data = (const unsigned int *)image.bits(); |
618 | unsigned int *outData = (unsigned int *)dest.bits(); |
619 | |
620 | if (dest.size() == image.size() && image.rect() == srcRect) { |
621 | // a bit faster loop for grayscaling everything |
622 | int pixels = dest.width() * dest.height(); |
623 | for (int i = 0; i < pixels; ++i) { |
624 | int val = qGray(rgb: data[i]); |
625 | outData[i] = qRgba(r: val, g: val, b: val, a: qAlpha(rgb: data[i])); |
626 | } |
627 | } else { |
628 | int yd = destRect.top(); |
629 | for (int y = srcRect.top(); y <= srcRect.bottom() && y < image.height(); y++) { |
630 | data = (const unsigned int*)image.scanLine(y); |
631 | outData = (unsigned int*)dest.scanLine(yd++); |
632 | int xd = destRect.left(); |
633 | for (int x = srcRect.left(); x <= srcRect.right() && x < image.width(); x++) { |
634 | int val = qGray(rgb: data[x]); |
635 | outData[xd++] = qRgba(r: val, g: val, b: val, a: qAlpha(rgb: data[x])); |
636 | } |
637 | } |
638 | } |
639 | } |
640 | |
641 | /*! |
642 | \class QPixmapColorizeFilter |
643 | \since 4.5 |
644 | \ingroup painting |
645 | |
646 | \brief The QPixmapColorizeFilter class provides colorizing |
647 | filtering for pixmaps. |
648 | |
649 | A colorize filter gives the pixmap a tint of its color(). The |
650 | filter first grayscales the pixmap and then converts those to |
651 | colorized values using QPainter::CompositionMode_Screen with the |
652 | chosen color. The alpha-channel is not changed. |
653 | |
654 | Example: |
655 | \snippet code/src_gui_image_qpixmapfilter.cpp 0 |
656 | |
657 | \sa QPainter::CompositionMode |
658 | |
659 | \internal |
660 | */ |
661 | class QPixmapColorizeFilterPrivate : public QPixmapFilterPrivate |
662 | { |
663 | Q_DECLARE_PUBLIC(QPixmapColorizeFilter) |
664 | public: |
665 | QColor color; |
666 | qreal strength; |
667 | quint32 opaque : 1; |
668 | quint32 alphaBlend : 1; |
669 | quint32 padding : 30; |
670 | }; |
671 | |
672 | /*! |
673 | Constructs an pixmap colorize filter. |
674 | |
675 | Default color value for colorizing is QColor(0, 0, 192). |
676 | |
677 | \internal |
678 | */ |
679 | QPixmapColorizeFilter::QPixmapColorizeFilter(QObject *parent) |
680 | : QPixmapFilter(*new QPixmapColorizeFilterPrivate, ColorizeFilter, parent) |
681 | { |
682 | Q_D(QPixmapColorizeFilter); |
683 | d->color = QColor(0, 0, 192); |
684 | d->strength = qreal(1); |
685 | d->opaque = true; |
686 | d->alphaBlend = false; |
687 | } |
688 | |
689 | /*! |
690 | \internal |
691 | */ |
692 | QPixmapColorizeFilter::~QPixmapColorizeFilter() |
693 | { |
694 | } |
695 | |
696 | /*! |
697 | Gets the color of the colorize filter. |
698 | |
699 | \internal |
700 | */ |
701 | QColor QPixmapColorizeFilter::color() const |
702 | { |
703 | Q_D(const QPixmapColorizeFilter); |
704 | return d->color; |
705 | } |
706 | |
707 | /*! |
708 | Sets the color of the colorize filter to the \a color specified. |
709 | |
710 | \internal |
711 | */ |
712 | void QPixmapColorizeFilter::setColor(const QColor &color) |
713 | { |
714 | Q_D(QPixmapColorizeFilter); |
715 | d->color = color; |
716 | } |
717 | |
718 | /*! |
719 | Gets the strength of the colorize filter, 1.0 means full colorized while |
720 | 0.0 equals to no filtering at all. |
721 | |
722 | \internal |
723 | */ |
724 | qreal QPixmapColorizeFilter::strength() const |
725 | { |
726 | Q_D(const QPixmapColorizeFilter); |
727 | return d->strength; |
728 | } |
729 | |
730 | /*! |
731 | Sets the strength of the colorize filter to \a strength. |
732 | |
733 | \internal |
734 | */ |
735 | void QPixmapColorizeFilter::setStrength(qreal strength) |
736 | { |
737 | Q_D(QPixmapColorizeFilter); |
738 | d->strength = qBound(min: qreal(0), val: strength, max: qreal(1)); |
739 | d->opaque = !qFuzzyIsNull(d: d->strength); |
740 | d->alphaBlend = !qFuzzyIsNull(d: d->strength - 1); |
741 | } |
742 | |
743 | /*! |
744 | \internal |
745 | */ |
746 | void QPixmapColorizeFilter::draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect) const |
747 | { |
748 | Q_D(const QPixmapColorizeFilter); |
749 | |
750 | if (src.isNull()) |
751 | return; |
752 | |
753 | // raster implementation |
754 | |
755 | if (!d->opaque) { |
756 | painter->drawPixmap(p: dest, pm: src, sr: srcRect); |
757 | return; |
758 | } |
759 | |
760 | QImage srcImage; |
761 | QImage destImage; |
762 | |
763 | if (srcRect.isNull()) { |
764 | srcImage = src.toImage(); |
765 | const auto format = srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; |
766 | srcImage = std::move(srcImage).convertToFormat(f: format); |
767 | destImage = QImage(srcImage.size(), srcImage.format()); |
768 | } else { |
769 | QRect rect = srcRect.toAlignedRect().intersected(other: src.rect()); |
770 | |
771 | srcImage = src.copy(rect).toImage(); |
772 | const auto format = srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; |
773 | srcImage = std::move(srcImage).convertToFormat(f: format); |
774 | destImage = QImage(rect.size(), srcImage.format()); |
775 | } |
776 | destImage.setDevicePixelRatio(src.devicePixelRatio()); |
777 | |
778 | // do colorizing |
779 | QPainter destPainter(&destImage); |
780 | grayscale(image: srcImage, dest&: destImage, rect: srcImage.rect()); |
781 | destPainter.setCompositionMode(QPainter::CompositionMode_Screen); |
782 | destPainter.fillRect(srcImage.rect(), color: d->color); |
783 | destPainter.end(); |
784 | |
785 | if (d->alphaBlend) { |
786 | // alpha blending srcImage and destImage |
787 | QImage buffer = srcImage; |
788 | QPainter bufPainter(&buffer); |
789 | bufPainter.setOpacity(d->strength); |
790 | bufPainter.drawImage(x: 0, y: 0, image: destImage); |
791 | bufPainter.end(); |
792 | destImage = std::move(buffer); |
793 | } |
794 | |
795 | if (srcImage.hasAlphaChannel()) { |
796 | Q_ASSERT(destImage.format() == QImage::Format_ARGB32_Premultiplied); |
797 | QPainter maskPainter(&destImage); |
798 | maskPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); |
799 | maskPainter.drawImage(x: 0, y: 0, image: srcImage); |
800 | } |
801 | |
802 | painter->drawImage(p: dest, image: destImage); |
803 | } |
804 | |
805 | class QPixmapDropShadowFilterPrivate : public QPixmapFilterPrivate |
806 | { |
807 | public: |
808 | QPixmapDropShadowFilterPrivate() |
809 | : offset(8, 8), color(63, 63, 63, 180), radius(1) {} |
810 | |
811 | QPointF offset; |
812 | QColor color; |
813 | qreal radius; |
814 | }; |
815 | |
816 | /*! |
817 | \class QPixmapDropShadowFilter |
818 | \since 4.5 |
819 | \ingroup painting |
820 | |
821 | \brief The QPixmapDropShadowFilter class is a convenience class |
822 | for drawing pixmaps with drop shadows. |
823 | |
824 | The drop shadow is produced by taking a copy of the source pixmap |
825 | and applying a color to the copy using a |
826 | QPainter::CompositionMode_DestinationIn operation. This produces a |
827 | homogeneously-colored pixmap which is then drawn using a |
828 | QPixmapConvolutionFilter at an offset. The original pixmap is |
829 | drawn on top. |
830 | |
831 | The QPixmapDropShadowFilter class provides some customization |
832 | options to specify how the drop shadow should appear. The color of |
833 | the drop shadow can be modified using the setColor() function, the |
834 | drop shadow offset can be modified using the setOffset() function, |
835 | and the blur radius of the drop shadow can be changed through the |
836 | setBlurRadius() function. |
837 | |
838 | By default, the drop shadow is a dark gray shadow, blurred with a |
839 | radius of 1 at an offset of 8 pixels towards the lower right. |
840 | |
841 | Example: |
842 | \snippet code/src_gui_image_qpixmapfilter.cpp 2 |
843 | |
844 | \sa QPixmapColorizeFilter, QPixmapConvolutionFilter |
845 | |
846 | \internal |
847 | */ |
848 | |
849 | /*! |
850 | Constructs drop shadow filter. |
851 | |
852 | \internal |
853 | */ |
854 | QPixmapDropShadowFilter::QPixmapDropShadowFilter(QObject *parent) |
855 | : QPixmapFilter(*new QPixmapDropShadowFilterPrivate, DropShadowFilter, parent) |
856 | { |
857 | } |
858 | |
859 | /*! |
860 | Destroys drop shadow filter. |
861 | |
862 | \internal |
863 | */ |
864 | QPixmapDropShadowFilter::~QPixmapDropShadowFilter() |
865 | { |
866 | } |
867 | |
868 | /*! |
869 | Returns the radius in pixels of the blur on the drop shadow. |
870 | |
871 | A smaller radius results in a sharper shadow. |
872 | |
873 | \sa color(), offset() |
874 | |
875 | \internal |
876 | */ |
877 | qreal QPixmapDropShadowFilter::blurRadius() const |
878 | { |
879 | Q_D(const QPixmapDropShadowFilter); |
880 | return d->radius; |
881 | } |
882 | |
883 | /*! |
884 | Sets the radius in pixels of the blur on the drop shadow to the \a radius specified. |
885 | |
886 | Using a smaller radius results in a sharper shadow. |
887 | |
888 | \sa setColor(), setOffset() |
889 | |
890 | \internal |
891 | */ |
892 | void QPixmapDropShadowFilter::setBlurRadius(qreal radius) |
893 | { |
894 | Q_D(QPixmapDropShadowFilter); |
895 | d->radius = radius; |
896 | } |
897 | |
898 | /*! |
899 | Returns the color of the drop shadow. |
900 | |
901 | \sa blurRadius(), offset() |
902 | |
903 | \internal |
904 | */ |
905 | QColor QPixmapDropShadowFilter::color() const |
906 | { |
907 | Q_D(const QPixmapDropShadowFilter); |
908 | return d->color; |
909 | } |
910 | |
911 | /*! |
912 | Sets the color of the drop shadow to the \a color specified. |
913 | |
914 | \sa setBlurRadius(), setOffset() |
915 | |
916 | \internal |
917 | */ |
918 | void QPixmapDropShadowFilter::setColor(const QColor &color) |
919 | { |
920 | Q_D(QPixmapDropShadowFilter); |
921 | d->color = color; |
922 | } |
923 | |
924 | /*! |
925 | Returns the shadow offset in pixels. |
926 | |
927 | \sa blurRadius(), color() |
928 | |
929 | \internal |
930 | */ |
931 | QPointF QPixmapDropShadowFilter::offset() const |
932 | { |
933 | Q_D(const QPixmapDropShadowFilter); |
934 | return d->offset; |
935 | } |
936 | |
937 | /*! |
938 | Sets the shadow offset in pixels to the \a offset specified. |
939 | |
940 | \sa setBlurRadius(), setColor() |
941 | |
942 | \internal |
943 | */ |
944 | void QPixmapDropShadowFilter::setOffset(const QPointF &offset) |
945 | { |
946 | Q_D(QPixmapDropShadowFilter); |
947 | d->offset = offset; |
948 | } |
949 | |
950 | /*! |
951 | \fn void QPixmapDropShadowFilter::setOffset(qreal dx, qreal dy) |
952 | \overload |
953 | |
954 | Sets the shadow offset in pixels to be the displacement specified by the |
955 | horizontal \a dx and vertical \a dy coordinates. |
956 | |
957 | \sa setBlurRadius(), setColor() |
958 | |
959 | \internal |
960 | */ |
961 | |
962 | /*! |
963 | \internal |
964 | */ |
965 | QRectF QPixmapDropShadowFilter::boundingRectFor(const QRectF &rect) const |
966 | { |
967 | Q_D(const QPixmapDropShadowFilter); |
968 | return rect.united(r: rect.translated(p: d->offset).adjusted(xp1: -d->radius, yp1: -d->radius, xp2: d->radius, yp2: d->radius)); |
969 | } |
970 | |
971 | /*! |
972 | \internal |
973 | */ |
974 | void QPixmapDropShadowFilter::draw(QPainter *p, |
975 | const QPointF &pos, |
976 | const QPixmap &px, |
977 | const QRectF &src) const |
978 | { |
979 | Q_D(const QPixmapDropShadowFilter); |
980 | |
981 | if (px.isNull()) |
982 | return; |
983 | |
984 | QImage tmp(px.size(), QImage::Format_ARGB32_Premultiplied); |
985 | tmp.setDevicePixelRatio(px.devicePixelRatio()); |
986 | tmp.fill(pixel: 0); |
987 | QPainter tmpPainter(&tmp); |
988 | tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); |
989 | tmpPainter.drawPixmap(p: d->offset, pm: px); |
990 | tmpPainter.end(); |
991 | |
992 | // blur the alpha channel |
993 | QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied); |
994 | blurred.setDevicePixelRatio(px.devicePixelRatio()); |
995 | blurred.fill(pixel: 0); |
996 | QPainter blurPainter(&blurred); |
997 | qt_blurImage(p: &blurPainter, blurImage&: tmp, radius: d->radius, quality: false, alphaOnly: true); |
998 | blurPainter.end(); |
999 | |
1000 | tmp = std::move(blurred); |
1001 | |
1002 | // blacken the image... |
1003 | tmpPainter.begin(&tmp); |
1004 | tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); |
1005 | tmpPainter.fillRect(tmp.rect(), color: d->color); |
1006 | tmpPainter.end(); |
1007 | |
1008 | // draw the blurred drop shadow... |
1009 | p->drawImage(p: pos, image: tmp); |
1010 | |
1011 | // Draw the actual pixmap... |
1012 | p->drawPixmap(p: pos, pm: px, sr: src); |
1013 | } |
1014 | |
1015 | QT_END_NAMESPACE |
1016 | |
1017 | #include "moc_qpixmapfilter_p.cpp" |
1018 |
Definitions
- QPixmapFilterPrivate
- QPixmapFilter
- QPixmapFilter
- ~QPixmapFilter
- type
- boundingRectFor
- QPixmapConvolutionFilterPrivate
- QPixmapConvolutionFilterPrivate
- ~QPixmapConvolutionFilterPrivate
- QPixmapConvolutionFilter
- ~QPixmapConvolutionFilter
- setConvolutionKernel
- convolutionKernel
- rows
- columns
- boundingRectFor
- convolute
- draw
- QPixmapBlurFilterPrivate
- QPixmapBlurFilterPrivate
- QPixmapBlurFilter
- ~QPixmapBlurFilter
- setRadius
- radius
- setBlurHints
- blurHints
- radiusScale
- boundingRectFor
- draw
- grayscale
- QPixmapColorizeFilterPrivate
- QPixmapColorizeFilter
- ~QPixmapColorizeFilter
- color
- setColor
- strength
- setStrength
- draw
- QPixmapDropShadowFilterPrivate
- QPixmapDropShadowFilterPrivate
- QPixmapDropShadowFilter
- ~QPixmapDropShadowFilter
- blurRadius
- setBlurRadius
- color
- setColor
- offset
- setOffset
- boundingRectFor
Learn to use CMake with our Intro Training
Find out more