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