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