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 }