Branch data Line data Source code
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 : :
31 : 0 : QgsMapRendererAbstractCustomPainterJob::QgsMapRendererAbstractCustomPainterJob( const QgsMapSettings &settings )
32 : 0 : : QgsMapRendererJob( settings )
33 : 0 : {
34 : :
35 : 0 : }
36 : :
37 : 0 : void QgsMapRendererAbstractCustomPainterJob::preparePainter( QPainter *painter, const QColor &backgroundColor )
38 : : {
39 : : // clear the background
40 : 0 : painter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), backgroundColor );
41 : :
42 : 0 : painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
43 : : #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
44 : 0 : painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
45 : : #endif
46 : :
47 : : #ifndef QT_NO_DEBUG
48 : : QPaintDevice *paintDevice = painter->device();
49 : : 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 : 0 : }
56 : :
57 : :
58 : : //
59 : : // QgsMapRendererCustomPainterJob
60 : : //
61 : :
62 : 0 : QgsMapRendererCustomPainterJob::QgsMapRendererCustomPainterJob( const QgsMapSettings &settings, QPainter *painter )
63 : 0 : : QgsMapRendererAbstractCustomPainterJob( settings )
64 : 0 : , mPainter( painter )
65 : 0 : , mActive( false )
66 : 0 : , mRenderSynchronously( false )
67 : 0 : {
68 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER construct" ), 5 );
69 : 0 : }
70 : :
71 : 0 : QgsMapRendererCustomPainterJob::~QgsMapRendererCustomPainterJob()
72 : 0 : {
73 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER destruct" ), 5 );
74 : : Q_ASSERT( !mFutureWatcher.isRunning() );
75 : : //cancel();
76 : 0 : }
77 : :
78 : 0 : void QgsMapRendererCustomPainterJob::start()
79 : : {
80 : 0 : if ( isActive() )
81 : 0 : return;
82 : :
83 : 0 : if ( !mPrepareOnly )
84 : 0 : mRenderingStart.start();
85 : :
86 : 0 : mActive = true;
87 : :
88 : 0 : mErrors.clear();
89 : :
90 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER run!" ), 5 );
91 : :
92 : 0 : QgsDebugMsgLevel( QStringLiteral( "Preparing list of layer jobs for rendering" ), 5 );
93 : 0 : QElapsedTimer prepareTime;
94 : 0 : prepareTime.start();
95 : :
96 : 0 : preparePainter( mPainter, mSettings.backgroundColor() );
97 : :
98 : 0 : mLabelingEngineV2.reset();
99 : :
100 : 0 : if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) )
101 : : {
102 : 0 : mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
103 : 0 : mLabelingEngineV2->setMapSettings( mSettings );
104 : 0 : }
105 : :
106 : 0 : bool canUseLabelCache = prepareLabelCache();
107 : 0 : mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2.get() );
108 : 0 : mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2.get(), canUseLabelCache );
109 : 0 : mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
110 : :
111 : 0 : QgsDebugMsgLevel( QStringLiteral( "Rendering prepared in (seconds): %1" ).arg( prepareTime.elapsed() / 1000.0 ), 4 );
112 : :
113 : 0 : if ( mRenderSynchronously )
114 : : {
115 : 0 : if ( !mPrepareOnly )
116 : : {
117 : : // do the rendering right now!
118 : 0 : doRender();
119 : 0 : }
120 : 0 : return;
121 : : }
122 : :
123 : : // now we are ready to start rendering!
124 : 0 : connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
125 : :
126 : 0 : mFuture = QtConcurrent::run( staticRender, this );
127 : 0 : mFutureWatcher.setFuture( mFuture );
128 : 0 : }
129 : :
130 : :
131 : 0 : void QgsMapRendererCustomPainterJob::cancel()
132 : : {
133 : 0 : if ( !isActive() )
134 : : {
135 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER not running!" ), 4 );
136 : 0 : return;
137 : : }
138 : :
139 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceling" ), 5 );
140 : 0 : disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
141 : 0 : cancelWithoutBlocking();
142 : :
143 : 0 : QElapsedTimer t;
144 : 0 : t.start();
145 : :
146 : 0 : mFutureWatcher.waitForFinished();
147 : :
148 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ), 5 );
149 : 0 :
150 : 0 : futureFinished();
151 : :
152 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER canceled" ), 5 );
153 : 0 : }
154 : :
155 : 0 : void QgsMapRendererCustomPainterJob::cancelWithoutBlocking()
156 : : {
157 : 0 : if ( !isActive() )
158 : : {
159 : 0 : QgsDebugMsg( QStringLiteral( "QPAINTER not running!" ) );
160 : 0 : return;
161 : : }
162 : :
163 : 0 : mLabelJob.context.setRenderingStopped( true );
164 : 0 : for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
165 : : {
166 : 0 : it->context.setRenderingStopped( true );
167 : 0 : if ( it->renderer && it->renderer->feedback() )
168 : 0 : it->renderer->feedback()->cancel();
169 : 0 : }
170 : 0 : }
171 : :
172 : 0 : void QgsMapRendererCustomPainterJob::waitForFinished()
173 : : {
174 : 0 : if ( !isActive() )
175 : 0 : return;
176 : :
177 : 0 : disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );
178 : :
179 : 0 : QElapsedTimer t;
180 : 0 : t.start();
181 : :
182 : 0 : mFutureWatcher.waitForFinished();
183 : :
184 : 0 : QgsDebugMsgLevel( QStringLiteral( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ), 4 );
185 : :
186 : 0 : futureFinished();
187 : 0 : }
188 : :
189 : 0 : bool QgsMapRendererCustomPainterJob::isActive() const
190 : : {
191 : 0 : return mActive;
192 : : }
193 : :
194 : 0 : bool QgsMapRendererCustomPainterJob::usedCachedLabels() const
195 : : {
196 : 0 : return mLabelJob.cached;
197 : : }
198 : :
199 : 0 : QgsLabelingResults *QgsMapRendererCustomPainterJob::takeLabelingResults()
200 : : {
201 : 0 : if ( mLabelingEngineV2 )
202 : 0 : return mLabelingEngineV2->takeResults();
203 : : else
204 : 0 : return nullptr;
205 : 0 : }
206 : :
207 : :
208 : 0 : void QgsMapRendererCustomPainterJob::waitForFinishedWithEventLoop( QEventLoop::ProcessEventsFlags flags )
209 : : {
210 : 0 : QEventLoop loop;
211 : 0 : connect( &mFutureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit );
212 : 0 : loop.exec( flags );
213 : 0 : }
214 : :
215 : :
216 : 0 : void QgsMapRendererCustomPainterJob::renderSynchronously()
217 : : {
218 : 0 : mRenderSynchronously = true;
219 : 0 : start();
220 : 0 : futureFinished();
221 : 0 : mRenderSynchronously = false;
222 : 0 : }
223 : :
224 : 0 : void QgsMapRendererCustomPainterJob::prepare()
225 : : {
226 : 0 : mRenderSynchronously = true;
227 : 0 : mPrepareOnly = true;
228 : 0 : start();
229 : 0 : mPrepared = true;
230 : 0 : }
231 : :
232 : 0 : void QgsMapRendererCustomPainterJob::renderPrepared()
233 : : {
234 : 0 : if ( !mPrepared )
235 : 0 : return;
236 : :
237 : 0 : doRender();
238 : 0 : futureFinished();
239 : 0 : mRenderSynchronously = false;
240 : 0 : mPrepareOnly = false;
241 : 0 : mPrepared = false;
242 : 0 : }
243 : :
244 : 0 : void QgsMapRendererCustomPainterJob::futureFinished()
245 : : {
246 : 0 : mActive = false;
247 : 0 : if ( !mPrepared ) // can't access from other thread
248 : 0 : mRenderingTime = mRenderingStart.elapsed();
249 : 0 : QgsDebugMsgLevel( QStringLiteral( "QPAINTER futureFinished" ), 5 );
250 : :
251 : 0 : if ( !mPrepared )
252 : 0 : logRenderingTime( mLayerJobs, {}, mLabelJob );
253 : :
254 : : // final cleanup
255 : 0 : cleanupJobs( mLayerJobs );
256 : 0 : cleanupSecondPassJobs( mSecondPassLayerJobs );
257 : 0 : cleanupLabelJob( mLabelJob );
258 : :
259 : 0 : emit finished();
260 : 0 : }
261 : :
262 : :
263 : 0 : void QgsMapRendererCustomPainterJob::staticRender( QgsMapRendererCustomPainterJob *self )
264 : : {
265 : : try
266 : : {
267 : 0 : self->doRender();
268 : 0 : }
269 : : catch ( QgsException &e )
270 : : {
271 : 0 : Q_UNUSED( e )
272 : 0 : QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
273 : 0 : }
274 : : catch ( std::exception &e )
275 : : {
276 : 0 : Q_UNUSED( e )
277 : 0 : QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
278 : 0 : }
279 : : catch ( ... )
280 : : {
281 : 0 : QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
282 : 0 : }
283 : 0 : }
284 : :
285 : 0 : void QgsMapRendererCustomPainterJob::doRender()
286 : : {
287 : 0 : bool hasSecondPass = ! mSecondPassLayerJobs.isEmpty();
288 : 0 : QgsDebugMsgLevel( QStringLiteral( "Starting to render layer stack." ), 5 );
289 : 0 : QElapsedTimer renderTime;
290 : 0 : renderTime.start();
291 : :
292 : 0 : for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
293 : : {
294 : 0 : LayerRenderJob &job = *it;
295 : :
296 : 0 : if ( job.context.renderingStopped() )
297 : 0 : break;
298 : :
299 : 0 : if ( ! hasSecondPass && job.context.useAdvancedEffects() )
300 : : {
301 : : // Set the QPainter composition mode so that this layer is rendered using
302 : : // the desired blending mode
303 : 0 : mPainter->setCompositionMode( job.blendMode );
304 : 0 : }
305 : :
306 : 0 : if ( !job.cached )
307 : : {
308 : 0 : QElapsedTimer layerTime;
309 : 0 : layerTime.start();
310 : :
311 : 0 : if ( job.img )
312 : : {
313 : 0 : job.img->fill( 0 );
314 : 0 : job.imageInitialized = true;
315 : 0 : }
316 : :
317 : 0 : job.completed = job.renderer->render();
318 : :
319 : 0 : job.renderingTime += layerTime.elapsed();
320 : 0 : }
321 : :
322 : 0 : if ( ! hasSecondPass && job.img )
323 : : {
324 : : // If we flattened this layer for alternate blend modes, composite it now
325 : 0 : mPainter->setOpacity( job.opacity );
326 : 0 : mPainter->drawImage( 0, 0, *job.img );
327 : 0 : mPainter->setOpacity( 1.0 );
328 : 0 : }
329 : :
330 : 0 : }
331 : :
332 : 0 : QgsDebugMsgLevel( QStringLiteral( "Done rendering map layers" ), 5 );
333 : :
334 : 0 : if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
335 : : {
336 : 0 : if ( !mLabelJob.cached )
337 : : {
338 : 0 : QElapsedTimer labelTime;
339 : 0 : labelTime.start();
340 : :
341 : 0 : if ( mLabelJob.img )
342 : : {
343 : 0 : QPainter painter;
344 : 0 : mLabelJob.img->fill( 0 );
345 : 0 : painter.begin( mLabelJob.img );
346 : 0 : mLabelJob.context.setPainter( &painter );
347 : 0 : drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), &painter );
348 : 0 : painter.end();
349 : 0 : }
350 : : else
351 : : {
352 : 0 : drawLabeling( mLabelJob.context, mLabelingEngineV2.get(), mPainter );
353 : : }
354 : :
355 : 0 : mLabelJob.complete = true;
356 : 0 : mLabelJob.renderingTime = labelTime.elapsed();
357 : 0 : mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
358 : 0 : }
359 : 0 : }
360 : :
361 : 0 : if ( ! hasSecondPass )
362 : : {
363 : 0 : if ( mLabelJob.img && mLabelJob.complete )
364 : : {
365 : 0 : mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
366 : 0 : mPainter->setOpacity( 1.0 );
367 : 0 : mPainter->drawImage( 0, 0, *mLabelJob.img );
368 : 0 : }
369 : 0 : }
370 : : else
371 : : {
372 : 0 : for ( LayerRenderJob &job : mSecondPassLayerJobs )
373 : : {
374 : 0 : if ( job.context.renderingStopped() )
375 : 0 : break;
376 : :
377 : 0 : if ( !job.cached )
378 : : {
379 : 0 : QElapsedTimer layerTime;
380 : 0 : layerTime.start();
381 : :
382 : 0 : if ( job.img )
383 : : {
384 : 0 : job.img->fill( 0 );
385 : 0 : job.imageInitialized = true;
386 : 0 : }
387 : :
388 : 0 : job.completed = job.renderer->render();
389 : :
390 : 0 : job.renderingTime += layerTime.elapsed();
391 : 0 : }
392 : : }
393 : :
394 : 0 : composeSecondPass( mSecondPassLayerJobs, mLabelJob );
395 : :
396 : 0 : QImage finalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
397 : :
398 : 0 : mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
399 : 0 : mPainter->setOpacity( 1.0 );
400 : 0 : mPainter->drawImage( 0, 0, finalImage );
401 : 0 : }
402 : :
403 : 0 : QgsDebugMsgLevel( QStringLiteral( "Rendering completed in (seconds): %1" ).arg( renderTime.elapsed() / 1000.0 ), 2 );
404 : 0 : }
405 : :
406 : :
|