/* ModelImageObject.cpp * * Copyright (C) 2013-2016 Jim Evins * * This file is part of gLabels-qt. * * gLabels-qt is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * gLabels-qt is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gLabels-qt. If not, see . */ #include "ModelImageObject.h" #include "Model.h" #include "Size.h" #include #include #include #include #include #include namespace glabels { namespace model { /// /// Static data /// QImage* ModelImageObject::smDefaultImage = nullptr; // // Private // namespace { const QColor fillColor = QColor( 224, 224, 224, 255 ); const QColor labelColor = QColor( 102, 102, 102, 255 ); const Distance pad = Distance::pt(2); } /// /// Constructor /// ModelImageObject::ModelImageObject() : mImage(nullptr), mSvgRenderer(nullptr) { mOutline = new Outline( this ); mHandles << new HandleNorthWest( this ); mHandles << new HandleNorth( this ); mHandles << new HandleNorthEast( this ); mHandles << new HandleEast( this ); mHandles << new HandleSouthEast( this ); mHandles << new HandleSouth( this ); mHandles << new HandleSouthWest( this ); mHandles << new HandleWest( this ); if ( smDefaultImage == nullptr ) { smDefaultImage = new QImage( ":images/checkerboard.png" ); } } /// /// Constructor /// ModelImageObject::ModelImageObject( const Distance& x0, const Distance& y0, const Distance& w, const Distance& h, const TextNode& filenameNode, const QMatrix& matrix, bool shadowState, const Distance& shadowX, const Distance& shadowY, double shadowOpacity, const ColorNode& shadowColorNode ) : ModelObject( x0, y0, w, h, matrix, shadowState, shadowX, shadowY, shadowOpacity, shadowColorNode ) { mOutline = new Outline( this ); mHandles << new HandleNorthWest( this ); mHandles << new HandleNorth( this ); mHandles << new HandleNorthEast( this ); mHandles << new HandleEast( this ); mHandles << new HandleSouthEast( this ); mHandles << new HandleSouth( this ); mHandles << new HandleSouthWest( this ); mHandles << new HandleWest( this ); if ( smDefaultImage == nullptr ) { smDefaultImage = new QImage( ":images/checkerboard.png" ); } mFilenameNode = filenameNode; mImage = nullptr; mSvgRenderer = nullptr; } /// /// Constructor /// ModelImageObject::ModelImageObject( const Distance& x0, const Distance& y0, const Distance& w, const Distance& h, const QString& filename, const QImage& image, const QMatrix& matrix, bool shadowState, const Distance& shadowX, const Distance& shadowY, double shadowOpacity, const ColorNode& shadowColorNode ) : ModelObject( x0, y0, w, h, matrix, shadowState, shadowX, shadowY, shadowOpacity, shadowColorNode ) { mOutline = new Outline( this ); mHandles << new HandleNorthWest( this ); mHandles << new HandleNorth( this ); mHandles << new HandleNorthEast( this ); mHandles << new HandleEast( this ); mHandles << new HandleSouthEast( this ); mHandles << new HandleSouth( this ); mHandles << new HandleSouthWest( this ); mHandles << new HandleWest( this ); if ( smDefaultImage == nullptr ) { smDefaultImage = new QImage( ":images/checkerboard.png" ); } mImage = new QImage(image); mFilenameNode = TextNode( false, filename ); mSvgRenderer = nullptr; } /// /// Constructor /// ModelImageObject::ModelImageObject( const Distance& x0, const Distance& y0, const Distance& w, const Distance& h, const QString& filename, const QByteArray& svg, const QMatrix& matrix, bool shadowState, const Distance& shadowX, const Distance& shadowY, double shadowOpacity, const ColorNode& shadowColorNode ) : ModelObject( x0, y0, w, h, matrix, shadowState, shadowX, shadowY, shadowOpacity, shadowColorNode ) { mOutline = new Outline( this ); mHandles << new HandleNorthWest( this ); mHandles << new HandleNorth( this ); mHandles << new HandleNorthEast( this ); mHandles << new HandleEast( this ); mHandles << new HandleSouthEast( this ); mHandles << new HandleSouth( this ); mHandles << new HandleSouthWest( this ); mHandles << new HandleWest( this ); if ( smDefaultImage == nullptr ) { smDefaultImage = new QImage( ":images/checkerboard.png" ); } mSvg = svg; mSvgRenderer = new QSvgRenderer( mSvg ); mFilenameNode = TextNode( false, filename ); mImage = nullptr; } /// /// Copy constructor /// ModelImageObject::ModelImageObject( const ModelImageObject* object ) : ModelObject(object) { mFilenameNode = object->mFilenameNode; if ( object->mImage ) { mImage = new QImage( *object->mImage ); } else { mImage = nullptr; } if ( object->mSvgRenderer ) { mSvgRenderer = new QSvgRenderer( object->mSvg ); } else { mSvgRenderer = nullptr; } mSvg = object->mSvg; } /// /// Destructor /// ModelImageObject::~ModelImageObject() { delete mOutline; foreach( Handle* handle, mHandles ) { delete handle; } mHandles.clear(); if ( mImage ) { delete mImage; } if ( mSvgRenderer ) { delete mSvgRenderer; } } /// /// Clone /// ModelImageObject* ModelImageObject::clone() const { return new ModelImageObject( this ); } /// /// Image filenameNode Property Getter /// TextNode ModelImageObject::filenameNode() const { return mFilenameNode; } /// /// Image filenameNode Property Setter /// void ModelImageObject::setFilenameNode( const TextNode& value ) { if ( mFilenameNode != value ) { mFilenameNode = value; loadImage(); emit changed(); } } /// /// Image image Property Getter /// const QImage* ModelImageObject::image() const { return mImage; } /// /// Image Property Setter /// void ModelImageObject::setImage( const QImage& value ) { if ( !value.isNull() ) { if ( mImage ) { delete mImage; mImage = nullptr; } if ( mSvgRenderer ) { delete mSvgRenderer; mSvgRenderer = nullptr; } mImage = new QImage(value); quint16 cs = qChecksum( (const char*)mImage->constBits(), mImage->byteCount() ); mFilenameNode = TextNode( false, QString("%image_%1%").arg( cs ) ); emit changed(); } } /// /// Image Property Setter /// void ModelImageObject::setImage( const QString& name, const QImage& value ) { if ( !value.isNull() ) { if ( mImage ) { delete mImage; mImage = nullptr; } if ( mSvgRenderer ) { delete mSvgRenderer; mSvgRenderer = nullptr; } mImage = new QImage(value); mFilenameNode = TextNode( false, name ); emit changed(); } } /// /// Image svg Property Getter /// QByteArray ModelImageObject::svg() const { return mSvg; } /// /// Image svgSource Property Setter /// void ModelImageObject::setSvg( const QString& name, const QByteArray& value ) { if ( !value.isEmpty() ) { if ( mImage ) { delete mImage; mImage = nullptr; } if ( mSvgRenderer ) { delete mSvgRenderer; mSvgRenderer = nullptr; } mSvg = value; mSvgRenderer = new QSvgRenderer( mSvg ); mFilenameNode = TextNode( false, name ); emit changed(); } } /// /// naturalSize Property Getter (assumes 72 DPI, i.e. 1pixel == 1pt) /// Size ModelImageObject::naturalSize() const { Size size( Distance::pt(72), Distance::pt(72) ); if ( mImage ) { QSize qsize = mImage->size(); size.setW( Distance::pt( qsize.width() ) ); size.setH( Distance::pt( qsize.height() ) ); } else if ( mSvgRenderer ) { QSize qsize = mSvgRenderer->defaultSize(); size.setW( Distance::pt( qsize.width() ) ); size.setH( Distance::pt( qsize.height() ) ); } return size; } /// /// Draw shadow of object /// void ModelImageObject::drawShadow( QPainter* painter, bool inEditor, merge::Record* record, Variables* variables ) const { QRectF destRect( 0, 0, mW.pt(), mH.pt() ); QColor shadowColor = mShadowColorNode.color( record, variables ); shadowColor.setAlphaF( mShadowOpacity ); if ( mImage && mImage->hasAlphaChannel() && (mImage->depth() == 32) ) { QImage* shadowImage = createShadowImage( *mImage, shadowColor ); painter->drawImage( destRect, *shadowImage ); delete shadowImage; } else if ( mImage || inEditor ) { painter->setBrush( shadowColor ); painter->setPen( QPen( Qt::NoPen ) ); painter->drawRect( destRect ); } else { // Look for image file relative to project file 1st then CWD 2nd auto* model = dynamic_cast( parent() ); QDir::setSearchPaths( "images", {model->dir(), QDir::currentPath()} ); QString filename = QString("images:") + mFilenameNode.text( record, variables ); auto* image = new QImage( filename ); if ( !image->isNull() && image->hasAlphaChannel() && (image->depth() == 32) ) { QImage* shadowImage = createShadowImage( *image, shadowColor ); painter->drawImage( destRect, *shadowImage ); delete shadowImage; } else if ( !image->isNull() ) { painter->setBrush( shadowColor ); painter->setPen( QPen( Qt::NoPen ) ); painter->drawRect( destRect ); } delete image; } } /// /// Draw object itself /// void ModelImageObject::drawObject( QPainter* painter, bool inEditor, merge::Record* record, Variables* variables ) const { QRectF destRect( 0, 0, mW.pt(), mH.pt() ); if ( inEditor && (mFilenameNode.isField() || (!mImage && !mSvgRenderer) ) ) { // // Render default place holder image // painter->save(); painter->setRenderHint( QPainter::SmoothPixmapTransform, false ); painter->drawImage( destRect, *smDefaultImage ); painter->restore(); // // Print label on top of place holder image, if we have room // if ( (mW > 6*pad) && (mH > 4*pad) ) { QString labelText = tr("No image"); if ( mFilenameNode.isField() ) { labelText = QString( "${%1}" ).arg( mFilenameNode.data() ); } // Determine font size for labelText QFont font( "Sans" ); font.setPointSizeF( 6 ); QFontMetricsF fm( font ); QRectF textRect = fm.boundingRect( labelText ); double wPts = (mW - 2*pad).pt(); double hPts = (mH - 2*pad).pt(); if ( (wPts < textRect.width()) || (hPts < textRect.height()) ) { double scaleX = wPts / textRect.width(); double scaleY = hPts / textRect.height(); font.setPointSizeF( 6 * std::min( scaleX, scaleY ) ); } // Render hole for text (font size may have changed above) fm = QFontMetricsF( font ); textRect = fm.boundingRect( labelText ); QRectF holeRect( (mW.pt() - textRect.width())/2 - pad.pt(), (mH.pt() - textRect.height())/2 - pad.pt(), textRect.width() + 2*pad.pt(), textRect.height() + 2*pad.pt() ); painter->setPen( Qt::NoPen ); painter->setBrush( QBrush( fillColor ) ); painter->drawRect( holeRect ); // Render text painter->setFont( font ); painter->setPen( QPen( labelColor ) ); painter->drawText( QRectF( 0, 0, mW.pt(), mH.pt() ), Qt::AlignCenter, labelText ); } } else if ( mImage ) { painter->drawImage( destRect, *mImage ); } else if ( mSvgRenderer ) { mSvgRenderer->render( painter, destRect ); } else if ( mFilenameNode.isField() ) { // Look for image file relative to project file 1st then CWD 2nd auto* model = dynamic_cast( parent() ); QDir::setSearchPaths( "images", {model->dir(), QDir::currentPath()} ); QString filename = QString("images:") + mFilenameNode.text( record, variables ); auto* image = new QImage( filename ); if ( !image->isNull() ) { painter->drawImage( destRect, *image ); } delete image; } } /// /// Path to test for hover condition /// QPainterPath ModelImageObject::hoverPath( double scale ) const { QPainterPath path; path.addRect( 0, 0, mW.pt(), mH.pt() ); return path; } /// /// Load image /// void ModelImageObject::loadImage() { if ( mImage ) { delete mImage; mImage = nullptr; } if ( mSvgRenderer ) { delete mSvgRenderer; mSvgRenderer = nullptr; } if ( !mFilenameNode.isField() ) { QString filename = mFilenameNode.data(); QFileInfo fileInfo( filename ); if ( fileInfo.isReadable() ) { if ( (fileInfo.suffix() == "svg") || (fileInfo.suffix() == "SVG") ) { QFile file( filename ); if ( file.open( QFile::ReadOnly ) ) { mSvg = file.readAll(); file.close(); mSvgRenderer = new QSvgRenderer( mSvg ); if ( !mSvgRenderer->isValid() ) { mSvgRenderer = nullptr; } else { // Adjust size based on aspect ratio of SVG image QRectF rect = mSvgRenderer->viewBoxF(); double aspectRatio = rect.height() / rect.width(); if ( mH > mW*aspectRatio ) { mH = mW*aspectRatio; } else { mW = mH/aspectRatio; } } } } else { mImage = new QImage( filename ); if ( mImage->isNull() ) { mImage = nullptr; } else { // Adjust size based on aspect ratio of image double imageW = mImage->width(); double imageH = mImage->height(); double aspectRatio = imageH / imageW; if ( mH > mW*aspectRatio ) { mH = mW*aspectRatio; } else { mW = mH/aspectRatio; } } } } } } /// /// Create shadow image /// QImage* ModelImageObject::createShadowImage( const QImage& image, const QColor& color ) const { int r = color.red(); int g = color.green(); int b = color.blue(); int a = color.alpha(); auto* shadow = new QImage( image ); for ( int iy = 0; iy < shadow->height(); iy++ ) { auto* scanLine = (QRgb*)shadow->scanLine( iy ); for ( int ix = 0; ix < shadow->width(); ix++ ) { scanLine[ix] = qRgba( r, g, b, (a*qAlpha(scanLine[ix]))/255 ); } } return shadow; } } }