1 /*************************************************************************** 2 qgsmaprendererjob.h 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 #ifndef QGSMAPRENDERERJOB_H 17 #define QGSMAPRENDERERJOB_H 18 19 #include "qgis_core.h" 20 #include "qgis_sip.h" 21 #include <QFutureWatcher> 22 #include <QImage> 23 #include <QPainter> 24 #include <QObject> 25 #include <QTime> 26 #include <QElapsedTimer> 27 28 #include "qgsrendercontext.h" 29 30 #include "qgsmapsettings.h" 31 #include "qgsmaskidprovider.h" 32 #include "qgssettingsentry.h" 33 34 35 class QgsLabelingEngine; 36 class QgsLabelingResults; 37 class QgsMapLayerRenderer; 38 class QgsMapRendererCache; 39 class QgsFeatureFilterProvider; 40 class QgsRenderedItemResults; 41 42 #ifndef SIP_RUN 43 /// @cond PRIVATE 44 45 /** 46 * \ingroup core 47 * \brief Structure keeping low-level rendering job information. 48 */ 49 class LayerRenderJob 50 { 51 public: 52 53 LayerRenderJob() = default; 54 55 //! LayerRenderJob cannot be copied 56 LayerRenderJob( const LayerRenderJob & ) = delete; 57 58 //! LayerRenderJob cannot be copied 59 LayerRenderJob &operator=( const LayerRenderJob & ) = delete; 60 61 LayerRenderJob( LayerRenderJob && ); 62 LayerRenderJob &operator=( LayerRenderJob && ); 63 64 /** 65 * Sets the render \a context for the job. 66 * 67 * \warning This should only be set once, and must be set before any map layer renderers 68 * are created for the job. 69 */ setContext(std::unique_ptr<QgsRenderContext> context)70 void setContext( std::unique_ptr< QgsRenderContext > context ) { mContext = std::move( context ); } 71 72 /** 73 * Returns the render context associated with the job. 74 * 75 * \see setContext() 76 */ context()77 QgsRenderContext *context() { return mContext.get(); } 78 79 /** 80 * Pointer to destination image. 81 * 82 * May be NULLPTR if it is not necessary to draw to separate image (e.g. sequential rendering). 83 */ 84 QImage *img = nullptr; 85 86 //! TRUE when img has been initialized (filled with transparent pixels) 87 bool imageInitialized = false; 88 89 bool imageCanBeComposed() const; 90 91 QgsMapLayerRenderer *renderer = nullptr; // must be deleted 92 93 QPainter::CompositionMode blendMode = QPainter::CompositionMode_SourceOver; 94 95 double opacity = 1.0; 96 97 //! If TRUE, img already contains cached image from previous rendering 98 bool cached = false; 99 100 QgsWeakMapLayerPointer layer; 101 102 /** 103 * TRUE if the render job was successfully completed in its entirety (i.e. it was 104 * not canceled or aborted early). 105 * 106 * \since QGIS 3.18 107 */ 108 bool completed = false; 109 110 //! Time it took to render the layer in ms (it is -1 if not rendered or still rendering) 111 int renderingTime = -1; 112 113 /** 114 * Estimated time for the layer to render, in ms. 115 * 116 * This can be used to specifies hints at the expected render times for the layer, so that 117 * the corresponding layer renderer can apply heuristics and determine appropriate update 118 * intervals during the render operation. 119 * 120 * \since QGIS 3.18 121 */ 122 int estimatedRenderingTime = 0; 123 124 QStringList errors; //!< Rendering errors 125 126 /** 127 * Identifies the associated layer by ID. 128 * 129 * \warning This should NEVER be used to retrieve map layers during a render job, and instead 130 * is intended for use as a string identifier only. 131 * 132 * \since QGIS 3.10 133 */ 134 QString layerId; 135 136 /** 137 * Selective masking handling. 138 * 139 * A layer can be involved in selective masking in two ways: 140 * 141 * - One of its symbol layer masks a symbol layer of another layer. 142 * In this case we need to compute a mask image during the regular 143 * rendering pass that will be stored here; 144 * - Some of its symbol layers are masked by a symbol layer of another layer (or by a label mask) 145 * In this case we need to render the layer once again in a second pass, but with some symbol 146 * layers disabled. 147 * This second rendering will be composed with mask images that have been computed in the first 148 * pass by another job. We then need to know which first pass image and which masks correspond. 149 */ 150 151 //! Mask image, needed during the first pass if a mask is defined 152 QImage *maskImage = nullptr; 153 154 /** 155 * Pointer to the first pass job, needed during the second pass 156 * to access first pass painter and image. 157 */ 158 LayerRenderJob *firstPassJob = nullptr; 159 160 /** 161 * Pointer to first pass jobs that carry a mask image, needed during the second pass. 162 * This can be either a LayerRenderJob, in which case the second element of the QPair is ignored. 163 * Or this can be a LabelRenderJob if the first element is nullptr. 164 * In this latter case, the second element of the QPair gives the label mask id. 165 */ 166 QList<QPair<LayerRenderJob *, int>> maskJobs; 167 168 private: 169 std::unique_ptr< QgsRenderContext > mContext; 170 171 }; 172 173 /** 174 * \ingroup core 175 * \brief Structure keeping low-level label rendering job information. 176 */ 177 struct LabelRenderJob 178 { 179 QgsRenderContext context; 180 181 /** 182 * May be NULLPTR if it is not necessary to draw to separate image (e.g. using composition modes which prevent "flattening" the layer). 183 * Note that if complete is FALSE then img will be uninitialized and contain random data!. 184 */ 185 QImage *img = nullptr; 186 187 /** 188 * Mask images 189 * 190 * There is only one label job, with labels coming from different layers or rules (for rule-based labeling). 191 * So we may have different labels with different label masks. We then need one different mask image for each configuration of label masks. 192 * Labels that share the same kind of label masks, i.e. having the same set of symbol layers that are to be masked, should share the same mask image. 193 * Labels that have different label masks, i.e. having different set of symbol layers that are to be masked, should have different mask images. 194 * The index in the vector corresponds to the mask identifier. 195 * \see maskIdProvider 196 */ 197 QVector<QImage *> maskImages; 198 199 /** 200 * A mask id provider that is used to compute a mask image identifier for each label layer. 201 * \see maskImages 202 */ 203 QgsMaskIdProvider maskIdProvider; 204 205 //! If TRUE, img already contains cached image from previous rendering 206 bool cached = false; 207 //! Will be TRUE if labeling is eligible for caching 208 bool canUseCache = false; 209 //! If TRUE then label render is complete 210 bool complete = false; 211 //! Time it took to render the labels in ms (it is -1 if not rendered or still rendering) 212 int renderingTime = -1; 213 //! List of layers which participated in the labeling solution 214 QList< QPointer< QgsMapLayer > > participatingLayers; 215 }; 216 217 ///@endcond PRIVATE 218 #endif 219 220 /** 221 * \ingroup core 222 * \brief Abstract base class for map rendering implementations. 223 * 224 * The API is designed in a way that rendering is done asynchronously, therefore 225 * the caller is not blocked while the rendering is in progress. Non-blocking 226 * operation is quite important because the rendering can take considerable 227 * amount of time. 228 * 229 * Common use case: 230 * 231 * 1. Prepare QgsMapSettings with rendering configuration (extent, layer, map size, ...) 232 * 2. Create QgsMapRendererJob subclass with QgsMapSettings instance 233 * 3. Connect to job's finished() signal 234 * 4. Call start(). Map rendering will start in background, the function immediately returns 235 * 5. At some point, slot connected to finished() signal is called, map rendering is done 236 * 237 * It is possible to cancel the rendering job while it is active by calling cancel() function. 238 * 239 * The following subclasses are available: 240 * 241 * - QgsMapRendererSequentialJob - renders map in one background thread to an image 242 * - QgsMapRendererParallelJob - renders map in multiple background threads to an image 243 * - QgsMapRendererCustomPainterJob - renders map with given QPainter in one background thread 244 * 245 * \since QGIS 2.4 246 */ 247 class CORE_EXPORT QgsMapRendererJob : public QObject SIP_ABSTRACT 248 { 249 Q_OBJECT 250 public: 251 252 QgsMapRendererJob( const QgsMapSettings &settings ); 253 254 ~QgsMapRendererJob() override; 255 256 /** 257 * Start the rendering job and immediately return. 258 * Does nothing if the rendering is already in progress. 259 */ 260 void start(); 261 262 /** 263 * Stop the rendering job - does not return until the job has terminated. 264 * Does nothing if the rendering is not active. 265 */ 266 virtual void cancel() = 0; 267 268 /** 269 * Triggers cancellation of the rendering job without blocking. The render job will continue 270 * to operate until it is able to cancel, at which stage the finished() signal will be emitted. 271 * Does nothing if the rendering is not active. 272 */ 273 virtual void cancelWithoutBlocking() = 0; 274 275 //! Block until the job has finished. 276 virtual void waitForFinished() = 0; 277 278 //! Tell whether the rendering job is currently running in background. 279 virtual bool isActive() const = 0; 280 281 /** 282 * Returns TRUE if the render job was able to use a cached labeling solution. 283 * If so, any previously stored labeling results (see takeLabelingResults()) 284 * should be retained. 285 * \see takeLabelingResults() 286 * \since QGIS 3.0 287 */ 288 virtual bool usedCachedLabels() const = 0; 289 290 /** 291 * Returns a list of the layer IDs for all layers which were redrawn from cached 292 * images. 293 * 294 * This method should only be called after the render job is completed. 295 * 296 * \since QGIS 3.22 297 */ 298 QStringList layersRedrawnFromCache() const; 299 300 /** 301 * Gets pointer to internal labeling engine (in order to get access to the results). 302 * This should not be used if cached labeling was redrawn - see usedCachedLabels(). 303 * \see usedCachedLabels() 304 */ 305 virtual QgsLabelingResults *takeLabelingResults() = 0 SIP_TRANSFER; 306 307 /** 308 * Takes the rendered item results from the map render job and returns them. 309 * 310 * Ownership is transferred to the caller. 311 * 312 * \since QGIS 3.22 313 */ 314 QgsRenderedItemResults *takeRenderedItemResults() SIP_TRANSFER; 315 316 /** 317 * Set the feature filter provider used by the QgsRenderContext of 318 * each LayerRenderJob. 319 * Ownership is not transferred and the provider must not be deleted 320 * before the render job. 321 * \since QGIS 3.0 322 */ setFeatureFilterProvider(const QgsFeatureFilterProvider * f)323 void setFeatureFilterProvider( const QgsFeatureFilterProvider *f ) { mFeatureFilterProvider = f; } 324 325 /** 326 * Returns the feature filter provider used by the QgsRenderContext of 327 * each LayerRenderJob. 328 * \since QGIS 3.0 329 */ featureFilterProvider()330 const QgsFeatureFilterProvider *featureFilterProvider() const { return mFeatureFilterProvider; } 331 332 struct Error 333 { ErrorError334 Error( const QString &lid, const QString &msg ) 335 : layerID( lid ) 336 , message( msg ) 337 {} 338 339 QString layerID; 340 QString message; 341 }; 342 343 typedef QList<QgsMapRendererJob::Error> Errors; 344 345 //! List of errors that happened during the rendering job - available when the rendering has been finished 346 Errors errors() const; 347 348 349 /** 350 * Assign a cache to be used for reading and storing rendered images of individual layers. 351 * Does not take ownership of the object. 352 */ 353 void setCache( QgsMapRendererCache *cache ); 354 355 /** 356 * Returns the total time it took to finish the job (in milliseconds). 357 * \see perLayerRenderingTime() 358 */ renderingTime()359 int renderingTime() const { return mRenderingTime; } 360 361 /** 362 * Returns the render time (in ms) per layer. 363 * \note Not available in Python bindings. 364 * \since QGIS 3.0 365 */ 366 QHash< QgsMapLayer *, int > perLayerRenderingTime() const SIP_SKIP; 367 368 /** 369 * Sets approximate render times (in ms) for map layers. 370 * 371 * This can be used to specifies hints at the expected render times for layers, so that 372 * the individual layer renderers can apply heuristics and determine appropriate update 373 * intervals during the render operation. 374 * 375 * The keys for \a hints must be set to the corresponding layer IDs. 376 * 377 * \note Not available in Python bindings. 378 * \since QGIS 3.18 379 */ 380 void setLayerRenderingTimeHints( const QHash< QString, int > &hints ) SIP_SKIP; 381 382 /** 383 * Returns map settings with which this job was started. 384 * \returns A QgsMapSettings instance with render settings 385 * \since QGIS 2.8 386 */ 387 const QgsMapSettings &mapSettings() const; 388 389 /** 390 * QgsMapRendererCache ID string for cached label image. 391 * \note not available in Python bindings 392 */ 393 static const QString LABEL_CACHE_ID SIP_SKIP; 394 395 /** 396 * QgsMapRendererCache ID string for cached label image during preview compositions only. 397 * \note not available in Python bindings 398 * \since QGIS 3.18 399 */ 400 static const QString LABEL_PREVIEW_CACHE_ID SIP_SKIP; 401 402 #ifndef SIP_RUN 403 //! Settings entry log canvas refresh event 404 static const inline QgsSettingsEntryBool settingsLogCanvasRefreshEvent = QgsSettingsEntryBool( QStringLiteral( "Map/logCanvasRefreshEvent" ), QgsSettings::NoSection, false ); 405 #endif 406 407 signals: 408 409 /** 410 * Emitted when the layers are rendered. 411 * Rendering labels is not yet done. If the fully rendered layer including labels is required use 412 * finished() instead. 413 * 414 * \since QGIS 3.0 415 */ 416 void renderingLayersFinished(); 417 418 //! emitted when asynchronous rendering is finished (or canceled). 419 void finished(); 420 421 protected: 422 423 QgsMapSettings mSettings; 424 QElapsedTimer mRenderingStart; 425 Errors mErrors; 426 427 QgsMapRendererCache *mCache = nullptr; 428 429 int mRenderingTime = 0; 430 431 //! Render time (in ms) per layer, by layer ID 432 QHash< QgsWeakMapLayerPointer, int > mPerLayerRenderingTime; 433 434 /** 435 * Approximate expected layer rendering time per layer, by layer ID 436 * 437 * \since QGIS 3.18 438 */ 439 QHash< QString, int > mLayerRenderingTimeHints; 440 441 /** 442 * TRUE if layer rendering time should be recorded. 443 */ 444 bool mRecordRenderingTime = true; 445 446 #ifndef SIP_RUN 447 std::unique_ptr< QgsRenderedItemResults > mRenderedItemResults; 448 #endif 449 450 QStringList mLayersRedrawnFromCache; 451 452 /** 453 * Prepares the cache for storing the result of labeling. Returns FALSE if 454 * the render cannot use cached labels and should not cache the result. 455 * \note not available in Python bindings 456 */ 457 bool prepareLabelCache() const SIP_SKIP; 458 459 /** 460 * Creates a list of layer rendering jobs and prepares them for later render. 461 * 462 * The \a painter argument specifies the destination painter. If not set, the jobs will 463 * be rendered to temporary images. Alternatively, if the \a deferredPainterSet flag is TRUE, 464 * then a \a painter value of NULLPTR skips this default temporary image creation. In this case, 465 * it is the caller's responsibility to correctly set a painter for all rendered jobs prior 466 * to rendering them. 467 * 468 * \note not available in Python bindings 469 */ 470 std::vector< LayerRenderJob > prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet = false ) SIP_SKIP; 471 472 /** 473 * Prepares a labeling job. 474 * \note not available in Python bindings 475 * \since QGIS 3.0 476 */ 477 LabelRenderJob prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache = true ) SIP_SKIP; 478 479 /** 480 * Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers). 481 * Must be called after prepareJobs and prepareLabelingJob. 482 * It returns a list of new jobs for a second pass and also modifies labelJob and firstPassJobs if needed 483 * (image and mask image allocation if needed) 484 * \note not available in Python bindings 485 * \since QGIS 3.12 486 */ 487 std::vector< LayerRenderJob > prepareSecondPassJobs( std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob ) SIP_SKIP; 488 489 //! \note not available in Python bindings 490 static QImage composeImage( const QgsMapSettings &settings, 491 const std::vector< LayerRenderJob > &jobs, 492 const LabelRenderJob &labelJob, 493 const QgsMapRendererCache *cache = nullptr ) SIP_SKIP; 494 495 //! \note not available in Python bindings 496 static QImage layerImageToBeComposed( const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache ) SIP_SKIP; 497 498 /** 499 * Compose second pass images into first pass images. 500 * First pass jobs pointed to by the second pass jobs must still exist. 501 * \note not available in Python bindings 502 * \since QGIS 3.12 503 */ 504 static void composeSecondPass( std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob ) SIP_SKIP; 505 506 //! \note not available in Python bindings 507 void logRenderingTime( const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob ) SIP_SKIP; 508 509 //! \note not available in Python bindings 510 void cleanupJobs( std::vector< LayerRenderJob > &jobs ) SIP_SKIP; 511 512 //! \note not available in Python bindings 513 void cleanupSecondPassJobs( std::vector< LayerRenderJob > &jobs ) SIP_SKIP; 514 515 /** 516 * Handles clean up tasks for a label job, including deletion of images and storing cached 517 * label results. 518 * \note not available in Python bindings 519 * \since QGIS 3.0 520 */ 521 void cleanupLabelJob( LabelRenderJob &job ) SIP_SKIP; 522 523 /** 524 * \note not available in Python bindings 525 * \deprecated Will be removed in QGIS 4.0 526 */ 527 Q_DECL_DEPRECATED static void drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter ) SIP_SKIP; 528 529 //! \note not available in Python bindings 530 static void drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter ) SIP_SKIP; 531 532 private: 533 534 /** 535 * Convenience function to project an extent into the layer source 536 * CRS, but also split it into two extents if it crosses 537 * the +/- 180 degree line. Modifies the given extent to be in the 538 * source CRS coordinates, and if it was split also sets the contents 539 * of the r2 parameter. 540 * 541 * If FALSE is returned then the extent could not be accurately 542 * transformed to the layer's CRS, and a "full globe" extent 543 * was used instead. 544 */ 545 static bool reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 ); 546 547 const QgsFeatureFilterProvider *mFeatureFilterProvider = nullptr; 548 549 //! Convenient method to allocate a new image and stack an error if not enough memory is available 550 QImage *allocateImage( QString layerId ); 551 552 //! Convenient method to allocate a new image and a new QPainter on this image 553 QPainter *allocateImageAndPainter( QString layerId, QImage *&image ); 554 555 /** 556 * This pure virtual method has to be implemented in derived class for starting the rendering. 557 * This method is called in start() method after ckecking if the map can be rendered. 558 * \since QGIS 3.20 559 */ 560 virtual void startPrivate() = 0; 561 562 }; 563 564 565 /** 566 * \ingroup core 567 * \brief Intermediate base class adding functionality that allows client to query the rendered image. 568 * 569 * The image can be queried even while the rendering is still in progress to get intermediate result 570 * 571 * \since QGIS 2.4 572 */ 573 class CORE_EXPORT QgsMapRendererQImageJob : public QgsMapRendererJob SIP_ABSTRACT 574 { 575 Q_OBJECT 576 577 public: 578 QgsMapRendererQImageJob( const QgsMapSettings &settings ); 579 580 //! Gets a preview/resulting image 581 virtual QImage renderedImage() = 0; 582 583 }; 584 585 586 #endif // QGSMAPRENDERERJOB_H 587