QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsmaprenderercustompainterjob.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaprenderercustompainterjob.cpp
3 --------------------------------------
4 Date : December 2013
5 Copyright : (C) 2013 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgsfeedback.h"
19#include "qgslabelingengine.h"
20#include "qgslogger.h"
21#include "qgsmaplayerrenderer.h"
25
26#include <QtConcurrentRun>
27
28Q_GUI_EXPORT extern int qt_defaultDpiX();
29Q_GUI_EXPORT extern int qt_defaultDpiY();
30
31static void _fixQPictureDPI( QPainter *p )
32{
33 // QPicture makes an assumption that we drawing to it with system DPI.
34 // Then when being drawn, it scales the painter. The following call
35 // negates the effect. There is no way of setting QPicture's DPI.
36 // See QTBUG-20361
37 p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
38 static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
39}
40
41//
42// QgsMapRendererAbstractCustomPainterJob
43//
44
46 : QgsMapRendererJob( settings )
47{
48
49}
50
51void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
52{
53 // clear the background
54 painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
55
56 painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing ) );
57 painter->setRenderHint( QPainter::SmoothPixmapTransform, mSettings.testFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms ) );
58 painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
59
60#ifndef QT_NO_DEBUG
61 QPaintDevice *paintDevice = painter->device();
62 const QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
63 .arg( paintDevice->logicalDpiX() )
64 .arg( mSettings.outputDpi() );
65 Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi(), 1.0 ),
66 "Job::startRender()", errMsg.toLatin1().data() );
67#endif
68}
69
70
71//
72// QgsMapRendererCustomPainterJob
73//
74
77 , mPainter( painter )
78 , mActive( false )
79 , mRenderSynchronously( false )
80{
81 QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
82}
83
85{
86 QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
87 Q_ASSERT( !mFutureWatcher.isRunning() );
88 //cancel();
89}
90
91void QgsMapRendererCustomPainterJob::startPrivate()
92{
93 if ( isActive() )
94 return;
95
96 if ( !mPrepareOnly )
97 mRenderingStart.start();
98
99 mActive = true;
100
101 mErrors.clear();
102
103 QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
104
105 QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
106 QElapsedTimer prepareTime;
107 prepareTime.start();
108
110
111 mLabelingEngineV2.reset();
112
114 {
115 mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
116 mLabelingEngineV2->setMapSettings( mSettings );
117 }
118
119 const bool canUseLabelCache = prepareLabelCache();
120 mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
121 mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
122 mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
123
124 QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
125
126 if ( mRenderSynchronously )
127 {
128 if ( !mPrepareOnly )
129 {
130 // do the rendering right now!
131 doRender();
132 }
133 return;
134 }
135
136 // now we are ready to start rendering!
137 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
138
139 mFuture = QtConcurrent::run( staticRender, this );
140 mFutureWatcher.setFuture( mFuture );
141}
142
143
145{
146 if ( !isActive() )
147 {
148 QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
149 return;
150 }
151
152 QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
153 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
155
156 QElapsedTimer t;
157 t.start();
158
159 mFutureWatcher.waitForFinished();
160
161 QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
162
163 futureFinished();
164
165 QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
166}
167
169{
170 if ( !isActive() )
171 {
172 QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
173 return;
174 }
175
176 mLabelJob.context.setRenderingStopped( true );
177 for ( LayerRenderJob &job : mLayerJobs )
178 {
179 job.context()->setRenderingStopped( true );
180 if ( job.renderer && job.renderer->feedback() )
181 job.renderer->feedback()->cancel();
182 }
183}
184
186{
187 if ( !isActive() )
188 return;
189
190 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
191
192 QElapsedTimer t;
193 t.start();
194
195 mFutureWatcher.waitForFinished();
196
197 QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
198
199 futureFinished();
200}
201
203{
204 return mActive;
205}
206
208{
209 return mLabelJob.cached;
210}
211
213{
214 if ( mLabelingEngineV2 )
215 return mLabelingEngineV2->takeResults();
216 else
217 return nullptr;
218}
219
220
221void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
222{
223 QEventLoop loop;
224 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
225 loop.exec( flags );
226}
227
228
230{
231 mRenderSynchronously = true;
232 start();
233 futureFinished();
234 mRenderSynchronously = false;
235}
236
238{
239 mRenderSynchronously = true;
240 mPrepareOnly = true;
241 start();
242 mPrepared = true;
243}
244
246{
247 if ( !mPrepared )
248 return;
249
250 doRender();
251 futureFinished();
252 mRenderSynchronously = false;
253 mPrepareOnly = false;
254 mPrepared = false;
255}
256
257void QgsMapRendererCustomPainterJob::futureFinished()
258{
259 mActive = false;
260 if ( !mPrepared ) // can't access from other thread
262 QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
263
264 if ( !mPrepared )
265 logRenderingTime( mLayerJobs, {}, mLabelJob );
266
267 // final cleanup
268 cleanupJobs( mLayerJobs );
269 cleanupSecondPassJobs( mSecondPassLayerJobs );
270 cleanupLabelJob( mLabelJob );
271
272 emit finished();
273}
274
275
276void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
277{
278 try
279 {
280 self->doRender();
281 }
282 catch ( QgsException &e )
283 {
284 Q_UNUSED( e )
285 QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
286 }
287 catch ( std::exception &e )
288 {
289 Q_UNUSED( e )
290 QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
291 }
292 catch ( ... )
293 {
294 QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
295 }
296}
297
298void QgsMapRendererCustomPainterJob::doRender()
299{
300 const bool hasSecondPass = ! mSecondPassLayerJobs.empty();
301 QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
302 QElapsedTimer renderTime;
303 renderTime.start();
304
305 for ( LayerRenderJob &job : mLayerJobs )
306 {
307 if ( job.context()->renderingStopped() )
308 break;
309
310 emit layerRenderingStarted( job.layerId );
311
312 if ( ! hasSecondPass && job.context()->useAdvancedEffects() )
313 {
314 // Set the QPainter composition mode so that this layer is rendered using
315 // the desired blending mode
316 mPainter->setCompositionMode( job.blendMode );
317 }
318
319 if ( !job.cached )
320 {
321 QElapsedTimer layerTime;
322 layerTime.start();
323
324 if ( job.img )
325 {
326 job.img->fill( 0 );
327 job.imageInitialized = true;
328 }
329
330 job.completed = job.renderer->render();
331
332 if ( job.picture )
333 {
334 job.renderer->renderContext()->painter()->end();
335 }
336
337 job.renderingTime += layerTime.elapsed();
338 }
339
340 if ( ! hasSecondPass && job.img )
341 {
342 // If we flattened this layer for alternate blend modes, composite it now
343 mPainter->setOpacity( job.opacity );
344 mPainter->drawImage( 0, 0, *job.img );
345 mPainter->setOpacity( 1.0 );
346 }
347
348 emit layerRendered( job.layerId );
349 }
350
352 QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
353
354 if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
355 {
356 if ( !mLabelJob.cached )
357 {
358 QElapsedTimer labelTime;
359 labelTime.start();
360
361 if ( mLabelJob.img )
362 {
363 QPainter painter;
364 mLabelJob.img->fill( 0 );
365 painter.begin( mLabelJob.img );
366 mLabelJob.context.setPainter( &painter );
367 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
368 painter.end();
369 }
370 else if ( mLabelJob.picture )
371 {
372 QPainter painter;
373 painter.begin( mLabelJob.picture.get() );
374 mLabelJob.context.setPainter( &painter );
375 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
376 painter.end();
377 }
378 else
379 {
380 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
381 }
382
383 mLabelJob.complete = true;
384 mLabelJob.renderingTime = labelTime.elapsed();
385 mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
386 }
387 }
388
389 if ( ! hasSecondPass )
390 {
391 if ( mLabelJob.img && mLabelJob.complete )
392 {
393 mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
394 mPainter->setOpacity( 1.0 );
395 mPainter->drawImage( 0, 0, *mLabelJob.img );
396 }
397 }
398 else
399 {
400 initSecondPassJobs( mSecondPassLayerJobs, mLabelJob );
401
402 for ( LayerRenderJob &job : mSecondPassLayerJobs )
403 {
404 if ( job.context()->renderingStopped() )
405 break;
406
407 if ( !job.cached )
408 {
409 QElapsedTimer layerTime;
410 layerTime.start();
411
412 if ( job.img )
413 {
414 job.img->fill( 0 );
415 job.imageInitialized = true;
416 }
417
418 job.completed = job.renderer->render();
419
420 if ( job.picture )
421 {
422 job.renderer->renderContext()->painter()->end();
423 }
424
425 job.renderingTime += layerTime.elapsed();
426 }
427 }
428
430 composeSecondPass( mSecondPassLayerJobs, mLabelJob, forceVector );
431
432 if ( !forceVector )
433 {
434 const QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
435
436 mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
437 mPainter->setOpacity( 1.0 );
438 mPainter->drawImage( 0, 0, finalImage );
439 }
440 else
441 {
442 //Vector composition is simply draw the saved picture on the painter
443 for ( LayerRenderJob &job : mLayerJobs )
444 {
445 // if there is vector rendering we use it, else we use the raster rendering
446 if ( job.picture )
447 {
448 mPainter->save();
449 _fixQPictureDPI( mPainter );
450 mPainter->drawPicture( 0, 0, *job.picture );
451 mPainter->restore();
452 }
453 else
454 mPainter->drawImage( 0, 0, *job.img );
455 }
456
457 if ( mLabelJob.picture )
458 {
459 mPainter->save();
460 _fixQPictureDPI( mPainter );
461 mPainter->drawPicture( 0, 0, *mLabelJob.picture );
462 mPainter->restore();
463 }
464 }
465 }
466
467 QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
468}
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ ForceRasterMasks
Force symbol masking to be applied using a raster method. This is considerably faster when compared t...
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Enable anti-aliasing for map rendering.
@ DrawLabeling
Enable drawing of labels on top of the map.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
Defines a QGIS exception class.
Definition: qgsexception.h:35
QString what() const
Definition: qgsexception.h:48
Class that stores computed placement from labeling engine.
Abstract base class for map renderer jobs which use custom painters.
void preparePainter(QPainter *painter, const QColor &backgroundColor=Qt::transparent)
Prepares the given painter ready for a map render.
QgsMapRendererAbstractCustomPainterJob(const QgsMapSettings &settings)
Constructor for QgsMapRendererAbstractCustomPainterJob, using the given map settings.
Job implementation that renders everything sequentially using a custom painter.
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void renderSynchronously()
Render the map synchronously in this thread.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
void waitForFinishedWithEventLoop(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Wait for the job to be finished - and keep the thread's event loop running while waiting.
void renderPrepared()
Render a pre-prepared job.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
void prepare()
Prepares the job for rendering synchronously in a background thread.
void waitForFinished() override
Block until the job has finished.
bool isActive() const override
Tell whether the rendering job is currently running in background.
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
Abstract base class for map rendering implementations.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
static QImage composeImage(const QgsMapSettings &settings, const std::vector< LayerRenderJob > &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
void cleanupSecondPassJobs(std::vector< LayerRenderJob > &jobs)
void initSecondPassJobs(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob) const
Initialize secondPassJobs according to what have been rendered (mask clipping path e....
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void layerRendered(const QString &layerId)
Emitted when a layer has completed rendering.
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void renderingLayersFinished()
Emitted when the layers are rendered.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
QElapsedTimer mRenderingStart
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapSettings mSettings
void start()
Start the rendering job and immediately return.
void layerRenderingStarted(const QString &layerId)
Emitted just before rendering starts for a particular layer.
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob, bool forceVector=false)
Compose second pass images into first pass images.
std::vector< LayerRenderJob > prepareSecondPassJobs(std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
The QgsMapSettings class contains configuration for rendering of the map.
QSize deviceOutputSize() const
Returns the device output size of the map render.
QColor backgroundColor() const
Returns the background color of the map.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Q_GUI_EXPORT int qt_defaultDpiX()
Q_GUI_EXPORT int qt_defaultDpiY()