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 <qbackingstore.h>
5#include <qwindow.h>
6#include <qpixmap.h>
7#include <qpa/qplatformbackingstore.h>
8#include <qpa/qplatformintegration.h>
9#include <qscreen.h>
10#include <qdebug.h>
11#include <qscopedpointer.h>
12
13#include <private/qguiapplication_p.h>
14#include <private/qwindow_p.h>
15
16#include <private/qhighdpiscaling_p.h>
17
18QT_BEGIN_NAMESPACE
19
20class QBackingStorePrivate
21{
22public:
23 QBackingStorePrivate(QWindow *w)
24 : window(w)
25 {
26 }
27
28 // Returns the DPR for the backing store. This is the DPR for the QWindow,
29 // possibly rounded up to the nearest integer.
30 qreal backingStoreDevicePixelRatio() const
31 {
32 // Note: keep in sync with QWidget::metric()!
33 qreal windowDpr = window->devicePixelRatio();
34 return downscale ? std::ceil(x: windowDpr) : windowDpr;
35 }
36
37 // Returns the factor used for converting from device independent to native
38 // backing store sizes. Normally this is just the gui scale factor, however
39 // if the backing store rounds the DPR up to the nearest integer then we also
40 // need to account for the factor introduced by that rounding.
41 qreal deviceIndependentToNativeFactor() const
42 {
43 const qreal roundingFactor = backingStoreDevicePixelRatio() / window->devicePixelRatio();
44 const qreal guiFactor = QHighDpiScaling::factor(context: window);
45 return roundingFactor * guiFactor;
46 }
47
48 QWindow *window;
49 QPlatformBackingStore *platformBackingStore = nullptr;
50 QScopedPointer<QImage> highDpiBackingstore;
51 QRegion staticContents;
52 QSize size;
53 QSize nativeSize;
54 bool downscale = qEnvironmentVariableIntValue(varName: "QT_WIDGETS_HIGHDPI_DOWNSCALE") > 0;
55};
56
57/*!
58 \class QBackingStore
59 \since 5.0
60 \inmodule QtGui
61
62 \brief The QBackingStore class provides a drawing area for QWindow.
63
64 QBackingStore enables the use of QPainter to paint on a QWindow with type
65 RasterSurface. The other way of rendering to a QWindow is through the use
66 of OpenGL with QOpenGLContext.
67
68 A QBackingStore contains a buffered representation of the window contents,
69 and thus supports partial updates by using QPainter to only update a sub
70 region of the window contents.
71
72 QBackingStore might be used by an application that wants to use QPainter
73 without OpenGL acceleration and without the extra overhead of using the
74 QWidget or QGraphicsView UI stacks. For an example of how to use
75 QBackingStore see the \l{Raster Window Example}.
76*/
77
78/*!
79 Constructs an empty surface for the given top-level \a window.
80*/
81QBackingStore::QBackingStore(QWindow *window)
82 : d_ptr(new QBackingStorePrivate(window))
83{
84 if (window->handle()) {
85 // Create platform backingstore up front if we have a platform window,
86 // otherwise delay the creation until absolutely necessary.
87 handle();
88 }
89}
90
91/*!
92 Destroys this surface.
93*/
94QBackingStore::~QBackingStore()
95{
96 delete d_ptr->platformBackingStore;
97}
98
99/*!
100 Returns a pointer to the top-level window associated with this
101 surface.
102*/
103QWindow* QBackingStore::window() const
104{
105 return d_ptr->window;
106}
107
108/*!
109 Begins painting on the backing store surface in the given \a region.
110
111 You should call this function before using the paintDevice() to
112 paint.
113
114 \sa endPaint(), paintDevice()
115*/
116
117void QBackingStore::beginPaint(const QRegion &region)
118{
119 const qreal toNativeFactor = d_ptr->deviceIndependentToNativeFactor();
120
121 if (d_ptr->nativeSize != QHighDpi::scale(value: size(), scaleFactor: toNativeFactor))
122 resize(size: size());
123
124 QPlatformBackingStore *platformBackingStore = handle();
125 platformBackingStore->beginPaint(QHighDpi::scale(region, scaleFactor: toNativeFactor));
126
127 // When QtGui is applying a high-dpi scale factor the backing store
128 // creates a "large" backing store image. This image needs to be
129 // painted on as a high-dpi image, which is done by setting
130 // devicePixelRatio. Do this on a separate image instance that shares
131 // the image data to avoid having the new devicePixelRatio be propagated
132 // back to the platform plugin.
133 QPaintDevice *device = platformBackingStore->paintDevice();
134 if (!qFuzzyCompare(p1: toNativeFactor, p2: qreal(1)) && device->devType() == QInternal::Image) {
135 QImage *source = static_cast<QImage *>(device);
136 const bool needsNewImage = d_ptr->highDpiBackingstore.isNull()
137 || source->constBits() != d_ptr->highDpiBackingstore->constBits()
138 || source->size() != d_ptr->highDpiBackingstore->size()
139 || source->bytesPerLine() != d_ptr->highDpiBackingstore->bytesPerLine()
140 || source->format() != d_ptr->highDpiBackingstore->format();
141 if (needsNewImage)
142 d_ptr->highDpiBackingstore.reset(
143 other: new QImage(source->bits(), source->width(), source->height(), source->bytesPerLine(), source->format()));
144
145 d_ptr->highDpiBackingstore->setDevicePixelRatio(d_ptr->backingStoreDevicePixelRatio());
146 } else {
147 d_ptr->highDpiBackingstore.reset();
148 }
149}
150
151/*!
152 Returns the paint device for this surface.
153
154 \warning The device is only valid between calls to beginPaint() and
155 endPaint(). You should not cache the returned value.
156*/
157QPaintDevice *QBackingStore::paintDevice()
158{
159 QPaintDevice *device = handle()->paintDevice();
160
161 if (!qFuzzyCompare(p1: d_ptr->deviceIndependentToNativeFactor(), p2: qreal(1)) && device->devType() == QInternal::Image)
162 return d_ptr->highDpiBackingstore.data();
163
164 return device;
165}
166
167/*!
168 Ends painting.
169
170 You should call this function after painting with the paintDevice()
171 has ended.
172
173 \sa beginPaint(), paintDevice()
174*/
175void QBackingStore::endPaint()
176{
177 if (paintDevice()->paintingActive())
178 qWarning(msg: "QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?");
179
180 handle()->endPaint();
181}
182
183/*!
184 Flushes the given \a region from the specified \a window onto the
185 screen.
186
187 The \a window must either be the top level window represented by
188 this backingstore, or a non-transient child of that window. Passing
189 \nullptr falls back to using the backingstore's top level window.
190
191 If the \a window is a child window, the \a region should be in child window
192 coordinates, and the \a offset should be the child window's offset in relation
193 to the backingstore's top level window.
194
195 You should call this function after ending painting with endPaint().
196*/
197void QBackingStore::flush(const QRegion &region, QWindow *window, const QPoint &offset)
198{
199 QWindow *topLevelWindow = this->window();
200
201 if (!window)
202 window = topLevelWindow;
203 if (!window->handle()) {
204 qWarning() << "QBackingStore::flush() called for "
205 << window << " which does not have a handle.";
206 return;
207 }
208
209 Q_ASSERT(window == topLevelWindow || topLevelWindow->isAncestorOf(window, QWindow::ExcludeTransients));
210
211 const qreal toNativeFactor = d_ptr->deviceIndependentToNativeFactor();
212
213 QRegion nativeRegion = QHighDpi::scale(region, scaleFactor: toNativeFactor);
214 QPoint nativeOffset;
215 if (!offset.isNull()) {
216 nativeOffset = QHighDpi::scale(pos: offset, scaleFactor: toNativeFactor);
217 // Under fractional DPR, rounding of region and offset may accumulate to an off-by-one
218 QPoint topLeft = region.boundingRect().topLeft() + offset;
219 QPoint nativeTopLeft = QHighDpi::scale(pos: topLeft, scaleFactor: toNativeFactor);
220 QPoint diff = nativeTopLeft - (nativeRegion.boundingRect().topLeft() + nativeOffset);
221 Q_ASSERT(qMax(qAbs(diff.x()), qAbs(diff.y())) <= 1);
222 nativeRegion.translate(p: diff);
223 }
224 handle()->flush(window, region: nativeRegion, offset: nativeOffset);
225}
226
227/*!
228 Sets the size of the window surface to \a size.
229
230 \sa size()
231*/
232void QBackingStore::resize(const QSize &size)
233{
234 const qreal factor = d_ptr->deviceIndependentToNativeFactor();
235 d_ptr->size = size;
236 d_ptr->nativeSize = QHighDpi::scale(value: size, scaleFactor: factor);
237 handle()->resize(size: d_ptr->nativeSize, staticContents: QHighDpi::scale(region: d_ptr->staticContents, scaleFactor: factor));
238}
239
240/*!
241 Returns the current size of the window surface.
242*/
243QSize QBackingStore::size() const
244{
245 return d_ptr->size;
246}
247
248/*!
249 Scrolls the given \a area \a dx pixels to the right and \a dy
250 downward; both \a dx and \a dy may be negative.
251
252 Returns \c true if the area was scrolled successfully; false otherwise.
253*/
254bool QBackingStore::scroll(const QRegion &area, int dx, int dy)
255{
256 // Disable scrolling for non-integer scroll deltas. For this case
257 // the existing rendered pixels can't be re-used, and we return
258 // false to signal that a repaint is needed.
259 const qreal toNativeFactor = d_ptr->deviceIndependentToNativeFactor();
260 const qreal nativeDx = QHighDpi::scale(value: qreal(dx), scaleFactor: toNativeFactor);
261 const qreal nativeDy = QHighDpi::scale(value: qreal(dy), scaleFactor: toNativeFactor);
262 if (qFloor(v: nativeDx) != nativeDx || qFloor(v: nativeDy) != nativeDy)
263 return false;
264
265 return handle()->scroll(area: QHighDpi::scale(region: area, scaleFactor: toNativeFactor), dx: nativeDx, dy: nativeDy);
266}
267
268/*!
269 Set \a region as the static contents of this window.
270*/
271void QBackingStore::setStaticContents(const QRegion &region)
272{
273 [[maybe_unused]] static const bool didCheckPlatformSupport = []{
274 const auto *integration = QGuiApplicationPrivate::platformIntegration();
275 if (!integration->hasCapability(cap: QPlatformIntegration::BackingStoreStaticContents))
276 qWarning(msg: "QBackingStore::setStaticContents(): Platform does not support static contents");
277 return true;
278 }();
279
280 d_ptr->staticContents = region;
281}
282
283/*!
284 Returns a QRegion representing the area of the window that
285 has static contents.
286*/
287QRegion QBackingStore::staticContents() const
288{
289 return d_ptr->staticContents;
290}
291
292/*!
293 Returns a boolean indicating if this window has static contents or not.
294*/
295bool QBackingStore::hasStaticContents() const
296{
297 return !d_ptr->staticContents.isEmpty();
298}
299
300void Q_GUI_EXPORT qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset)
301{
302 // make sure we don't detach
303 uchar *mem = const_cast<uchar*>(img.constBits());
304
305 qsizetype lineskip = img.bytesPerLine();
306 int depth = img.depth() >> 3;
307
308 const QRect imageRect(0, 0, img.width(), img.height());
309 const QRect sourceRect = rect.intersected(other: imageRect).intersected(other: imageRect.translated(p: -offset));
310 if (sourceRect.isEmpty())
311 return;
312
313 const QRect destRect = sourceRect.translated(p: offset);
314 Q_ASSERT_X(imageRect.contains(destRect), "qt_scrollRectInImage",
315 "The sourceRect should already account for clipping, both pre and post scroll");
316
317 const uchar *src;
318 uchar *dest;
319
320 if (sourceRect.top() < destRect.top()) {
321 src = mem + sourceRect.bottom() * lineskip + sourceRect.left() * depth;
322 dest = mem + (destRect.top() + sourceRect.height() - 1) * lineskip + destRect.left() * depth;
323 lineskip = -lineskip;
324 } else {
325 src = mem + sourceRect.top() * lineskip + sourceRect.left() * depth;
326 dest = mem + destRect.top() * lineskip + destRect.left() * depth;
327 }
328
329 const int w = sourceRect.width();
330 int h = sourceRect.height();
331 const int bytes = w * depth;
332
333 // overlapping segments?
334 if (offset.y() == 0 && qAbs(t: offset.x()) < w) {
335 do {
336 ::memmove(dest: dest, src: src, n: bytes);
337 dest += lineskip;
338 src += lineskip;
339 } while (--h);
340 } else {
341 do {
342 ::memcpy(dest: dest, src: src, n: bytes);
343 dest += lineskip;
344 src += lineskip;
345 } while (--h);
346 }
347}
348
349/*!
350 Returns a pointer to the QPlatformBackingStore implementation
351*/
352QPlatformBackingStore *QBackingStore::handle() const
353{
354 if (!d_ptr->platformBackingStore) {
355 d_ptr->platformBackingStore = QGuiApplicationPrivate::platformIntegration()->createPlatformBackingStore(window: d_ptr->window);
356 d_ptr->platformBackingStore->setBackingStore(const_cast<QBackingStore*>(this));
357 }
358 return d_ptr->platformBackingStore;
359}
360
361QT_END_NAMESPACE
362

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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