41 #include <QDomDocument>
42 #include <QDomElement>
44 #include <QImageReader>
46 #include <QSvgRenderer>
47 #include <QNetworkRequest>
48 #include <QNetworkReply>
50 #include <QCoreApplication>
95 const bool prevSmoothTransform = painter->testRenderHint( QPainter::RenderHint::SmoothPixmapTransform );
97 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform,
true );
102 double boundRectWidthMM;
103 double boundRectHeightMM;
107 boundRectWidthMM = mPictureWidth;
108 boundRectHeightMM = mPictureHeight;
109 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
113 boundRectWidthMM = rect().width();
114 boundRectHeightMM = rect().height();
115 imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
119 boundRectWidthMM = rect().width();
120 boundRectHeightMM = rect().height();
121 const int imageRectWidthPixels = mImage.width();
122 const int imageRectHeightPixels = mImage.height();
123 imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
124 QSize( imageRectWidthPixels, imageRectHeightPixels ) );
128 boundRectWidthMM = rect().width();
129 boundRectHeightMM = rect().height();
135 if ( mResizeMode ==
Zoom )
141 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
142 painter->rotate( mPictureRotation );
143 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
148 const double diffX = rect().width() - boundRectWidthMM;
149 const double diffY = rect().height() - boundRectHeightMM;
153 switch ( mPictureAnchor )
171 switch ( mPictureAnchor )
189 painter->translate( dX, dY );
196 painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
197 painter->rotate( mPictureRotation );
198 painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
204 mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
208 painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
211 painter->setRenderHint( QPainter::RenderHint::SmoothPixmapTransform, prevSmoothTransform );
216 const QSizeF currentPictureSize = pictureSize();
217 QSizeF newSize = targetSize;
220 mPictureWidth = targetSize.width();
221 mPictureHeight = targetSize.height();
225 if ( mResizeMode ==
ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
227 QSizeF targetImageSize;
230 targetImageSize = currentPictureSize;
236 tr.rotate( mPictureRotation );
237 const QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
238 targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
243 if ( std::fabs( rect().width() - targetSize.width() ) <
244 std::fabs( rect().height() - targetSize.height() ) )
246 newSize.setHeight( targetImageSize.height() * newSize.width() / targetImageSize.width() );
250 newSize.setWidth( targetImageSize.width() * newSize.height() / targetImageSize.height() );
255 if ( !( currentPictureSize.isEmpty() ) )
258 newSize.setWidth( sizeMM.
width() * 25.4 /
mLayout->renderContext().dpi() );
259 newSize.setHeight( sizeMM.
height() * 25.4 /
mLayout->renderContext().dpi() );
267 QRectF( 0, 0, newSize.width(), newSize.height() ), mPictureRotation );
268 mPictureWidth = rotatedImageRect.width();
269 mPictureHeight = rotatedImageRect.height();
273 mPictureWidth = newSize.width();
274 mPictureHeight = newSize.height();
277 if ( newSize != targetSize )
286 QRect QgsLayoutItemPicture::clippedImageRect(
double &boundRectWidthMM,
double &boundRectHeightMM, QSize imageRectPixels )
288 const int boundRectWidthPixels = boundRectWidthMM *
mLayout->renderContext().dpi() / 25.4;
289 const int boundRectHeightPixels = boundRectHeightMM *
mLayout->renderContext().dpi() / 25.4;
292 boundRectWidthMM = boundRectWidthPixels * 25.4 /
mLayout->renderContext().dpi();
293 boundRectHeightMM = boundRectHeightPixels * 25.4 /
mLayout->renderContext().dpi();
300 switch ( mPictureAnchor )
310 leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
315 leftClip = imageRectPixels.width() - boundRectWidthPixels;
320 switch ( mPictureAnchor )
330 topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
335 topClip = imageRectPixels.height() - boundRectHeightPixels;
339 return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
349 QVariant source( mSourcePath );
352 mHasExpressionError =
false;
358 source = sourceProperty.
value( *evalContext, source, &ok );
359 if ( !ok || !source.canConvert( QMetaType::QString ) )
361 mHasExpressionError =
true;
365 else if ( source.type() != QVariant::ByteArray )
367 source = source.toString().trimmed();
368 QgsDebugMsgLevel( QStringLiteral(
"exprVal PictureSource:%1" ).arg( source.toString() ), 2 );
372 loadPicture( source );
375 void QgsLayoutItemPicture::loadRemotePicture(
const QString &url )
385 loop.exec( QEventLoop::ExcludeUserInputEvents );
387 QNetworkReply *reply = fetcher.
reply();
390 QImageReader imageReader( reply );
391 imageReader.setAutoTransform(
true );
393 if ( imageReader.format() ==
"pdf" )
402 const QSize sizeAt72Dpi = imageReader.size();
403 const QSize sizeAtTargetDpi = sizeAt72Dpi *
mLayout->renderContext().dpi() / 72;
404 imageReader.setScaledSize( sizeAtTargetDpi );
407 mImage = imageReader.read();
416 void QgsLayoutItemPicture::loadLocalPicture(
const QString &path )
419 pic.setFileName( path );
427 const QFileInfo sourceFileInfo( pic );
428 const QString sourceFileSuffix = sourceFileInfo.suffix();
429 if ( sourceFileSuffix.compare( QLatin1String(
"svg" ), Qt::CaseInsensitive ) == 0 )
439 1.0, 0,
false, evaluatedParameters );
440 mSVG.load( svgContent );
441 if ( mSVG.isValid() )
444 const QRect viewBox = mSVG.viewBox();
445 mDefaultSvgSize.setWidth( viewBox.width() );
446 mDefaultSvgSize.setHeight( viewBox.height() );
456 QImageReader imageReader( pic.fileName() );
457 imageReader.setAutoTransform(
true );
459 if ( imageReader.format() ==
"pdf" )
468 const QSize sizeAt72Dpi = imageReader.size();
469 const QSize sizeAtTargetDpi = sizeAt72Dpi *
mLayout->renderContext().dpi() / 72;
470 imageReader.setScaledSize( sizeAtTargetDpi );
473 if ( imageReader.read( &mImage ) )
485 void QgsLayoutItemPicture::loadPictureUsingCache(
const QString &path )
487 if ( path.isEmpty() )
500 bool fitsInCache =
false;
501 bool isMissing =
false;
503 if ( mImage.isNull() || isMissing )
520 mSVG.load( svgContent );
524 const QRect viewBox = mSVG.viewBox();
525 mDefaultSvgSize.setWidth( viewBox.width() );
526 mDefaultSvgSize.setHeight( viewBox.height() );
537 void QgsLayoutItemPicture::updateNorthArrowRotation(
double rotation )
543 void QgsLayoutItemPicture::loadPicture(
const QVariant &data )
545 const Format origFormat = mMode;
546 mIsMissingImage =
false;
547 QVariant imageData( data );
548 mEvaluatedPath = data.toString();
550 if ( mEvaluatedPath.startsWith( QLatin1String(
"base64:" ), Qt::CaseInsensitive ) && mMode ==
FormatUnknown )
552 const QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit();
553 imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
556 if ( imageData.type() == QVariant::ByteArray )
558 if ( mImage.loadFromData( imageData.toByteArray() ) )
563 else if ( mMode ==
FormatUnknown && mEvaluatedPath.startsWith( QLatin1String(
"http" ) ) )
566 loadRemotePicture( mEvaluatedPath );
571 loadLocalPicture( mEvaluatedPath );
575 loadPictureUsingCache( mEvaluatedPath );
582 else if ( mHasExpressionError || !mEvaluatedPath.isEmpty() )
585 mIsMissingImage =
true;
588 const QString badFile( QStringLiteral(
":/images/composer/missing_image.png" ) );
589 QImageReader imageReader( badFile );
590 if ( imageReader.read( &mImage ) )
595 const QString badFile( QStringLiteral(
":/images/composer/missing_image.svg" ) );
596 mSVG.load( badFile );
597 if ( mSVG.isValid() )
600 const QRect viewBox = mSVG.viewBox();
601 mDefaultSvgSize.setWidth( viewBox.width() );
602 mDefaultSvgSize.setHeight( viewBox.height() );
612 QRectF QgsLayoutItemPicture::boundedImageRect(
double deviceWidth,
double deviceHeight )
614 double imageToDeviceRatio;
615 if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
617 imageToDeviceRatio = deviceWidth / mImage.width();
618 const double height = imageToDeviceRatio * mImage.height();
619 return QRectF( 0, 0, deviceWidth, height );
623 imageToDeviceRatio = deviceHeight / mImage.height();
624 const double width = imageToDeviceRatio * mImage.width();
625 return QRectF( 0, 0, width, deviceHeight );
629 QRectF QgsLayoutItemPicture::boundedSVGRect(
double deviceWidth,
double deviceHeight )
631 double imageToSvgRatio;
632 if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
634 imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
635 const double width = mDefaultSvgSize.width() * imageToSvgRatio;
636 return QRectF( 0, 0, width, deviceHeight );
640 imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
641 const double height = mDefaultSvgSize.height() * imageToSvgRatio;
642 return QRectF( 0, 0, deviceWidth, height );
646 QSizeF QgsLayoutItemPicture::pictureSize()
650 return mDefaultSvgSize;
654 return QSizeF( mImage.width(), mImage.height() );
658 return QSizeF( 0, 0 );
664 return mIsMissingImage;
669 return mEvaluatedPath;
674 const QVariantMap parameters =
mCustomProperties.
value( QStringLiteral(
"svg-dynamic-parameters" ), QVariantMap() ).toMap();
685 void QgsLayoutItemPicture::shapeChanged()
687 if ( mMode ==
FormatSVG && !mLoadingSvg )
697 const double oldRotation = mPictureRotation;
698 mPictureRotation = rotation;
700 if ( mResizeMode ==
Zoom )
703 const QSizeF currentPictureSize = pictureSize();
705 mPictureWidth = rotatedImageRect.width();
706 mPictureHeight = rotatedImageRect.height();
711 const QSizeF currentPictureSize = pictureSize();
712 const QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
719 tr.rotate( mPictureRotation );
720 QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
723 newRect.moveCenter( oldRect.center() );
782 QString imagePath = mSourcePath;
786 if ( imagePath.endsWith( QLatin1String(
".svg" ), Qt::CaseInsensitive ) )
789 imagePath = pathResolver.
writePath( imagePath );
791 elem.setAttribute( QStringLiteral(
"file" ), imagePath );
792 elem.setAttribute( QStringLiteral(
"pictureWidth" ), QString::number( mPictureWidth ) );
793 elem.setAttribute( QStringLiteral(
"pictureHeight" ), QString::number( mPictureHeight ) );
794 elem.setAttribute( QStringLiteral(
"resizeMode" ), QString::number(
static_cast< int >( mResizeMode ) ) );
795 elem.setAttribute( QStringLiteral(
"anchorPoint" ), QString::number(
static_cast< int >( mPictureAnchor ) ) );
798 elem.setAttribute( QStringLiteral(
"svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
799 elem.setAttribute( QStringLiteral(
"mode" ), mMode );
802 elem.setAttribute( QStringLiteral(
"pictureRotation" ), QString::number( mPictureRotation ) );
805 elem.setAttribute( QStringLiteral(
"mapUuid" ), QString() );
809 elem.setAttribute( QStringLiteral(
"mapUuid" ), mNorthArrowHandler->
linkedMap()->
uuid() );
811 elem.setAttribute( QStringLiteral(
"northMode" ), mNorthArrowHandler->
northMode() );
812 elem.setAttribute( QStringLiteral(
"northOffset" ), mNorthArrowHandler->
northOffset() );
818 mPictureWidth = itemElem.attribute( QStringLiteral(
"pictureWidth" ), QStringLiteral(
"10" ) ).toDouble();
819 mPictureHeight = itemElem.attribute( QStringLiteral(
"pictureHeight" ), QStringLiteral(
"10" ) ).toDouble();
826 mSvgStrokeWidth = itemElem.attribute( QStringLiteral(
"svgBorderWidth" ), QStringLiteral(
"0.2" ) ).toDouble();
827 mMode =
static_cast< Format >( itemElem.attribute( QStringLiteral(
"mode" ), QString::number(
FormatUnknown ) ).toInt() );
829 const QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral(
"ComposerItem" ) );
830 if ( !composerItemList.isEmpty() )
832 const QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
834 if ( !
qgsDoubleNear( composerItemElem.attribute( QStringLiteral(
"rotation" ), QStringLiteral(
"0" ) ).toDouble(), 0.0 ) )
837 mPictureRotation = composerItemElem.attribute( QStringLiteral(
"rotation" ), QStringLiteral(
"0" ) ).toDouble();
841 mDefaultSvgSize = QSize( 0, 0 );
843 if ( itemElem.hasAttribute( QStringLiteral(
"sourceExpression" ) ) )
846 const QString sourceExpression = itemElem.attribute( QStringLiteral(
"sourceExpression" ), QString() );
847 const QString useExpression = itemElem.attribute( QStringLiteral(
"useExpression" ) );
848 bool expressionActive;
849 expressionActive = ( useExpression.compare( QLatin1String(
"true" ), Qt::CaseInsensitive ) == 0 );
854 QString imagePath = itemElem.attribute( QStringLiteral(
"file" ) );
858 if ( imagePath.endsWith( QLatin1String(
".svg" ), Qt::CaseInsensitive ) )
861 imagePath = pathResolver.
readPath( imagePath );
863 mSourcePath = imagePath;
866 if ( !
qgsDoubleNear( itemElem.attribute( QStringLiteral(
"pictureRotation" ), QStringLiteral(
"0" ) ).toDouble(), 0.0 ) )
868 mPictureRotation = itemElem.attribute( QStringLiteral(
"pictureRotation" ), QStringLiteral(
"0" ) ).toDouble();
873 mNorthArrowHandler->
setNorthOffset( itemElem.attribute( QStringLiteral(
"northOffset" ), QStringLiteral(
"0" ) ).toDouble() );
876 mRotationMapUuid = itemElem.attribute( QStringLiteral(
"mapUuid" ) );
908 mPictureAnchor = anchor;
914 mSvgFillColor = color;
920 mSvgStrokeColor = color;
926 mSvgStrokeWidth = width;
941 if ( !
mLayout || mRotationMapUuid.isEmpty() )
947 mNorthArrowHandler->
setLinkedMap( qobject_cast< QgsLayoutItemMap * >(
mLayout->itemByUuid( mRotationMapUuid,
true ) ) );