File indexing completed on 2024-05-12 03:47:29

0001 /*
0002     File                 : DatapickerImage.cpp
0003     Project              : LabPlot
0004     Description          : Worksheet for Datapicker
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-FileCopyrightText: 2015-2022 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "DatapickerImage.h"
0013 #include "DatapickerImagePrivate.h"
0014 #include "backend/core/Project.h"
0015 #include "backend/datapicker/DatapickerPoint.h"
0016 #include "backend/datapicker/ImageEditor.h"
0017 #include "backend/datapicker/Segments.h"
0018 #include "backend/lib/XmlStreamReader.h"
0019 #include "backend/lib/commandtemplates.h"
0020 #include "backend/lib/trace.h"
0021 #include "backend/worksheet/Worksheet.h"
0022 #include "backend/worksheet/plots/cartesian/Symbol.h"
0023 #include "commonfrontend/datapicker/DatapickerImageView.h"
0024 #include "kdefrontend/worksheet/ExportWorksheetDialog.h"
0025 
0026 #include <QBuffer>
0027 #include <QDir>
0028 #include <QFileInfo>
0029 #include <QGraphicsScene>
0030 #include <QMenu>
0031 #include <QPrintDialog>
0032 #include <QPrintPreviewDialog>
0033 #include <QPrinter>
0034 #include <QScreen>
0035 
0036 #include <KConfig>
0037 #include <KConfigGroup>
0038 #include <KLocalizedString>
0039 
0040 /**
0041  * \class DatapickerImage
0042  * \brief container to open image/plot.
0043  *
0044  * Top-level container for DatapickerPoint.
0045  *
0046  * * \ingroup datapicker
0047  */
0048 DatapickerImage::DatapickerImage(const QString& name, bool loading)
0049     : AbstractPart(name, AspectType::DatapickerImage)
0050     , foregroundBins(new int[ImageEditor::colorAttributeMax(ColorAttributes::Foreground) + 1])
0051     , hueBins(new int[ImageEditor::colorAttributeMax(ColorAttributes::Hue) + 1])
0052     , saturationBins(new int[ImageEditor::colorAttributeMax(ColorAttributes::Saturation) + 1])
0053     , valueBins(new int[ImageEditor::colorAttributeMax(ColorAttributes::Value) + 1])
0054     , intensityBins(new int[ImageEditor::colorAttributeMax(ColorAttributes::Intensity) + 1])
0055     , d_ptr(new DatapickerImagePrivate(this))
0056     , m_segments(new Segments(this)) {
0057     Q_D(DatapickerImage);
0058     if (!loading)
0059         init();
0060     else {
0061         d->symbol = new Symbol(QString());
0062         addChild(d->symbol);
0063         d->symbol->setHidden(true);
0064         connect(d->symbol, &Symbol::updateRequested, [=] {
0065             d->retransform();
0066         });
0067         connect(d->symbol, &Symbol::updatePixmapRequested, [=] {
0068             d->retransform();
0069         });
0070     }
0071 }
0072 
0073 DatapickerImage::~DatapickerImage() {
0074     delete[] hueBins;
0075     delete[] saturationBins;
0076     delete[] valueBins;
0077     delete[] intensityBins;
0078     delete[] foregroundBins;
0079     delete m_segments;
0080     delete d_ptr;
0081 }
0082 
0083 void DatapickerImage::init() {
0084     Q_D(DatapickerImage);
0085     KConfig config;
0086     KConfigGroup group = config.group(QStringLiteral("DatapickerImage"));
0087 
0088     // general properties
0089     d->fileName = group.readEntry(QStringLiteral("FileName"), QString());
0090     d->rotationAngle = group.readEntry(QStringLiteral("RotationAngle"), 0.0);
0091     d->minSegmentLength = group.readEntry(QStringLiteral("MinSegmentLength"), 30);
0092     d->pointSeparation = group.readEntry(QStringLiteral("PointSeparation"), 30);
0093     d->axisPoints.type = static_cast<GraphType>(group.readEntry(QStringLiteral("GraphType"), static_cast<int>(GraphType::Linear)));
0094     d->axisPoints.ternaryScale = group.readEntry(QStringLiteral("TernaryScale"), 1);
0095 
0096     // edit image settings
0097     d->plotImageType = DatapickerImage::PlotImageType::OriginalImage;
0098     d->settings.foregroundThresholdHigh = group.readEntry(QStringLiteral("ForegroundThresholdHigh"), d->settings.foregroundThresholdHigh);
0099     d->settings.foregroundThresholdLow = group.readEntry(QStringLiteral("ForegroundThresholdLow"), d->settings.foregroundThresholdLow);
0100     d->settings.hueThresholdHigh = group.readEntry(QStringLiteral("HueThresholdHigh"), d->settings.hueThresholdHigh);
0101     d->settings.hueThresholdLow = group.readEntry(QStringLiteral("HueThresholdLow"), d->settings.hueThresholdLow);
0102     d->settings.intensityThresholdHigh = group.readEntry(QStringLiteral("IntensityThresholdHigh"), d->settings.intensityThresholdHigh);
0103     d->settings.intensityThresholdLow = group.readEntry(QStringLiteral("IntensityThresholdLow"), d->settings.intensityThresholdLow);
0104     d->settings.saturationThresholdHigh = group.readEntry(QStringLiteral("SaturationThresholdHigh"), d->settings.saturationThresholdHigh);
0105     d->settings.saturationThresholdLow = group.readEntry(QStringLiteral("SaturationThresholdLow"), d->settings.saturationThresholdLow);
0106     d->settings.valueThresholdHigh = group.readEntry(QStringLiteral("ValueThresholdHigh"), d->settings.valueThresholdHigh);
0107     d->settings.valueThresholdLow = group.readEntry(QStringLiteral("ValueThresholdLow"), d->settings.valueThresholdLow);
0108 
0109     // reference point symbol properties
0110     d->symbol = new Symbol(QString());
0111     addChild(d->symbol);
0112     d->symbol->setHidden(true);
0113     connect(d->symbol, &Symbol::updateRequested, [=] {
0114         d->retransform();
0115     });
0116     connect(d->symbol, &Symbol::updatePixmapRequested, [=] {
0117         d->retransform();
0118     });
0119     d->symbol->init(group);
0120     d->pointVisibility = group.readEntry(QStringLiteral("PointVisibility"), true);
0121 }
0122 
0123 /*!
0124     Returns an icon to be used in the project explorer.
0125 */
0126 QIcon DatapickerImage::icon() const {
0127     return QIcon::fromTheme(QStringLiteral("image-x-generic"));
0128 }
0129 
0130 /*!
0131     Return a new context menu
0132 */
0133 QMenu* DatapickerImage::createContextMenu() {
0134     QMenu* menu = new QMenu(nullptr);
0135     Q_EMIT requestProjectContextMenu(menu);
0136     return menu;
0137 }
0138 
0139 void DatapickerImage::createContextMenu(QMenu* menu) {
0140     Q_EMIT requestProjectContextMenu(menu);
0141 }
0142 
0143 //! Construct a primary view on me.
0144 /**
0145  * This method may be called multiple times during the life time of an Aspect, or it might not get
0146  * called at all. Aspects must not depend on the existence of a view for their operation.
0147  */
0148 QWidget* DatapickerImage::view() const {
0149     if (!m_partView) {
0150         m_view = new DatapickerImageView(const_cast<DatapickerImage*>(this));
0151         m_partView = m_view;
0152         connect(m_view, &DatapickerImageView::statusInfo, this, &DatapickerImage::statusInfo);
0153     }
0154     return m_partView;
0155 }
0156 
0157 bool DatapickerImage::exportView() const {
0158     auto* dlg = new ExportWorksheetDialog(m_view);
0159     dlg->setProjectFileName(const_cast<DatapickerImage*>(this)->project()->fileName());
0160     dlg->setFileName(name());
0161     bool ret;
0162     if ((ret = (dlg->exec() == QDialog::Accepted))) {
0163         const QString path = dlg->path();
0164         const auto format = dlg->exportFormat();
0165         const int resolution = dlg->exportResolution();
0166 
0167         WAIT_CURSOR;
0168         m_view->exportToFile(path, format, resolution);
0169         RESET_CURSOR;
0170     }
0171     delete dlg;
0172     return ret;
0173 }
0174 
0175 bool DatapickerImage::printView() {
0176     QPrinter printer;
0177     auto* dlg = new QPrintDialog(&printer, m_view);
0178     bool ret;
0179     dlg->setWindowTitle(i18nc("@title:window", "Print Datapicker Image"));
0180     if ((ret = (dlg->exec() == QDialog::Accepted)))
0181         m_view->print(&printer);
0182 
0183     delete dlg;
0184     return ret;
0185 }
0186 
0187 bool DatapickerImage::printPreview() const {
0188     auto* dlg = new QPrintPreviewDialog(m_view);
0189     connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &DatapickerImageView::print);
0190     return dlg->exec();
0191 }
0192 
0193 /*!
0194     Selects or deselects the Datapicker/DatapickerImage in the project explorer.
0195     This function is called in \c DatapickerImageView.
0196     The DatapickerImage gets deselected if there are selected items in the view,
0197     and selected if there are no selected items in the view.
0198 */
0199 void DatapickerImage::setSelectedInView(const bool b) {
0200     if (b)
0201         Q_EMIT childAspectSelectedInView(this);
0202     else
0203         Q_EMIT childAspectDeselectedInView(this);
0204 }
0205 
0206 void DatapickerImage::setSegmentsHoverEvent(const bool on) {
0207     m_segments->setAcceptHoverEvents(on);
0208 }
0209 
0210 QGraphicsScene* DatapickerImage::scene() const {
0211     Q_D(const DatapickerImage);
0212     return d->m_scene;
0213 }
0214 
0215 QRectF DatapickerImage::pageRect() const {
0216     Q_D(const DatapickerImage);
0217     return d->m_scene->sceneRect();
0218 }
0219 
0220 void DatapickerImage::setPlotImageType(const DatapickerImage::PlotImageType type) {
0221     Q_D(DatapickerImage);
0222     d->plotImageType = type;
0223     if (d->plotImageType == DatapickerImage::PlotImageType::ProcessedImage)
0224         d->discretize();
0225 
0226     Q_EMIT requestUpdate();
0227 }
0228 
0229 int DatapickerImage::currentSelectedReferencePoint() const {
0230     return m_currentRefPoint;
0231 }
0232 
0233 DatapickerImage::PlotImageType DatapickerImage::plotImageType() const {
0234     Q_D(const DatapickerImage);
0235     return d->plotImageType;
0236 }
0237 
0238 class DatapickerImageSetOriginalImageCmd : public QUndoCommand {
0239 public:
0240     DatapickerImageSetOriginalImageCmd(DatapickerImage::Private* target,
0241                                        const QImage& newImage,
0242                                        const QString& filename,
0243                                        bool embedded,
0244                                        const KLocalizedString& description,
0245                                        QUndoCommand* parent = nullptr)
0246         : QUndoCommand(parent)
0247         , m_img(newImage)
0248         , m_filename(filename)
0249         , m_embedded(embedded)
0250         , m_target(target) {
0251         setText(description.subs(m_target->name()).toString());
0252     }
0253     virtual void redo() override {
0254         const QImage tmp = m_target->q->originalPlotImage;
0255         const QString tmpFilename = m_target->q->fileName();
0256         const bool tmpEmbedded = m_target->q->embedded();
0257 
0258         if (m_embedded && !m_img.isNull())
0259             m_target->q->originalPlotImage = m_img;
0260         else
0261             m_target->q->originalPlotImage.load(m_filename);
0262         m_target->fileName = m_filename;
0263         m_target->embedded = m_embedded;
0264 
0265         if (tmpEmbedded)
0266             m_img = tmp;
0267         else
0268             m_img = QImage();
0269         m_filename = tmpFilename;
0270         m_embedded = tmpEmbedded;
0271         QUndoCommand::redo(); // redo all childs
0272 
0273         finalize();
0274         Q_EMIT m_target->q->fileNameChanged(m_target->fileName);
0275         Q_EMIT m_target->q->embeddedChanged(m_target->embedded);
0276     }
0277 
0278     virtual void undo() override {
0279         redo();
0280     }
0281 
0282     void finalize() {
0283         m_target->updateImage();
0284     }
0285 
0286 private:
0287     QImage m_img;
0288     QString m_filename;
0289     bool m_embedded;
0290     DatapickerImage::Private* m_target;
0291 };
0292 
0293 // 1. Image from clipboard, 2. file from path (embedded or not embedded)
0294 //     -> important to store qimage, because otherwise image is lost when doing undo
0295 // 1. Image from clipboard, 2. image from clipboard
0296 //     -> important to store qimage, because otherwise image is lost when doing undo
0297 // 1. Image from path (embedded or not embedded), 2. image from clipboard
0298 //     -> important to store qimage, because when doing redo after undo the image must be available
0299 // 1. Image from path (not embedded), 2. image from path
0300 //     -> storing qimage is not important
0301 // 1. Image from path (embedded), 2. image from path
0302 //     -> storing qimage is important because otherwise if undo and path is anymore valid image is anymore available
0303 
0304 void DatapickerImage::setImage(const QString& fileName, bool embedded) {
0305     return setImage(QImage(), fileName, embedded);
0306 }
0307 
0308 void DatapickerImage::setImage(const QImage& image, const QString& filename, bool embedded) {
0309     Q_D(DatapickerImage);
0310     if (image != originalPlotImage || filename != fileName() || embedded != this->embedded())
0311         exec(new DatapickerImageSetOriginalImageCmd(d, image, filename, embedded, ki18n("%1: upload image")));
0312 }
0313 
0314 /* =============================== getter methods for background options ================================= */
0315 BASIC_D_READER_IMPL(DatapickerImage, QString, fileName, fileName)
0316 BASIC_D_READER_IMPL(DatapickerImage, bool, isRelativeFilePath, isRelativeFilePath)
0317 BASIC_D_READER_IMPL(DatapickerImage, bool, embedded, embedded)
0318 BASIC_D_READER_IMPL(DatapickerImage, DatapickerImage::ReferencePoints, axisPoints, axisPoints)
0319 BASIC_D_READER_IMPL(DatapickerImage, DatapickerImage::EditorSettings, settings, settings)
0320 BASIC_D_READER_IMPL(DatapickerImage, float, rotationAngle, rotationAngle)
0321 BASIC_D_READER_IMPL(DatapickerImage, DatapickerImage::PointsType, plotPointsType, plotPointsType)
0322 BASIC_D_READER_IMPL(DatapickerImage, int, pointSeparation, pointSeparation)
0323 BASIC_D_READER_IMPL(DatapickerImage, int, minSegmentLength, minSegmentLength)
0324 
0325 // symbols
0326 Symbol* DatapickerImage::symbol() const {
0327     Q_D(const DatapickerImage);
0328     return d->symbol;
0329 }
0330 
0331 BASIC_D_READER_IMPL(DatapickerImage, bool, pointVisibility, pointVisibility)
0332 /* ============================ setter methods and undo commands  for background options  ================= */
0333 void DatapickerImage::setFileName(const QString& fileName) {
0334     setImage(fileName, embedded());
0335 }
0336 
0337 class DatapickerImageSetRelativeFilePathCmd : public StandardSetterCmd<DatapickerImage::Private, bool> {
0338 public:
0339     DatapickerImageSetRelativeFilePathCmd(DatapickerImage::Private* target, bool newValue, const KLocalizedString& description, QUndoCommand* parent = nullptr)
0340         : StandardSetterCmd<DatapickerImage::Private, bool>(target, &DatapickerImage::Private::isRelativeFilePath, newValue, description, parent) {
0341     }
0342     virtual void finalize() override {
0343         if (m_target->q->project()) {
0344             QString filename;
0345             if (m_target->isRelativeFilePath) {
0346                 // Calculate from absolute to relative
0347                 QFileInfo fi(m_target->q->project()->fileName());
0348                 filename = fi.absoluteDir().relativeFilePath(m_target->fileName);
0349             } else {
0350                 // Calculate from relative to absolute
0351                 QFileInfo fi(m_target->q->project()->fileName());
0352                 fi.setFile(m_target->fileName);
0353                 filename = fi.absoluteFilePath();
0354             }
0355             // setting relative is only possible if the image is not embedded!
0356             m_target->q->setImage(filename, false);
0357         }
0358         Q_EMIT m_target->q->relativeFilePathChanged(m_target->*m_field);
0359     }
0360 };
0361 
0362 void DatapickerImage::setRelativeFilePath(bool relative) {
0363     Q_D(DatapickerImage);
0364     if (relative != d->isRelativeFilePath) {
0365         beginMacro(i18n("%1: upload new image", name()));
0366         exec(new DatapickerImageSetRelativeFilePathCmd(d, relative, ki18n("%1: upload image")));
0367         endMacro();
0368     }
0369 }
0370 
0371 void DatapickerImage::setEmbedded(bool embedded) {
0372     Q_D(DatapickerImage);
0373     if (embedded != d->embedded) {
0374         if (embedded)
0375             setImage(originalPlotImage, fileName(), true);
0376         else
0377             setImage(fileName(), false);
0378     }
0379 }
0380 
0381 STD_SETTER_CMD_IMPL_S(DatapickerImage, SetRotationAngle, float, rotationAngle)
0382 void DatapickerImage::setRotationAngle(float angle) {
0383     Q_D(DatapickerImage);
0384     if (angle != d->rotationAngle)
0385         exec(new DatapickerImageSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle")));
0386 }
0387 
0388 STD_SETTER_CMD_IMPL_S(DatapickerImage, SetAxisPoints, DatapickerImage::ReferencePoints, axisPoints)
0389 void DatapickerImage::setAxisPoints(const DatapickerImage::ReferencePoints& points) {
0390     Q_D(DatapickerImage);
0391     if (memcmp(&points, &d->axisPoints, sizeof(points)) != 0) // valgrind: Conditional jump or move depends on uninitialised value(s)
0392         exec(new DatapickerImageSetAxisPointsCmd(d, points, ki18n("%1: set Axis points")));
0393 }
0394 
0395 STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetSettings, DatapickerImage::EditorSettings, settings, discretize)
0396 void DatapickerImage::setSettings(const DatapickerImage::EditorSettings& editorSettings) {
0397     Q_D(DatapickerImage);
0398     if (memcmp(&editorSettings, &d->settings, sizeof(editorSettings)) != 0)
0399         exec(new DatapickerImageSetSettingsCmd(d, editorSettings, ki18n("%1: set editor settings")));
0400 }
0401 
0402 STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetMinSegmentLength, int, minSegmentLength, makeSegments)
0403 void DatapickerImage::setminSegmentLength(const int value) {
0404     Q_D(DatapickerImage);
0405     if (d->minSegmentLength != value)
0406         exec(new DatapickerImageSetMinSegmentLengthCmd(d, value, ki18n("%1: set minimum segment length")));
0407     ;
0408 }
0409 
0410 STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointVisibility, bool, pointVisibility, retransform)
0411 void DatapickerImage::setPointVisibility(const bool on) {
0412     Q_D(DatapickerImage);
0413     if (on != d->pointVisibility)
0414         exec(new DatapickerImageSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible")));
0415 }
0416 
0417 void DatapickerImage::setPrinting(bool on) const {
0418     auto points = parentAspect()->children<DatapickerPoint>(ChildIndexFlag::Recursive | ChildIndexFlag::IncludeHidden);
0419     for (auto* point : points)
0420         point->setPrinting(on);
0421 }
0422 
0423 void DatapickerImage::setPlotPointsType(const PointsType pointsType) {
0424     Q_D(DatapickerImage);
0425     if (d->plotPointsType == pointsType)
0426         return;
0427 
0428     d->plotPointsType = pointsType;
0429 
0430     if (pointsType == DatapickerImage::PointsType::AxisPoints) {
0431         // clear image
0432         auto points = children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0433         if (!points.isEmpty()) {
0434             beginMacro(i18n("%1: remove all axis points", name()));
0435 
0436             for (auto* point : points)
0437                 point->remove();
0438             endMacro();
0439         }
0440         m_segments->setSegmentsVisible(false);
0441     } else if (pointsType == DatapickerImage::PointsType::CurvePoints) {
0442         m_segments->setSegmentsVisible(false);
0443 
0444         // make the reference points non-interactive
0445         const auto& points = children<DatapickerPoint>(ChildIndexFlag::IncludeHidden);
0446         for (auto* point : points) {
0447             auto* item = point->graphicsItem();
0448             item->setFlag(QGraphicsItem::ItemIsSelectable, false);
0449             item->setFlag(QGraphicsItem::ItemIsFocusable, false);
0450             item->setAcceptHoverEvents(false);
0451             item->setAcceptedMouseButtons(Qt::NoButton);
0452         }
0453     } else if (pointsType == DatapickerImage::PointsType::SegmentPoints) {
0454         d->makeSegments();
0455         m_segments->setSegmentsVisible(true);
0456     }
0457 }
0458 
0459 void DatapickerImage::setPointSeparation(const int value) {
0460     Q_D(DatapickerImage);
0461     d->pointSeparation = value;
0462 }
0463 
0464 void DatapickerImage::referencePointSelected(const DatapickerPoint* point) {
0465     const auto points = children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0466     for (int i = 0; i < points.count(); i++) {
0467         if (points.at(i) == point) {
0468             m_currentRefPoint = i;
0469             Q_EMIT referencePointSelected(i);
0470             return;
0471         }
0472     }
0473     m_currentRefPoint = -1;
0474 }
0475 
0476 // ##############################################################################
0477 // ######################  Private implementation ###############################
0478 // ##############################################################################
0479 DatapickerImagePrivate::DatapickerImagePrivate(DatapickerImage* owner)
0480     : q(owner)
0481     , pageRect(0, 0, 1000, 1000)
0482     , m_scene(new QGraphicsScene(pageRect)) {
0483 }
0484 
0485 QString DatapickerImagePrivate::name() const {
0486     return q->name();
0487 }
0488 
0489 void DatapickerImagePrivate::retransform() {
0490     if (q->isLoading())
0491         return;
0492     auto points = q->children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0493     for (auto* point : points)
0494         point->retransform();
0495 }
0496 
0497 bool DatapickerImagePrivate::uploadImage() {
0498     const bool rc = !q->originalPlotImage.isNull();
0499 
0500     if (rc) {
0501         // convert the image to 32bit-format if this is not the case yet
0502         QImage::Format format = q->originalPlotImage.format();
0503         if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_ARGB32_Premultiplied)
0504             q->originalPlotImage = q->originalPlotImage.convertToFormat(QImage::Format_RGB32);
0505 
0506         q->processedPlotImage = q->originalPlotImage;
0507         q->background = ImageEditor::findBackgroundColor(&q->originalPlotImage);
0508         // upload Histogram
0509         ImageEditor::uploadHistogram(q->intensityBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Intensity);
0510         ImageEditor::uploadHistogram(q->foregroundBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Foreground);
0511         ImageEditor::uploadHistogram(q->hueBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Hue);
0512         ImageEditor::uploadHistogram(q->saturationBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Saturation);
0513         ImageEditor::uploadHistogram(q->valueBins, &q->originalPlotImage, q->background, DatapickerImage::ColorAttributes::Value);
0514         discretize();
0515 
0516         // resize the screen
0517         double w = Worksheet::convertToSceneUnits(q->originalPlotImage.width(), Worksheet::Unit::Inch) / QApplication::primaryScreen()->physicalDotsPerInchX();
0518         double h = Worksheet::convertToSceneUnits(q->originalPlotImage.height(), Worksheet::Unit::Inch) / QApplication::primaryScreen()->physicalDotsPerInchY();
0519         m_scene->setSceneRect(0, 0, w, h);
0520         q->isLoaded = true;
0521     }
0522     return rc;
0523 }
0524 
0525 void DatapickerImagePrivate::discretize() {
0526     PERFTRACE(QLatin1String(Q_FUNC_INFO));
0527     if (plotImageType != DatapickerImage::PlotImageType::ProcessedImage)
0528         return;
0529 
0530     ImageEditor::discretize(&q->processedPlotImage, &q->originalPlotImage, settings, q->background);
0531 
0532     if (plotPointsType != DatapickerImage::PointsType::SegmentPoints)
0533         Q_EMIT q->requestUpdate();
0534     else
0535         makeSegments();
0536 }
0537 
0538 void DatapickerImagePrivate::makeSegments() {
0539     if (plotPointsType != DatapickerImage::PointsType::SegmentPoints)
0540         return;
0541 
0542     PERFTRACE(QLatin1String(Q_FUNC_INFO));
0543     q->m_segments->makeSegments(q->processedPlotImage);
0544     q->m_segments->setSegmentsVisible(true);
0545     Q_EMIT q->requestUpdate();
0546 }
0547 
0548 DatapickerImagePrivate::~DatapickerImagePrivate() {
0549     delete m_scene;
0550 }
0551 
0552 void DatapickerImagePrivate::updateImage() {
0553     WAIT_CURSOR;
0554     q->isLoaded = false;
0555 
0556     if (q->originalPlotImage.isNull()) {
0557         // hide segments if they are visible
0558         q->m_segments->setSegmentsVisible(false);
0559     } else
0560         uploadImage();
0561 
0562     auto points = q->parentAspect()->children<DatapickerPoint>(AbstractAspect::ChildIndexFlag::Recursive | AbstractAspect::ChildIndexFlag::IncludeHidden);
0563     if (!points.isEmpty()) {
0564         for (auto* point : points)
0565             point->remove();
0566     }
0567 
0568     Q_EMIT q->requestUpdate();
0569     Q_EMIT q->requestUpdateActions();
0570     RESET_CURSOR;
0571 }
0572 
0573 // ##############################################################################
0574 // ##################  Serialization/Deserialization  ###########################
0575 // ##############################################################################
0576 
0577 //! Save as XML
0578 void DatapickerImage::save(QXmlStreamWriter* writer) const {
0579     Q_D(const DatapickerImage);
0580     writer->writeStartElement(QStringLiteral("datapickerImage"));
0581     writeBasicAttributes(writer);
0582 
0583     // general properties
0584     writer->writeStartElement(QStringLiteral("general"));
0585     writer->writeAttribute(QStringLiteral("embedded"), QString::number(d->embedded));
0586     writer->writeAttribute(QStringLiteral("relativePath"), QString::number(d->isRelativeFilePath));
0587     writer->writeAttribute(QStringLiteral("fileName"), d->fileName);
0588     writer->writeAttribute(QStringLiteral("plotPointsType"), QString::number(static_cast<int>(d->plotPointsType)));
0589     writer->writeAttribute(QStringLiteral("pointVisibility"), QString::number(d->pointVisibility));
0590     writer->writeEndElement();
0591 
0592     // image data
0593     if (d->embedded && !originalPlotImage.isNull()) {
0594         writer->writeStartElement(QStringLiteral("data"));
0595         QByteArray data;
0596         QBuffer buffer(&data);
0597         buffer.open(QIODevice::WriteOnly);
0598         originalPlotImage.save(&buffer, "PNG");
0599         writer->writeCharacters(QLatin1String(data.toBase64()));
0600         writer->writeEndElement();
0601     }
0602 
0603     writer->writeStartElement(QStringLiteral("axisPoint"));
0604     writer->writeAttribute(QStringLiteral("graphType"), QString::number(static_cast<int>(d->axisPoints.type)));
0605     writer->writeAttribute(QStringLiteral("ternaryScale"), QString::number(d->axisPoints.ternaryScale));
0606     writer->writeAttribute(QStringLiteral("axisPointLogicalX1"), QString::number(d->axisPoints.logicalPos[0].x()));
0607     writer->writeAttribute(QStringLiteral("axisPointLogicalY1"), QString::number(d->axisPoints.logicalPos[0].y()));
0608     writer->writeAttribute(QStringLiteral("axisPointLogicalX2"), QString::number(d->axisPoints.logicalPos[1].x()));
0609     writer->writeAttribute(QStringLiteral("axisPointLogicalY2"), QString::number(d->axisPoints.logicalPos[1].y()));
0610     writer->writeAttribute(QStringLiteral("axisPointLogicalX3"), QString::number(d->axisPoints.logicalPos[2].x()));
0611     writer->writeAttribute(QStringLiteral("axisPointLogicalY3"), QString::number(d->axisPoints.logicalPos[2].y()));
0612     writer->writeAttribute(QStringLiteral("axisPointLogicalZ1"), QString::number(d->axisPoints.logicalPos[0].z()));
0613     writer->writeAttribute(QStringLiteral("axisPointLogicalZ2"), QString::number(d->axisPoints.logicalPos[1].z()));
0614     writer->writeAttribute(QStringLiteral("axisPointLogicalZ3"), QString::number(d->axisPoints.logicalPos[2].z()));
0615     writer->writeAttribute(QStringLiteral("axisPointSceneX1"), QString::number(d->axisPoints.scenePos[0].x()));
0616     writer->writeAttribute(QStringLiteral("axisPointSceneY1"), QString::number(d->axisPoints.scenePos[0].y()));
0617     writer->writeAttribute(QStringLiteral("axisPointSceneX2"), QString::number(d->axisPoints.scenePos[1].x()));
0618     writer->writeAttribute(QStringLiteral("axisPointSceneY2"), QString::number(d->axisPoints.scenePos[1].y()));
0619     writer->writeAttribute(QStringLiteral("axisPointSceneX3"), QString::number(d->axisPoints.scenePos[2].x()));
0620     writer->writeAttribute(QStringLiteral("axisPointSceneY3"), QString::number(d->axisPoints.scenePos[2].y()));
0621     writer->writeEndElement();
0622 
0623     // editor and segment settings
0624     writer->writeStartElement(QStringLiteral("editorSettings"));
0625     writer->writeAttribute(QStringLiteral("plotImageType"), QString::number(static_cast<int>(d->plotImageType)));
0626     writer->writeAttribute(QStringLiteral("rotationAngle"), QString::number(d->rotationAngle));
0627     writer->writeAttribute(QStringLiteral("minSegmentLength"), QString::number(d->minSegmentLength));
0628     writer->writeAttribute(QStringLiteral("pointSeparation"), QString::number(d->pointSeparation));
0629     writer->writeAttribute(QStringLiteral("foregroundThresholdHigh"), QString::number(d->settings.foregroundThresholdHigh));
0630     writer->writeAttribute(QStringLiteral("foregroundThresholdLow"), QString::number(d->settings.foregroundThresholdLow));
0631     writer->writeAttribute(QStringLiteral("hueThresholdHigh"), QString::number(d->settings.hueThresholdHigh));
0632     writer->writeAttribute(QStringLiteral("hueThresholdLow"), QString::number(d->settings.hueThresholdLow));
0633     writer->writeAttribute(QStringLiteral("intensityThresholdHigh"), QString::number(d->settings.intensityThresholdHigh));
0634     writer->writeAttribute(QStringLiteral("intensityThresholdLow"), QString::number(d->settings.intensityThresholdLow));
0635     writer->writeAttribute(QStringLiteral("saturationThresholdHigh"), QString::number(d->settings.saturationThresholdHigh));
0636     writer->writeAttribute(QStringLiteral("saturationThresholdLow"), QString::number(d->settings.saturationThresholdLow));
0637     writer->writeAttribute(QStringLiteral("valueThresholdHigh"), QString::number(d->settings.valueThresholdHigh));
0638     writer->writeAttribute(QStringLiteral("valueThresholdLow"), QString::number(d->settings.valueThresholdLow));
0639     writer->writeEndElement();
0640 
0641     // Symbols
0642     d->symbol->save(writer);
0643 
0644     // serialize all children
0645     for (auto* child : children<AbstractAspect>(ChildIndexFlag::IncludeHidden))
0646         child->save(writer);
0647 
0648     writer->writeEndElement();
0649 }
0650 
0651 //! Load from XML
0652 bool DatapickerImage::load(XmlStreamReader* reader, bool preview) {
0653     if (!readBasicAttributes(reader))
0654         return false;
0655 
0656     Q_D(DatapickerImage);
0657     QXmlStreamAttributes attribs;
0658     QString str;
0659 
0660     while (!reader->atEnd()) {
0661         reader->readNext();
0662         if (reader->isEndElement() && reader->name() == QLatin1String("datapickerImage"))
0663             break;
0664 
0665         if (!reader->isStartElement())
0666             continue;
0667 
0668         if (!preview && reader->name() == QLatin1String("general")) {
0669             attribs = reader->attributes();
0670 
0671             READ_INT_VALUE("embedded", embedded, bool);
0672             READ_INT_VALUE("relativePath", isRelativeFilePath, bool);
0673             str = attribs.value(QStringLiteral("fileName")).toString();
0674             d->fileName = str;
0675 
0676             READ_INT_VALUE("plotPointsType", plotPointsType, DatapickerImage::PointsType);
0677             READ_INT_VALUE("pointVisibility", pointVisibility, bool);
0678         } else if (reader->name() == QLatin1String("data")) {
0679             QByteArray ba = QByteArray::fromBase64(reader->readElementText().toLatin1());
0680             if (!originalPlotImage.loadFromData(ba))
0681                 reader->raiseWarning(i18n("Failed to read image data"));
0682         } else if (!preview && reader->name() == QLatin1String("axisPoint")) {
0683             attribs = reader->attributes();
0684             READ_INT_VALUE_DIRECT("graphType", d->axisPoints.type, GraphType);
0685             READ_INT_VALUE("ternaryScale", axisPoints.ternaryScale, int);
0686 
0687             str = attribs.value(QStringLiteral("axisPointLogicalX1")).toString();
0688             if (str.isEmpty())
0689                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalX1"));
0690             else
0691                 d->axisPoints.logicalPos[0].setX(str.toDouble());
0692 
0693             str = attribs.value(QStringLiteral("axisPointLogicalY1")).toString();
0694             if (str.isEmpty())
0695                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalY1"));
0696             else
0697                 d->axisPoints.logicalPos[0].setY(str.toDouble());
0698 
0699             str = attribs.value(QStringLiteral("axisPointLogicalZ1")).toString();
0700             if (str.isEmpty())
0701                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalZ1"));
0702             else
0703                 d->axisPoints.logicalPos[0].setZ(str.toDouble());
0704 
0705             str = attribs.value(QStringLiteral("axisPointLogicalX2")).toString();
0706             if (str.isEmpty())
0707                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalX2"));
0708             else
0709                 d->axisPoints.logicalPos[1].setX(str.toDouble());
0710 
0711             str = attribs.value(QStringLiteral("axisPointLogicalY2")).toString();
0712             if (str.isEmpty())
0713                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalY2"));
0714             else
0715                 d->axisPoints.logicalPos[1].setY(str.toDouble());
0716 
0717             str = attribs.value(QStringLiteral("axisPointLogicalZ2")).toString();
0718             if (str.isEmpty())
0719                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalZ2"));
0720             else
0721                 d->axisPoints.logicalPos[1].setZ(str.toDouble());
0722 
0723             str = attribs.value(QStringLiteral("axisPointLogicalX3")).toString();
0724             if (str.isEmpty())
0725                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalX3"));
0726             else
0727                 d->axisPoints.logicalPos[2].setX(str.toDouble());
0728 
0729             str = attribs.value(QStringLiteral("axisPointLogicalY3")).toString();
0730             if (str.isEmpty())
0731                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalY3"));
0732             else
0733                 d->axisPoints.logicalPos[2].setY(str.toDouble());
0734 
0735             str = attribs.value(QStringLiteral("axisPointLogicalZ3")).toString();
0736             if (str.isEmpty())
0737                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointLogicalZ3"));
0738             else
0739                 d->axisPoints.logicalPos[2].setZ(str.toDouble());
0740 
0741             str = attribs.value(QStringLiteral("axisPointSceneX1")).toString();
0742             if (str.isEmpty())
0743                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointSceneX1"));
0744             else
0745                 d->axisPoints.scenePos[0].setX(str.toDouble());
0746 
0747             str = attribs.value(QStringLiteral("axisPointSceneY1")).toString();
0748             if (str.isEmpty())
0749                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointSceneY1"));
0750             else
0751                 d->axisPoints.scenePos[0].setY(str.toDouble());
0752 
0753             str = attribs.value(QStringLiteral("axisPointSceneX2")).toString();
0754             if (str.isEmpty())
0755                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointSceneX2"));
0756             else
0757                 d->axisPoints.scenePos[1].setX(str.toDouble());
0758 
0759             str = attribs.value(QStringLiteral("axisPointSceneY2")).toString();
0760             if (str.isEmpty())
0761                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointSceneY2"));
0762             else
0763                 d->axisPoints.scenePos[1].setY(str.toDouble());
0764 
0765             str = attribs.value(QStringLiteral("axisPointSceneX3")).toString();
0766             if (str.isEmpty())
0767                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointSceneX3"));
0768             else
0769                 d->axisPoints.scenePos[2].setX(str.toDouble());
0770 
0771             str = attribs.value(QStringLiteral("axisPointSceneY3")).toString();
0772             if (str.isEmpty())
0773                 reader->raiseMissingAttributeWarning(QStringLiteral("axisPointSceneY3"));
0774             else
0775                 d->axisPoints.scenePos[2].setY(str.toDouble());
0776 
0777         } else if (!preview && reader->name() == QLatin1String("editorSettings")) {
0778             attribs = reader->attributes();
0779 
0780             READ_INT_VALUE("plotImageType", plotImageType, DatapickerImage::PlotImageType);
0781             READ_DOUBLE_VALUE("rotationAngle", rotationAngle);
0782             READ_INT_VALUE("minSegmentLength", minSegmentLength, int);
0783             READ_INT_VALUE("pointSeparation", pointSeparation, int);
0784             READ_INT_VALUE("foregroundThresholdHigh", settings.foregroundThresholdHigh, int);
0785             READ_INT_VALUE("foregroundThresholdLow", settings.foregroundThresholdLow, int);
0786             READ_INT_VALUE("hueThresholdHigh", settings.hueThresholdHigh, int);
0787             READ_INT_VALUE("hueThresholdLow", settings.hueThresholdLow, int);
0788             READ_INT_VALUE("intensityThresholdHigh", settings.intensityThresholdHigh, int);
0789             READ_INT_VALUE("intensityThresholdLow", settings.intensityThresholdLow, int);
0790             READ_INT_VALUE("saturationThresholdHigh", settings.saturationThresholdHigh, int);
0791             READ_INT_VALUE("saturationThresholdLow", settings.saturationThresholdLow, int);
0792             READ_INT_VALUE("valueThresholdHigh", settings.valueThresholdHigh, int);
0793             READ_INT_VALUE("valueThresholdLow", settings.valueThresholdLow, int);
0794         } else if (!preview && reader->name() == QLatin1String("symbolProperties")) {
0795             // old serialization that was used before the switch to Symbol::load().
0796             // in the old serialization the symbol properties and "point visibility" where saved
0797             // under "symbolProperties".
0798             attribs = reader->attributes();
0799 
0800             str = attribs.value(QStringLiteral("pointRotationAngle")).toString();
0801             if (str.isEmpty())
0802                 reader->raiseMissingAttributeWarning(QStringLiteral("pointRotationAngle"));
0803             else
0804                 d->symbol->setRotationAngle(str.toDouble());
0805 
0806             str = attribs.value(QStringLiteral("pointOpacity")).toString();
0807             if (str.isEmpty())
0808                 reader->raiseMissingAttributeWarning(QStringLiteral("pointOpacity"));
0809             else
0810                 d->symbol->setOpacity(str.toDouble());
0811 
0812             str = attribs.value(QStringLiteral("pointSize")).toString();
0813             if (str.isEmpty())
0814                 reader->raiseMissingAttributeWarning(QStringLiteral("pointSize"));
0815             else
0816                 d->symbol->setSize(str.toDouble());
0817 
0818             str = attribs.value(QStringLiteral("pointStyle")).toString();
0819             if (str.isEmpty())
0820                 reader->raiseMissingAttributeWarning(QStringLiteral("pointStyle"));
0821             else
0822                 d->symbol->setStyle(static_cast<Symbol::Style>(str.toInt()));
0823 
0824             // brush
0825             QBrush brush;
0826             str = attribs.value(QStringLiteral("brush_style")).toString();
0827             if (str.isEmpty())
0828                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_style"));
0829             else
0830                 brush.setStyle(static_cast<Qt::BrushStyle>(str.toInt()));
0831 
0832             QColor color;
0833             str = attribs.value(QStringLiteral("brush_color_r")).toString();
0834             if (str.isEmpty())
0835                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_color_r"));
0836             else
0837                 color.setRed(str.toInt());
0838 
0839             str = attribs.value(QStringLiteral("brush_color_g")).toString();
0840             if (str.isEmpty())
0841                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_color_g"));
0842             else
0843                 color.setGreen(str.toInt());
0844 
0845             str = attribs.value(QStringLiteral("brush_color_b")).toString();
0846             if (str.isEmpty())
0847                 reader->raiseMissingAttributeWarning(QStringLiteral("brush_color_b"));
0848             else
0849                 color.setBlue(str.toInt());
0850 
0851             brush.setColor(color);
0852             d->symbol->setBrush(brush);
0853 
0854             // pen
0855             QPen pen;
0856             str = attribs.value(QStringLiteral("style")).toString();
0857             if (str.isEmpty())
0858                 reader->raiseMissingAttributeWarning(QStringLiteral("style"));
0859             else
0860                 pen.setStyle(static_cast<Qt::PenStyle>(str.toInt()));
0861 
0862             str = attribs.value(QStringLiteral("color_r")).toString();
0863             if (str.isEmpty())
0864                 reader->raiseMissingAttributeWarning(QStringLiteral("color_r"));
0865             else
0866                 color.setRed(str.toInt());
0867 
0868             str = attribs.value(QStringLiteral("color_g")).toString();
0869             if (str.isEmpty())
0870                 reader->raiseMissingAttributeWarning(QStringLiteral("color_g"));
0871             else
0872                 color.setGreen(str.toInt());
0873 
0874             str = attribs.value(QStringLiteral("color_b")).toString();
0875             if (str.isEmpty())
0876                 reader->raiseMissingAttributeWarning(QStringLiteral("color_b"));
0877             else
0878                 color.setBlue(str.toInt());
0879 
0880             pen.setColor(color);
0881 
0882             str = attribs.value(QStringLiteral("width")).toString();
0883             if (str.isEmpty())
0884                 reader->raiseMissingAttributeWarning(QStringLiteral("width"));
0885             else
0886                 pen.setWidthF(str.toDouble());
0887 
0888             d->symbol->setPen(pen);
0889 
0890             READ_INT_VALUE("pointVisibility", pointVisibility, bool);
0891         } else if (!preview && reader->name() == QLatin1String("symbols")) {
0892             d->symbol->load(reader, preview);
0893         } else if (reader->name() == QLatin1String("datapickerPoint")) {
0894             auto* datapickerPoint = new DatapickerPoint(QString());
0895             if (!datapickerPoint->load(reader, preview)) {
0896                 delete datapickerPoint;
0897                 return false;
0898             } else {
0899                 datapickerPoint->setHidden(true);
0900                 datapickerPoint->setIsReferencePoint(true);
0901                 addChild(datapickerPoint);
0902             }
0903         } else { // unknown element
0904             reader->raiseUnknownElementWarning();
0905             if (!reader->skipToEndElement())
0906                 return false;
0907         }
0908     }
0909 
0910     // No undo redo
0911     if (originalPlotImage.isNull())
0912         originalPlotImage.load(d->fileName);
0913     d->uploadImage();
0914     d->retransform();
0915     return true;
0916 }