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