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