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 "qmath.h" |
5 | #include "qdrawhelper_p.h" |
6 | #include "qmemrotate_p.h" |
7 | #include "qpainter.h" |
8 | |
9 | #include <memory> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | namespace { |
14 | |
15 | template <int shift> |
16 | inline int qt_static_shift(int value) |
17 | { |
18 | if (shift == 0) |
19 | return value; |
20 | else if (shift > 0) |
21 | return value << (uint(shift) & 0x1f); |
22 | else |
23 | return value >> (uint(-shift) & 0x1f); |
24 | } |
25 | |
26 | template<int aprec, int zprec> |
27 | inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) |
28 | { |
29 | QRgb *pixel = (QRgb *)bptr; |
30 | |
31 | #define Z_MASK (0xff << zprec) |
32 | const int A_zprec = qt_static_shift<zprec - 24>(*pixel) & Z_MASK; |
33 | const int R_zprec = qt_static_shift<zprec - 16>(*pixel) & Z_MASK; |
34 | const int G_zprec = qt_static_shift<zprec - 8>(*pixel) & Z_MASK; |
35 | const int B_zprec = qt_static_shift<zprec>(*pixel) & Z_MASK; |
36 | #undef Z_MASK |
37 | |
38 | const int zR_zprec = zR >> aprec; |
39 | const int zG_zprec = zG >> aprec; |
40 | const int zB_zprec = zB >> aprec; |
41 | const int zA_zprec = zA >> aprec; |
42 | |
43 | zR += alpha * (R_zprec - zR_zprec); |
44 | zG += alpha * (G_zprec - zG_zprec); |
45 | zB += alpha * (B_zprec - zB_zprec); |
46 | zA += alpha * (A_zprec - zA_zprec); |
47 | |
48 | #define ZA_MASK (0xff << (zprec + aprec)) |
49 | *pixel = |
50 | qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK) |
51 | | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK) |
52 | | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK) |
53 | | qt_static_shift<-zprec - aprec>(zB & ZA_MASK); |
54 | #undef ZA_MASK |
55 | } |
56 | |
57 | const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); |
58 | |
59 | template<int aprec, int zprec> |
60 | inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha) |
61 | { |
62 | const int A_zprec = int(*(bptr)) << zprec; |
63 | const int z_zprec = z >> aprec; |
64 | z += alpha * (A_zprec - z_zprec); |
65 | *(bptr) = z >> (zprec + aprec); |
66 | } |
67 | |
68 | template<int aprec, int zprec, bool alphaOnly> |
69 | inline void qt_blurrow(QImage & im, int line, int alpha) |
70 | { |
71 | uchar *bptr = im.scanLine(line); |
72 | |
73 | int zR = 0, zG = 0, zB = 0, zA = 0; |
74 | |
75 | if (alphaOnly && im.format() != QImage::Format_Indexed8) |
76 | bptr += alphaIndex; |
77 | |
78 | const int stride = im.depth() >> 3; |
79 | const int im_width = im.width(); |
80 | for (int index = 0; index < im_width; ++index) { |
81 | if (alphaOnly) |
82 | qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha); |
83 | else |
84 | qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha); |
85 | bptr += stride; |
86 | } |
87 | |
88 | bptr -= stride; |
89 | |
90 | for (int index = im_width - 2; index >= 0; --index) { |
91 | bptr -= stride; |
92 | if (alphaOnly) |
93 | qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha); |
94 | else |
95 | qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha); |
96 | } |
97 | } |
98 | |
99 | /* |
100 | * expblur(QImage &img, int radius) |
101 | * |
102 | * Based on exponential blur algorithm by Jani Huhtanen |
103 | * |
104 | * In-place blur of image 'img' with kernel |
105 | * of approximate radius 'radius'. |
106 | * |
107 | * Blurs with two sided exponential impulse |
108 | * response. |
109 | * |
110 | * aprec = precision of alpha parameter |
111 | * in fixed-point format 0.aprec |
112 | * |
113 | * zprec = precision of state parameters |
114 | * zR,zG,zB and zA in fp format 8.zprec |
115 | */ |
116 | template <int aprec, int zprec, bool alphaOnly> |
117 | void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0) |
118 | { |
119 | // halve the radius if we're using two passes |
120 | if (improvedQuality) |
121 | radius *= qreal(0.5); |
122 | |
123 | Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied |
124 | || img.format() == QImage::Format_RGB32 |
125 | || img.format() == QImage::Format_Indexed8 |
126 | || img.format() == QImage::Format_Grayscale8); |
127 | |
128 | // choose the alpha such that pixels at radius distance from a fully |
129 | // saturated pixel will have an alpha component of no greater than |
130 | // the cutOffIntensity |
131 | const qreal cutOffIntensity = 2; |
132 | int alpha = radius <= qreal(1e-5) |
133 | ? ((1 << aprec)-1) |
134 | : qRound(d: (1<<aprec)*(1 - qPow(x: cutOffIntensity * (1 / qreal(255)), y: 1 / radius))); |
135 | |
136 | int img_height = img.height(); |
137 | for (int row = 0; row < img_height; ++row) { |
138 | for (int i = 0; i <= int(improvedQuality); ++i) |
139 | qt_blurrow<aprec, zprec, alphaOnly>(img, row, alpha); |
140 | } |
141 | |
142 | QImage temp(img.height(), img.width(), img.format()); |
143 | temp.setDevicePixelRatio(img.devicePixelRatio()); |
144 | if (transposed >= 0) { |
145 | if (img.depth() == 8) { |
146 | qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()), |
147 | img.width(), img.height(), img.bytesPerLine(), |
148 | reinterpret_cast<quint8*>(temp.bits()), |
149 | temp.bytesPerLine()); |
150 | } else { |
151 | qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()), |
152 | img.width(), img.height(), img.bytesPerLine(), |
153 | reinterpret_cast<quint32*>(temp.bits()), |
154 | temp.bytesPerLine()); |
155 | } |
156 | } else { |
157 | if (img.depth() == 8) { |
158 | qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()), |
159 | img.width(), img.height(), img.bytesPerLine(), |
160 | reinterpret_cast<quint8*>(temp.bits()), |
161 | temp.bytesPerLine()); |
162 | } else { |
163 | qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()), |
164 | img.width(), img.height(), img.bytesPerLine(), |
165 | reinterpret_cast<quint32*>(temp.bits()), |
166 | temp.bytesPerLine()); |
167 | } |
168 | } |
169 | |
170 | img_height = temp.height(); |
171 | for (int row = 0; row < img_height; ++row) { |
172 | for (int i = 0; i <= int(improvedQuality); ++i) |
173 | qt_blurrow<aprec, zprec, alphaOnly>(temp, row, alpha); |
174 | } |
175 | |
176 | if (transposed == 0) { |
177 | if (img.depth() == 8) { |
178 | qt_memrotate90(reinterpret_cast<const quint8*>(temp.bits()), |
179 | temp.width(), temp.height(), temp.bytesPerLine(), |
180 | reinterpret_cast<quint8*>(img.bits()), |
181 | img.bytesPerLine()); |
182 | } else { |
183 | qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()), |
184 | temp.width(), temp.height(), temp.bytesPerLine(), |
185 | reinterpret_cast<quint32*>(img.bits()), |
186 | img.bytesPerLine()); |
187 | } |
188 | } else { |
189 | img = temp; |
190 | } |
191 | } |
192 | |
193 | } // namespace |
194 | |
195 | #define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) ) |
196 | #define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) ) |
197 | |
198 | QImage qt_halfScaled(const QImage &source) |
199 | { |
200 | if (source.width() < 2 || source.height() < 2) |
201 | return QImage(); |
202 | |
203 | QImage srcImage = source; |
204 | |
205 | if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) { |
206 | // assumes grayscale |
207 | QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); |
208 | dest.setDevicePixelRatio(source.devicePixelRatio()); |
209 | |
210 | const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits()); |
211 | qsizetype sx = srcImage.bytesPerLine(); |
212 | qsizetype sx2 = sx << 1; |
213 | |
214 | uchar *dst = reinterpret_cast<uchar*>(dest.bits()); |
215 | qsizetype dx = dest.bytesPerLine(); |
216 | int ww = dest.width(); |
217 | int hh = dest.height(); |
218 | |
219 | for (int y = hh; y; --y, dst += dx, src += sx2) { |
220 | const uchar *p1 = src; |
221 | const uchar *p2 = src + sx; |
222 | uchar *q = dst; |
223 | for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2) |
224 | *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2; |
225 | } |
226 | |
227 | return dest; |
228 | } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) { |
229 | QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); |
230 | dest.setDevicePixelRatio(source.devicePixelRatio()); |
231 | |
232 | const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits()); |
233 | qsizetype sx = srcImage.bytesPerLine(); |
234 | qsizetype sx2 = sx << 1; |
235 | |
236 | uchar *dst = reinterpret_cast<uchar*>(dest.bits()); |
237 | qsizetype dx = dest.bytesPerLine(); |
238 | int ww = dest.width(); |
239 | int hh = dest.height(); |
240 | |
241 | for (int y = hh; y; --y, dst += dx, src += sx2) { |
242 | const uchar *p1 = src; |
243 | const uchar *p2 = src + sx; |
244 | uchar *q = dst; |
245 | for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) { |
246 | // alpha |
247 | q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3])); |
248 | // rgb |
249 | const quint16 p16_1 = (p1[2] << 8) | p1[1]; |
250 | const quint16 p16_2 = (p1[5] << 8) | p1[4]; |
251 | const quint16 p16_3 = (p2[2] << 8) | p2[1]; |
252 | const quint16 p16_4 = (p2[5] << 8) | p2[4]; |
253 | const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4)); |
254 | q[1] = result & 0xff; |
255 | q[2] = result >> 8; |
256 | } |
257 | } |
258 | |
259 | return dest; |
260 | } else if (source.format() != QImage::Format_ARGB32_Premultiplied |
261 | && source.format() != QImage::Format_RGB32) |
262 | { |
263 | srcImage = source.convertToFormat(f: QImage::Format_ARGB32_Premultiplied); |
264 | } |
265 | |
266 | QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); |
267 | dest.setDevicePixelRatio(source.devicePixelRatio()); |
268 | |
269 | const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits()); |
270 | qsizetype sx = srcImage.bytesPerLine() >> 2; |
271 | qsizetype sx2 = sx << 1; |
272 | |
273 | quint32 *dst = reinterpret_cast<quint32*>(dest.bits()); |
274 | qsizetype dx = dest.bytesPerLine() >> 2; |
275 | int ww = dest.width(); |
276 | int hh = dest.height(); |
277 | |
278 | for (int y = hh; y; --y, dst += dx, src += sx2) { |
279 | const quint32 *p1 = src; |
280 | const quint32 *p2 = src + sx; |
281 | quint32 *q = dst; |
282 | for (int x = ww; x; --x, q++, p1 += 2, p2 += 2) |
283 | *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1])); |
284 | } |
285 | |
286 | return dest; |
287 | } |
288 | |
289 | #undef AVG |
290 | #undef AVG16 |
291 | |
292 | Q_GUI_EXPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0) |
293 | { |
294 | if (blurImage.format() != QImage::Format_ARGB32_Premultiplied |
295 | && blurImage.format() != QImage::Format_RGB32) |
296 | { |
297 | blurImage = std::move(blurImage).convertToFormat(f: QImage::Format_ARGB32_Premultiplied); |
298 | } |
299 | |
300 | qreal scale = 1; |
301 | if (radius >= 4 && blurImage.width() >= 2 && blurImage.height() >= 2) { |
302 | blurImage = qt_halfScaled(source: blurImage); |
303 | scale = 2; |
304 | radius *= qreal(0.5); |
305 | } |
306 | |
307 | if (alphaOnly) |
308 | expblur<12, 10, true>(img&: blurImage, radius, improvedQuality: quality, transposed); |
309 | else |
310 | expblur<12, 10, false>(img&: blurImage, radius, improvedQuality: quality, transposed); |
311 | |
312 | if (p) { |
313 | p->scale(sx: scale, sy: scale); |
314 | p->setRenderHint(hint: QPainter::SmoothPixmapTransform); |
315 | p->drawImage(r: QRect(QPoint(0, 0), blurImage.deviceIndependentSize().toSize()), image: blurImage); |
316 | } |
317 | } |
318 | |
319 | Q_GUI_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0) |
320 | { |
321 | if (blurImage.format() == QImage::Format_Indexed8 || blurImage.format() == QImage::Format_Grayscale8) |
322 | expblur<12, 10, true>(img&: blurImage, radius, improvedQuality: quality, transposed); |
323 | else |
324 | expblur<12, 10, false>(img&: blurImage, radius, improvedQuality: quality, transposed); |
325 | } |
326 | |
327 | QT_END_NAMESPACE |
328 | |