Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading SVG files for Icons (annotations). #10104

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@
/testsuite/libraries-for-testing/.ccache/
/testsuite/flattening/modelica/ffi/FFITest/Resources/BuildProjects/gcc/libFFITestLib.dll
trace.txt
/.cache/
compile_commands.json
196 changes: 158 additions & 38 deletions OMEdit/OMEditLIB/Annotations/BitmapAnnotation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,58 @@
#include "BitmapAnnotation.h"
#include "Modeling/Commands.h"
#include "Util/ResourceCache.h"
#include "Renderers.h"

#include <QMessageBox>

const char* bitmapResourceName = ":/Resources/icons/bitmap-shape.svg";

/*!
* \brief A very simple heuristic to clasify the content type.
*
* Note that it's not super critical if this logic breaks for a weirdly contaminated SVG
* file. In a worst-case scenario, we will return `false` and fall back to processing the file
* with QImage, which will work and only make the rendering less perfect at larger zoom levels.
* Therefore not overinvesting into this here.
*
* \return true if the given are of an SVG image, false otherwise.
*/
bool isSvgImage(const QByteArray &bytes)
{
return bytes.left(1024).toLower().contains("<svg") && bytes.right(256).toLower().contains("</svg>");
}

/*!
* \brief A factory function for a Renderers, based on input binary image data.
* \param bytes The binry data bytes representing the image.
* \return A pointer to a newly created rendered object.
*/
std::unique_ptr<Renderer> makeRenderer(const QByteArray &bytes)
{
if (isSvgImage(bytes)) {
return std::make_unique<SvgRenderer>(bytes);
}
return std::make_unique<BitmapRenderer>(bytes);
}

/*!
* \brief A helper function to get a file content as byte array.
*/
QByteArray getFileAsBytes(const QString &fileName)
{
QFile file(fileName);
file.open(QIODevice::ReadOnly);
return file.readAll();
}

BitmapAnnotation::BitmapAnnotation(QString classFileName, QString annotation, GraphicsView *pGraphicsView)
: ShapeAnnotation(false, pGraphicsView, 0, 0)
{
mpOriginItem = new OriginItem(this);
mpOriginItem->setPassive();
mClassFileName = classFileName;
// set the default values
GraphicItem::setDefaults();
ShapeAnnotation::setDefaults();
setDefaults();
// set users default value by reading the settings file.
ShapeAnnotation::setUserDefaults();
parseShapeAnnotation(annotation);
Expand All @@ -61,21 +101,22 @@ BitmapAnnotation::BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString
mpBitmap = pBitmap;
mClassFileName = classFileName;
// set the default values
GraphicItem::setDefaults();
FilledShape::setDefaults();
ShapeAnnotation::setDefaults();
setDefaults();
// set users default value by reading the settings file.
ShapeAnnotation::setUserDefaults();
parseShapeAnnotation();
setShapeFlags(true);
}

BitmapAnnotation::BitmapAnnotation(ShapeAnnotation *pShapeAnnotation, Element *pParent)
: ShapeAnnotation(pShapeAnnotation, pParent)
BitmapAnnotation::BitmapAnnotation(BitmapAnnotation *pBitmapAnnotation, Element *pParent)
: ShapeAnnotation(pBitmapAnnotation, pParent)
{
mpOriginItem = 0;
updateShape(pShapeAnnotation);
updateShape(pBitmapAnnotation);
applyTransformation();
setFileName(pBitmapAnnotation->getFileName());
setImageSource(pBitmapAnnotation->getImageSource());
updateRenderer();
}

BitmapAnnotation::BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString &classFileName, Element *pParent)
Expand All @@ -85,9 +126,7 @@ BitmapAnnotation::BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString
mpBitmap = pBitmap;
mClassFileName = classFileName;
// set the default values
GraphicItem::setDefaults();
FilledShape::setDefaults();
ShapeAnnotation::setDefaults();
setDefaults();
// set users default value by reading the settings file.
ShapeAnnotation::setUserDefaults();
parseShapeAnnotation();
Expand Down Expand Up @@ -119,8 +158,7 @@ BitmapAnnotation::BitmapAnnotation(QString classFileName, GraphicsView *pGraphic
mpOriginItem->setPassive();
mClassFileName = classFileName;
// set the default values
GraphicItem::setDefaults();
ShapeAnnotation::setDefaults();
setDefaults();
// set users default value by reading the settings file.
ShapeAnnotation::setUserDefaults();
QVector<QPointF> extents;
Expand All @@ -131,10 +169,24 @@ BitmapAnnotation::BitmapAnnotation(QString classFileName, GraphicsView *pGraphic
setShapeFlags(true);

setFileName(mClassFileName);
if (!mFileName.isEmpty() && QFile::exists(mFileName)) {
mImage.load(mFileName);
setImageSource("");
updateRenderer();
}

// No, we can not put this simply in the declaration and have unique_ptr accepting incomplete type.
BitmapAnnotation::~BitmapAnnotation() = default;

void BitmapAnnotation::updateRenderer()
{
if (!mImageSource.isEmpty()) {
QByteArray bytes = QByteArray::fromBase64(mImageSource.toLatin1());
mpRenderer = makeRenderer(bytes);
} else {
mImage = ResourceCache::getImage(":/Resources/icons/bitmap-shape.svg");
if (!mFileName.isEmpty() && QFile::exists(mFileName)) {
mpRenderer = makeRenderer(getFileAsBytes(getFileName()));
} else {
mpRenderer = std::make_unique<SvgRenderer>(getFileAsBytes(bitmapResourceName));
}
}
}

Expand All @@ -152,15 +204,9 @@ void BitmapAnnotation::parseShapeAnnotation(QString annotation)
setFileName(StringHandler::removeFirstLastQuotes(stripDynamicSelect(list.at(4))));
// 6th item is the imageSource
if (list.size() >= 6) {
mImageSource = StringHandler::removeFirstLastQuotes(stripDynamicSelect(list.at(5)));
}
if (!mImageSource.isEmpty()) {
mImage.loadFromData(QByteArray::fromBase64(mImageSource.toLatin1()));
} else if (!mFileName.isEmpty() && QFile::exists(mFileName)) {
mImage.load(mFileName);
} else {
mImage = ResourceCache::getImage(":/Resources/icons/bitmap-shape.svg");
setImageSource(StringHandler::removeFirstLastQuotes(stripDynamicSelect(list.at(5))));
}
updateRenderer();
}

void BitmapAnnotation::parseShapeAnnotation()
Expand All @@ -170,14 +216,8 @@ void BitmapAnnotation::parseShapeAnnotation()
mExtent = mpBitmap->getExtent();
mExtent.evaluate(mpBitmap->getParentModel());
setFileName(StringHandler::removeFirstLastQuotes(stripDynamicSelect(mpBitmap->getFileName())));
mImageSource = StringHandler::removeFirstLastQuotes(stripDynamicSelect(mpBitmap->getImageSource()));
if (!mImageSource.isEmpty()) {
mImage.loadFromData(QByteArray::fromBase64(mImageSource.toLatin1()));
} else if (!mFileName.isEmpty() && QFile::exists(mFileName)) {
mImage.load(mFileName);
} else {
mImage = ResourceCache::getImage(":/Resources/icons/bitmap-shape.svg");
}
setImageSource(StringHandler::removeFirstLastQuotes(stripDynamicSelect(mpBitmap->getImageSource())));
updateRenderer();
}

QRectF BitmapAnnotation::boundingRect() const
Expand Down Expand Up @@ -209,11 +249,10 @@ void BitmapAnnotation::paint(QPainter *painter, const QStyleOptionGraphicsItem *
void BitmapAnnotation::drawBitmapAnnotation(QPainter *painter)
{
QRectF rect = getBoundingRect().normalized();
QImage image = mImage.scaled(rect.width(), rect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPointF centerPoint = rect.center() - image.rect().center();
QRectF target(centerPoint.x(), centerPoint.y(), image.width(), image.height());

painter->drawImage(target, mImage.mirrored());
Copy link
Contributor Author

@achary achary Jan 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: This is the root place where the change actually needed to be applied at. Instead of always drawing a bitmap image, now, we now delegate the painting operation to whatever the mpRenderer (runtime polymorphic type) has been constructed. Bitmap images render pretty much the same way (see BitmapRenderer class) and SVG images use QSvgRenderer under the hood instead (see `SvgRenderer class).

if (mpRenderer) {
mpRenderer->render(painter, rect, true);
}
}

/*!
Expand Down Expand Up @@ -271,8 +310,6 @@ QString BitmapAnnotation::getShapeAnnotation()
void BitmapAnnotation::updateShape(ShapeAnnotation *pShapeAnnotation)
{
// set the default values
GraphicItem::setDefaults(pShapeAnnotation);
FilledShape::setDefaults(pShapeAnnotation);
ShapeAnnotation::setDefaults(pShapeAnnotation);
}

Expand Down Expand Up @@ -304,3 +341,86 @@ void BitmapAnnotation::duplicate()
setSelected(false);
pBitmapAnnotation->setSelected(true);
}

/*!
* \brief ShapeAnnotation::setFileName
* Sets the file name.
* \param fileName
*/
void BitmapAnnotation::setFileName(QString fileName)
{
if (fileName.isEmpty()) {
mOriginalFileName = fileName;
mFileName = fileName;
return;
}

OMCProxy* pOMCProxy = MainWindow::instance()->getOMCProxy();
mOriginalFileName = fileName;
QUrl fileUrl(mOriginalFileName);
QFileInfo fileInfo(mOriginalFileName);
QFileInfo classFileInfo(mClassFileName);
/* if its a modelica:// link then make it absolute path */
if (fileUrl.scheme().toLower().compare("modelica") == 0) {
mFileName = pOMCProxy->uriToFilename(mOriginalFileName);
} else if (fileInfo.isRelative()) {
mFileName = QString(classFileInfo.absoluteDir().absolutePath()).append("/").append(mOriginalFileName);
} else if (fileInfo.isAbsolute()) {
mFileName = mOriginalFileName;
} else {
mFileName = "";
}
}

/*!
Returns the file name.
\return the file name.
*/
QString BitmapAnnotation::getFileName()
{
return mOriginalFileName;
}

/*!
\brief Sets the image source.
*/
void BitmapAnnotation::setImageSource(QString imageSource)
{
mImageSource = imageSource;
}

/*!
Returns the base 64 image source.
\return the image source.
*/
QString BitmapAnnotation::getImageSource()
{
return mImageSource;
}

/*!
Returns the image.
\return the image.
*/
QImage BitmapAnnotation::getImage()
{
if (mpRenderer)
{
return mpRenderer->getImage();
}
return BitmapAnnotation::getPlaceholderImage();
}

QImage BitmapAnnotation::getPlaceholderImage()
{
return ResourceCache::getImage(bitmapResourceName);
}

void BitmapAnnotation::setDefaults()
{
ShapeAnnotation::setDefaults();

mFileName = "";
mImageSource = "";
updateRenderer();
}
40 changes: 31 additions & 9 deletions OMEdit/OMEditLIB/Annotations/BitmapAnnotation.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,57 @@
#include "ShapeAnnotation.h"
#include "Util/Utilities.h"

#include <memory>

class Renderer;
class Element;
class BitmapAnnotation : public ShapeAnnotation
{
class BitmapAnnotation : public ShapeAnnotation {
Q_OBJECT
public:
// Used for icon/diagram shape
BitmapAnnotation(QString classFileName, QString annotation, GraphicsView *pGraphicsView);
BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString &classFileName, bool inherited, GraphicsView *pGraphicsView);
BitmapAnnotation(QString classFileName, QString annotation,
GraphicsView *pGraphicsView);
BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString &classFileName,
bool inherited, GraphicsView *pGraphicsView);
// Used for shape inside a component
BitmapAnnotation(ShapeAnnotation *pShapeAnnotation, Element *pParent);
BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString &classFileName, Element *pParent);
BitmapAnnotation(BitmapAnnotation *pBitmapAnnotation, Element *pParent);
BitmapAnnotation(ModelInstance::Bitmap *pBitmap, const QString &classFileName,
Element *pParent);
// Used for icon/diagram inherited shape
BitmapAnnotation(ShapeAnnotation *pShapeAnnotation, GraphicsView *pGraphicsView);
BitmapAnnotation(ShapeAnnotation *pShapeAnnotation,
GraphicsView *pGraphicsView);
// Used for OMSimulator FMU
BitmapAnnotation(QString classFileName, GraphicsView *pGraphicsView);
~BitmapAnnotation();
void parseShapeAnnotation(QString annotation) override;
void parseShapeAnnotation();
QRectF boundingRect() const override;
QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget = 0) override;
void drawBitmapAnnotation(QPainter *painter);
QString getOMCShapeAnnotation() override;
QString getOMCShapeAnnotationWithShapeName() override;
QString getShapeAnnotation() override;
void updateShape(ShapeAnnotation *pShapeAnnotation) override;
ModelInstance::Model *getParentModel() const override;
void setBitmap(ModelInstance::Bitmap *pBitmap) {mpBitmap = pBitmap;}
void setBitmap(ModelInstance::Bitmap *pBitmap) { mpBitmap = pBitmap; }
void setFileName(QString fileName);
QString getFileName();
void setImageSource(QString imageSource);
QString getImageSource();
QImage getImage();
void setDefaults();
void updateRenderer();
static QImage getPlaceholderImage();

private:
ModelInstance::Bitmap *mpBitmap;

protected:
QString mFileName;
QString mImageSource;
std::unique_ptr<Renderer> mpRenderer;
public slots:
void duplicate() override;
};
Expand Down