From 141b33884d842438ddb0fc6e7ffd30dba902e7b3 Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Tue, 11 Jan 2022 17:24:16 +0200 Subject: [PATCH] Redesign of pattern image. Preparations for support of background image. #43 --- .../dialogs/dialogpatternproperties.cpp | 161 ++- .../dialogs/dialogpatternproperties.h | 5 +- src/libs/ifc/ifc.pro | 2 +- src/libs/ifc/schema.qrc | 1 + src/libs/ifc/schema/pattern/v0.9.0.xsd | 1132 +++++++++++++++++ src/libs/ifc/xml/vabstractpattern.cpp | 42 +- src/libs/ifc/xml/vabstractpattern.h | 8 +- src/libs/ifc/xml/vdomdocument.cpp | 35 +- src/libs/ifc/xml/vdomdocument.h | 4 +- src/libs/ifc/xml/vpatternconverter.cpp | 83 +- src/libs/ifc/xml/vpatternconverter.h | 5 +- src/libs/ifc/xml/vpatternimage.cpp | 221 ++++ src/libs/ifc/xml/vpatternimage.h | 68 + src/libs/ifc/xml/xml.pri | 2 + src/test/CollectionTest/CollectionTest.pro | 2 +- .../TranslationsTest/TranslationsTest.pro | 2 +- src/test/ValentinaTest/ValentinaTest.pro | 2 +- 17 files changed, 1662 insertions(+), 113 deletions(-) create mode 100644 src/libs/ifc/schema/pattern/v0.9.0.xsd create mode 100644 src/libs/ifc/xml/vpatternimage.cpp create mode 100644 src/libs/ifc/xml/vpatternimage.h diff --git a/src/app/valentina/dialogs/dialogpatternproperties.cpp b/src/app/valentina/dialogs/dialogpatternproperties.cpp index 0868f34e7..47f540e4f 100644 --- a/src/app/valentina/dialogs/dialogpatternproperties.cpp +++ b/src/app/valentina/dialogs/dialogpatternproperties.cpp @@ -36,6 +36,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "../xml/vpattern.h" #include "../vpatterndb/vcontainer.h" @@ -44,6 +49,7 @@ #include "dialogknownmaterials.h" #include "../vmisc/vvalentinasettings.h" #include "../qmuparser/qmudef.h" +#include "../ifc/xml/vpatternimage.h" //--------------------------------------------------------------------------------------------------------------------- DialogPatternProperties::DialogPatternProperties(VPattern *doc, VContainer *pattern, QWidget *parent) @@ -250,21 +256,6 @@ void DialogPatternProperties::SaveReadOnlyState() } } -//--------------------------------------------------------------------------------------------------------------------- -QImage DialogPatternProperties::GetImage() -{ -// we set an image from file.val - QImage image; - QByteArray byteArray; - byteArray.append(doc->GetImage().toUtf8()); - QByteArray ba = QByteArray::fromBase64(byteArray); - QBuffer buffer(&ba); - buffer.open(QIODevice::ReadOnly); - QString extension = doc->GetImageExtension(); - image.load(&buffer, extension.toLatin1().data()); // writes image into ba in 'extension' format - return image; -} - //--------------------------------------------------------------------------------------------------------------------- void DialogPatternProperties::ValidatePassmarkLength() const { @@ -318,19 +309,12 @@ void DialogPatternProperties::InitImage() connect(changeImageAction, &QAction::triggered, this, &DialogPatternProperties::ChangeImage); connect(saveImageAction, &QAction::triggered, this, &DialogPatternProperties::SaveImage); - connect(showImageAction, &QAction::triggered, this, [this]() - { - QLabel *label = new QLabel(this, Qt::Window); - const QImage image = GetImage(); - label->setPixmap(QPixmap::fromImage(image)); - label->setGeometry(QRect(QCursor::pos(), image.size())); - label->show(); - }); + connect(showImageAction, &QAction::triggered, this, &DialogPatternProperties::ShowImage); - const QImage image = GetImage(); - if (not image.isNull()) + const VPatternImage image = doc->GetImage(); + if (image.IsValid()) { - ui->imageLabel->setPixmap(QPixmap::fromImage(image)); + ui->imageLabel->setPixmap(image.GetPixmap(ui->imageLabel->width(), ui->imageLabel->height())); } else { @@ -343,35 +327,46 @@ void DialogPatternProperties::InitImage() //--------------------------------------------------------------------------------------------------------------------- void DialogPatternProperties::ChangeImage() { - const QString filter = tr("Images") + QLatin1String(" (*.png *.jpg *.jpeg *.bmp)"); - const QString fileName = QFileDialog::getOpenFileName(this, tr("Image for pattern"), QString(), filter, nullptr, - VAbstractApplication::VApp()->NativeFileDialog()); + auto PrepareFilter = []() + { + const QList supportedFormats = QImageReader::supportedImageFormats(); + const QSet filterFormats{"bmp", "jpeg", "jpg", "png", "svg", "svgz", "tif", "tiff", "webp"}; + QStringList sufixes; + for (const auto& format : supportedFormats) + { + if (filterFormats.contains(format)) + { + sufixes.append(QStringLiteral("*.%1").arg(QString(format))); + } + } + + QStringList filters; + + if (not sufixes.isEmpty()) + { + filters.append(tr("Images") + QStringLiteral(" (%1)").arg(sufixes.join(' '))); + } + + filters.append(tr("All files") + QStringLiteral(" (*.*)")); + + return filters.join(QStringLiteral(";;")); + }; + + const QString fileName = QFileDialog::getOpenFileName(this, tr("Image for pattern"), QString(), PrepareFilter(), + nullptr, VAbstractApplication::VApp()->NativeFileDialog()); if (not fileName.isEmpty()) { - QImage image; - if (not image.load(fileName)) + VPatternImage image = VPatternImage::FromFile(fileName); + + if (not image.IsValid()) { + qCritical() << tr("Invalid image. Error: %1").arg(image.ErrorString()); return; } - ui->imageLabel->setPixmap(QPixmap::fromImage(image)); - QFileInfo f(fileName); - QString extension = f.suffix().toUpper(); - if (extension == QLatin1String("JPEG")) - { - extension = "JPG"; - } - if (extension == QLatin1String("PNG") || extension == QLatin1String("JPG") || extension == QLatin1String("BMP")) - { - QByteArray byteArray; - QBuffer buffer(&byteArray); - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, extension.toLatin1().data()); //writes the image in 'extension' format inside the buffer - QString iconBase64 = QString::fromLatin1(byteArray.toBase64().data()); + doc->SetImage(image); + ui->imageLabel->setPixmap(image.GetPixmap(ui->imageLabel->width(), ui->imageLabel->height())); - // save our image to file.val - doc->SetImage(iconBase64, extension); - } deleteAction->setEnabled(true); saveImageAction->setEnabled(true); showImageAction->setEnabled(true); @@ -381,24 +376,70 @@ void DialogPatternProperties::ChangeImage() //--------------------------------------------------------------------------------------------------------------------- void DialogPatternProperties::SaveImage() { - QByteArray byteArray; - byteArray.append(doc->GetImage().toUtf8()); - QByteArray ba = QByteArray::fromBase64(byteArray); - const QString extension = doc->GetImageExtension().prepend(QChar('.')); - QString filter = tr("Images") + QStringLiteral(" (*") + extension + QChar(')'); - QString filename = QFileDialog::getSaveFileName(this, tr("Save File"), tr("untitled"), filter, &filter, + const VPatternImage image = doc->GetImage(); + + if (not image.IsValid()) + { + qCritical() << tr("Unable to save image. Error: %1").arg(image.ErrorString()); + return; + } + + QMimeType mime = image.MimeTypeFromData(); + QString path = QDir::homePath() + QDir::separator() + tr("untitled"); + + QStringList suffixes = mime.suffixes(); + if (not suffixes.isEmpty()) + { + path += '.' + suffixes.at(0); + } + + QString filter = mime.filterString(); + QString filename = QFileDialog::getSaveFileName(this, tr("Save Image"), path, filter, nullptr, VAbstractApplication::VApp()->NativeFileDialog()); if (not filename.isEmpty()) { - if (not filename.endsWith(extension.toUpper())) - { - filename.append(extension); - } QFile file(filename); if (file.open(QIODevice::WriteOnly)) { - file.write(ba); - file.close(); + file.write(QByteArray::fromBase64(image.ContentData())); + } + else + { + qCritical() << tr("Unable to save image. Error: %1").arg(file.errorString()); } } } + +//--------------------------------------------------------------------------------------------------------------------- +void DialogPatternProperties::ShowImage() +{ + const VPatternImage image = doc->GetImage(); + + if (not image.IsValid()) + { + qCritical() << tr("Unable to show image. Error: %1").arg(image.ErrorString()); + return; + } + + QMimeType mime = image.MimeTypeFromData(); + QString name = QDir::tempPath() + QDir::separator() + QStringLiteral("image.XXXXXX"); + + QStringList suffixes = mime.suffixes(); + if (not suffixes.isEmpty()) + { + name += '.' + suffixes.at(0); + } + + delete m_tmpImage; + m_tmpImage = new QTemporaryFile(name, this); + if (m_tmpImage->open()) + { + m_tmpImage->write(QByteArray::fromBase64(image.ContentData())); + m_tmpImage->flush(); + QDesktopServices::openUrl(QUrl::fromLocalFile(m_tmpImage->fileName())); + } + else + { + qCritical() << tr("Unable to open temp file"); + } +} diff --git a/src/app/valentina/dialogs/dialogpatternproperties.h b/src/app/valentina/dialogs/dialogpatternproperties.h index ac756ef16..aafc3f26b 100644 --- a/src/app/valentina/dialogs/dialogpatternproperties.h +++ b/src/app/valentina/dialogs/dialogpatternproperties.h @@ -31,6 +31,7 @@ #include #include +#include #include "../vmisc/def.h" #include "../ifc/ifcdef.h" @@ -39,6 +40,7 @@ class VPattern; class VContainer; class QCheckBox; class QCompleter; +class QTemporaryFile; namespace Ui { @@ -61,6 +63,7 @@ private slots: void DescEdited(); void ChangeImage(); void SaveImage(); + void ShowImage(); private: Q_DISABLE_COPY(DialogPatternProperties) Ui::DialogPatternProperties *ui; @@ -77,12 +80,12 @@ private: QCompleter *m_completer{nullptr}; QStringList m_variables{}; QString m_oldPassmarkLength{}; + QPointer m_tmpImage{}; void SaveDescription(); void SaveReadOnlyState(); void InitImage(); - QImage GetImage(); void ValidatePassmarkLength() const; }; diff --git a/src/libs/ifc/ifc.pro b/src/libs/ifc/ifc.pro index bb30563d5..fe48bf81d 100644 --- a/src/libs/ifc/ifc.pro +++ b/src/libs/ifc/ifc.pro @@ -8,7 +8,7 @@ include(../../../common.pri) # Library work with xml. -QT += xml xmlpatterns printsupport concurrent +QT += xml xmlpatterns printsupport concurrent svg # We don't need gui library. QT -= gui diff --git a/src/libs/ifc/schema.qrc b/src/libs/ifc/schema.qrc index 58af0de67..41e597e8b 100644 --- a/src/libs/ifc/schema.qrc +++ b/src/libs/ifc/schema.qrc @@ -65,6 +65,7 @@ schema/pattern/v0.8.11.xsd schema/pattern/v0.8.12.xsd schema/pattern/v0.8.13.xsd + schema/pattern/v0.9.0.xsd schema/multisize_measurements/v0.3.0.xsd schema/multisize_measurements/v0.4.0.xsd schema/multisize_measurements/v0.4.1.xsd diff --git a/src/libs/ifc/schema/pattern/v0.9.0.xsd b/src/libs/ifc/schema/pattern/v0.9.0.xsd new file mode 100644 index 000000000..2bc8afac8 --- /dev/null +++ b/src/libs/ifc/schema/pattern/v0.9.0.xsd @@ -0,0 +1,1132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libs/ifc/xml/vabstractpattern.cpp b/src/libs/ifc/xml/vabstractpattern.cpp index f40a30d5a..c4ce5b1fd 100644 --- a/src/libs/ifc/xml/vabstractpattern.cpp +++ b/src/libs/ifc/xml/vabstractpattern.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include "../exception/vexceptionemptyparameter.h" #include "../exception/vexceptionobjecterror.h" @@ -57,6 +58,7 @@ #include "../vmisc/vabstractvalapplication.h" #include "../vmisc/compatibility.h" #include "../vlayout/vtextmanager.h" +#include "vpatternimage.h" class QDomElement; @@ -137,7 +139,7 @@ const QString VAbstractPattern::AttrPassmarkLength = QStringLiteral("passmark const QString VAbstractPattern::AttrOpacity = QStringLiteral("opacity"); const QString VAbstractPattern::AttrTags = QStringLiteral("tags"); -const QString VAbstractPattern::AttrExtension = QStringLiteral("extension"); +const QString VAbstractPattern::AttrContentType = QStringLiteral("contentType"); const QString VAbstractPattern::AttrFormula = QStringLiteral("formula"); const QString VAbstractPattern::AttrDescription = QStringLiteral("description"); @@ -1228,44 +1230,32 @@ void VAbstractPattern::SetPassmarkLengthVariable(const QString &name) } //--------------------------------------------------------------------------------------------------------------------- -QString VAbstractPattern::GetImage() const +auto VAbstractPattern::GetImage() const -> VPatternImage { - return UniqueTagText(TagImage); -} + VPatternImage image; -//--------------------------------------------------------------------------------------------------------------------- -QString VAbstractPattern::GetImageExtension() const -{ - const QString defExt = QStringLiteral("PNG"); - const QDomNodeList nodeList = this->elementsByTagName(TagImage); - if (nodeList.isEmpty()) + const QDomNodeList list = elementsByTagName(TagImage); + if (not list.isEmpty()) { - return defExt; - } - else - { - const QDomNode domNode = nodeList.at(0); - if (domNode.isNull() == false && domNode.isElement()) + QDomElement imgTag = list.at(0).toElement(); + if (not imgTag.isNull()) { - const QDomElement domElement = domNode.toElement(); - if (domElement.isNull() == false) - { - const QString ext = domElement.attribute(AttrExtension, defExt); - return ext; - } + image.SetContentData(imgTag.text().toLatin1(), imgTag.attribute(AttrContentType)); } } - return defExt; + + return image; } //--------------------------------------------------------------------------------------------------------------------- -void VAbstractPattern::SetImage(const QString &text, const QString &extension) +auto VAbstractPattern::SetImage(const VPatternImage &image) -> bool { QDomElement imageElement = CheckTagExists(TagImage); - setTagText(imageElement, text); - CheckTagExists(TagImage).setAttribute(AttrExtension, extension); + setTagText(imageElement, image.ContentData()); + imageElement.setAttribute(AttrContentType, image.ContentType()); modified = true; emit patternChanged(false); + return true; } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/libs/ifc/xml/vabstractpattern.h b/src/libs/ifc/xml/vabstractpattern.h index f349ed0f8..3b28de601 100644 --- a/src/libs/ifc/xml/vabstractpattern.h +++ b/src/libs/ifc/xml/vabstractpattern.h @@ -48,6 +48,7 @@ class QDomElement; class VPiecePath; class VPieceNode; +class VPatternImage; enum class Document : qint8 { FullLiteParse, LiteParse, LitePPParse, FullParse }; enum class LabelType : qint8 {NewPatternPiece, NewLabel}; @@ -200,9 +201,8 @@ public: QString GetPassmarkLengthVariable() const; void SetPassmarkLengthVariable(const QString &name); - QString GetImage() const; - QString GetImageExtension() const; - void SetImage(const QString &text, const QString &extension); + VPatternImage GetImage() const; + bool SetImage(const VPatternImage &image); void DeleteImage(); QString GetVersion() const; @@ -319,7 +319,7 @@ public: static const QString AttrOpacity; static const QString AttrTags; - static const QString AttrExtension; + static const QString AttrContentType; static const QString AttrFormula; static const QString AttrDescription; diff --git a/src/libs/ifc/xml/vdomdocument.cpp b/src/libs/ifc/xml/vdomdocument.cpp index 91bdeece8..6beeadf86 100644 --- a/src/libs/ifc/xml/vdomdocument.cpp +++ b/src/libs/ifc/xml/vdomdocument.cpp @@ -891,7 +891,7 @@ auto VDomDocument::GetFormatVersion(const QString &version) -> unsigned } //--------------------------------------------------------------------------------------------------------------------- -bool VDomDocument::setTagText(const QString &tag, const QString &text) +auto VDomDocument::setTagText(const QString &tag, const QString &text) -> bool { const QDomNodeList nodeList = this->elementsByTagName(tag); if (nodeList.isEmpty()) @@ -900,10 +900,10 @@ bool VDomDocument::setTagText(const QString &tag, const QString &text) } else { - const QDomNode domNode = nodeList.at(0); - if (domNode.isNull() == false && domNode.isElement()) + QDomNode domNode = nodeList.at(0); + if (not domNode.isNull() && domNode.isElement()) { - const QDomElement domElement = domNode.toElement(); + QDomElement domElement = domNode.toElement(); return setTagText(domElement, text); } } @@ -911,17 +911,30 @@ bool VDomDocument::setTagText(const QString &tag, const QString &text) } //--------------------------------------------------------------------------------------------------------------------- -bool VDomDocument::setTagText(const QDomElement &domElement, const QString &text) +auto VDomDocument::setTagText(QDomElement &domElement, const QString &text) -> bool { - if (domElement.isNull() == false) + if (not domElement.isNull()) { - QDomElement parent = domElement.parentNode().toElement(); - QDomElement newTag = createElement(domElement.tagName()); + QDomNode oldText = domElement.firstChild(); + const QDomText newText = createTextNode(text); - const QDomText newTagText = createTextNode(text); - newTag.appendChild(newTagText); + if (oldText.isNull()) + { + domElement.appendChild(newText); + } + else + { + if (oldText.nodeType() == QDomNode::TextNode) + { + domElement.replaceChild(newText, oldText); + } + else + { + RemoveAllChildren(domElement); + domElement.appendChild(newText); + } + } - parent.replaceChild(newTag, domElement); return true; } return false; diff --git a/src/libs/ifc/xml/vdomdocument.h b/src/libs/ifc/xml/vdomdocument.h index 1df9ea43d..20c2d09ee 100644 --- a/src/libs/ifc/xml/vdomdocument.h +++ b/src/libs/ifc/xml/vdomdocument.h @@ -149,11 +149,11 @@ public: protected: bool setTagText(const QString &tag, const QString &text); - bool setTagText(const QDomElement &domElement, const QString &text); + bool setTagText(QDomElement &domElement, const QString &text); QString UniqueTagText(const QString &tagName, const QString &defVal = QString()) const; void CollectId(const QDomElement &node, QVector &vector)const; - static void ValidateVersion(const QString &version); + static void ValidateVersion(const QString &version); private slots: void CacheRefreshed(); diff --git a/src/libs/ifc/xml/vpatternconverter.cpp b/src/libs/ifc/xml/vpatternconverter.cpp index cf1655dad..ac5c5d825 100644 --- a/src/libs/ifc/xml/vpatternconverter.cpp +++ b/src/libs/ifc/xml/vpatternconverter.cpp @@ -60,8 +60,8 @@ class QDomElement; */ const QString VPatternConverter::PatternMinVerStr = QStringLiteral("0.1.4"); -const QString VPatternConverter::PatternMaxVerStr = QStringLiteral("0.8.13"); -const QString VPatternConverter::CurrentSchema = QStringLiteral("://schema/pattern/v0.8.13.xsd"); +const QString VPatternConverter::PatternMaxVerStr = QStringLiteral("0.9.0"); +const QString VPatternConverter::CurrentSchema = QStringLiteral("://schema/pattern/v0.9.0.xsd"); //VPatternConverter::PatternMinVer; // <== DON'T FORGET TO UPDATE TOO!!!! //VPatternConverter::PatternMaxVer; // <== DON'T FORGET TO UPDATE TOO!!!! @@ -167,6 +167,8 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, strUserDefined, (QLatin1String("userDef Q_GLOBAL_STATIC_WITH_ARGS(const QString, strPlacement, (QLatin1String("placement"))) Q_GLOBAL_STATIC_WITH_ARGS(const QString, strCutNumber, (QLatin1String("cutNumber"))) Q_GLOBAL_STATIC_WITH_ARGS(const QString, strQuantity, (QLatin1String("quantity"))) +Q_GLOBAL_STATIC_WITH_ARGS(const QString, strExtension, (QLatin1String("extension"))) +Q_GLOBAL_STATIC_WITH_ARGS(const QString, strContentType, (QLatin1String("contentType"))) } // anonymous namespace //--------------------------------------------------------------------------------------------------------------------- @@ -246,7 +248,8 @@ auto VPatternConverter::XSDSchema(unsigned ver) const -> QString std::make_pair(FormatVersion(0, 8, 10), QStringLiteral("://schema/pattern/v0.8.10.xsd")), std::make_pair(FormatVersion(0, 8, 11), QStringLiteral("://schema/pattern/v0.8.11.xsd")), std::make_pair(FormatVersion(0, 8, 12), QStringLiteral("://schema/pattern/v0.8.12.xsd")), - std::make_pair(FormatVersion(0, 8, 13), CurrentSchema) + std::make_pair(FormatVersion(0, 8, 13), QStringLiteral("://schema/pattern/v0.8.13.xsd")), + std::make_pair(FormatVersion(0, 9, 0), CurrentSchema) }; if (schemas.contains(ver)) @@ -519,6 +522,10 @@ void VPatternConverter::ApplyPatches() ValidateXML(XSDSchema(FormatVersion(0, 8, 13))); Q_FALLTHROUGH(); case (FormatVersion(0, 8, 13)): + ToV0_9_0(); + ValidateXML(XSDSchema(FormatVersion(0, 9, 0))); + Q_FALLTHROUGH(); + case (FormatVersion(0, 9, 0)): break; default: InvalidVersion(m_ver); @@ -536,7 +543,7 @@ void VPatternConverter::DowngradeToCurrentMaxVersion() bool VPatternConverter::IsReadOnly() const { // Check if attribute readOnly was not changed in file format - Q_STATIC_ASSERT_X(VPatternConverter::PatternMaxVer == FormatVersion(0, 8, 13), + Q_STATIC_ASSERT_X(VPatternConverter::PatternMaxVer == FormatVersion(0, 9, 0), "Check attribute readOnly."); // Possibly in future attribute readOnly will change position etc. @@ -1238,6 +1245,19 @@ void VPatternConverter::ToV0_8_13() Save(); } +//--------------------------------------------------------------------------------------------------------------------- +void VPatternConverter::ToV0_9_0() +{ + // TODO. Delete if minimal supported version is 0.9.0 + Q_STATIC_ASSERT_X(VPatternConverter::PatternMinVer < FormatVersion(0, 9, 0), + "Time to refactor the code."); + + ConvertImageToV0_9_0(); + + SetVersion(QStringLiteral("0.9.0")); + Save(); +} + //--------------------------------------------------------------------------------------------------------------------- void VPatternConverter::TagUnitToV0_2_0() { @@ -2790,6 +2810,61 @@ void VPatternConverter::AddPieceUUIDV0_8_8() } } +//--------------------------------------------------------------------------------------------------------------------- +void VPatternConverter::ConvertImageToV0_9_0() +{ + // TODO. Delete if minimal supported version is 0.9.0 + Q_STATIC_ASSERT_X(VPatternConverter::PatternMinVer < FormatVersion(0, 9, 0), + "Time to refactor the code."); + + const QDomNodeList list = elementsByTagName(*strImage); + if (not list.isEmpty()) + { + QDomElement img = list.at(0).toElement(); + if (not img.isNull()) + { + QString extension = img.attribute(*strExtension); + img.removeAttribute(*strExtension); + + if (not extension.isEmpty()) + { + QMap mimeTypes{ + {"BMP", "image/bmp"}, + {"JPG", "image/jpeg"}, + {"PNG", "image/png"} + }; + + if (mimeTypes.contains(extension)) + { + img.setAttribute(*strContentType, mimeTypes.value(extension)); + } + } + + const QString content = img.text(); + if (not content.isEmpty()) + { + auto SplitString = [content]() + { + const int n = 80; + QStringList list; + QString tmp(content); + + while (not tmp.isEmpty()) + { + list.append(tmp.left(n)); + tmp.remove(0, n); + } + + return list; + }; + + QStringList data = SplitString(); + setTagText(img, data.join("\n")); + } + } + } +} + //--------------------------------------------------------------------------------------------------------------------- void VPatternConverter::TagUnionDetailsToV0_4_0() { diff --git a/src/libs/ifc/xml/vpatternconverter.h b/src/libs/ifc/xml/vpatternconverter.h index bd140e23c..b75adbacc 100644 --- a/src/libs/ifc/xml/vpatternconverter.h +++ b/src/libs/ifc/xml/vpatternconverter.h @@ -53,7 +53,7 @@ public: static const QString PatternMaxVerStr; static const QString CurrentSchema; static Q_DECL_CONSTEXPR const unsigned PatternMinVer = FormatVersion(0, 1, 4); - static Q_DECL_CONSTEXPR const unsigned PatternMaxVer = FormatVersion(0, 8, 13); + static Q_DECL_CONSTEXPR const unsigned PatternMaxVer = FormatVersion(0, 9, 0); protected: virtual unsigned MinVer() const override; @@ -136,6 +136,7 @@ private: void ToV0_8_11(); void ToV0_8_12(); void ToV0_8_13(); + void ToV0_9_0(); void TagUnitToV0_2_0(); void TagIncrementToV0_2_0(); @@ -191,6 +192,8 @@ private: void RemoveGradationV0_8_8(); void AddPieceUUIDV0_8_8(); + + void ConvertImageToV0_9_0(); }; //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/libs/ifc/xml/vpatternimage.cpp b/src/libs/ifc/xml/vpatternimage.cpp new file mode 100644 index 000000000..d37489af1 --- /dev/null +++ b/src/libs/ifc/xml/vpatternimage.cpp @@ -0,0 +1,221 @@ +/************************************************************************ + ** + ** @file vpatternimage.cpp + ** @author Roman Telezhynskyi + ** @date 11 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "vpatternimage.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +//--------------------------------------------------------------------------------------------------------------------- +auto IsMimeTypeImage(const QMimeType &mime) -> bool +{ + QStringList aliases = mime.aliases(); + aliases.prepend(mime.name()); + + QRegularExpression rx(QStringLiteral("^image\\/[-\\w]+(\\.[-\\w]+)*([+][-\\w]+)?$")); + + return std::any_of(aliases.begin(), aliases.end(), [rx](const QString &name) { return rx.match(name).hasMatch(); }); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto SplitString(QString str) -> QStringList +{ + QStringList list; + + const int n = 80; + while (not str.isEmpty()) + { + list.append(str.left(n)); + str.remove(0, n); + } + + return list; +} +} // namespace + + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::FromFile(const QString &fileName) -> VPatternImage +{ + VPatternImage image; + QMimeType mime = QMimeDatabase().mimeTypeForFile(fileName); + + if (not IsMimeTypeImage(mime)) + { + qCritical() << tr("Unexpected mime type: %1").arg(mime.name()); + return {}; + } + + QFile file(fileName); + if (not file.open(QIODevice::ReadOnly)) + { + qCritical() << tr("Couldn't read the image. Error: %1").arg(file.errorString()); + return {}; + } + + QString base64 = SplitString(QString::fromLatin1(file.readAll().toBase64().data())).join('\n'); + + image.SetContentData(base64.toLatin1(), mime.name()); + return image; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::ContentType() const -> const QString & +{ + return m_contentType; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::ContentData() const -> const QByteArray & +{ + return m_contentData; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VPatternImage::SetContentData(const QByteArray &newContentData, const QString &newContentType) +{ + m_contentData = newContentData; + m_contentType = not newContentType.isEmpty() ? newContentType : MimeTypeFromData().name(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::IsNull() const -> bool +{ + return m_contentData.isEmpty(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::IsValid() const -> bool +{ + m_errorString.clear(); + + if (IsNull()) + { + m_errorString = tr("No data."); + return false; + } + + if (m_contentType.isEmpty()) + { + m_errorString = tr("Content type is empty."); + return false; + } + + QMimeType mime = MimeTypeFromData(); + QSet aliases = mime.aliases().toSet(); + aliases.insert(mime.name()); + + QSet gzipMime {"application/gzip", "application/x-gzip"}; + + if (gzipMime.contains(aliases)) + { + QSvgRenderer render(QByteArray::fromBase64(m_contentData)); + if (render.isValid()) + { + mime = QMimeDatabase().mimeTypeForName(QStringLiteral("image/svg+xml-compressed")); + aliases = mime.aliases().toSet(); + aliases.insert(mime.name()); + } + } + + if (not aliases.contains(m_contentType)) + { + m_errorString = tr("Content type mistmatch."); + return false; + } + + if (not IsMimeTypeImage(mime)) + { + m_errorString = tr("Not image."); + return false; + } + + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::GetPixmap(int width, int height) const -> QPixmap +{ + if (not IsValid()) + { + return {}; + } + + QByteArray array = QByteArray::fromBase64(m_contentData); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + + QImageReader imageReader(&buffer); + imageReader.setScaledSize(QSize(width, height)); + + QImage image = imageReader.read(); + if (image.isNull()) + { + qCritical()<< tr("Couldn't read the image. Error: %1").arg(imageReader.errorString()); + return {}; + } + + return QPixmap::fromImage(image); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::ErrorString() const -> const QString & +{ + return m_errorString; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::MimeTypeFromData() const -> QMimeType +{ + return QMimeDatabase().mimeTypeForData(QByteArray::fromBase64(m_contentData)); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::Size() const -> QSize +{ + if (not IsValid()) + { + return {}; + } + + QByteArray array = QByteArray::fromBase64(m_contentData); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + + return QImageReader(&buffer).size(); +} diff --git a/src/libs/ifc/xml/vpatternimage.h b/src/libs/ifc/xml/vpatternimage.h new file mode 100644 index 000000000..615b0bb79 --- /dev/null +++ b/src/libs/ifc/xml/vpatternimage.h @@ -0,0 +1,68 @@ +/************************************************************************ + ** + ** @file vpatternimage.h + ** @author Roman Telezhynskyi + ** @date 11 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VPATTERNIMAGE_H +#define VPATTERNIMAGE_H + +#include +#include + + +class QPixmap; +class QMimeType; + +class VPatternImage +{ + Q_DECLARE_TR_FUNCTIONS(VPatternImage) +public: + VPatternImage() = default; + + static auto FromFile(const QString &fileName) -> VPatternImage; + + auto ContentType() const -> const QString &; + + auto ContentData() const -> const QByteArray &; + void SetContentData(const QByteArray &newContentData, const QString & newContentType); + + auto IsNull() const -> bool; + auto IsValid() const -> bool; + + auto GetPixmap(int width, int height) const -> QPixmap; + + auto ErrorString() const -> const QString &; + + auto MimeTypeFromData() const -> QMimeType; + + auto Size() const -> QSize; + +private: + QString m_contentType{}; + QByteArray m_contentData{}; + mutable QString m_errorString{}; +}; + +#endif // VPATTERNIMAGE_H diff --git a/src/libs/ifc/xml/xml.pri b/src/libs/ifc/xml/xml.pri index 0a6f39591..fa8c2ce4a 100644 --- a/src/libs/ifc/xml/xml.pri +++ b/src/libs/ifc/xml/xml.pri @@ -6,6 +6,7 @@ HEADERS += \ $$PWD/vdomdocument.h \ $$PWD/vlayoutconverter.h \ $$PWD/vpatternconverter.h \ + $$PWD/vpatternimage.h \ $$PWD/vtoolrecord.h \ $$PWD/vabstractpattern.h \ $$PWD/vvstconverter.h \ @@ -19,6 +20,7 @@ SOURCES += \ $$PWD/vdomdocument.cpp \ $$PWD/vlayoutconverter.cpp \ $$PWD/vpatternconverter.cpp \ + $$PWD/vpatternimage.cpp \ $$PWD/vtoolrecord.cpp \ $$PWD/vabstractpattern.cpp \ $$PWD/vvstconverter.cpp \ diff --git a/src/test/CollectionTest/CollectionTest.pro b/src/test/CollectionTest/CollectionTest.pro index 109207bd0..4a7c83495 100644 --- a/src/test/CollectionTest/CollectionTest.pro +++ b/src/test/CollectionTest/CollectionTest.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += testlib widgets printsupport concurrent xml xmlpatterns +QT += testlib widgets printsupport concurrent xml xmlpatterns svg QT -= gui diff --git a/src/test/TranslationsTest/TranslationsTest.pro b/src/test/TranslationsTest/TranslationsTest.pro index d3c4b34ee..427e27f19 100644 --- a/src/test/TranslationsTest/TranslationsTest.pro +++ b/src/test/TranslationsTest/TranslationsTest.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += testlib widgets xml printsupport concurrent xmlpatterns +QT += testlib widgets xml printsupport concurrent xmlpatterns svg QT -= gui diff --git a/src/test/ValentinaTest/ValentinaTest.pro b/src/test/ValentinaTest/ValentinaTest.pro index 360a4852c..d6089fd70 100644 --- a/src/test/ValentinaTest/ValentinaTest.pro +++ b/src/test/ValentinaTest/ValentinaTest.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core testlib gui printsupport xml xmlpatterns concurrent +QT += core testlib gui printsupport xml xmlpatterns concurrent svg TARGET = ValentinaTests