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