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