QGIS API Documentation 3.43.0-Master (8fc5848dca1)
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#include "moc_qgsmaprenderercustompainterjob.cpp"
18
19#include "qgsfeedback.h"
20#include "qgslabelingengine.h"
21#include "qgslogger.h"
22#include "qgsmaplayerrenderer.h"
24#include "qgselevationmap.h"
25#include "qgspainting.h"
26
27#include <QtConcurrentRun>
28
29//
30// QgsMapRendererAbstractCustomPainterJob
31//
32
38
39void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
40{
41 // clear the background
42 painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
43
44 painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing ) );
45 painter->setRenderHint( QPainter::SmoothPixmapTransform, mSettings.testFlag( Qgis::MapSettingsFlag::HighQualityImageTransforms ) );
46 painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
47
48#ifndef QT_NO_DEBUG
49 QPaintDevice *paintDevice = painter->device();
50 const QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
51 .arg( paintDevice->logicalDpiX() )
52 .arg( mSettings.outputDpi() );
53 Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi(), 1.0 ),
54 "Job::startRender()", errMsg.toLatin1().data() );
55#endif
56}
57
58
59//
60// QgsMapRendererCustomPainterJob
61//
62
65 , mPainter( painter )
66 , mActive( false )
67 , mRenderSynchronously( false )
68{
69 QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
70}
71
73{
74 QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
75 Q_ASSERT( !mFutureWatcher.isRunning() );
76 //cancel();
77}
78
79void QgsMapRendererCustomPainterJob::startPrivate()
80{
81 if ( isActive() )
82 return;
83
84 if ( !mPrepareOnly )
85 mRenderingStart.start();
86
87 mActive = true;
88
89 mErrors.clear();
90
91 QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
92
93 QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
94 QElapsedTimer prepareTime;
95 prepareTime.start();
96
98
99 mLabelingEngineV2.reset();
100
102 {
103 mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
104 mLabelingEngineV2->setMapSettings( mSettings );
105 }
106
107 const bool canUseLabelCache = prepareLabelCache();
108 mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
109 mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
110 mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
111
112 QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
113
114 if ( mRenderSynchronously )
115 {
116 if ( !mPrepareOnly )
117 {
118 // do the rendering right now!
119 doRender();
120 }
121 return;
122 }
123
124 // now we are ready to start rendering!
125 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
126
127 mFuture = QtConcurrent::run( staticRender, this );
128 mFutureWatcher.setFuture( mFuture );
129}
130
131
133{
134 if ( !isActive() )
135 {
136 QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
137 return;
138 }
139
140 QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
141 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
143
144 QElapsedTimer t;
145 t.start();
146
147 mFutureWatcher.waitForFinished();
148
149 QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
150
151 futureFinished();
152
153 QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
154}
155
157{
158 if ( !isActive() )
159 {
160 QgsDebugError( QStringLiteral( "QPAINTER not running!" ) );
161 return;
162 }
163
164 mLabelJob.context.setRenderingStopped( true );
165 for ( LayerRenderJob &job : mLayerJobs )
166 {
167 job.context()->setRenderingStopped( true );
168 if ( job.renderer && job.renderer->feedback() )
169 job.renderer->feedback()->cancel();
170 }
171}
172
174{
175 if ( !isActive() )
176 return;
177
178 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
179
180 QElapsedTimer t;
181 t.start();
182
183 mFutureWatcher.waitForFinished();
184
185 QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
186
187 futureFinished();
188}
189
191{
192 return mActive;
193}
194
196{
197 return mLabelJob.cached;
198}
199
201{
202 if ( mLabelingEngineV2 )
203 return mLabelingEngineV2->takeResults();
204 else
205 return nullptr;
206}
207
208
209void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
210{
211 QEventLoop loop;
212 connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
213 loop.exec( flags );
214}
215
216
218{
219 mRenderSynchronously = true;
220 start();
221 futureFinished();
222 mRenderSynchronously = false;
223}
224
226{
227 mRenderSynchronously = true;
228 mPrepareOnly = true;
229 start();
230 mPrepared = true;
231}
232
234{
235 if ( !mPrepared )
236 return;
237
238 doRender();
239 futureFinished();
240 mRenderSynchronously = false;
241 mPrepareOnly = false;
242 mPrepared = false;
243}
244
245void QgsMapRendererCustomPainterJob::futureFinished()
246{
247 mActive = false;
248 if ( !mPrepared ) // can't access from other thread
250 QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
251
252 if ( !mPrepared )
253 logRenderingTime( mLayerJobs, {}, mLabelJob );
254
255 // final cleanup
256 cleanupJobs( mLayerJobs );
257 cleanupSecondPassJobs( mSecondPassLayerJobs );
258 cleanupLabelJob( mLabelJob );
259
260 emit finished();
261}
262
263
264void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
265{
266 try
267 {
268 self->doRender();
269 }
270 catch ( QgsException &e )
271 {
272 Q_UNUSED( e )
273 QgsDebugError( "Caught unhandled QgsException: " + e.what() );
274 }
275 catch ( std::exception &e )
276 {
277 Q_UNUSED( e )
278 QgsDebugError( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
279 }
280 catch ( ... )
281 {
282 QgsDebugError( QStringLiteral( "Caught unhandled unknown exception" ) );
283 }
284}
285
286void QgsMapRendererCustomPainterJob::doRender()
287{
288 const bool hasSecondPass = ! mSecondPassLayerJobs.empty();
289 QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
290 QElapsedTimer renderTime;
291 renderTime.start();
292
294 std::unique_ptr<QgsElevationMap> mainElevationMap;
295 if ( mapShadingRenderer.isActive() )
296 mainElevationMap.reset( new QgsElevationMap( mSettings.deviceOutputSize(), mSettings.devicePixelRatio() ) );
297
298 for ( LayerRenderJob &job : mLayerJobs )
299 {
300 if ( job.context()->renderingStopped() )
301 break;
302
303 emit layerRenderingStarted( job.layerId );
304
305 if ( ! hasSecondPass && ( job.context()->flags().testFlag( Qgis::RenderContextFlag::UseAdvancedEffects )
306 && job.context()->rasterizedRenderingPolicy() != Qgis::RasterizedRenderingPolicy::ForceVector )
307 )
308 {
309 // Set the QPainter composition mode so that this layer is rendered using
310 // the desired blending mode
311 mPainter->setCompositionMode( job.blendMode );
312 }
313
314 if ( !job.cached )
315 {
316 QElapsedTimer layerTime;
317 layerTime.start();
318
319 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
320 {
321 job.previewRenderImage->fill( 0 );
322 job.previewRenderImageInitialized = true;
323 }
324
325 if ( job.img )
326 {
327 job.img->fill( 0 );
328 job.imageInitialized = true;
329 }
330
331 job.completed = job.renderer->render();
332
333 if ( job.picture )
334 {
335 job.renderer->renderContext()->painter()->end();
336 }
337
338 job.renderingTime += layerTime.elapsed();
339 }
340
341 if ( ! hasSecondPass && job.img )
342 {
343 // If we flattened this layer for alternate blend modes, composite it now
344 mPainter->setOpacity( job.opacity );
345 mPainter->drawImage( 0, 0, *job.img );
346 mPainter->setOpacity( 1.0 );
347 }
348
349 if ( mainElevationMap && job.context()->elevationMap() )
350 {
351 const QgsElevationMap &layerElevationMap = *job.context()->elevationMap();
352 if ( layerElevationMap.isValid() )
353 mainElevationMap->combine( layerElevationMap, mapShadingRenderer.combinedElevationMethod() );
354 }
355
356 emit layerRendered( job.layerId );
357 }
358
360 QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
361
362 if ( mapShadingRenderer.isActive() && mainElevationMap )
363 {
364 QImage image( mainElevationMap->rawElevationImage().size(), QImage::Format_RGB32 );
365 image.setDevicePixelRatio( mSettings.devicePixelRatio() );
366 image.fill( Qt::white );
367 mapShadingRenderer.renderShading( *mainElevationMap.get(), image, QgsRenderContext::fromMapSettings( mSettings ) );
368 mPainter->save();
369 mPainter->setCompositionMode( QPainter::CompositionMode_Multiply );
370 mPainter->drawImage( 0, 0, image );
371 mPainter->restore();
372 }
373
374 if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
375 {
376 if ( !mLabelJob.cached )
377 {
378 QElapsedTimer labelTime;
379 labelTime.start();
380
381 if ( mLabelJob.img )
382 {
383 QPainter painter;
384 mLabelJob.img->fill( 0 );
385 painter.begin( mLabelJob.img );
386 mLabelJob.context.setPainter( &painter );
387 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
388 painter.end();
389 }
390 else if ( mLabelJob.picture )
391 {
392 QPainter painter;
393 painter.begin( mLabelJob.picture.get() );
394 mLabelJob.context.setPainter( &painter );
395 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
396 painter.end();
397 }
398 else
399 {
400 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
401 }
402
403 mLabelJob.complete = true;
404 mLabelJob.renderingTime = labelTime.elapsed();
405 mLabelJob.participatingLayers = participatingLabelLayers( mLabelingEngineV2.get() );
406 }
407 }
408
409 if ( ! hasSecondPass )
410 {
411 if ( mLabelJob.img && mLabelJob.complete )
412 {
413 mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
414 mPainter->setOpacity( 1.0 );
415 mPainter->drawImage( 0, 0, *mLabelJob.img );
416 }
417 }
418 else
419 {
420 initSecondPassJobs( mSecondPassLayerJobs, mLabelJob );
421
422 for ( LayerRenderJob &job : mSecondPassLayerJobs )
423 {
424 if ( job.context()->renderingStopped() )
425 break;
426
427 if ( !job.cached )
428 {
429 QElapsedTimer layerTime;
430 layerTime.start();
431
432 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
433 {
434 job.previewRenderImage->fill( 0 );
435 job.previewRenderImageInitialized = true;
436 }
437
438 if ( job.img )
439 {
440 job.img->fill( 0 );
441 job.imageInitialized = true;
442 }
443
444 job.completed = job.renderer->render();
445
446 if ( job.picture )
447 {
448 job.renderer->renderContext()->painter()->end();
449 }
450
451 job.renderingTime += layerTime.elapsed();
452 }
453 }
454
456 composeSecondPass( mSecondPassLayerJobs, mLabelJob, forceVector );
457
458 if ( !forceVector )
459 {
460 const QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
461
462 mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
463 mPainter->setOpacity( 1.0 );
464 mPainter->drawImage( 0, 0, finalImage );
465 }
466 else
467 {
468 //Vector composition is simply draw the saved picture on the painter
469 for ( LayerRenderJob &job : mLayerJobs )
470 {
471 // if there is vector rendering we use it, else we use the raster rendering
472 if ( job.picture )
473 {
474 QgsPainting::drawPicture( mPainter, QPointF( 0, 0 ), *job.picture );
475 }
476 else
477 mPainter->drawImage( 0, 0, *job.img );
478 }
479
480 if ( mLabelJob.picture )
481 {
482 QgsPainting::drawPicture( mPainter, QPointF( 0, 0 ), *mLabelJob.picture );
483 }
484 }
485 }
486
487 QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
488}
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
@ UseAdvancedEffects
Enable layer opacity and blending effects.
@ 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...
Stores a digital elevation model in a raster image which may get updated as a part of the map layer r...
void combine(const QgsElevationMap &otherElevationMap, Qgis::ElevationMapCombineMethod method)
Combines this elevation map with otherElevationMap.
bool isValid() const
Returns whether the elevation map is valid.
Renders elevation shading on an image with different methods (eye dome lighting, hillshading,...
Qgis::ElevationMapCombineMethod combinedElevationMethod() const
Returns the method used when conbining different elevation sources.
bool isActive() const
Returns whether this shading renderer is active.
void renderShading(const QgsElevationMap &elevation, QImage &image, const QgsRenderContext &context) const
Render shading on image condidering the elevation map elevation and the renderer context context If e...
Defines a QGIS exception class.
QString what() const
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)
QList< QPointer< QgsMapLayer > > participatingLabelLayers(QgsLabelingEngine *engine)
Returns a list of the layers participating in the map labeling.
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.
Contains configuration for rendering maps.
QSize deviceOutputSize() const
Returns the device output size of the map render.
QColor backgroundColor() const
Returns the background color of the map.
float devicePixelRatio() const
Returns the device pixel ratio.
const QgsElevationShadingRenderer & elevationShadingRenderer() const
Returns the shading renderer used to render shading on the entire 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.
static void drawPicture(QPainter *painter, const QPointF &point, const QPicture &picture)
Draws a picture onto a painter, correctly applying workarounds to avoid issues with incorrect scaling...
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6367
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40