QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 <QSvgRenderer>
48#include <QUrl>
49
50#include "moc_qgslayoutitempicture.cpp"
51
54 , mNorthArrowHandler( new QgsLayoutNorthArrowHandler( this ) )
55{
56 //default to no background
57 setBackgroundEnabled( false );
58
59 //connect some signals
60
61 //connect to atlas feature changing
62 //to update the picture source expression
63 connect( &layout->reportContext(), &QgsLayoutReportContext::changed, this, [this] { refreshPicture(); } );
64
65 //connect to layout print resolution changing
67
68 connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItemPicture::shapeChanged );
69 connect( mNorthArrowHandler, &QgsLayoutNorthArrowHandler::arrowRotationChanged, this, &QgsLayoutItemPicture::updateNorthArrowRotation );
70}
71
76
78{
79 return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPicture.svg" ) );
80}
81
86
88{
89 QPainter *painter = context.renderContext().painter();
90 const QgsScopedQPainterState painterState( painter );
91 // painter is scaled to dots, so scale back to layout units
92 painter->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
93
94 const bool prevSmoothTransform = painter->testRenderHint( QPainter::RenderHint::SmoothPixmapTransform );
95 if ( mLayout->renderContext().testFlag( Qgis::LayoutRenderFlag::Antialiasing ) )
96 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, true );
97
98 //picture resizing
99 if ( mMode != Qgis::PictureFormat::Unknown )
100 {
101 double boundRectWidthMM;
102 double boundRectHeightMM;
103 QRect imageRect;
104 if ( mResizeMode == QgsLayoutItemPicture::Zoom || mResizeMode == QgsLayoutItemPicture::ZoomResizeFrame )
105 {
106 boundRectWidthMM = mPictureWidth;
107 boundRectHeightMM = mPictureHeight;
108 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
109 }
110 else if ( mResizeMode == QgsLayoutItemPicture::Stretch )
111 {
112 boundRectWidthMM = rect().width();
113 boundRectHeightMM = rect().height();
114 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
115 }
116 else if ( mResizeMode == QgsLayoutItemPicture::Clip )
117 {
118 boundRectWidthMM = rect().width();
119 boundRectHeightMM = rect().height();
120 const int imageRectWidthPixels = mImage.width();
121 const int imageRectHeightPixels = mImage.height();
122 imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
123 QSize( imageRectWidthPixels, imageRectHeightPixels ) );
124 }
125 else
126 {
127 boundRectWidthMM = rect().width();
128 boundRectHeightMM = rect().height();
129 imageRect = QRect( 0, 0, mLayout->convertFromLayoutUnits( rect().width(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4,
130 mLayout->convertFromLayoutUnits( rect().height(), Qgis::LayoutUnit::Millimeters ).length() * mLayout->renderContext().dpi() / 25.4 );
131 }
132
133 //zoom mode - calculate anchor point and rotation
134 if ( mResizeMode == Zoom )
135 {
136 //TODO - allow placement modes with rotation set. for now, setting a rotation
137 //always places picture in center of frame
138 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
139 {
140 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
141 painter->rotate( mPictureRotation );
142 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
143 }
144 else
145 {
146 //shift painter to edge/middle of frame depending on placement
147 const double diffX = rect().width() - boundRectWidthMM;
148 const double diffY = rect().height() - boundRectHeightMM;
149
150 double dX = 0;
151 double dY = 0;
152 switch ( mPictureAnchor )
153 {
154 case UpperLeft:
155 case MiddleLeft:
156 case LowerLeft:
157 //nothing to do
158 break;
159 case UpperMiddle:
160 case Middle:
161 case LowerMiddle:
162 dX = diffX / 2.0;
163 break;
164 case UpperRight:
165 case MiddleRight:
166 case LowerRight:
167 dX = diffX;
168 break;
169 }
170 switch ( mPictureAnchor )
171 {
172 case UpperLeft:
173 case UpperMiddle:
174 case UpperRight:
175 //nothing to do
176 break;
177 case MiddleLeft:
178 case Middle:
179 case MiddleRight:
180 dY = diffY / 2.0;
181 break;
182 case LowerLeft:
183 case LowerMiddle:
184 case LowerRight:
185 dY = diffY;
186 break;
187 }
188 painter->translate( dX, dY );
189 }
190 }
191 else if ( mResizeMode == ZoomResizeFrame )
192 {
193 if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
194 {
195 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
196 painter->rotate( mPictureRotation );
197 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
198 }
199 }
200
201 if ( mMode == Qgis::PictureFormat::SVG )
202 {
203 mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
204 }
205 else if ( mMode == Qgis::PictureFormat::Raster )
206 {
207 painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
208 }
209 }
210 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, prevSmoothTransform );
211}
212
213QSizeF QgsLayoutItemPicture::applyItemSizeConstraint( const QSizeF targetSize )
214{
215 const QSizeF currentPictureSize = pictureSize();
216 QSizeF newSize = targetSize;
217 if ( mResizeMode == QgsLayoutItemPicture::Clip )
218 {
219 mPictureWidth = targetSize.width();
220 mPictureHeight = targetSize.height();
221 }
222 else
223 {
224 if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
225 {
226 QSizeF targetImageSize;
227 if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
228 {
229 targetImageSize = currentPictureSize;
230 }
231 else
232 {
233 //calculate aspect ratio of bounds of rotated image
234 QTransform tr;
235 tr.rotate( mPictureRotation );
236 const QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
237 targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
238 }
239
240 //if height has changed more than width, then fix width and set height correspondingly
241 //else, do the opposite
242 if ( std::fabs( rect().width() - targetSize.width() ) <
243 std::fabs( rect().height() - targetSize.height() ) )
244 {
245 newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
246 }
247 else
248 {
249 newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
250 }
251 }
252 else if ( mResizeMode == FrameToImageSize )
253 {
254 if ( !( currentPictureSize.isEmpty() ) )
255 {
256 const QgsLayoutSize sizeMM = mLayout->convertFromLayoutUnits( currentPictureSize, Qgis::LayoutUnit::Millimeters );
257 newSize.setWidth( sizeMM.width() * 25.4 / mLayout->renderContext().dpi() );
258 newSize.setHeight( sizeMM.height() * 25.4 / mLayout->renderContext().dpi() );
259 }
260 }
261
262 //find largest scaling of picture with this rotation which fits in item
263 if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
264 {
265 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ),
266 QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
267 mPictureWidth = rotatedImageRect.width();
268 mPictureHeight = rotatedImageRect.height();
269 }
270 else
271 {
272 mPictureWidth = newSize.width();
273 mPictureHeight = newSize.height();
274 }
275
276 if ( newSize != targetSize )
277 {
278 emit changed();
279 }
280 }
281
282 return newSize;
283}
284
285QRect QgsLayoutItemPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
286{
287 const int boundRectWidthPixels = boundRectWidthMM * mLayout->renderContext().dpi() / 25.4;
288 const int boundRectHeightPixels = boundRectHeightMM * mLayout->renderContext().dpi() / 25.4;
289
290 //update boundRectWidth/Height so that they exactly match pixel bounds
291 boundRectWidthMM = boundRectWidthPixels * 25.4 / mLayout->renderContext().dpi();
292 boundRectHeightMM = boundRectHeightPixels * 25.4 / mLayout->renderContext().dpi();
293
294 //calculate part of image which fits in bounds
295 int leftClip = 0;
296 int topClip = 0;
297
298 //calculate left crop
299 switch ( mPictureAnchor )
300 {
301 case UpperLeft:
302 case MiddleLeft:
303 case LowerLeft:
304 leftClip = 0;
305 break;
306 case UpperMiddle:
307 case Middle:
308 case LowerMiddle:
309 leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
310 break;
311 case UpperRight:
312 case MiddleRight:
313 case LowerRight:
314 leftClip = imageRectPixels.width() - boundRectWidthPixels;
315 break;
316 }
317
318 //calculate top crop
319 switch ( mPictureAnchor )
320 {
321 case UpperLeft:
322 case UpperMiddle:
323 case UpperRight:
324 topClip = 0;
325 break;
326 case MiddleLeft:
327 case Middle:
328 case MiddleRight:
329 topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
330 break;
331 case LowerLeft:
332 case LowerMiddle:
333 case LowerRight:
334 topClip = imageRectPixels.height() - boundRectHeightPixels;
335 break;
336 }
337
338 return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
339}
340
342{
343 const QgsExpressionContext scopedContext = createExpressionContext();
344 const QgsExpressionContext *evalContext = context ? context : &scopedContext;
345
346 mDataDefinedProperties.prepare( *evalContext );
347
348 QVariant source( mSourcePath );
349
350 //data defined source set?
351 mHasExpressionError = false;
353 {
355 bool ok = false;
357 source = sourceProperty.value( *evalContext, source, &ok );
358 if ( !ok || !source.canConvert( QMetaType::QString ) )
359 {
360 mHasExpressionError = true;
361 source = QString();
362 if ( scopedContext.feature().isValid() )
363 {
364 QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( tr( "Picture expression eval error" ), sourceProperty.asExpression() ) );
365 }
366 }
367 else if ( source.userType() != QMetaType::Type::QByteArray )
368 {
369 source = source.toString().trimmed();
370 QgsDebugMsgLevel( QStringLiteral( "exprVal PictureSource:%1" ).arg( source.toString() ), 2 );
371 }
372 }
373
374 loadPicture( source );
375}
376
377void QgsLayoutItemPicture::loadRemotePicture( const QString &url )
378{
379 //remote location
380
382 QEventLoop loop;
383 connect( &fetcher, &QgsNetworkContentFetcher::finished, &loop, &QEventLoop::quit );
384 fetcher.fetchContent( QUrl( url ) );
385
386 //wait until picture fetched
387 loop.exec( QEventLoop::ExcludeUserInputEvents );
388
389 QNetworkReply *reply = fetcher.reply();
390 if ( reply )
391 {
392 QImageReader imageReader( reply );
393 imageReader.setAutoTransform( true );
394
395 if ( imageReader.format() == "pdf" )
396 {
397 // special handling for this format -- we need to pass the desired target size onto the image reader
398 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
399 // a very low resolution image (the driver assumes points == pixels!)
400 // For other image formats, we read the original image size only and defer resampling to later in this
401 // function. That gives us more control over the resampling method used.
402
403 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
404 const QSize sizeAt72Dpi = imageReader.size();
405 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
406 imageReader.setScaledSize( sizeAtTargetDpi );
407 }
408
409 mImage = imageReader.read();
411 }
412 else
413 {
415 }
416}
417
418void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
419{
420 QFile pic;
421 pic.setFileName( path );
422
423 if ( !pic.exists() )
424 {
426 }
427 else
428 {
429 const QFileInfo sourceFileInfo( pic );
430 const QString sourceFileSuffix = sourceFileInfo.suffix();
431 if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
432 {
433 //try to open svg
434 const QgsExpressionContext context = createExpressionContext();
435 const QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgBackgroundColor, context, mSvgFillColor );
436 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
437 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
438 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
439
440 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
441 1.0, 0, false, evaluatedParameters );
442 mSVG.load( svgContent );
443 if ( mSVG.isValid() )
444 {
446 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
447 mDefaultSvgSize.setWidth( viewBox.width() );
448 mDefaultSvgSize.setHeight( viewBox.height() );
449 }
450 else
451 {
453 }
454 }
455 else
456 {
457 //try to open raster with QImageReader
458 QImageReader imageReader( pic.fileName() );
459 imageReader.setAutoTransform( true );
460
461 if ( imageReader.format() == "pdf" )
462 {
463 // special handling for this format -- we need to pass the desired target size onto the image reader
464 // so that it can correctly render the (vector) pdf content at the desired dpi. Otherwise it returns
465 // a very low resolution image (the driver assumes points == pixels!)
466 // For other image formats, we read the original image size only and defer resampling to later in this
467 // function. That gives us more control over the resampling method used.
468
469 // driver assumes points == pixels, so driver image size is reported assuming 72 dpi.
470 const QSize sizeAt72Dpi = imageReader.size();
471 const QSize sizeAtTargetDpi = sizeAt72Dpi * mLayout->renderContext().dpi() / 72;
472 imageReader.setScaledSize( sizeAtTargetDpi );
473 }
474
475 if ( imageReader.read( &mImage ) )
476 {
478 }
479 else
480 {
482 }
483 }
484 }
485}
486
487void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
488{
489 if ( path.isEmpty() )
490 {
491 mImage = QImage();
492 mSVG.load( QByteArray() );
493 return;
494 }
495
496 switch ( mMode )
497 {
499 break;
500
502 {
503 bool fitsInCache = false;
504 bool isMissing = false;
505 mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true, mLayout->renderContext().dpi(), -1, &isMissing );
506 if ( mImage.isNull() || isMissing )
508 break;
509 }
510
512 {
513 const QgsExpressionContext context = createExpressionContext();
514 const QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgBackgroundColor, context, mSvgFillColor );
515 const QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeColor, context, mSvgStrokeColor );
516 const double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::DataDefinedProperty::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
517
518 const QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );
519
520 bool isMissingImage = false;
521 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
522 1.0, 0, false, evaluatedParameters, &isMissingImage );
523 mSVG.load( svgContent );
524 if ( mSVG.isValid() && !isMissingImage )
525 {
527 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
528 mDefaultSvgSize.setWidth( viewBox.width() );
529 mDefaultSvgSize.setHeight( viewBox.height() );
530 }
531 else
532 {
534 }
535 break;
536 }
537 }
538}
539
540void QgsLayoutItemPicture::updateNorthArrowRotation( double rotation )
541{
542 setPictureRotation( rotation );
543 emit pictureRotationChanged( rotation );
544}
545
546void QgsLayoutItemPicture::loadPicture( const QVariant &data )
547{
548 mIsMissingImage = false;
549 QVariant imageData( data );
550 mEvaluatedPath = data.toString();
551
552 if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == Qgis::PictureFormat::Unknown )
553 {
554 const QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
555 imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
556 }
557
558 if ( imageData.userType() == QMetaType::Type::QByteArray )
559 {
560 if ( mImage.loadFromData( imageData.toByteArray() ) )
561 {
563 }
564 }
565 else if ( mMode == Qgis::PictureFormat::Unknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
566 {
567 //remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
568 loadRemotePicture( mEvaluatedPath );
569 }
570 else if ( mMode == Qgis::PictureFormat::Unknown )
571 {
572 //local location - for old API/project compatibility only!!
573 loadLocalPicture( mEvaluatedPath );
574 }
575 else
576 {
577 loadPictureUsingCache( mEvaluatedPath );
578 }
579
580 mLoaded = false;
581 if ( mMode != Qgis::PictureFormat::Unknown ) //make sure we start with a new QImage
582 {
584 mLoaded = true;
585 }
586 else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
587 {
588 //trying to load an invalid file or bad expression, show cross picture
589 mIsMissingImage = true;
590 if ( mOriginalMode == Qgis::PictureFormat::Raster )
591 {
592 const QString badFile( QStringLiteral( ":/images/composer/missing_image.png" ) );
593 QImageReader imageReader( badFile );
594 if ( imageReader.read( &mImage ) )
596 }
597 else if ( mOriginalMode == Qgis::PictureFormat::SVG )
598 {
599 const QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
600 mSVG.load( badFile );
601 if ( mSVG.isValid() )
602 {
604 const QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
605 mDefaultSvgSize.setWidth( viewBox.width() );
606 mDefaultSvgSize.setHeight( viewBox.height() );
607 }
608 }
610 }
611
612 update();
613 emit changed();
614}
615
616QSizeF QgsLayoutItemPicture::pictureSize()
617{
618 if ( mMode == Qgis::PictureFormat::SVG )
619 {
620 return mDefaultSvgSize;
621 }
622 else if ( mMode == Qgis::PictureFormat::Raster )
623 {
624 return QSizeF( mImage.width(), mImage.height() );
625 }
626 else
627 {
628 return QSizeF( 0, 0 );
629 }
630}
631
633{
634 return mIsMissingImage;
635}
636
638{
639 return mEvaluatedPath;
640}
641
642QMap<QString, QgsProperty> QgsLayoutItemPicture::svgDynamicParameters() const
643{
644 const QVariantMap parameters = mCustomProperties.value( QStringLiteral( "svg-dynamic-parameters" ), QVariantMap() ).toMap();
645 return QgsProperty::variantMapToPropertyMap( parameters );
646}
647
648void QgsLayoutItemPicture::setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters )
649{
650 const QVariantMap variantMap = QgsProperty::propertyMapToVariantMap( parameters );
651 mCustomProperties.setValue( QStringLiteral( "svg-dynamic-parameters" ), variantMap );
653}
654
655void QgsLayoutItemPicture::shapeChanged()
656{
657 if ( mMode == Qgis::PictureFormat::SVG && !mLoadingSvg )
658 {
659 mLoadingSvg = true;
661 mLoadingSvg = false;
662 }
663}
664
666{
667 const double oldRotation = mPictureRotation;
668 mPictureRotation = rotation;
669 const QSizeF currentPictureSize = pictureSize();
670
671 // If the picture is not loaded yet, do not compute its rotated size
672 if ( !mLoaded || currentPictureSize == QSizeF( 0, 0 ) )
673 return;
674
675 if ( mResizeMode == Zoom )
676 {
677 //find largest scaling of picture with this rotation which fits in item
678 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
679 mPictureWidth = rotatedImageRect.width();
680 mPictureHeight = rotatedImageRect.height();
681 update();
682 }
683 else if ( mResizeMode == ZoomResizeFrame )
684 {
685 const QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
686
687 //calculate actual size of image inside frame
688 const QRectF rotatedImageRect = QgsLayoutUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
689
690 //rotate image rect by new rotation and get bounding box
691 QTransform tr;
692 tr.rotate( mPictureRotation );
693 QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
694
695 //keep the center in the same location
696 newRect.moveCenter( oldRect.center() );
697 attemptSetSceneRect( newRect );
698 emit changed();
699 }
700
701 emit pictureRotationChanged( mPictureRotation );
702}
703
705{
706 mNorthArrowHandler->setLinkedMap( map );
707}
708
710{
711 mResizeMode = mode;
713 || ( mode == QgsLayoutItemPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
714 {
715 //call set scene rect to force item to resize to fit picture
717 }
718 update();
719}
720
722{
723 //call set scene rect with current position/size, as this will trigger the
724 //picture item to recalculate its frame and image size
725 attemptSetSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
726}
727
740
742{
743 mOriginalMode = format;
744 mMode = format;
745 mSourcePath = path;
747}
748
750{
751 return mSourcePath;
752}
753
754bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocument &, const QgsReadWriteContext &context ) const
755{
756 QString imagePath = mSourcePath;
757
758 // convert from absolute path to relative. For SVG we also need to consider system SVG paths
759 const QgsPathResolver pathResolver = context.pathResolver();
760 if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
761 imagePath = QgsSymbolLayerUtils::svgSymbolPathToName( imagePath, pathResolver );
762 else
763 imagePath = pathResolver.writePath( imagePath );
764
765 elem.setAttribute( QStringLiteral( "file" ), imagePath );
766 elem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
767 elem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
768 elem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
769 elem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
770 elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsColorUtils::colorToString( mSvgFillColor ) );
771 elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsColorUtils::colorToString( mSvgStrokeColor ) );
772 elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
773 elem.setAttribute( QStringLiteral( "mode" ), static_cast< int >( mOriginalMode ) );
774
775 //rotation
776 elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
777 if ( !mNorthArrowHandler->linkedMap() )
778 {
779 elem.setAttribute( QStringLiteral( "mapUuid" ), QString() );
780 }
781 else
782 {
783 elem.setAttribute( QStringLiteral( "mapUuid" ), mNorthArrowHandler->linkedMap()->uuid() );
784 }
785 elem.setAttribute( QStringLiteral( "northMode" ), mNorthArrowHandler->northMode() );
786 elem.setAttribute( QStringLiteral( "northOffset" ), mNorthArrowHandler->northOffset() );
787 return true;
788}
789
790bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext &context )
791{
792 mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
793 mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
794 mResizeMode = QgsLayoutItemPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
795 //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
796 mPictureAnchor = static_cast< QgsLayoutItem::ReferencePoint >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsLayoutItem::Middle ) ).toInt() );
797
798 mSvgFillColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsColorUtils::colorToString( QColor( 255, 255, 255 ) ) ) );
799 mSvgStrokeColor = QgsColorUtils::colorFromString( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsColorUtils::colorToString( QColor( 0, 0, 0 ) ) ) );
800 mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
801 mOriginalMode = static_cast< Qgis::PictureFormat >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( static_cast< int >( Qgis::PictureFormat::Unknown ) ) ).toInt() );
802 mMode = mOriginalMode;
803
804 const QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
805 if ( !composerItemList.isEmpty() )
806 {
807 const QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
808
809 if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
810 {
811 //in versions prior to 2.1 picture rotation was stored in the rotation attribute
812 mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
813 }
814 }
815
816 mDefaultSvgSize = QSize( 0, 0 );
817
818 if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
819 {
820 //update pre 2.5 picture expression to use data defined expression
821 const QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QString() );
822 const QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
823 bool expressionActive;
824 expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
825
827 }
828
829 QString imagePath = itemElem.attribute( QStringLiteral( "file" ) );
830
831 // convert from relative path to absolute. For SVG we also need to consider system SVG paths
832 const QgsPathResolver pathResolver = context.pathResolver();
833 if ( imagePath.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
834 imagePath = QgsSymbolLayerUtils::svgSymbolNameToPath( imagePath, pathResolver );
835 else
836 imagePath = pathResolver.readPath( imagePath );
837
838 mSourcePath = imagePath;
839
840 //picture rotation
841 if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
842 {
843 mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
844 }
845
846 //rotation map
847 mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() ) );
848 mNorthArrowHandler->setNorthOffset( itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble() );
849
850 mNorthArrowHandler->setLinkedMap( nullptr );
851 mRotationMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
852
853 return true;
854}
855
857{
858 return mNorthArrowHandler->linkedMap();
859}
860
862{
863 return static_cast< QgsLayoutItemPicture::NorthMode >( mNorthArrowHandler->northMode() );
864}
865
867{
868 mNorthArrowHandler->setNorthMode( static_cast< QgsLayoutNorthArrowHandler::NorthMode >( mode ) );
869}
870
872{
873 return mNorthArrowHandler->northOffset();
874}
875
877{
878 mNorthArrowHandler->setNorthOffset( offset );
879}
880
882{
883 mPictureAnchor = anchor;
884 update();
885}
886
887void QgsLayoutItemPicture::setSvgFillColor( const QColor &color )
888{
889 mSvgFillColor = color;
891}
892
894{
895 mSvgStrokeColor = color;
897}
898
900{
901 mSvgStrokeWidth = width;
903}
904
906{
907 if ( mOriginalMode == mode )
908 return;
909
910 mOriginalMode = mode;
911 mMode = mode;
913}
914
916{
917 if ( !mLayout || mRotationMapUuid.isEmpty() )
918 {
919 mNorthArrowHandler->setLinkedMap( nullptr );
920 }
921 else
922 {
923 mNorthArrowHandler->setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mRotationMapUuid, true ) ) );
924 }
925
927}
@ Millimeters
Millimeters.
Definition qgis.h:5204
PictureFormat
Picture formats.
Definition qgis.h:5272
@ Raster
Raster image.
Definition qgis.h:5274
@ Unknown
Invalid or unknown image type.
Definition qgis.h:5275
@ SVG
SVG image.
Definition qgis.h:5273
@ Antialiasing
Use antialiasing when drawing items.
Definition qgis.h:5240
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 finished()
Emitted when content has loaded.
QNetworkReply * reply()
Returns a reference to the network reply.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
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:6607
QMap< QString, QString > QgsStringMap
Definition qgis.h:7132
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61