1 | /* |
2 | * Copyright 2005 by Aaron Seigo <aseigo@kde.org> |
3 | * Copyright 2008 by Andrew Lake <jamboarder@yahoo.com> |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU Library General Public License as |
7 | * published by the Free Software Foundation; either version 2, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this program; if not, write to the |
17 | * Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #include <paintutils.h> |
22 | |
23 | #include <QImage> |
24 | #include <QPainter> |
25 | #include <QPaintEngine> |
26 | #include <QPixmap> |
27 | |
28 | #include "private/effects/blur.cpp" |
29 | #include "private/effects/halopainter_p.h" |
30 | #include "svg.h" |
31 | |
32 | namespace Plasma |
33 | { |
34 | |
35 | namespace PaintUtils |
36 | { |
37 | |
38 | void shadowBlur(QImage &image, int radius, const QColor &color) |
39 | { |
40 | if (radius < 1) { |
41 | return; |
42 | } |
43 | if (image.isNull()) { |
44 | return; |
45 | } |
46 | |
47 | expblur<16, 7>(image, radius); |
48 | |
49 | QPainter p(&image); |
50 | p.setCompositionMode(QPainter::CompositionMode_SourceIn); |
51 | p.fillRect(image.rect(), color); |
52 | p.end(); |
53 | } |
54 | |
55 | //TODO: we should have shadowText methods that paint the results directly into a QPainter passed in |
56 | QPixmap shadowText(QString text, QColor textColor, QColor shadowColor, QPoint offset, int radius) |
57 | { |
58 | return shadowText(text, qApp->font(), textColor, shadowColor, offset, radius); |
59 | } |
60 | |
61 | QPixmap shadowText(QString text, const QFont &font, QColor textColor, QColor shadowColor, QPoint offset, int radius) |
62 | { |
63 | //don't try to paint stuff on a future null pixmap because the text is empty |
64 | if (text.isEmpty()) { |
65 | return QPixmap(); |
66 | } |
67 | |
68 | // Draw text |
69 | QFontMetrics fm(font); |
70 | QRect textRect = fm.boundingRect(text); |
71 | QPixmap textPixmap(textRect.width(), fm.height()); |
72 | textPixmap.fill(Qt::transparent); |
73 | QPainter p(&textPixmap); |
74 | p.setPen(textColor); |
75 | p.setFont(font); |
76 | // FIXME: the center alignment here is odd: the rect should be the size needed by |
77 | // the text, but for some fonts and configurations this is off by a pixel or so |
78 | // and "centering" the text painting 'fixes' that. Need to research why |
79 | // this is the case and determine if we should be painting it differently here, |
80 | // doing soething different with the boundingRect call or if it's a problem |
81 | // in Qt itself |
82 | p.drawText(textPixmap.rect(), Qt::AlignCenter, text); |
83 | p.end(); |
84 | |
85 | //Draw blurred shadow |
86 | QImage img(textRect.size() + QSize(radius * 2, radius * 2), QImage::Format_ARGB32_Premultiplied); |
87 | img.fill(0); |
88 | p.begin(&img); |
89 | p.drawImage(QPoint(radius, radius), textPixmap.toImage()); |
90 | p.end(); |
91 | shadowBlur(img, radius, shadowColor); |
92 | |
93 | //Compose text and shadow |
94 | int addSizeX = qMax(0, qAbs(offset.x()) - radius); |
95 | int addSizeY = qMax(0, qAbs(offset.y()) - radius); |
96 | |
97 | QPixmap finalPixmap(img.size() + QSize(addSizeX, addSizeY)); |
98 | finalPixmap.fill(Qt::transparent); |
99 | p.begin(&finalPixmap); |
100 | p.drawImage(qMax(0, offset.x()), qMax(0, offset.y()), img); |
101 | p.drawPixmap(radius + qMax(0, -offset.x()), radius + qMax(0, -offset.y()), textPixmap); |
102 | p.end(); |
103 | |
104 | return finalPixmap; |
105 | } |
106 | |
107 | QPixmap texturedText(const QString &text, const QFont &font, Plasma::Svg *texture) |
108 | { |
109 | QFontMetrics fm(font); |
110 | //the text will be moved a bit from contentsRect |
111 | QRect contentsRect = fm.boundingRect(text).adjusted(0, 0, 2, 2); |
112 | contentsRect.moveTo(0,0); |
113 | |
114 | QPixmap pixmap(contentsRect.size()); |
115 | pixmap.fill(Qt::transparent); |
116 | |
117 | QPainter buffPainter(&pixmap); |
118 | buffPainter.setPen(Qt::black); |
119 | |
120 | buffPainter.setFont(font); |
121 | buffPainter.drawText(contentsRect, Qt::AlignCenter, text); |
122 | buffPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); |
123 | texture->paint(&buffPainter, contentsRect, "foreground" ); |
124 | buffPainter.end(); |
125 | |
126 | //do the shadow |
127 | QImage image(pixmap.size() + QSize(2, 2), QImage::Format_ARGB32_Premultiplied); |
128 | image.fill(Qt::transparent); |
129 | buffPainter.begin(&image); |
130 | buffPainter.setFont(font); |
131 | buffPainter.drawText(contentsRect.translated(1, 1), Qt::AlignCenter, text); |
132 | buffPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); |
133 | texture->paint(&buffPainter, contentsRect.adjusted(-1, -1, 1, 1), "shadow" ); |
134 | buffPainter.end(); |
135 | |
136 | expblur<16, 7>(image, 1); |
137 | //hole in the shadow |
138 | buffPainter.begin(&image); |
139 | buffPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut); |
140 | buffPainter.setFont(font); |
141 | buffPainter.drawText(contentsRect.translated(1, 1), Qt::AlignCenter, text); |
142 | buffPainter.end(); |
143 | |
144 | QPixmap ret(image.size()); |
145 | ret.fill(Qt::transparent); |
146 | buffPainter.begin(&ret); |
147 | buffPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); |
148 | buffPainter.drawImage(QPoint(0,0), image); |
149 | buffPainter.drawPixmap(QPoint(1,1), pixmap); |
150 | buffPainter.end(); |
151 | return ret; |
152 | } |
153 | |
154 | void drawHalo(QPainter *painter, const QRectF &rect) |
155 | { |
156 | HaloPainter::instance()->drawHalo(painter, rect.toRect()); |
157 | } |
158 | |
159 | QPainterPath roundedRectangle(const QRectF &rect, qreal radius) |
160 | { |
161 | QPainterPath path(QPointF(rect.left(), rect.top() + radius)); |
162 | path.quadTo(rect.left(), rect.top(), rect.left() + radius, rect.top()); // Top left corner |
163 | path.lineTo(rect.right() - radius, rect.top()); // Top side |
164 | path.quadTo(rect.right(), rect.top(), rect.right(), rect.top() + radius); // Top right corner |
165 | path.lineTo(rect.right(), rect.bottom() - radius); // Right side |
166 | path.quadTo(rect.right(), rect.bottom(), rect.right() - radius, rect.bottom()); // Bottom right corner |
167 | path.lineTo(rect.left() + radius, rect.bottom()); // Bottom side |
168 | path.quadTo(rect.left(), rect.bottom(), rect.left(), rect.bottom() - radius); // Bottom left corner |
169 | path.closeSubpath(); |
170 | |
171 | return path; |
172 | } |
173 | |
174 | void centerPixmaps(QPixmap &from, QPixmap &to) |
175 | { |
176 | if (from.size() == to.size() && from.hasAlphaChannel() && to.hasAlphaChannel()) { |
177 | return; |
178 | } |
179 | |
180 | QRect fromRect(from.rect()); |
181 | QRect toRect(to.rect()); |
182 | |
183 | QRect actualRect = QRect(QPoint(0,0), fromRect.size().expandedTo(toRect.size())); |
184 | fromRect.moveCenter(actualRect.center()); |
185 | toRect.moveCenter(actualRect.center()); |
186 | |
187 | if (from.size() != actualRect.size() || !from.hasAlphaChannel()) { |
188 | QPixmap result(actualRect.size()); |
189 | result.fill(Qt::transparent); |
190 | QPainter p(&result); |
191 | p.setCompositionMode(QPainter::CompositionMode_Source); |
192 | p.drawPixmap(fromRect.topLeft(), from); |
193 | p.end(); |
194 | from = result; |
195 | } |
196 | |
197 | if (to.size() != actualRect.size() || !to.hasAlphaChannel()) { |
198 | QPixmap result(actualRect.size()); |
199 | result.fill(Qt::transparent); |
200 | QPainter p(&result); |
201 | p.setCompositionMode(QPainter::CompositionMode_Source); |
202 | p.drawPixmap(toRect.topLeft(), to); |
203 | p.end(); |
204 | to = result; |
205 | } |
206 | } |
207 | |
208 | QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) |
209 | { |
210 | if (from.isNull() && to.isNull()) { |
211 | return from; |
212 | } |
213 | |
214 | if (qFuzzyCompare(amount + 1, qreal(1.0))) { |
215 | return from; |
216 | } |
217 | |
218 | QRect startRect(from.rect()); |
219 | QRect targetRect(to.rect()); |
220 | QSize pixmapSize = startRect.size().expandedTo(targetRect.size()); |
221 | QRect toRect = QRect(QPoint(0,0), pixmapSize); |
222 | targetRect.moveCenter(toRect.center()); |
223 | startRect.moveCenter(toRect.center()); |
224 | |
225 | //paint to in the center of from |
226 | QColor color; |
227 | color.setAlphaF(amount); |
228 | |
229 | // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus |
230 | QPaintEngine *paintEngine = from.paintEngine(); |
231 | if (paintEngine && |
232 | paintEngine->hasFeature(QPaintEngine::PorterDuff) && |
233 | paintEngine->hasFeature(QPaintEngine::BlendModes)) { |
234 | QPixmap startPixmap(pixmapSize); |
235 | startPixmap.fill(Qt::transparent); |
236 | |
237 | QPixmap targetPixmap(pixmapSize); |
238 | targetPixmap.fill(Qt::transparent); |
239 | |
240 | QPainter p; |
241 | p.begin(&targetPixmap); |
242 | p.drawPixmap(targetRect, to); |
243 | p.setCompositionMode(QPainter::CompositionMode_DestinationIn); |
244 | p.fillRect(targetRect, color); |
245 | p.end(); |
246 | |
247 | p.begin(&startPixmap); |
248 | p.drawPixmap(startRect, from); |
249 | p.setCompositionMode(QPainter::CompositionMode_DestinationOut); |
250 | p.fillRect(startRect, color); |
251 | p.setCompositionMode(QPainter::CompositionMode_Plus); |
252 | p.drawPixmap(targetRect, targetPixmap); |
253 | p.end(); |
254 | |
255 | return startPixmap; |
256 | } |
257 | #if defined(Q_WS_X11) && defined(HAVE_XRENDER) |
258 | // We have Xrender support |
259 | else if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff)) { |
260 | // QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3, |
261 | // which we need to be able to do a transition from one pixmap to |
262 | // another. |
263 | // |
264 | // In order to avoid the overhead of converting the pixmaps to images |
265 | // and doing the operation entirely in software, this function has a |
266 | // specialized path for X11 that uses Xrender directly to do the |
267 | // transition. This operation can be fully accelerated in HW. |
268 | // |
269 | // This specialization can be removed when QX11PaintEngine supports |
270 | // CompositionMode_Plus. |
271 | QPixmap source(targetPixmap), destination(startPixmap); |
272 | |
273 | source.detach(); |
274 | destination.detach(); |
275 | |
276 | Display *dpy = QX11Info::display(); |
277 | |
278 | XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8); |
279 | XRenderPictureAttributes pa; |
280 | pa.repeat = 1; // RepeatNormal |
281 | |
282 | // Create a 1x1 8 bit repeating alpha picture |
283 | Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8); |
284 | Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa); |
285 | XFreePixmap(dpy, pixmap); |
286 | |
287 | // Fill the alpha picture with the opacity value |
288 | XRenderColor xcolor; |
289 | xcolor.alpha = quint16(0xffff * amount); |
290 | XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1); |
291 | |
292 | // Reduce the alpha of the destination with 1 - opacity |
293 | XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(), |
294 | 0, 0, 0, 0, 0, 0, destination.width(), destination.height()); |
295 | |
296 | // Add source * opacity to the destination |
297 | XRenderComposite(dpy, PictOpAdd, source.x11PictureHandle(), alpha, |
298 | destination.x11PictureHandle(), |
299 | toRect.x(), toRect.y(), 0, 0, 0, 0, destination.width(), destination.height()); |
300 | |
301 | XRenderFreePicture(dpy, alpha); |
302 | return destination; |
303 | } |
304 | #endif |
305 | else { |
306 | // Fall back to using QRasterPaintEngine to do the transition. |
307 | QImage under(pixmapSize, QImage::Format_ARGB32_Premultiplied); |
308 | under.fill(Qt::transparent); |
309 | QImage over(pixmapSize, QImage::Format_ARGB32_Premultiplied); |
310 | over.fill(Qt::transparent); |
311 | |
312 | QPainter p; |
313 | p.begin(&over); |
314 | p.drawPixmap(targetRect, to); |
315 | p.setCompositionMode(QPainter::CompositionMode_DestinationIn); |
316 | p.fillRect(over.rect(), color); |
317 | p.end(); |
318 | |
319 | p.begin(&under); |
320 | p.drawPixmap(startRect, from); |
321 | p.setCompositionMode(QPainter::CompositionMode_DestinationOut); |
322 | p.fillRect(startRect, color); |
323 | p.setCompositionMode(QPainter::CompositionMode_Plus); |
324 | p.drawImage(toRect.topLeft(), over); |
325 | p.end(); |
326 | |
327 | return QPixmap::fromImage(under); |
328 | } |
329 | } |
330 | |
331 | } // PaintUtils namespace |
332 | |
333 | } // Plasma namespace |
334 | |
335 | |