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
16 #include "qgsmaprenderercustompainterjob.h"
17
18 #include "qgsfeedback.h"
19 #include "qgslabelingengine.h"
20 #include "qgslogger.h"
21 #include "qgsmaplayerrenderer.h"
22 #include "qgsmaplayerlistutils.h"
23 #include "qgsvectorlayerlabeling.h"
24
25 #include <QtConcurrentRun>
26
27 //
28 // QgsMapRendererAbstractCustomPainterJob
29 //
30
QgsMapRendererAbstractCustomPainterJob(const QgsMapSettings & settings)31 QgsMapRendererAbstractCustomPainterJob::QgsMapRendererAbstractCustomPainterJob( const QgsMapSettings &settings )
32 : QgsMapRendererJob( settings )
33 {
34
35 }
36
preparePainter(QPainter * painter,const QColor & backgroundColor)37 void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
38 {
39 // clear the background
40 painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
41
42 painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing ) );
43 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
44 painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( Qgis::MapSettingsFlag::LosslessImageRendering ) );
45 #endif
46
47 #ifndef QT_NO_DEBUG
48 QPaintDevice *paintDevice = painter->device();
49 const QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
50 .arg( paintDevice->logicalDpiX() )
51 .arg( mSettings.outputDpi() * mSettings.devicePixelRatio() );
52 Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() * mSettings.devicePixelRatio(), 1.0 ),
53 "Job::startRender()", errMsg.toLatin1().data() );
54 #endif
55 }
56
57
58 //
59 // QgsMapRendererCustomPainterJob
60 //
61
QgsMapRendererCustomPainterJob(const QgsMapSettings & settings,QPainter * painter)62 QgsMapRendererCustomPainterJob::QgsMapRendererCustomPainterJob( const QgsMapSettings &settings, QPainter *painter )
63 : QgsMapRendererAbstractCustomPainterJob( settings )
64 , mPainter( painter )
65 , mActive( false )
66 , mRenderSynchronously( false )
67 {
68 QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
69 }
70
~QgsMapRendererCustomPainterJob()71 QgsMapRendererCustomPainterJob::~QgsMapRendererCustomPainterJob()
72 {
73 QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
74 Q_ASSERT( !mFutureWatcher.isRunning() );
75 //cancel();
76 }
77
startPrivate()78 void 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( QStringLiteral( "QPAINTER run!" ), 5 );
91
92 QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
93 QElapsedTimer prepareTime;
94 prepareTime.start();
95
96 preparePainter( mPainter, mSettings.backgroundColor() );
97
98 mLabelingEngineV2.reset();
99
100 if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) )
101 {
102 mLabelingEngineV2.reset( new 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( QStringLiteral( "Rendering prepared in (seconds): %1" ).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
cancel()131 void QgsMapRendererCustomPainterJob::cancel()
132 {
133 if ( !isActive() )
134 {
135 QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
136 return;
137 }
138
139 QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
140 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
141 cancelWithoutBlocking();
142
143 QElapsedTimer t;
144 t.start();
145
146 mFutureWatcher.waitForFinished();
147
148 QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
149
150 futureFinished();
151
152 QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
153 }
154
cancelWithoutBlocking()155 void QgsMapRendererCustomPainterJob::cancelWithoutBlocking()
156 {
157 if ( !isActive() )
158 {
159 QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
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
waitForFinished()172 void QgsMapRendererCustomPainterJob::waitForFinished()
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( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
185
186 futureFinished();
187 }
188
isActive() const189 bool QgsMapRendererCustomPainterJob::isActive() const
190 {
191 return mActive;
192 }
193
usedCachedLabels() const194 bool QgsMapRendererCustomPainterJob::usedCachedLabels() const
195 {
196 return mLabelJob.cached;
197 }
198
takeLabelingResults()199 QgsLabelingResults *QgsMapRendererCustomPainterJob::takeLabelingResults()
200 {
201 if ( mLabelingEngineV2 )
202 return mLabelingEngineV2->takeResults();
203 else
204 return nullptr;
205 }
206
207
waitForFinishedWithEventLoop(QEventLoop::ProcessEventsFlags flags)208 void 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
renderSynchronously()216 void QgsMapRendererCustomPainterJob::renderSynchronously()
217 {
218 mRenderSynchronously = true;
219 start();
220 futureFinished();
221 mRenderSynchronously = false;
222 }
223
prepare()224 void QgsMapRendererCustomPainterJob::prepare()
225 {
226 mRenderSynchronously = true;
227 mPrepareOnly = true;
228 start();
229 mPrepared = true;
230 }
231
renderPrepared()232 void QgsMapRendererCustomPainterJob::renderPrepared()
233 {
234 if ( !mPrepared )
235 return;
236
237 doRender();
238 futureFinished();
239 mRenderSynchronously = false;
240 mPrepareOnly = false;
241 mPrepared = false;
242 }
243
futureFinished()244 void QgsMapRendererCustomPainterJob::futureFinished()
245 {
246 mActive = false;
247 if ( !mPrepared ) // can't access from other thread
248 mRenderingTime = mRenderingStart.elapsed();
249 QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 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
staticRender(QgsMapRendererCustomPainterJob * self)263 void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
264 {
265 try
266 {
267 self->doRender();
268 }
269 catch ( QgsException &e )
270 {
271 Q_UNUSED( e )
272 QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
273 }
274 catch ( std::exception &e )
275 {
276 Q_UNUSED( e )
277 QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
278 }
279 catch ( ... )
280 {
281 QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
282 }
283 }
284
doRender()285 void QgsMapRendererCustomPainterJob::doRender()
286 {
287 const bool hasSecondPass = ! mSecondPassLayerJobs.empty();
288 QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
289 QElapsedTimer renderTime;
290 renderTime.start();
291
292 for ( LayerRenderJob &job : mLayerJobs )
293 {
294 if ( job.context()->renderingStopped() )
295 break;
296
297 if ( ! hasSecondPass && job.context()->useAdvancedEffects() )
298 {
299 // Set the QPainter composition mode so that this layer is rendered using
300 // the desired blending mode
301 mPainter->setCompositionMode( job.blendMode );
302 }
303
304 if ( !job.cached )
305 {
306 QElapsedTimer layerTime;
307 layerTime.start();
308
309 if ( job.img )
310 {
311 job.img->fill( 0 );
312 job.imageInitialized = true;
313 }
314
315 job.completed = job.renderer->render();
316
317 job.renderingTime += layerTime.elapsed();
318 }
319
320 if ( ! hasSecondPass && job.img )
321 {
322 // If we flattened this layer for alternate blend modes, composite it now
323 mPainter->setOpacity( job.opacity );
324 mPainter->drawImage( 0, 0, *job.img );
325 mPainter->setOpacity( 1.0 );
326 }
327
328 }
329
330 QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
331
332 if ( mSettings.testFlag( Qgis::MapSettingsFlag::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
333 {
334 if ( !mLabelJob.cached )
335 {
336 QElapsedTimer labelTime;
337 labelTime.start();
338
339 if ( mLabelJob.img )
340 {
341 QPainter painter;
342 mLabelJob.img->fill( 0 );
343 painter.begin( mLabelJob.img );
344 mLabelJob.context.setPainter( &painter );
345 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
346 painter.end();
347 }
348 else
349 {
350 drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
351 }
352
353 mLabelJob.complete = true;
354 mLabelJob.renderingTime = labelTime.elapsed();
355 mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
356 }
357 }
358
359 if ( ! hasSecondPass )
360 {
361 if ( mLabelJob.img && mLabelJob.complete )
362 {
363 mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
364 mPainter->setOpacity( 1.0 );
365 mPainter->drawImage( 0, 0, *mLabelJob.img );
366 }
367 }
368 else
369 {
370 for ( LayerRenderJob &job : mSecondPassLayerJobs )
371 {
372 if ( job.context()->renderingStopped() )
373 break;
374
375 if ( !job.cached )
376 {
377 QElapsedTimer layerTime;
378 layerTime.start();
379
380 if ( job.img )
381 {
382 job.img->fill( 0 );
383 job.imageInitialized = true;
384 }
385
386 job.completed = job.renderer->render();
387
388 job.renderingTime += layerTime.elapsed();
389 }
390 }
391
392 composeSecondPass( mSecondPassLayerJobs, mLabelJob );
393
394 const QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
395
396 mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
397 mPainter->setOpacity( 1.0 );
398 mPainter->drawImage( 0, 0, finalImage );
399 }
400
401 QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
402 }
403
404
405