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
54QT_BEGIN_NAMESPACE
55
56class QBackingStorePrivate
57{
58public:
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*/
95QBackingStore::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*/
108QBackingStore::~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*/
117QWindow* 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
131void QBackingStore::beginPaint(const QRegion &region)
132{
133 if (d_ptr->highDpiBackingstore &&
134 d_ptr->highDpiBackingstore->devicePixelRatio() != d_ptr->window->devicePixelRatio())
135 resize(size: size());
136
137 QPlatformBackingStore *platformBackingStore = handle();
138 platformBackingStore->beginPaint(QHighDpi::toNativeLocalRegion(pointRegion: region, window: 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 other: 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*/
173QPaintDevice *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*/
191void QBackingStore::endPaint()
192{
193 if (paintDevice()->paintingActive())
194 qWarning(msg: "QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?");
195
196 handle()->endPaint();
197}
198
199static 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*/
224void 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(classname: "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, region: QHighDpi::toNativeLocalRegion(pointRegion: region, window),
253 offset: QHighDpi::toNativeLocalPosition(value: offset, context: window));
254}
255
256/*!
257 Sets the size of the window surface to \a size.
258
259 \sa size()
260*/
261void QBackingStore::resize(const QSize &size)
262{
263 d_ptr->size = size;
264 handle()->resize(size: QHighDpi::toNativePixels(value: size, context: d_ptr->window), staticContents: d_ptr->staticContents);
265}
266
267/*!
268 Returns the current size of the window surface.
269*/
270QSize 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*/
281bool 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(value: qreal(dx), context: d_ptr->window);
287 const qreal nativeDy = QHighDpi::toNativePixels(value: qreal(dy), context: d_ptr->window);
288 if (qFloor(v: nativeDx) != nativeDx || qFloor(v: nativeDy) != nativeDy)
289 return false;
290
291 return handle()->scroll(area: QHighDpi::toNativeLocalRegion(pointRegion: area, window: d_ptr->window),
292 dx: nativeDx, dy: nativeDy);
293}
294
295/*!
296 Set \a region as the static contents of this window.
297*/
298void 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*/
307QRegion 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*/
315bool QBackingStore::hasStaticContents() const
316{
317 return !d_ptr->staticContents.isEmpty();
318}
319
320void 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*>(img.constBits());
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 sourceRect = rect.intersected(other: imageRect).intersected(other: imageRect.translated(p: -offset));
330 if (sourceRect.isEmpty())
331 return;
332
333 const QRect destRect = sourceRect.translated(p: offset);
334 Q_ASSERT_X(imageRect.contains(destRect), "qt_scrollRectInImage",
335 "The sourceRect should already account for clipping, both pre and post scroll");
336
337 const uchar *src;
338 uchar *dest;
339
340 if (sourceRect.top() < destRect.top()) {
341 src = mem + sourceRect.bottom() * lineskip + sourceRect.left() * depth;
342 dest = mem + (destRect.top() + sourceRect.height() - 1) * lineskip + destRect.left() * depth;
343 lineskip = -lineskip;
344 } else {
345 src = mem + sourceRect.top() * lineskip + sourceRect.left() * depth;
346 dest = mem + destRect.top() * lineskip + destRect.left() * depth;
347 }
348
349 const int w = sourceRect.width();
350 int h = sourceRect.height();
351 const int bytes = w * depth;
352
353 // overlapping segments?
354 if (offset.y() == 0 && qAbs(t: offset.x()) < w) {
355 do {
356 ::memmove(dest: dest, src: src, n: bytes);
357 dest += lineskip;
358 src += lineskip;
359 } while (--h);
360 } else {
361 do {
362 ::memcpy(dest: dest, src: src, n: bytes);
363 dest += lineskip;
364 src += lineskip;
365 } while (--h);
366 }
367}
368
369/*!
370 Returns a pointer to the QPlatformBackingStore implementation
371*/
372QPlatformBackingStore *QBackingStore::handle() const
373{
374 if (!d_ptr->platformBackingStore) {
375 d_ptr->platformBackingStore = QGuiApplicationPrivate::platformIntegration()->createPlatformBackingStore(window: d_ptr->window);
376 d_ptr->platformBackingStore->setBackingStore(const_cast<QBackingStore*>(this));
377 }
378 return d_ptr->platformBackingStore;
379}
380
381QT_END_NAMESPACE
382

source code of qtbase/src/gui/painting/qbackingstore.cpp