1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <qbackingstore.h>
41 #include <qwindow.h>
42 #include <qpixmap.h>
43 #include <qpa/qplatformbackingstore.h>
44 #include <qpa/qplatformintegration.h>
45 #include <qscreen.h>
46 #include <qdebug.h>
47 #include <qscopedpointer.h>
48 
49 #include <private/qguiapplication_p.h>
50 #include <private/qwindow_p.h>
51 
52 #include <private/qhighdpiscaling_p.h>
53 
54 QT_BEGIN_NAMESPACE
55 
56 class QBackingStorePrivate
57 {
58 public:
QBackingStorePrivate(QWindow * w)59     QBackingStorePrivate(QWindow *w)
60         : window(w)
61     {
62     }
63 
64     QWindow *window;
65     QPlatformBackingStore *platformBackingStore = nullptr;
66     QScopedPointer<QImage> highDpiBackingstore;
67     QRegion staticContents;
68     QSize size;
69 };
70 
71 /*!
72     \class QBackingStore
73     \since 5.0
74     \inmodule QtGui
75 
76     \brief The QBackingStore class provides a drawing area for QWindow.
77 
78     QBackingStore enables the use of QPainter to paint on a QWindow with type
79     RasterSurface. The other way of rendering to a QWindow is through the use
80     of OpenGL with QOpenGLContext.
81 
82     A QBackingStore contains a buffered representation of the window contents,
83     and thus supports partial updates by using QPainter to only update a sub
84     region of the window contents.
85 
86     QBackingStore might be used by an application that wants to use QPainter
87     without OpenGL acceleration and without the extra overhead of using the
88     QWidget or QGraphicsView UI stacks. For an example of how to use
89     QBackingStore see the \l{Raster Window Example}.
90 */
91 
92 /*!
93     Constructs an empty surface for the given top-level \a window.
94 */
QBackingStore(QWindow * window)95 QBackingStore::QBackingStore(QWindow *window)
96     : d_ptr(new QBackingStorePrivate(window))
97 {
98     if (window->handle()) {
99         // Create platform backingstore up front if we have a platform window,
100         // otherwise delay the creation until absolutely necessary.
101         handle();
102     }
103 }
104 
105 /*!
106     Destroys this surface.
107 */
~QBackingStore()108 QBackingStore::~QBackingStore()
109 {
110     delete d_ptr->platformBackingStore;
111 }
112 
113 /*!
114     Returns a pointer to the top-level window associated with this
115     surface.
116 */
window() const117 QWindow* QBackingStore::window() const
118 {
119     return d_ptr->window;
120 }
121 
122 /*!
123     Begins painting on the backing store surface in the given \a region.
124 
125     You should call this function before using the paintDevice() to
126     paint.
127 
128     \sa endPaint(), paintDevice()
129 */
130 
beginPaint(const QRegion & region)131 void QBackingStore::beginPaint(const QRegion &region)
132 {
133     if (d_ptr->highDpiBackingstore &&
134         d_ptr->highDpiBackingstore->devicePixelRatio() != d_ptr->window->devicePixelRatio())
135         resize(size());
136 
137     QPlatformBackingStore *platformBackingStore = handle();
138     platformBackingStore->beginPaint(QHighDpi::toNativeLocalRegion(region, d_ptr->window));
139 
140     // When QtGui is applying a high-dpi scale factor the backing store
141     // creates a "large" backing store image. This image needs to be
142     // painted on as a high-dpi image, which is done by setting
143     // devicePixelRatio. Do this on a separate image instance that shares
144     // the image data to avoid having the new devicePixelRatio be propagated
145     // back to the platform plugin.
146     QPaintDevice *device = platformBackingStore->paintDevice();
147     if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image) {
148         QImage *source = static_cast<QImage *>(device);
149         const bool needsNewImage = d_ptr->highDpiBackingstore.isNull()
150             || source->data_ptr() != d_ptr->highDpiBackingstore->data_ptr()
151             || source->size() != d_ptr->highDpiBackingstore->size()
152             || source->devicePixelRatio() != d_ptr->highDpiBackingstore->devicePixelRatio();
153         if (needsNewImage) {
154             qCDebug(lcScaling) << "QBackingStore::beginPaint new backingstore for" << d_ptr->window;
155             qCDebug(lcScaling) << "  source size" << source->size() << "dpr" << source->devicePixelRatio();
156             d_ptr->highDpiBackingstore.reset(
157                 new QImage(source->bits(), source->width(), source->height(), source->bytesPerLine(), source->format()));
158 
159             qreal targetDevicePixelRatio = d_ptr->window->devicePixelRatio();
160             d_ptr->highDpiBackingstore->setDevicePixelRatio(targetDevicePixelRatio);
161             qCDebug(lcScaling) <<"  destination size" << d_ptr->highDpiBackingstore->size()
162                                << "dpr" << targetDevicePixelRatio;
163         }
164     }
165 }
166 
167 /*!
168     Returns the paint device for this surface.
169 
170     \warning The device is only valid between calls to beginPaint() and
171     endPaint(). You should not cache the returned value.
172 */
paintDevice()173 QPaintDevice *QBackingStore::paintDevice()
174 {
175     QPaintDevice *device = handle()->paintDevice();
176 
177     if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image)
178         return d_ptr->highDpiBackingstore.data();
179 
180     return device;
181 }
182 
183 /*!
184     Ends painting.
185 
186     You should call this function after painting with the paintDevice()
187     has ended.
188 
189     \sa beginPaint(), paintDevice()
190 */
endPaint()191 void QBackingStore::endPaint()
192 {
193     if (paintDevice()->paintingActive())
194         qWarning("QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?");
195 
196     handle()->endPaint();
197 }
198 
isRasterSurface(QWindow * window)199 static bool isRasterSurface(QWindow *window)
200 {
201     switch (window->surfaceType()) {
202     case QSurface::RasterSurface:
203     case QSurface::RasterGLSurface:
204         return true;
205     default:
206         return false;
207     };
208 }
209 
210 /*!
211     Flushes the given \a region from the specified \a window onto the
212     screen.
213 
214     The \a window must either be the top level window represented by
215     this backingstore, or a non-transient child of that window. Passing
216     \nullptr falls back to using the backingstore's top level window.
217 
218     If the \a window is a child window, the \a region should be in child window
219     coordinates, and the \a offset should be the child window's offset in relation
220     to the backingstore's top level window.
221 
222     You should call this function after ending painting with endPaint().
223 */
flush(const QRegion & region,QWindow * window,const QPoint & offset)224 void QBackingStore::flush(const QRegion &region, QWindow *window, const QPoint &offset)
225 {
226     QWindow *topLevelWindow = this->window();
227 
228     if (!window)
229         window = topLevelWindow;
230     if (!window->handle()) {
231         qWarning() << "QBackingStore::flush() called for "
232             << window << " which does not have a handle.";
233         return;
234     }
235 
236     if (!isRasterSurface(window)) {
237         qWarning() << "Attempted flush to non-raster surface" << window << "of type" << window->surfaceType()
238             << (window->inherits("QWidgetWindow") ? "(consider using Qt::WA_PaintOnScreen to exclude "
239                                                    "from backingstore sync)" : "");
240         return;
241     }
242 
243 #ifdef QBACKINGSTORE_DEBUG
244     if (window && window->isTopLevel() && !qt_window_private(window)->receivedExpose) {
245         qWarning().nospace() << "QBackingStore::flush() called with non-exposed window "
246             << window << ", behavior is undefined";
247     }
248 #endif
249 
250     Q_ASSERT(window == topLevelWindow || topLevelWindow->isAncestorOf(window, QWindow::ExcludeTransients));
251 
252     handle()->flush(window, QHighDpi::toNativeLocalRegion(region, window),
253                                             QHighDpi::toNativeLocalPosition(offset, window));
254 }
255 
256 /*!
257     Sets the size of the window surface to \a size.
258 
259     \sa size()
260 */
resize(const QSize & size)261 void QBackingStore::resize(const QSize &size)
262 {
263     d_ptr->size = size;
264     handle()->resize(QHighDpi::toNativePixels(size, d_ptr->window), d_ptr->staticContents);
265 }
266 
267 /*!
268     Returns the current size of the window surface.
269 */
size() const270 QSize QBackingStore::size() const
271 {
272     return d_ptr->size;
273 }
274 
275 /*!
276     Scrolls the given \a area \a dx pixels to the right and \a dy
277     downward; both \a dx and \a dy may be negative.
278 
279     Returns \c true if the area was scrolled successfully; false otherwise.
280 */
scroll(const QRegion & area,int dx,int dy)281 bool QBackingStore::scroll(const QRegion &area, int dx, int dy)
282 {
283     // Disable scrolling for non-integer scroll deltas. For this case
284     // the existing rendered pixels can't be re-used, and we return
285     // false to signal that a repaint is needed.
286     const qreal nativeDx = QHighDpi::toNativePixels(qreal(dx), d_ptr->window);
287     const qreal nativeDy = QHighDpi::toNativePixels(qreal(dy), d_ptr->window);
288     if (qFloor(nativeDx) != nativeDx || qFloor(nativeDy) != nativeDy)
289         return false;
290 
291     return handle()->scroll(QHighDpi::toNativeLocalRegion(area, d_ptr->window),
292                                                nativeDx, nativeDy);
293 }
294 
295 /*!
296     Set \a region as the static contents of this window.
297 */
setStaticContents(const QRegion & region)298 void QBackingStore::setStaticContents(const QRegion &region)
299 {
300     d_ptr->staticContents = region;
301 }
302 
303 /*!
304     Returns a QRegion representing the area of the window that
305     has static contents.
306 */
staticContents() const307 QRegion QBackingStore::staticContents() const
308 {
309     return d_ptr->staticContents;
310 }
311 
312 /*!
313     Returns a boolean indicating if this window has static contents or not.
314 */
hasStaticContents() const315 bool QBackingStore::hasStaticContents() const
316 {
317     return !d_ptr->staticContents.isEmpty();
318 }
319 
qt_scrollRectInImage(QImage & img,const QRect & rect,const QPoint & offset)320 void Q_GUI_EXPORT qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset)
321 {
322     // make sure we don't detach
323     uchar *mem = const_cast<uchar*>(const_cast<const QImage &>(img).bits());
324 
325     int lineskip = img.bytesPerLine();
326     int depth = img.depth() >> 3;
327 
328     const QRect imageRect(0, 0, img.width(), img.height());
329     const QRect r = rect & imageRect & imageRect.translated(-offset);
330     const QPoint p = rect.topLeft() + offset;
331 
332     if (r.isEmpty())
333         return;
334 
335     const uchar *src;
336     uchar *dest;
337 
338     if (r.top() < p.y()) {
339         src = mem + r.bottom() * lineskip + r.left() * depth;
340         dest = mem + (p.y() + r.height() - 1) * lineskip + p.x() * depth;
341         lineskip = -lineskip;
342     } else {
343         src = mem + r.top() * lineskip + r.left() * depth;
344         dest = mem + p.y() * lineskip + p.x() * depth;
345     }
346 
347     const int w = r.width();
348     int h = r.height();
349     const int bytes = w * depth;
350 
351     // overlapping segments?
352     if (offset.y() == 0 && qAbs(offset.x()) < w) {
353         do {
354             ::memmove(dest, src, bytes);
355             dest += lineskip;
356             src += lineskip;
357         } while (--h);
358     } else {
359         do {
360             ::memcpy(dest, src, bytes);
361             dest += lineskip;
362             src += lineskip;
363         } while (--h);
364     }
365 }
366 
367 /*!
368     Returns a pointer to the QPlatformBackingStore implementation
369 */
handle() const370 QPlatformBackingStore *QBackingStore::handle() const
371 {
372     if (!d_ptr->platformBackingStore) {
373         d_ptr->platformBackingStore = QGuiApplicationPrivate::platformIntegration()->createPlatformBackingStore(d_ptr->window);
374         d_ptr->platformBackingStore->setBackingStore(const_cast<QBackingStore*>(this));
375     }
376     return d_ptr->platformBackingStore;
377 }
378 
379 QT_END_NAMESPACE
380