19 #include <QElapsedTimer>
21 #include <QtConcurrentMap>
52 : mSettings( settings )
76 QHash<QgsMapLayer *, int> result;
80 result.insert( it.key(), it.value() );
95 QSet< QgsMapLayer * > labeledLayers;
144 static const double SPLIT_COORD = 180.0;
156 QgsDebugMsgLevel( QStringLiteral(
"\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
159 .arg( std::fabs( 1.0 - extent2.
width() / extent.
width() ) )
160 .arg( std::fabs( 1.0 - extent2.
height() / extent.
height() ) )
193 if ( ll.
x() > ur.
x() )
220 extent =
QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
228 QgsDebugMsg( QStringLiteral(
"Transform error caught" ) );
229 extent =
QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
230 r2 =
QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
236 QImage *QgsMapRendererJob::allocateImage( QString layerId )
241 if ( image->isNull() )
250 QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image )
252 QPainter *painter =
nullptr;
253 image = allocateImage( layerId );
256 painter =
new QPainter( image );
264 LayerRenderJobs layerJobs;
273 Q_UNUSED( cacheValid )
274 QgsDebugMsgLevel( QStringLiteral(
"CACHE VALID: %1" ).arg( cacheValid ), 4 );
279 while ( li.hasPrevious() )
283 QgsDebugMsgLevel( QStringLiteral(
"layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
300 QgsDebugMsgLevel( QStringLiteral(
"Layer not rendered because it is not within the defined visibility scale range" ), 3 );
306 QgsDebugMsgLevel( QStringLiteral(
"Layer not rendered because it is not visible within the map's time range" ), 3 );
317 reprojectToLayerExtent( ml, ct, r1, r2 );
322 mErrors.append( Error( ml->
id(), tr(
"There was a problem transforming the layer's extent. Layer skipped." ) ) );
332 bool requiresLabeling =
false;
340 layerJobs.append( LayerRenderJob() );
341 LayerRenderJob &job = layerJobs.last();
345 job.layerId = ml->
id();
346 job.renderingTime = -1;
350 job.context.setPainter( painter );
351 job.context.setLabelingEngine( labelingEngine2 );
352 job.context.setCoordinateTransform( ct );
353 job.context.setExtent( r1 );
355 if ( mFeatureFilterProvider )
356 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
373 job.imageInitialized =
true;
376 job.renderer =
nullptr;
377 job.context.setPainter(
nullptr );
384 if (
mCache || ( !painter && !deferredPainterSet ) || needTemporaryImage( ml ) )
387 job.context.setPainter( allocateImageAndPainter( ml->
id(), job.img ) );
390 layerJobs.removeLast();
395 QElapsedTimer layerTime;
398 job.renderingTime = layerTime.elapsed();
406 LayerRenderJobs secondPassJobs;
409 QHash<QString, LayerRenderJob *> layerJobMapping;
412 QSet<QString> layerHasMask;
419 MaskSource(
const QString &layerId_,
const QString &labelRuleId_,
int labelMaskId_ ):
420 layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ) {}
425 QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
427 for ( LayerRenderJob &job : firstPassJobs )
433 layerJobMapping[job.layer->id()] = &job;
436 auto collectMasks = [&]( QHash<QString, QSet<QgsSymbolLayerId>> *masks, QString sourceLayerId, QString ruleId = QString(),
int labelMaskId = -1 )
438 for (
auto it = masks->begin(); it != masks->end(); ++it )
440 auto lit = maskedSymbolLayers.find( it.key() );
441 if ( lit == maskedSymbolLayers.end() )
443 maskedSymbolLayers[it.key()] = qMakePair( it.value(), QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId ) );
447 if ( lit->first != it.value() )
449 QgsLogger::warning( QStringLiteral(
"Layer %1 : Different sets of symbol layers are masked by different sources ! Only one (arbitrary) set will be retained !" ).arg( it.key() ) );
452 lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId ) );
455 if ( ! masks->isEmpty() )
456 layerHasMask.insert( sourceLayerId );
461 for (
auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
463 QString labelRule = it.key();
464 QHash<QString, QSet<QgsSymbolLayerId>> masks = it.value();
467 QSet<QgsSymbolLayerReference> slRefs;
468 for (
auto mit = masks.begin(); mit != masks.end(); mit++ )
470 for (
auto slIt = mit.value().begin(); slIt != mit.value().end(); slIt++ )
476 int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->
id(), it.key(), slRefs );
479 collectMasks( &masks, vl->
id(), labelRule, labelMaskId );
487 if ( maskedSymbolLayers.isEmpty() )
488 return secondPassJobs;
492 for ( LayerRenderJob &job : firstPassJobs )
496 if ( job.img ==
nullptr )
498 job.context.setPainter( allocateImageAndPainter( ml->
id(), job.img ) );
500 if ( layerHasMask.contains( ml->
id() ) )
503 job.context.setMaskPainter( allocateImageAndPainter( ml->
id(), job.maskImage ) );
504 job.maskImage->fill( 0 );
509 if ( labelJob.img ==
nullptr )
511 labelJob.img = allocateImage( QStringLiteral(
"labels" ) );
515 for (
int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
518 labelJob.context.setMaskPainter( allocateImageAndPainter( QStringLiteral(
"label mask" ), maskImage ), maskId );
519 maskImage->fill( 0 );
520 labelJob.maskImages.push_back( maskImage );
522 labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
525 for ( LayerRenderJob &job : firstPassJobs )
529 auto it = maskedSymbolLayers.find( ml->
id() );
530 if ( it == maskedSymbolLayers.end() )
533 QList<MaskSource> &sourceList = it->second;
534 const QSet<QgsSymbolLayerId> &symbolList = it->first;
537 secondPassJobs.append( LayerRenderJob() );
538 LayerRenderJob &job2 = secondPassJobs.last();
541 job2.firstPassJob = &job;
542 QgsVectorLayer *vl1 = qobject_cast<QgsVectorLayer *>( job.layer );
545 job2.context.setMaskPainter(
nullptr );
546 job2.context.setPainter( allocateImageAndPainter( vl1->
id(), job2.img ) );
549 secondPassJobs.removeLast();
554 for ( MaskSource &source : sourceList )
556 if ( source.labelMaskId != -1 )
557 job2.maskJobs.push_back( qMakePair(
nullptr, source.labelMaskId ) );
559 job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
565 job2.renderer = mapRenderer;
572 return secondPassJobs;
579 job.context.setPainter( painter );
580 job.context.setLabelingEngine( labelingEngine2 );
582 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
592 job.context.setPainter(
nullptr );
596 if ( canUseLabelCache && (
mCache || !painter ) )
598 job.img = allocateImage( QStringLiteral(
"labels" ) );
608 for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
610 LayerRenderJob &job = *it;
613 delete job.context.painter();
614 job.context.setPainter(
nullptr );
616 if (
mCache && !job.cached && !job.context.renderingStopped() && job.layer )
618 QgsDebugMsgLevel( QStringLiteral(
"caching image for %1" ).arg( job.layerId ), 2 );
629 delete job.context.maskPainter();
630 job.context.setMaskPainter(
nullptr );
631 delete job.maskImage;
636 const auto constErrors = job.renderer->errors();
637 for (
const QString &message : constErrors )
638 mErrors.append( Error( job.renderer->layerId(), message ) );
641 job.renderer =
nullptr;
653 for (
auto &job : jobs )
657 delete job.context.painter();
658 job.context.setPainter(
nullptr );
667 job.renderer =
nullptr;
681 if (
mCache && !job.cached && !job.context.renderingStopped() )
691 for (
int maskId = 0; maskId < job.maskImages.size(); maskId++ )
693 delete job.context.maskPainter( maskId );
694 job.context.setMaskPainter(
nullptr, maskId );
695 delete job.maskImages[maskId];
700 #define DEBUG_RENDERING 0
706 image.setDotsPerMeterX(
static_cast<int>( settings.
outputDpi() * 39.37 ) );
707 image.setDotsPerMeterY(
static_cast<int>( settings.
outputDpi() * 39.37 ) );
710 QPainter painter( &image );
715 for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
717 const LayerRenderJob &job = *it;
719 if ( job.layer && job.layer->customProperty( QStringLiteral(
"rendering/renderAboveLabels" ) ).toBool() )
722 if ( !job.imageInitialized )
725 painter.setCompositionMode( job.blendMode );
726 painter.setOpacity( job.opacity );
729 job.img->save( QString(
"/tmp/final_%1.png" ).arg( i ) );
734 painter.drawImage( 0, 0, *job.img );
740 if ( labelJob.img && labelJob.complete )
742 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
743 painter.setOpacity( 1.0 );
744 painter.drawImage( 0, 0, *labelJob.img );
748 for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
750 const LayerRenderJob &job = *it;
752 if ( !job.layer || !job.layer->customProperty( QStringLiteral(
"rendering/renderAboveLabels" ) ).toBool() )
755 if ( !job.imageInitialized )
758 painter.setCompositionMode( job.blendMode );
759 painter.setOpacity( job.opacity );
763 painter.drawImage( 0, 0, *job.img );
768 image.save(
"/tmp/final.png" );
779 for ( LayerRenderJob &job : secondPassJobs )
783 job.img->save( QString(
"/tmp/second_%1.png" ).arg( i ) );
788 if ( job.maskJobs.size() > 1 )
790 QPainter *maskPainter =
nullptr;
791 for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
793 QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
795 maskImage->save( QString(
"/tmp/mask_%1_%2.png" ).arg( i ).arg( mask++ ) );
799 maskPainter = p.first ? p.first->context.maskPainter() : labelJob.context.maskPainter( p.second );
803 maskPainter->drawImage( 0, 0, *maskImage );
808 if ( ! job.maskJobs.isEmpty() )
811 QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
812 QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
814 maskImage->save( QString(
"/tmp/mask_%1.png" ).arg( i ) );
818 QPainter *painter = job.context.painter();
819 painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
824 QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
825 QVector<QRgb> mswTable;
826 mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
827 mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
828 maskBinAlpha.setColorTable( mswTable );
829 painter->drawImage( 0, 0, maskBinAlpha );
831 job.img->save( QString(
"/tmp/second_%1_a.png" ).arg( i ) );
836 QPainter tempPainter;
839 QPainter *painter1 = job.firstPassJob->context.painter();
842 tempPainter.begin( job.firstPassJob->img );
843 painter1 = &tempPainter;
846 job.firstPassJob->img->save( QString(
"/tmp/second_%1_first_pass_1.png" ).arg( i ) );
849 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
850 painter1->drawImage( 0, 0, *maskImage );
853 job.firstPassJob->img->save( QString(
"/tmp/second_%1_first_pass_2.png" ).arg( i ) );
856 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
857 painter1->drawImage( 0, 0, *job.img );
859 job.img->save( QString(
"/tmp/second_%1_b.png" ).arg( i ) );
860 if ( job.firstPassJob )
861 job.firstPassJob->img->save( QString(
"/tmp/second_%1_first_pass_3.png" ).arg( i ) );
871 if ( !settings.
value( QStringLiteral(
"Map/logCanvasRefreshEvent" ),
false ).toBool() )
874 QMultiMap<int, QString> elapsed;
875 const auto constJobs = jobs;
876 for (
const LayerRenderJob &job : constJobs )
877 elapsed.insert( job.renderingTime, job.layerId );
878 const auto constSecondPassJobs = secondPassJobs;
879 for (
const LayerRenderJob &job : constSecondPassJobs )
880 elapsed.insert( job.renderingTime, job.layerId + QString(
" (second pass)" ) );
882 elapsed.insert( labelJob.renderingTime, tr(
"Labeling" ) );
884 QList<int> tt( elapsed.uniqueKeys() );
885 std::sort( tt.begin(), tt.end(), std::greater<int>() );
886 const auto constTt = tt;
887 for (
int t : constTt )
889 QgsMessageLog::logMessage( tr(
"%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QStringLiteral(
", " ) ) ), tr(
"Rendering" ) );
894 bool QgsMapRendererJob::needTemporaryImage(
QgsMapLayer *ml )
896 switch ( ml->
type() )
907 ( ( vl->
blendMode() != QPainter::CompositionMode_SourceOver )
941 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
945 if ( labelingEngine2 )
947 labelingEngine2->
run( renderContext );
950 QgsDebugMsgLevel( QStringLiteral(
"Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
957 drawLabeling( renderContext, labelingEngine2, painter );