QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgslayoutitempicture.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitempicture.cpp
3 ------------------------
4 begin : October 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgscolorutils.h"
21#include "qgsimagecache.h"
22#include "qgslayout.h"
23#include "qgslayoutitemmap.h"
28#include "qgslayoututils.h"
29#include "qgslogger.h"
30#include "qgsmessagelog.h"
32#include "qgspathresolver.h"
33#include "qgsproperty.h"
34#include "qgsreadwritecontext.h"
35#include "qgssvgcache.h"
36#include "qgssymbollayerutils.h"
37
38#include <QCoreApplication>
39#include <QDomDocument>
40#include <QDomElement>
41#include <QEventLoop>
42#include <QFileInfo>
43#include <QImageReader>
44#include <QNetworkReply>
45#include <QNetworkRequest>
46#include <QPainter>
47#include <QString>
48#include <QSvgRenderer>
49#include <QUrl>
50
51#include "moc_qgslayoutitempicture.cpp"
52
53using namespace Qt::StringLiterals;
54
57 , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
58{
59 //default to no background
60 setBackgroundEnabled( false );
61
62 //connect some signals
63
64 //connect to atlas feature changing
65 //to update the picture source expression
66 connect( &layout->reportContext(), &QgsLayoutReportContext::changed, this, [this] { refreshPicture(); } );
67
68 //connect to layout print resolution changing
70
71 connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
72 connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemPicture::updateNorthArrowRotation );
73}
74
79
81{
82 return QgsApplication::getThemeIcon( u"/mLayoutItemPicture.svg"_s );
83}
84
89
91{
92 QPainter *painter = context.renderContext().painter();
93 const QgsScopedQPainterState painterState( painter );
94 // painter is scaled to dots, so scale back to layout units
95 painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
96
97 const bool prevSmoothTransform = painter->testRenderHint( QPainter::RenderHint::SmoothPixmapTransform );
98 if ( mLayout->renderContext().testFlag( Qgis::LayoutRenderFlag::Antialiasing ) )
99 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, true );
100
101 //picture resizing
102 if ( mMode != Qgis::PictureFormat::Unknown )
103 {
104 double boundRectWidthMM;
105 double boundRectHeightMM;
106 QRect imageRect;
107 if ( mResizeMode == QgsLayoutItemPicture::Zoom || mResizeMode == QgsLayoutItemPicture::ZoomResizeFrame )
108 {
109 boundRectWidthMM = mPictureWidth;
110 boundRectHeightMM = mPictureHeight;
111 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
112 }
113 else if ( mResizeMode == QgsLayoutItemPicture::Stretch )
114 {
115 boundRectWidthMM = rect().width();
116 boundRectHeightMM = rect().height();
117 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
118 }
119 else if ( mResizeMode == QgsLayoutItemPicture::Clip )
120 {
121 boundRectWidthMM = rect().width();
122 boundRectHeightMM = rect().height();
123 const int imageRectWidthPixels = mImage.width();
124 const int imageRectHeightPixels = mImage.height();
125 imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
126 QSize( imageRectWidthPixels, imageRectHeightPixels ) );
127 }
128 else
129 {
130 boundRectWidthMM = rect().width();
131 boundRectHeightMM = rect().height();
132 imageRect = QRect( 0, 0, mLayout->convertFromLayoutUnits( rect().width(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4,
133 mLayout->convertFromLayoutUnits( rect().height(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4 );
134 }
135
136 //zoom mode - calculate anchor point and rotation
137 if ( mResizeMode == Zoom )
138 {
139 //TODO - allow placement modes with rotation set. for now, setting a rotation
140 //always places picture in center of frame
141 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
142 {
143 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
144 painter->rotate( mPictureRotation );
145 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
146 }
147 else
148 {
149 //shift painter to edge/middle of frame depending on placement
150 const double diffX = rect().width() - boundRectWidthMM;
151 const double diffY = rect().height() - boundRectHeightMM;
152
153 double dX = 0;
154 double dY = 0;
155 switch ( mPictureAnchor )
156 {
157 case UpperLeft:
158 case MiddleLeft:
159 case LowerLeft:
160 //nothing to do
161 break;
162 case UpperMiddle:
163 case Middle:
164 case LowerMiddle:
165 dX = diffX / 2.0;
166 break;
167 case UpperRight:
168 case MiddleRight:
169 case LowerRight:
170 dX = diffX;
171 break;
172 }
173 switch ( mPictureAnchor )
174 {
175 case UpperLeft:
176 case UpperMiddle:
177 case UpperRight:
178 //nothing to do
179 break;
180 case MiddleLeft:
181 case Middle:
182 case MiddleRight:
183 dY = diffY / 2.0;
184 break;
185 case LowerLeft:
186 case LowerMiddle:
187 case LowerRight:
188 dY = diffY;
189 break;
190 }
191 painter->translate( dX, dY );
192 }
193 }
194 else if ( mResizeMode == ZoomResizeFrame )
195 {
196 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
197 {
198 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
199 painter->rotate( mPictureRotation );
200 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
201 }
202 }
203
204 if ( mMode == Qgis::PictureFormat::SVG )
205 {
206 mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
207 }
208 else if ( mMode == Qgis::PictureFormat::Raster )
209 {
210 painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
211 }
212 }
213 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, prevSmoothTransform );
214}
215
216QSizeF QgsLayoutItemPicture::applyItemSizeConstraint( const QSizeF targetSize )
217{
218 const QSizeF currentPictureSize = pictureSize();
219 QSizeF newSize = targetSize;
220 if ( mResizeMode == QgsLayoutItemPicture::Clip )
221 {
222 mPictureWidth = targetSize.width();
223 mPictureHeight = targetSize.height();
224 }
225 else
226 {
227 if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
228 {
229 QSizeF targetImageSize;
230 if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
231 {
232 targetImageSize = currentPictureSize;
233 }
234 else
235 {
236 //calculate aspect ratio of bounds of rotated image
237 QTransform tr;
238 tr.rotate( mPictureRotation );
239 const QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
240 targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
241 }
242
243 //if height has changed more than width, then fix width and set height correspondingly
244 //else, do the opposite
245 if ( std::fabs( rect().width() - targetSize.width() ) <
246 std::fabs( rect().height() - targetSize.height() ) )
247 {
248 newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
249 }
250 else
251 {
252 newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
253 }
254 }
255 else if ( mResizeMode == FrameToImageSize )
256 {
257 if ( !( currentPictureSize.isEmpty() ) )
258 {
259 const QgsLayoutSize sizeMM = mLayout->convertFromLayoutUnits( currentPictureSize, Qgis::LayoutUnit::Millimeters );
260 newSize.setWidth( sizeMM.width() * 25.4 / mLayout->renderContext().dpi() );
261 newSize.setHeight( sizeMM.height() * 25.4 / mLayout->renderContext().dpi() );
262 }
263 }
264
265 //find largest scaling of picture with this rotation which fits in item
266 if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
267 {
268 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ),
269 QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
270 mPictureWidth = rotatedImageRect.width();
271 mPictureHeight = rotatedImageRect.height();
272 }
273 else
274 {
275 mPictureWidth = newSize.width();
276 mPictureHeight = newSize.height();
277 }
278
279 if ( newSize != targetSize )
280 {
281 emit changed();
282 }
283 }
284
285 return newSize;
286}
287
288QRect QgsLayoutItemPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
289{
290 const int boundRectWidthPixels = boundRectWidthMM * mLayout->renderContext().dpi() / 25.4;
291 const int boundRectHeightPixels = boundRectHeightMM * mLayout->renderContext().dpi() / 25.4;
292
293 //update boundRectWidth/Height so that they exactly match pixel bounds
294 boundRectWidthMM = boundRectWidthPixels * 25.4 / mLayout->renderContext().dpi();
295 boundRectHeightMM = boundRectHeightPixels * 25.4 / mLayout->renderContext().dpi();
296
297 //calculate part of image which fits in bounds
298 int leftClip = 0;
299 int topClip = 0;
300
301 //calculate left crop
302 switch ( mPictureAnchor )
303 {
304 case UpperLeft:
305 case MiddleLeft:
306 case LowerLeft:
307 leftClip = 0;
308 break;
309 case UpperMiddle:
310 case Middle:
311 case LowerMiddle:
312 leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
313 break;
314 case UpperRight:
315 case MiddleRight:
316 case LowerRight:
317 leftClip = imageRectPixels.width() - boundRectWidthPixels;
318 break;
319 }
320
321 //calculate top crop
322 switch ( mPictureAnchor )
323 {
324 case UpperLeft:
325 case UpperMiddle:
326 case UpperRight:
327 topClip = 0;
328 break;
329 case MiddleLeft:
330 case Middle:
331 case MiddleRight:
332 topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
333 break;
334 case LowerLeft:
335 case LowerMiddle:
336 case LowerRight:
337 topClip = imageRectPixels.height() - boundRectHeightPixels;
338 break;
339 }
340
341 return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
342}
343
345{
346 const QgsExpressionContext scopedContext = createExpressionContext();
347 const QgsExpressionContext *evalContext = context ? context : &scopedContext;
348
349 mDataDefinedProperties.prepare( *evalContext );
350
351 QVariant source( mSourcePath );
352
353 //data defined source set?
354 mHasExpressionError = false;
356 {
358 bool ok = false;
360 source = sourceProperty.value( *evalContext, source, &ok );
361 if ( !ok || !source.canConvert( QMetaType::QString ) )
362 {
363 mHasExpressionError = true;
364 source = QString();
365 if ( scopedContext.feature().isValid() )
366 {
367 QgsMessageLog::logMessage( u"%1: %2"_s.arg( tr( "Picture expression eval error" ), sourceProperty.asExpression() ) );
368 }
369 }
370 else if ( source.userType() != QMetaType::Type::QByteArray )
371 {
372 source = source.toString().trimmed();
373 QgsDebugMsgLevel( u"exprVal PictureSource:%1"_s.arg( source.toString() ), 2 );
374 }
375 }
376
377 loadPicture( source );
378}
379
380void QgsLayoutItemPicture::loadRemotePicture( const QString &url )
381{
382 //remote location
383
385 QEventLoop loop;
386 connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
387 fetcher.fetchContent( QUrl( url ) );
388
389 //wait until picture fetched
390 loop.exec( QEventLoop::ExcludeUserInputEvents );
391
392 QNetworkReply *reply = fetcher.reply();
393 if ( reply )
394 {
395 QImageReader imageReader( reply );
396 imageReader.setAutoTransform( true );
397
398 if ( imageReader.format() == "pdf" )
399 {
400 // special handling for this format -- we need to pass the desired target size onto the image reader
401 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
402 // a very low resolution image (the driver assumes points == pixels!)
403 // For other image formats, we read the original image size only and defer resampling to later in this
404 // function. That gives us more control over the resampling method used.
405
406 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
407 const QSize sizeAt72Dpi = imageReader.size();
408 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
409 imageReader.setScaledSize( sizeAtTargetDpi );
410 }
411
412 mImage = imageReader.read();
414 }
415 else
416 {
418 }
419}
420
421void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
422{
423 QFile pic;
424 pic.setFileName( path );
425
426 if ( !pic.exists() )
427 {
429 }
430 else
431 {
432 const QFileInfo sourceFileInfo( pic );
433 const QString sourceFileSuffix = sourceFileInfo.suffix();
434 if ( sourceFileSuffix.compare( "svg"_L1, Qt::CaseInsensitive ) == 0 )
435 {
436 //try to open svg
437 const QgsExpressionContext context = createExpressionContext();
438 const QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgBackgroundColor, context, mSvgFillColor );
439 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
440 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
441 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
442
443 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
444 1.0, 0, false, evaluatedParameters );
445 mSVG.load( svgContent );
446 if ( mSVG.isValid() )
447 {
449 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
450 mDefaultSvgSize.setWidth( viewBox.width() );
451 mDefaultSvgSize.setHeight( viewBox.height() );
452 }
453 else
454 {
456 }
457 }
458 else
459 {
460 //try to open raster with QImageReader
461 QImageReader imageReader( pic.fileName() );
462 imageReader.setAutoTransform( true );
463
464 if ( imageReader.format() == "pdf" )
465 {
466 // special handling for this format -- we need to pass the desired target size onto the image reader
467 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
468 // a very low resolution image (the driver assumes points == pixels!)
469 // For other image formats, we read the original image size only and defer resampling to later in this
470 // function. That gives us more control over the resampling method used.
471
472 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
473 const QSize sizeAt72Dpi = imageReader.size();
474 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
475 imageReader.setScaledSize( sizeAtTargetDpi );
476 }
477
478 if ( imageReader.read( &mImage ) )
479 {
481 }
482 else
483 {
485 }
486 }
487 }
488}
489
490void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
491{
492 if ( path.isEmpty() )
493 {
494 mImage = QImage();
495 mSVG.load( QByteArray() );
496 return;
497 }
498
499 switch ( mMode )
500 {
502 break;
503
505 {
506 bool fitsInCache = false;
507 bool isMissing = false;
508 mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true, mLayout->renderContext().dpi(), -1, &isMissing );
509 if ( mImage.isNull() || isMissing )
511 break;
512 }
513
515 {
516 const QgsExpressionContext context = createExpressionContext();
517 const QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgBackgroundColor, context, mSvgFillColor );
518 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
519 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
520
521 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
522
523 bool isMissingImage = false;
524 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
525 1.0, 0, false, evaluatedParameters, &isMissingImage );
526 mSVG.load( svgContent );
527 if ( mSVG.isValid() && !isMissingImage )
528 {
530 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
531 mDefaultSvgSize.setWidth( viewBox.width() );
532 mDefaultSvgSize.setHeight( viewBox.height() );
533 }
534 else
535 {
537 }
538 break;
539 }
540 }
541}
542
543void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
544{
545 setPictureRotation( rotation );
546 emit pictureRotationChanged( rotation );
547}
548
549void QgsLayoutItemPicture::loadPicture( const QVariant &data )
550{
551 mIsMissingImage = false;
552 QVariant imageData( data );
553 mEvaluatedPath = data.toString();
554
555 if ( mEvaluatedPath.startsWith( "base64:"_L1, Qt::CaseInsensitive ) && mMode == Qgis::PictureFormat::Unknown )
556 {
557 const QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
558 imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
559 }
560
561 if ( imageData.userType() == QMetaType::Type::QByteArray )
562 {
563 if ( mImage.loadFromData( imageData.toByteArray() ) )
564 {
566 }
567 }
568 else if ( mMode == Qgis::PictureFormat::Unknown && mEvaluatedPath.startsWith( "http"_L1 ) )
569 {
570 //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
571 loadRemotePicture( mEvaluatedPath );
572 }
573 else if ( mMode == Qgis::PictureFormat::Unknown )
574 {
575 //local location - for old API/project compatibility only!!
576 loadLocalPicture( mEvaluatedPath );
577 }
578 else
579 {
580 loadPictureUsingCache( mEvaluatedPath );
581 }
582
583 mLoaded = false;
584 if ( mMode != Qgis::PictureFormat::Unknown ) //make sure we start with a new QImage
585 {
587 mLoaded = true;
588 }
589 else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
590 {
591 //trying to load an invalid file or bad expression, show cross picture
592 mIsMissingImage = true;
593 if ( mOriginalMode == Qgis::PictureFormat::Raster )
594 {
595 const QString badFile( u":/images/composer/missing_image.png"_s );
596 QImageReader imageReader( badFile );
597 if ( imageReader.read( &mImage ) )
599 }
600 else if ( mOriginalMode == Qgis::PictureFormat::SVG )
601 {
602 const QString badFile( u":/images/composer/missing_image.svg"_s );
603 mSVG.load( badFile );
604 if ( mSVG.isValid() )
605 {
607 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
608 mDefaultSvgSize.setWidth( viewBox.width() );
609 mDefaultSvgSize.setHeight( viewBox.height() );
610 }
611 }
613 }
614
615 update();
616 emit changed();
617}
618
619QSizeF QgsLayoutItemPicture::pictureSize()
620{
621 if ( mMode == Qgis::PictureFormat::SVG )
622 {
623 return mDefaultSvgSize;
624 }
625 else if ( mMode == Qgis::PictureFormat::Raster )
626 {
627 return QSizeF( mImage.width(), mImage.height() );
628 }
629 else
630 {
631 return QSizeF( 0, 0 );
632 }
633}
634
636{
637 return mIsMissingImage;
638}
639
641{
642 return mEvaluatedPath;
643}
644
645QMap<QString, QgsProperty> QgsLayoutItemPicture::svgDynamicParameters() const
646{
647 const QVariantMap parameters = mCustomProperties.value( u"svg-dynamic-parameters"_s, QVariantMap() ).toMap();
648 return QgsProperty::variantMapToPropertyMap( parameters );
649}
650
651void QgsLayoutItemPicture::setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters )
652{
653 const QVariantMap variantMap = QgsProperty::propertyMapToVariantMap( parameters );
654 mCustomProperties.setValue( u"svg-dynamic-parameters"_s, variantMap );
656}
657
658void QgsLayoutItemPicture::shapeChanged()
659{
660 if ( mMode == Qgis::PictureFormat::SVG && !mLoadingSvg )
661 {
662 mLoadingSvg = true;
664 mLoadingSvg = false;
665 }
666}
667
669{
670 const double oldRotation = mPictureRotation;
671 mPictureRotation = rotation;
672 const QSizeF currentPictureSize = pictureSize();
673
674 // If the picture is not loaded yet, do not compute its rotated size
675 if ( !mLoaded || currentPictureSize == QSizeF( 0, 0 ) )
676 return;
677
678 if ( mResizeMode == Zoom )
679 {
680 //find largest scaling of picture with this rotation which fits in item
681 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
682 mPictureWidth = rotatedImageRect.width();
683 mPictureHeight = rotatedImageRect.height();
684 update();
685 }
686 else if ( mResizeMode == ZoomResizeFrame )
687 {
688 const QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
689
690 //calculate actual size of image inside frame
691 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
692
693 //rotate image rect by new rotation and get bounding box
694 QTransform tr;
695 tr.rotate( mPictureRotation );
696 QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
697
698 //keep the center in the same location
699 newRect.moveCenter( oldRect.center() );
700 attemptSetSceneRect( newRect );
701 emit changed();
702 }
703
704 emit pictureRotationChanged( mPictureRotation );
705}
706
708{
709 mNorthArrowHandler->setLinkedMap( map );
710}
711
713{
714 mResizeMode = mode;
716 || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
717 {
718 //call set scene rect to force item to resize to fit picture
720 }
721 update();
722}
723
725{
726 //call set scene rect with current position/size, as this will trigger the
727 //picture item to recalculate its frame and image size
728 attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
729}
730
743
745{
746 mOriginalMode = format;
747 mMode = format;
748 mSourcePath = path;
750}
751
753{
754 return mSourcePath;
755}
756
757bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
758{
759 QString imagePath = mSourcePath;
760
761 // convert from absolute path to relative. For SVG we also need to consider system SVG paths
762 const QgsPathResolver pathResolver = context.pathResolver();
763 if ( imagePath.endsWith( ".svg"_L1, Qt::CaseInsensitive ) )
764 imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
765 else
766 imagePath = pathResolver.writePath( imagePath );
767
768 elem.setAttribute( u"file"_s, imagePath );
769 elem.setAttribute( u"pictureWidth"_s, QString::number( mPictureWidth ) );
770 elem.setAttribute( u"pictureHeight"_s, QString::number( mPictureHeight ) );
771 elem.setAttribute( u"resizeMode"_s, QString::number( static_cast< int >( mResizeMode ) ) );
772 elem.setAttribute( u"anchorPoint"_s, QString::number( static_cast< int >( mPictureAnchor ) ) );
773 elem.setAttribute( u"svgFillColor"_s, QgsColorUtils::colorToString( mSvgFillColor ) );
774 elem.setAttribute( u"svgBorderColor"_s, QgsColorUtils::colorToString( mSvgStrokeColor ) );
775 elem.setAttribute( u"svgBorderWidth"_s, QString::number( mSvgStrokeWidth ) );
776 elem.setAttribute( u"mode"_s, static_cast< int >( mOriginalMode ) );
777
778 //rotation
779 elem.setAttribute( u"pictureRotation"_s, QString::number( mPictureRotation ) );
780 if ( !mNorthArrowHandler->linkedMap() )
781 {
782 elem.setAttribute( u"mapUuid"_s, QString() );
783 }
784 else
785 {
786 elem.setAttribute( u"mapUuid"_s, mNorthArrowHandler->linkedMap()->uuid() );
787 }
788 elem.setAttribute( u"northMode"_s, mNorthArrowHandler->northMode() );
789 elem.setAttribute( u"northOffset"_s, mNorthArrowHandler->northOffset() );
790 return true;
791}
792
793bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
794{
795 mPictureWidth = itemElem.attribute( u"pictureWidth"_s, u"10"_s ).toDouble();
796 mPictureHeight = itemElem.attribute( u"pictureHeight"_s, u"10"_s ).toDouble();
797 mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( u"resizeMode"_s, u"0"_s ).toInt() );
798 //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
799 mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( u"anchorPoint"_s, QString::number( QgsLayoutItem::Middle ) ).toInt() );
800
801 mSvgFillColor = QgsColorUtils::colorFromString( itemElem.attribute( u"svgFillColor"_s, QgsColorUtils::colorToString( QColor( 255, 255, 255 ) ) ) );
802 mSvgStrokeColor = QgsColorUtils::colorFromString( itemElem.attribute( u"svgBorderColor"_s, QgsColorUtils::colorToString( QColor( 0, 0, 0 ) ) ) );
803 mSvgStrokeWidth = itemElem.attribute( u"svgBorderWidth"_s, u"0.2"_s ).toDouble();
804 mOriginalMode = static_cast< Qgis::PictureFormat >( itemElem.attribute( u"mode"_s, QString::number( static_cast< int >( Qgis::PictureFormat::Unknown ) ) ).toInt() );
805 mMode = mOriginalMode;
806
807 const QDomNodeList composerItemList = itemElem.elementsByTagName( u"ComposerItem"_s );
808 if ( !composerItemList.isEmpty() )
809 {
810 const QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
811
812 if ( !qgsDoubleNear( composerItemElem.attribute( u"rotation"_s, u"0"_s ).toDouble(), 0.0 ) )
813 {
814 //in versions prior to 2.1 picture rotation was stored in the rotation attribute
815 mPictureRotation = composerItemElem.attribute( u"rotation"_s, u"0"_s ).toDouble();
816 }
817 }
818
819 mDefaultSvgSize = QSize( 0, 0 );
820
821 if ( itemElem.hasAttribute( u"sourceExpression"_s ) )
822 {
823 //update pre 2.5 picture expression to use data defined expression
824 const QString sourceExpression = itemElem.attribute( u"sourceExpression"_s, QString() );
825 const QString useExpression = itemElem.attribute( u"useExpression"_s );
826 bool expressionActive;
827 expressionActive = ( useExpression.compare( "true"_L1, Qt::CaseInsensitive ) == 0 );
828
830 }
831
832 QString imagePath = itemElem.attribute( u"file"_s );
833
834 // convert from relative path to absolute. For SVG we also need to consider system SVG paths
835 const QgsPathResolver pathResolver = context.pathResolver();
836 if ( imagePath.endsWith( ".svg"_L1, Qt::CaseInsensitive ) )
837 imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
838 else
839 imagePath = pathResolver.readPath( imagePath );
840
841 mSourcePath = imagePath;
842
843 //picture rotation
844 if ( !qgsDoubleNear( itemElem.attribute( u"pictureRotation"_s, u"0"_s ).toDouble(), 0.0 ) )
845 {
846 mPictureRotation = itemElem.attribute( u"pictureRotation"_s, u"0"_s ).toDouble();
847 }
848
849 //rotation map
850 mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( u"northMode"_s, u"0"_s ).toInt() ) );
851 mNorthArrowHandler->setNorthOffset( itemElem.attribute( u"northOffset"_s, u"0"_s ).toDouble() );
852
853 mNorthArrowHandler->setLinkedMap( nullptr );
854 mRotationMapUuid = itemElem.attribute( u"mapUuid"_s );
855
856 return true;
857}
858
860{
861 return mNorthArrowHandler->linkedMap();
862}
863
865{
866 return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
867}
868
870{
871 mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( mode ) );
872}
873
875{
876 return mNorthArrowHandler->northOffset();
877}
878
880{
881 mNorthArrowHandler->setNorthOffset( offset );
882}
883
885{
886 mPictureAnchor = anchor;
887 update();
888}
889
890void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
891{
892 mSvgFillColor = color;
894}
895
897{
898 mSvgStrokeColor = color;
900}
901
903{
904 mSvgStrokeWidth = width;
906}
907
909{
910 if ( mOriginalMode == mode )
911 return;
912
913 mOriginalMode = mode;
914 mMode = mode;
916}
917
919{
920 if ( !mLayout || mRotationMapUuid.isEmpty() )
921 {
922 mNorthArrowHandler->setLinkedMap( nullptr );
923 }
924 else
925 {
926 mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
927 }
928
930}
@ Millimeters
Millimeters.
Definition qgis.h:5276
PictureFormat
Picture formats.
Definition qgis.h:5344
@ Raster
Raster image.
Definition qgis.h:5346
@ Unknown
Invalid or unknown image type.
Definition qgis.h:5347
@ SVG
SVG image.
Definition qgis.h:5345
@ Antialiasing
Use antialiasing when drawing items.
Definition qgis.h:5312
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsFeature feature() const
Convenience function for retrieving the feature for the context, if set.
bool isValid() const
Returns the validity of this feature.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
void setResizeMode(QgsLayoutItemPicture::ResizeMode mode)
Sets the resize mode used for drawing the picture within the item bounds.
void setMode(Qgis::PictureFormat mode)
Sets the current picture mode (image format).
void setSvgDynamicParameters(const QMap< QString, QgsProperty > &parameters)
Sets the SVG dynamic parameters.
QMap< QString, QgsProperty > svgDynamicParameters() const
Returns the SVG dynamic parameters.
QString picturePath() const
Returns the path of the source image.
void setPictureAnchor(QgsLayoutItem::ReferencePoint anchor)
Sets the picture's anchor point, which controls how it is placed within the picture item's frame.
void setNorthMode(NorthMode mode)
Sets the mode used to align the picture to a map's North.
double northOffset() const
Returns the offset added to the picture's rotation from a map's North.
QgsLayoutItemPicture(QgsLayout *layout)
Constructor for QgsLayoutItemPicture, with the specified parent layout.
void setLinkedMap(QgsLayoutItemMap *map)
Sets the map object for rotation.
void setPicturePath(const QString &path, Qgis::PictureFormat format=Qgis::PictureFormat::Unknown)
Sets the source path of the image (may be svg or a raster format).
void setPictureRotation(double rotation)
Sets the picture rotation within the item bounds, in degrees clockwise.
bool isMissingImage() const
Returns true if the source image is missing and the picture cannot be rendered.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
void setSvgStrokeWidth(double width)
Sets the stroke width (in layout units) used for parametrized SVG files.
QgsLayoutItemMap * linkedMap() const
Returns the linked rotation map, if set.
void pictureRotationChanged(double newRotation)
Emitted on picture rotation change.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
static QgsLayoutItemPicture * create(QgsLayout *layout)
Returns a new picture item for the specified layout.
Qgis::PictureFormat mode() const
Returns the current picture mode (image format), FormatUnknown if given picture format is unknown.
NorthMode
Method for syncing rotation to a map's North direction.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
QString evaluatedPath() const
Returns the current evaluated picture path, which includes the result of data defined path overrides.
QSizeF applyItemSizeConstraint(QSizeF targetSize) override
Applies any item-specific size constraint handling to a given targetSize in layout units.
void setSvgStrokeColor(const QColor &color)
Sets the stroke color used for parametrized SVG files.
int type() const override
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties) override
QIcon icon() const override
Returns the item's icon.
void refreshPicture(const QgsExpressionContext *context=nullptr)
Recalculates the source image (if using an expression for picture's source) and reloads and redraws t...
NorthMode northMode() const
Returns the mode used to align the picture to a map's North.
void setSvgFillColor(const QColor &color)
Sets the fill color used for parametrized SVG files.
void recalculateSize()
Forces a recalculation of the picture's frame size.
ResizeMode
Controls how pictures are scaled within the item's frame.
@ FrameToImageSize
Sets size of frame to match original size of image without scaling.
@ ZoomResizeFrame
Enlarges image to fit frame, then resizes frame to fit resultant image.
@ Clip
Draws image at original size and clips any portion which falls outside frame.
@ Stretch
Stretches image to fit frame, ignores aspect ratio.
@ Zoom
Enlarges image to fit frame while maintaining aspect ratio of picture.
void setNorthOffset(double offset)
Sets the offset added to the picture's rotation from a map's North.
Contains settings and helpers relating to a render of a QgsLayoutItem.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
friend class QgsLayout
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties)
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
QgsLayoutItem(QgsLayout *layout, bool manageZValue=true)
Constructor for QgsLayoutItem, with the specified parent layout.
ReferencePoint
Fixed position reference point.
@ LowerMiddle
Lower center of item.
@ MiddleLeft
Middle left of item.
@ Middle
Center of item.
@ UpperRight
Upper right corner of item.
@ LowerLeft
Lower left corner of item.
@ UpperLeft
Upper left corner of item.
@ UpperMiddle
Upper center of item.
@ MiddleRight
Middle right of item.
@ LowerRight
Lower right corner of item.
friend class QgsLayoutItemMap
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void sizePositionChanged()
Emitted when the item's size or position changes.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
An object which handles north-arrow type behavior for layout items.
void arrowRotationChanged(double newRotation)
Emitted on arrow rotation change.
NorthMode
Method for syncing rotation to a map's North direction.
QgsObjectCustomProperties mCustomProperties
Custom properties for object.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
DataDefinedProperty
Data defined properties for different item types.
@ PictureSvgBackgroundColor
SVG background color.
@ AllProperties
All properties for item.
void dpiChanged()
Emitted when the context's DPI is changed.
void changed()
Emitted certain settings in the context is changed, e.g.
Provides a method of storing sizes, consisting of a width and height, for use in QGIS layouts.
double height() const
Returns the height of the size.
double width() const
Returns the width of the size.
static QRectF largestRotatedRectWithinBounds(const QRectF &originalRect, const QRectF &boundsRect, double rotation)
Calculates the largest scaled version of originalRect which fits within boundsRect,...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
HTTP network content fetcher.
void fetchContent(const QUrl &url, const QString &authcfg=QString(), const QgsHttpHeaders &headers=QgsHttpHeaders())
Fetches content from a remote URL and handles redirects.
void finished()
Emitted when content has loaded.
QNetworkReply * reply()
Returns a reference to the network reply.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
A store for object properties.
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
static QVariantMap propertyMapToVariantMap(const QMap< QString, QgsProperty > &propertyMap)
Convert a map of QgsProperty to a map of QVariant This is useful to save a map of properties.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QMap< QString, QgsProperty > variantMapToPropertyMap(const QVariantMap &variantMap)
Convert a map of QVariant to a map of QgsProperty This is useful to restore a map of properties.
A container for the context for various read/write operations on objects.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >(), bool *isMissingImage=nullptr)
Gets the SVG content corresponding to the given path.
static QgsStringMap evaluatePropertiesMap(const QMap< QString, QgsProperty > &propertiesMap, const QgsExpressionContext &context)
Evaluates a map of properties using the given context and returns a variant map with evaluated expres...
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
QMap< QString, QString > QgsStringMap
Definition qgis.h:7413
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63