39 #include <QDomDocument> 40 #include <QDomElement> 42 #include <QImageReader> 44 #include <QSvgRenderer> 45 #include <QNetworkRequest> 46 #include <QNetworkReply> 48 #include <QCoreApplication> 93 double boundRectWidthMM;
94 double boundRectHeightMM;
98 boundRectWidthMM = mPictureWidth;
99 boundRectHeightMM = mPictureHeight;
100 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
104 boundRectWidthMM = rect().width();
105 boundRectHeightMM = rect().height();
106 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
110 boundRectWidthMM = rect().width();
111 boundRectHeightMM = rect().height();
112 int imageRectWidthPixels = mImage.width();
113 int imageRectHeightPixels = mImage.height();
114 imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
115 QSize( imageRectWidthPixels, imageRectHeightPixels ) );
119 boundRectWidthMM = rect().width();
120 boundRectHeightMM = rect().height();
126 if ( mResizeMode ==
Zoom )
132 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
133 painter->rotate( mPictureRotation );
134 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
139 double diffX = rect().width() - boundRectWidthMM;
140 double diffY = rect().height() - boundRectHeightMM;
144 switch ( mPictureAnchor )
162 switch ( mPictureAnchor )
180 painter->translate( dX, dY );
187 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
188 painter->rotate( mPictureRotation );
189 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
195 mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
199 painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
208 QSizeF currentPictureSize = pictureSize();
209 QSizeF newSize = targetSize;
212 mPictureWidth = targetSize.width();
213 mPictureHeight = targetSize.height();
217 if ( mResizeMode ==
ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
219 QSizeF targetImageSize;
222 targetImageSize = currentPictureSize;
228 tr.rotate( mPictureRotation );
229 QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
230 targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
235 if ( std::fabs( rect().width() - targetSize.width() ) <
236 std::fabs( rect().height() - targetSize.height() ) )
238 newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
242 newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
247 if ( !( currentPictureSize.isEmpty() ) )
250 newSize.setWidth( sizeMM.
width() * 25.4 /
mLayout->renderContext().dpi() );
251 newSize.setHeight( sizeMM.
height() * 25.4 /
mLayout->renderContext().dpi() );
259 QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
260 mPictureWidth = rotatedImageRect.width();
261 mPictureHeight = rotatedImageRect.height();
265 mPictureWidth = newSize.width();
266 mPictureHeight = newSize.height();
269 if ( newSize != targetSize )
278 QRect QgsLayoutItemPicture::clippedImageRect(
double &boundRectWidthMM,
double &boundRectHeightMM, QSize imageRectPixels )
280 int boundRectWidthPixels = boundRectWidthMM *
mLayout->renderContext().dpi() / 25.4;
281 int boundRectHeightPixels = boundRectHeightMM *
mLayout->renderContext().dpi() / 25.4;
284 boundRectWidthMM = boundRectWidthPixels * 25.4 /
mLayout->renderContext().dpi();
285 boundRectHeightMM = boundRectHeightPixels * 25.4 /
mLayout->renderContext().dpi();
292 switch ( mPictureAnchor )
302 leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
307 leftClip = imageRectPixels.width() - boundRectWidthPixels;
312 switch ( mPictureAnchor )
322 topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
327 topClip = imageRectPixels.height() - boundRectHeightPixels;
331 return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
341 QVariant source( mSourcePath );
344 mHasExpressionError =
false;
349 source = sourceProperty.
value( *evalContext, source, &ok );
350 if ( !ok || !source.canConvert( QMetaType::QString ) )
352 mHasExpressionError =
true;
356 else if ( source.type() != QVariant::ByteArray )
358 source = source.toString().trimmed();
359 QgsDebugMsg( QStringLiteral(
"exprVal PictureSource:%1" ).arg( source.toString() ) );
363 loadPicture( source );
366 void QgsLayoutItemPicture::loadRemotePicture(
const QString &url )
376 loop.exec( QEventLoop::ExcludeUserInputEvents );
378 QNetworkReply *reply = fetcher.
reply();
381 QImageReader imageReader( reply );
382 mImage = imageReader.read();
391 void QgsLayoutItemPicture::loadLocalPicture(
const QString &path )
394 pic.setFileName( path );
402 QFileInfo sourceFileInfo( pic );
403 QString sourceFileSuffix = sourceFileInfo.suffix();
404 if ( sourceFileSuffix.compare( QLatin1String(
"svg" ), Qt::CaseInsensitive ) == 0 )
413 mSVG.load( svgContent );
414 if ( mSVG.isValid() )
417 QRect viewBox = mSVG.viewBox();
418 mDefaultSvgSize.setWidth( viewBox.width() );
419 mDefaultSvgSize.setHeight( viewBox.height() );
429 QImageReader imageReader( pic.fileName() );
430 if ( imageReader.read( &mImage ) )
451 void QgsLayoutItemPicture::updateMapRotation()
457 double rotation = mRotationMap->mapRotation();
460 switch ( mNorthMode )
467 QgsPointXY center = mRotationMap->extent().center();
479 QgsDebugMsg( QStringLiteral(
"Caught exception %1" ).arg( e.
what() ) );
485 rotation += mNorthOffset;
489 void QgsLayoutItemPicture::loadPicture(
const QVariant &data )
491 mIsMissingImage =
false;
492 QVariant imageData( data );
493 mEvaluatedPath = data.toString();
495 if ( mEvaluatedPath.startsWith( QLatin1String(
"base64:" ), Qt::CaseInsensitive ) )
497 QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit();
498 imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
501 if ( imageData.type() == QVariant::ByteArray )
503 if ( mImage.loadFromData( imageData.toByteArray() ) )
508 else if ( mEvaluatedPath.startsWith( QLatin1String(
"http" ) ) )
511 loadRemotePicture( mEvaluatedPath );
516 loadLocalPicture( mEvaluatedPath );
522 else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
526 mIsMissingImage =
true;
527 QString badFile( QStringLiteral(
":/images/composer/missing_image.svg" ) );
528 mSVG.load( badFile );
529 if ( mSVG.isValid() )
532 QRect viewBox = mSVG.viewBox();
533 mDefaultSvgSize.setWidth( viewBox.width() );
534 mDefaultSvgSize.setHeight( viewBox.height() );
543 QRectF QgsLayoutItemPicture::boundedImageRect(
double deviceWidth,
double deviceHeight )
545 double imageToDeviceRatio;
546 if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
548 imageToDeviceRatio = deviceWidth / mImage.width();
549 double height = imageToDeviceRatio * mImage.height();
550 return QRectF( 0, 0, deviceWidth, height );
554 imageToDeviceRatio = deviceHeight / mImage.height();
555 double width = imageToDeviceRatio * mImage.width();
556 return QRectF( 0, 0, width, deviceHeight );
560 QRectF QgsLayoutItemPicture::boundedSVGRect(
double deviceWidth,
double deviceHeight )
562 double imageToSvgRatio;
563 if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
565 imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
566 double width = mDefaultSvgSize.width() * imageToSvgRatio;
567 return QRectF( 0, 0, width, deviceHeight );
571 imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
572 double height = mDefaultSvgSize.height() * imageToSvgRatio;
573 return QRectF( 0, 0, deviceWidth, height );
577 QSizeF QgsLayoutItemPicture::pictureSize()
581 return mDefaultSvgSize;
585 return QSizeF( mImage.width(), mImage.height() );
589 return QSizeF( 0, 0 );
595 return mIsMissingImage;
600 return mEvaluatedPath;
603 void QgsLayoutItemPicture::shapeChanged()
605 if ( mMode ==
FormatSVG && !mLoadingSvg )
615 double oldRotation = mPictureRotation;
616 mPictureRotation = rotation;
618 if ( mResizeMode ==
Zoom )
621 QSizeF currentPictureSize = pictureSize();
623 mPictureWidth = rotatedImageRect.width();
624 mPictureHeight = rotatedImageRect.height();
629 QSizeF currentPictureSize = pictureSize();
630 QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
637 tr.rotate( mPictureRotation );
638 QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
641 newRect.moveCenter( oldRect.center() );
653 disconnectMap( mRotationMap );
658 mRotationMap =
nullptr;
716 QString imagePath = mSourcePath;
720 if ( imagePath.endsWith( QLatin1String(
".svg" ), Qt::CaseInsensitive ) )
723 imagePath = pathResolver.
writePath( imagePath );
725 elem.setAttribute( QStringLiteral(
"file" ), imagePath );
726 elem.setAttribute( QStringLiteral(
"pictureWidth" ), QString::number( mPictureWidth ) );
727 elem.setAttribute( QStringLiteral(
"pictureHeight" ), QString::number( mPictureHeight ) );
728 elem.setAttribute( QStringLiteral(
"resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
729 elem.setAttribute( QStringLiteral(
"anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
732 elem.setAttribute( QStringLiteral(
"svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
735 elem.setAttribute( QStringLiteral(
"pictureRotation" ), QString::number( mPictureRotation ) );
738 elem.setAttribute( QStringLiteral(
"mapUuid" ), QString() );
742 elem.setAttribute( QStringLiteral(
"mapUuid" ), mRotationMap->uuid() );
744 elem.setAttribute( QStringLiteral(
"northMode" ), mNorthMode );
745 elem.setAttribute( QStringLiteral(
"northOffset" ), mNorthOffset );
751 mPictureWidth = itemElem.attribute( QStringLiteral(
"pictureWidth" ), QStringLiteral(
"10" ) ).toDouble();
752 mPictureHeight = itemElem.attribute( QStringLiteral(
"pictureHeight" ), QStringLiteral(
"10" ) ).toDouble();
759 mSvgStrokeWidth = itemElem.attribute( QStringLiteral(
"svgBorderWidth" ), QStringLiteral(
"0.2" ) ).toDouble();
761 QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral(
"ComposerItem" ) );
762 if ( !composerItemList.isEmpty() )
764 QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
766 if ( !
qgsDoubleNear( composerItemElem.attribute( QStringLiteral(
"rotation" ), QStringLiteral(
"0" ) ).toDouble(), 0.0 ) )
769 mPictureRotation = composerItemElem.attribute( QStringLiteral(
"rotation" ), QStringLiteral(
"0" ) ).toDouble();
773 mDefaultSvgSize = QSize( 0, 0 );
775 if ( itemElem.hasAttribute( QStringLiteral(
"sourceExpression" ) ) )
778 QString sourceExpression = itemElem.attribute( QStringLiteral(
"sourceExpression" ), QString() );
779 QString useExpression = itemElem.attribute( QStringLiteral(
"useExpression" ) );
780 bool expressionActive;
781 expressionActive = ( useExpression.compare( QLatin1String(
"true" ), Qt::CaseInsensitive ) == 0 );
786 QString imagePath = itemElem.attribute( QStringLiteral(
"file" ) );
790 if ( imagePath.endsWith( QLatin1String(
".svg" ), Qt::CaseInsensitive ) )
793 imagePath = pathResolver.
readPath( imagePath );
795 mSourcePath = imagePath;
798 if ( !
qgsDoubleNear( itemElem.attribute( QStringLiteral(
"pictureRotation" ), QStringLiteral(
"0" ) ).toDouble(), 0.0 ) )
800 mPictureRotation = itemElem.attribute( QStringLiteral(
"pictureRotation" ), QStringLiteral(
"0" ) ).toDouble();
804 mNorthMode =
static_cast< NorthMode >( itemElem.attribute( QStringLiteral(
"northMode" ), QStringLiteral(
"0" ) ).toInt() );
805 mNorthOffset = itemElem.attribute( QStringLiteral(
"northOffset" ), QStringLiteral(
"0" ) ).toDouble();
807 disconnectMap( mRotationMap );
808 mRotationMap =
nullptr;
809 mRotationMapUuid = itemElem.attribute( QStringLiteral(
"mapUuid" ) );
827 mNorthOffset = offset;
833 mPictureAnchor = anchor;
839 mSvgFillColor = color;
845 mSvgStrokeColor = color;
851 mSvgStrokeWidth = width;
857 if ( !
mLayout || mRotationMapUuid.isEmpty() )
859 mRotationMap =
nullptr;
865 disconnectMap( mRotationMap );
867 if ( ( mRotationMap = qobject_cast< QgsLayoutItemMap * >(
mLayout->itemByUuid( mRotationMapUuid,
true ) ) ) )
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
The class is used as a container of context for various read/write operations on other objects...
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color...
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QSizeF applyItemSizeConstraint(QSizeF targetSize) override
Applies any item-specific size constraint handling to a given targetSize in layout units...
QString picturePath() const
Returns the path of the source image.
static double bearingTrueNorth(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext, const QgsPointXY &point)
Returns the direction to true north from a specified point and for a specified coordinate reference s...
Base class for graphical items within a QgsLayout.
Lower left corner of item.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double...
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
void finalizeRestoreFromXml() override
Called after all pending items have been restored from XML.
A class to represent a 2D point.
void changed()
Emitted certain settings in the context is changed, e.g.
void extentChanged()
Emitted when the map's extent changes.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Stretches image to fit frame, ignores aspect ratio.
int type() const override
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
A layout item subclass that displays SVG files or raster format images (jpg, png, ...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
void setNorthOffset(double offset)
Sets the offset added to the picture's rotation from a map's North.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Format mode() const
Returns the current picture mode (image format).
void setSvgFillColor(const QColor &color)
Sets the fill color used for parametrized SVG files.
QNetworkReply * reply()
Returns a reference to the network reply.
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 QgsLayoutItemPicture * create(QgsLayout *layout)
Returns a new picture item for the specified layout.
QString evaluatedPath() const
Returns the current evaluated picture path, which includes the result of data defined path overrides...
void setPictureAnchor(QgsLayoutItem::ReferencePoint anchor)
Sets the picture's anchor point, which controls how it is placed within the picture item's frame...
Lower right corner of item.
const QgsCoordinateReferenceSystem & crs
QgsLayoutItemPicture(QgsLayout *layout)
Constructor for QgsLayoutItemPicture, with the specified parent layout.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false)
Gets SVG content.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties)
Refreshes a data defined property for the item by reevaluating the property's value and redrawing the...
void setPictureRotation(double rotation)
Sets the picture rotation within the item bounds, in degrees clockwise.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
void setSvgStrokeColor(const QColor &color)
Sets the stroke color used for parametrized SVG files.
void dpiChanged()
Emitted when the context's DPI is changed.
static QString encodeColor(const QColor &color)
ReferencePoint
Fixed position reference point.
void sizePositionChanged()
Emitted when the item's size or position changes.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
Invalid or unknown image type.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const override
Prepares the collection against a specified expression context.
Layout graphical items for displaying a map.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
Upper right corner of item.
HTTP network content fetcher.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
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...
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QPointer< QgsLayout > mLayout
void setLinkedMap(QgsLayoutItemMap *map)
Sets the map object for rotation.
void finished()
Emitted when content has loaded.
void pictureRotationChanged(double newRotation)
Emitted on picture rotation change.
void setPicturePath(const QString &path)
Sets the source path of the image (may be svg or a raster format).
Upper left corner of item.
Contains information about the context in which a coordinate transform is executed.
A store for object properties.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
void mapRotationChanged(double newRotation)
Emitted when the map's rotation changes.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
void recalculateSize()
Forces a recalculation of the picture's frame size.
void setSvgStrokeWidth(double width)
Sets the stroke width (in layout units) used for parametrized SVG files.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Enlarges image to fit frame, then resizes frame to fit resultant image.
QgsLayoutReportContext & reportContext()
Returns a reference to the layout's report context, which stores information relating to the current ...
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
NorthMode
Method for syncing rotation to a map's North direction.
QPainter * painter()
Returns the destination QPainter for the render operation.
bool isMissingImage() const
Returns true if the source image is missing and the picture cannot be rendered.
static QRectF largestRotatedRectWithinBounds(const QRectF &originalRect, const QRectF &boundsRect, double rotation)
Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by the a specified rotation amount.
ResizeMode
Controls how pictures are scaled within the item's frame.
void setResizeMode(QgsLayoutItemPicture::ResizeMode mode)
Sets the resize mode used for drawing the picture within the item bounds.
This class represents a coordinate reference system (CRS).
Sets size of frame to match original size of image without scaling.
Draws image at original size and clips any portion which falls outside frame.
void setNorthMode(NorthMode mode)
Sets the mode used to align the picture to a map's North.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
void refreshPicture(const QgsExpressionContext *context=nullptr)
Recalculates the source image (if using an expression for picture's source) and reloads and redraws t...
QIcon icon() const override
Returns the item's icon.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
Enlarges image to fit frame while maintaining aspect ratio of picture.
Resolves relative paths into absolute paths and vice versa.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
void changed()
Emitted when the object's properties change.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double height() const
Returns the height of the size.
QgsLayoutItemMap * linkedMap() const
Returns the linked rotation map, if set.
DataDefinedProperty
Data defined properties for different item types.
Defines a QGIS exception class.
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
static QColor decodeColor(const QString &str)
double width() const
Returns the width of the size.