1 /*
2 * Copyright (c) 2005-2019 Libor Pecháček.
3 * Copyright 2020 Kai Pastor
4 *
5 * This file is part of CoVe
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "mainform.h"
22 #include "ui_mainform.h"
23
24 // IWYU pragma: no_include <algorithm>
25 #include <cstdlib>
26 #include <ctime>
27 #include <iosfwd>
28 #include <iterator>
29 #include <utility>
30
31 #include <QtGlobal>
32 #include <QByteArray>
33 #include <QColor>
34 #include <QComboBox>
35 #include <QDir>
36 #include <QException>
37 #include <QFileInfo>
38 #include <QFileDialog>
39 #include <QGridLayout>
40 #include <QGroupBox>
41 #include <QIcon>
42 #include <QImage>
43 #include <QImageReader>
44 #include <QLabel>
45 #include <QList>
46 #include <QMessageBox>
47 #include <QPainter>
48 #include <QPixmap>
49 #include <QPoint>
50 #include <QPointF>
51 #include <QPushButton>
52 #include <QSpinBox>
53 #include <QTabWidget>
54 #include <QUndoStack>
55
56 #include "core/map.h"
57 #include "core/map_coord.h"
58 #include "core/map_part.h"
59 #include "core/objects/object.h"
60 #include "core/symbols/line_symbol.h"
61 #include "core/symbols/symbol.h"
62 #include "gui/widgets/symbol_dropdown.h"
63 #include "templates/template.h"
64 #include "templates/template_image.h"
65 #include "undo/object_undo.h"
66
67 #include "libvectorizer/Concurrency.h"
68 #include "libvectorizer/FIRFilter.h"
69 #include "libvectorizer/Polygons.h"
70 #include "libvectorizer/Vectorizer.h"
71
72 #include "ImageView.h"
73 #include "PolygonsView.h"
74 #include "Settings.h"
75 #include "UIProgressDialog.h"
76 #include "classificationconfigform.h"
77 #include "colorseditform.h"
78 #include "vectorizationconfigform.h"
79
80 using namespace std;
81 namespace cove {
82
83 class ProgressObserver;
84
85 //@{
86 /*! \defgroup gui GUI classes */
87
88 /*! \class mainForm
89 \brief Main form that is used as centralWidget() of QMainWindow.
90 */
91
92 /*! \var Ui::mainForm mainForm::ui
93 User interface as created by Qt Designer.
94 */
95
96 /*! \var UIProgressDialog* mainForm::progressDialog
97 Progress dialog pointer. Is 0 when there is no progress dialog currently
98 displayed.
99 */
100
101 /*! \var Vectorizer* mainForm::vectorizerApp
102 Vectorizer application.
103 \sa Vectorizer
104 */
105
106 /*! \var QString mainForm::imageFileName
107 Filename of the currently loaded image.
108 */
109
110 /*! \var QImage mainForm::imageBitmap
111 The currently loaded image.
112 */
113
114 /*! \var QImage mainForm::classifiedBitmap
115 The classified image on the colors tab.
116 */
117
118 /*! \var QImage mainForm::bwBitmap
119 The BW image on the thinning tab.
120 */
121
122 /*! \var bool mainForm::rollbackHistory
123 Boolean indicating whether the history was committed.
124 \sa prepareBWImageHistory, commitBWImageHistory
125 */
126
127 /*! \var QList<QImage> mainForm::bwBitmapHistory
128 Container for all the images in the history.
129 */
130
131 /*! \var QList<QImage>::iterator mainForm::bwBitmapHistoryIterator
132 Pointer to \a bwBitmapHistory.
133 */
134
135 /*! \var int mainForm::numberOfColorButtons
136 Number of color buttons in the ColorButtonsGroup on the Colors tab.
137 \sa colorButtons
138 */
139
140 /*! \var QPushButton** mainForm::colorButtons
141 Array of pointers to color buttons in the ColorButtonsGroup on the Colors tab.
142 \sa numberOfColorButtons
143 */
144
145 /*! \var Settings mainForm::settings
146 Current status data (configuration variables).
147 */
148
149 /*! \var QDoubleValidator mainForm::positiveDoubleValid
150 Input validator for LineEdit.
151 */
152
153 //! Constructor initializing the menu, actions and connecting signals from
154 //! actions
mainForm(QWidget * parent,OpenOrienteering::Map * map,OpenOrienteering::TemplateImage * templ,Qt::WindowFlags flags)155 mainForm::mainForm(QWidget* parent, OpenOrienteering::Map* map,
156 OpenOrienteering::TemplateImage* templ,
157 Qt::WindowFlags flags)
158 : QDialog(parent, flags)
159 , ooMap(map)
160 , ooTempl(templ)
161 {
162 ui.setupUi(this);
163
164 vectorizerApp = nullptr;
165
166 setTabEnabled(ui.imageTab, true);
167 setTabEnabled(ui.thinningTab, false);
168 setTabEnabled(ui.colorsTab, false);
169
170 ui.howManyColorsSpinBox->setValue(settings.getInt("nColors"));
171
172 bwBitmapUndo = new QUndoStack(this);
173 connect(bwBitmapUndo, SIGNAL(canUndoChanged(bool)), ui.bwImageHistoryBack, SLOT(setEnabled(bool)));
174 connect(bwBitmapUndo, SIGNAL(canRedoChanged(bool)), ui.bwImageHistoryForward, SLOT(setEnabled(bool)));
175
176 loadImage(templ->getImage(), templ->getTemplateFilename());
177
178 ui.symbolComboBox->init(map, OpenOrienteering::Symbol::Line);
179 ui.symbolComboBox->setCurrentIndex(std::min(ui.symbolComboBox->count() - 1, 1));
180 }
181
~mainForm()182 mainForm::~mainForm()
183 {
184 clearColorButtonsGroup();
185 }
186
187 //! Clears the Thinning tab, i.e. removes displayed image and polygons
clearBWImageTab()188 void mainForm::clearBWImageTab()
189 {
190 bwBitmapUndo->clear();
191 bwBitmapVectorizable = false;
192 ui.bwImageView->setPolygons(PolygonList());
193 ui.saveVectorsButton->setEnabled(false);
194 ui.bwImageView->setImage(nullptr);
195 }
196
197 //! Clears the Colors tab, i.e. removes displayed image and color buttons
clearColorsTab()198 void mainForm::clearColorsTab()
199 {
200 if (!imageBitmap.isNull())
201 {
202 ui.classifiedColorsView->setImage(&imageBitmap);
203 }
204 else
205 {
206 ui.classifiedColorsView->setImage(nullptr);
207 }
208 clearColorButtonsGroup();
209 }
210
211 //! Loads image into form.
loadImage(const QImage & imageToLoad,const QString & imageName)212 void mainForm::loadImage(const QImage& imageToLoad, const QString& imageName)
213 {
214 imageFileName = imageName;
215
216 // no alpha channel desired in the vectorized image
217 // we also want to have white background to mimic Mapper look and feel
218 imageBitmap = QImage(imageToLoad.size(), QImage::Format_RGB32);
219 imageBitmap.fill(Qt::white);
220 QPainter painter(&imageBitmap);
221 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
222 painter.drawImage(0, 0, imageToLoad);
223 painter.end();
224
225 afterLoadImage();
226 }
227
228 //! Performs form modifications after image load.
afterLoadImage()229 void mainForm::afterLoadImage()
230 {
231 ui.mainTabWidget->setCurrentIndex(ui.mainTabWidget->indexOf(ui.imageTab));
232 setTabEnabled(ui.thinningTab, false);
233 clearBWImageTab();
234 setTabEnabled(ui.colorsTab, false);
235 clearColorsTab();
236 ui.imageView->setImage(&imageBitmap);
237 ui.imageView->reset();
238 ui.classifiedColorsView->setImage(&imageBitmap);
239 ui.classifiedColorsView->reset();
240
241 if (!imageBitmap.isNull())
242 {
243 if (imageBitmap.format() == QImage::Format_Mono ||
244 imageBitmap.format() == QImage::Format_MonoLSB)
245 {
246 setTabEnabled(ui.thinningTab, true);
247 }
248 setTabEnabled(ui.colorsTab, true);
249 }
250 }
251
252 //! Simple About dialog.
aboutDialog()253 void mainForm::aboutDialog()
254 {
255 QMessageBox::about(this, tr("About"),
256 trUtf8("<h3>COntour VEctorizer</h3><br />"
257 "Author: Libor Pecháček<br />"
258 "License: GPL"));
259 }
260
261 /*! Return color randomly created from image pixels. It takes 5 scanlines and
262 * makes an average color of its pixels. */
getColorFromImage(const QImage & image)263 QRgb mainForm::getColorFromImage(const QImage& image)
264 {
265 srand(time(nullptr)); // NOLINT
266 unsigned long r, g, b, divisor;
267 r = g = b = divisor = 0;
268 for (int a = 0; a < 5; a++)
269 {
270 int line = rand() % image.height(); // NOLINT
271 int w = image.width();
272 for (int x = 0; x < w; x++)
273 {
274 QRgb c = image.pixel(x, line);
275 r += qRed(c);
276 g += qGreen(c);
277 b += qBlue(c);
278 divisor++;
279 }
280 }
281 return qRgb(r / divisor, g / divisor, b / divisor);
282 }
283
284 /*! Performs actions requested by clicking 'Now' button on Colors tab.
285 \sa classificationFinished() */
on_runClassificationButton_clicked()286 void mainForm::on_runClassificationButton_clicked()
287 {
288 ui.runClassificationButton->setEnabled(false);
289 vectorizerApp = std::make_unique<Vectorizer>(imageBitmap);
290 UIProgressDialog progressDialog(tr("Colors classification in progress"),
291 tr("Cancel"), this);
292 auto totalProgress = Concurrency::TransformedProgress{progressDialog, 0.5};
293 switch (settings.getInt("learnMethod"))
294 {
295 case 0:
296 vectorizerApp->setClassificationMethod(Vectorizer::KOHONEN_CLASSIC);
297 break;
298 case 1:
299 vectorizerApp->setClassificationMethod(Vectorizer::KOHONEN_BATCH);
300 break;
301 }
302 vectorizerApp->setP(settings.getDouble("p"));
303 switch (settings.getInt("colorSpace"))
304 {
305 case 0:
306 vectorizerApp->setColorSpace(Vectorizer::COLSPC_RGB);
307 break;
308 case 1:
309 vectorizerApp->setColorSpace(Vectorizer::COLSPC_HSV);
310 break;
311 }
312 switch (settings.getInt("alphaStrategy"))
313 {
314 case 0:
315 vectorizerApp->setAlphaStrategy(Vectorizer::ALPHA_CLASSIC);
316 break;
317 case 1:
318 vectorizerApp->setAlphaStrategy(Vectorizer::ALPHA_NEUQUANT);
319 break;
320 }
321 switch (settings.getInt("patternStrategy"))
322 {
323 case 0:
324 vectorizerApp->setPatternStrategy(Vectorizer::PATTERN_RANDOM);
325 break;
326 case 1:
327 vectorizerApp->setPatternStrategy(Vectorizer::PATTERN_NEUQUANT);
328 break;
329 }
330 vectorizerApp->setInitAlpha(settings.getDouble("initAlpha"));
331 vectorizerApp->setMinAlpha(settings.getDouble("minAlpha"));
332 vectorizerApp->setQ(settings.getDouble("q"));
333 vectorizerApp->setE(settings.getInt("E"));
334 int nColors = settings.getInt("nColors");
335 vectorizerApp->setNumberOfColors(nColors);
336 std::vector<QRgb> colors;
337 switch (settings.getInt("initColorsSource"))
338 {
339 case ColorsEditForm::Random:
340 break;
341 case ColorsEditForm::RandomFromImage:
342 colors.resize(nColors);
343 for (auto& c : colors)
344 c = getColorFromImage(imageBitmap);
345 break;
346 case ColorsEditForm::Predefined:
347 colors = settings.getInitColors();
348 break;
349 }
350 vectorizerApp->setInitColors(colors);
351 auto classification = [](Vectorizer* v, ProgressObserver& o) -> bool {
352 return v->performClassification(&o);
353 };
354 Concurrency::process<bool>(&totalProgress, classification, vectorizerApp.get());
355
356 auto colorsFound = vectorizerApp->getClassifiedColors();
357 setColorButtonsGroup(colorsFound);
358
359 // in case there are some colors (learning was not aborted)
360 if (colorsFound.size())
361 {
362 double quality;
363 totalProgress.offset = 50.0;
364 QImage newClassifiedBitmap =
365 vectorizerApp->getClassifiedImage(&quality, &totalProgress);
366 if (!newClassifiedBitmap.isNull())
367 {
368 classifiedBitmap = newClassifiedBitmap;
369 ui.classifiedColorsView->setImage(&classifiedBitmap);
370 ui.learnQualityLabel->setText(QString("LQ: %1").arg(quality));
371 }
372 else
373 {
374 clearColorButtonsGroup();
375 }
376 }
377 else
378 {
379 ui.learnQualityLabel->setText(QString("LQ: -"));
380 }
381 ui.runClassificationButton->setEnabled(true);
382 }
383
384 /*! Removes all color buttons from the frame on Colors tab.
385 \sa setColorButtonsGroup(QRgb* colors, int nColors) */
clearColorButtonsGroup()386 void mainForm::clearColorButtonsGroup()
387 {
388 setTabEnabled(ui.thinningTab, false);
389 for (auto button : colorButtons)
390 {
391 disconnect(button);
392 ui.gridLayout->removeWidget(button);
393 button->deleteLater();
394 }
395 colorButtons.clear();
396 }
397
398 /*! Creates color buttons in the frame on Colors tab.
399 \sa clearColorButtonsGroup() */
setColorButtonsGroup(std::vector<QRgb> colors)400 void mainForm::setColorButtonsGroup(std::vector<QRgb> colors)
401 {
402 clearColorButtonsGroup();
403 auto nColors = colors.size();
404 if (!nColors) return;
405 colorButtons.resize(nColors + 1);
406 unsigned i;
407 for (i = 0; i < nColors; i++)
408 {
409 QPixmap buttonFace(20, 20);
410 buttonFace.fill(QColor(colors[i]));
411 QPushButton* button =
412 new QPushButton(buttonFace, QString(), ui.colorButtonsGroup);
413 ui.gridLayout->addWidget(button, i % 2, i / 2);
414 colorButtons[i] = button;
415 button->setCheckable(true);
416 button->setMaximumSize(30, 30);
417 button->setMinimumSize(10, 10);
418 button->show();
419 connect(button, SIGNAL(toggled(bool)), this,
420 SLOT(colorButtonToggled(bool)));
421 }
422 QPushButton* button = new QPushButton(ui.colorButtonsGroup);
423 ui.gridLayout->addWidget(button, i % 2, i / 2);
424 colorButtons[i] = button;
425 button->setMaximumSize(30, 30);
426 button->setMinimumSize(10, 10);
427 button->setToolTip(tr("Set these colors as initial."));
428 button->setText(tr("IC"));
429 button->show();
430 connect(button, SIGNAL(clicked(bool)), this, SLOT(setInitialColors(bool)));
431 ui.colorButtonsGroup->adjustSize();
432 }
433
434 //! Performs action requested by clicking IC button in the color buttons frame,
435 // i.e. sets current colors as initial.
setInitialColors(bool)436 void mainForm::setInitialColors(bool /*on*/)
437 {
438 auto colorsFound = vectorizerApp->getClassifiedColors();
439 auto n = colorsFound.size();
440 if (!n) return;
441 auto comments = std::vector<QString>(n);
442 settings.setInitColors(colorsFound, comments);
443 }
444
445 //! Invoked by clicking a color button in the color buttons frame, changes the
446 // classified bitmap palette so that pixels of current selected colors appear in
447 // dark orange.
colorButtonToggled(bool)448 void mainForm::colorButtonToggled(bool /*on*/)
449 {
450 int sels = 0;
451 auto colorsFound = vectorizerApp->getClassifiedColors();
452 auto selectedColors = getSelectedColors();
453 for (std::size_t i = 0; i < colorButtons.size(); i++)
454 {
455 classifiedBitmap.setColor(
456 i, selectedColors[i] ? (sels++, qRgb(255, 27, 0)) : colorsFound[i]);
457 }
458 setTabEnabled(ui.thinningTab, sels != 0);
459 ui.classifiedColorsView->setImage(&classifiedBitmap);
460 ui.classifiedColorsView->update();
461 }
462
463 //! Returns bool array indicating which colors are selected by color buttons.
getSelectedColors()464 std::vector<bool> mainForm::getSelectedColors()
465 {
466 auto n = vectorizerApp->getClassifiedColors().size();
467 // do not allow different number of colors and buttons
468 Q_ASSERT(colorButtons.size() >= n);
469
470 auto retval = std::vector<bool>(n);
471 // copy states of the buttons into array
472 for (std::size_t i = 0; i < colorButtons.size(); i++)
473 retval[i] = colorButtons[i]->isChecked();
474
475 return retval;
476 }
477
478 //! Performs generating the BW image on change to Thinning tab.
on_mainTabWidget_currentChanged(int tabindex)479 void mainForm::on_mainTabWidget_currentChanged(int tabindex)
480 {
481 if (ui.mainTabWidget->widget(tabindex) != ui.thinningTab) return;
482
483 if ((imageBitmap.format() == QImage::Format_Mono ||
484 imageBitmap.format() == QImage::Format_MonoLSB) &&
485 colorButtons.empty())
486 {
487 clearBWImageTab();
488 bwBitmap = imageBitmap;
489 ui.bwImageView->setImage(&bwBitmap);
490 ui.bwImageView->reset();
491 return;
492 }
493
494 auto selectedColors = getSelectedColors();
495 UIProgressDialog progressDialog(tr("Creating B/W image"), tr("Cancel"), this);
496 QImage newBWBitmap =
497 vectorizerApp->getBWImage(selectedColors, &progressDialog);
498 if (newBWBitmap.isNull())
499 {
500 // no BW image
501 clearBWImageTab();
502 setTabEnabled(ui.thinningTab, false);
503 return;
504 }
505
506 const auto* undoCommand = static_cast<const BwBitmapUndoStep*>
507 (bwBitmapUndo->command(0));
508 if (!undoCommand
509 || undoCommand->image.constBits() != newBWBitmap.constBits())
510 {
511 // new BW image
512 clearBWImageTab();
513 bwBitmap = newBWBitmap;
514 ui.bwImageView->setImage(&bwBitmap);
515 ui.bwImageView->reset();
516 }
517 setTabEnabled(ui.thinningTab, true);
518 }
519
520 //! Performs one morphological operation on the bwImage.
performMorphologicalOperation(Vectorizer::MorphologicalOperation mo)521 bool mainForm::performMorphologicalOperation(
522 Vectorizer::MorphologicalOperation mo)
523 {
524 QString text;
525 bool transVectorizable = false;
526
527 switch (mo)
528 {
529 case Vectorizer::THINNING_ROSENFELD:
530 text = tr("Thinning B/W image");
531 transVectorizable = true;
532 break;
533 case Vectorizer::PRUNING:
534 text = tr("Pruning B/W image");
535 transVectorizable = bwBitmapVectorizable;
536 break;
537 case Vectorizer::EROSION:
538 text = tr("Eroding B/W image");
539 transVectorizable = bwBitmapVectorizable; // formally, does not change the status
540 break;
541 case Vectorizer::DILATION:
542 text = tr("Dilating B/W image");
543 transVectorizable = false;
544 break;
545 }
546 UIProgressDialog progressDialog(text, tr("Cancel"), this);
547 QImage transBitmap =
548 Vectorizer::getTransformedImage(bwBitmap, mo, &progressDialog);
549
550 if (transBitmap.isNull())
551 return false;
552
553 // store the current image onto undo stack
554 auto* command = new BwBitmapUndoStep(*this, bwBitmap, bwBitmapVectorizable);
555 bwBitmapUndo->push(command);
556
557 bwBitmap = transBitmap;
558 bwBitmapVectorizable = transVectorizable;
559 ui.bwImageView->setImage(&bwBitmap);
560 return true;
561 }
562
563 //! Returns one image back in the history.
on_bwImageHistoryBack_clicked()564 void mainForm::on_bwImageHistoryBack_clicked()
565 {
566 bwBitmapUndo->undo();
567 }
568
569 //! Advances one image forward in the history.
on_bwImageHistoryForward_clicked()570 void mainForm::on_bwImageHistoryForward_clicked()
571 {
572 bwBitmapUndo->redo();
573 }
574
575 /*! Runs the thinning.
576 \sa performMorphologicalOperation */
on_runThinningButton_clicked()577 void mainForm::on_runThinningButton_clicked()
578 {
579 performMorphologicalOperation(Vectorizer::THINNING_ROSENFELD);
580 }
581
582 /*! Runs the pruning.
583 \sa performMorphologicalOperation */
on_runPruningButton_clicked()584 void mainForm::on_runPruningButton_clicked()
585 {
586 performMorphologicalOperation(Vectorizer::PRUNING);
587 }
588
589 /*! Erodes the image.
590 \sa performMorphologicalOperation */
on_runErosionButton_clicked()591 void mainForm::on_runErosionButton_clicked()
592 {
593 performMorphologicalOperation(Vectorizer::EROSION);
594 }
595
596 /*! Dilates the image.
597 \sa performMorphologicalOperation */
on_runDilationButton_clicked()598 void mainForm::on_runDilationButton_clicked()
599 {
600 performMorphologicalOperation(Vectorizer::DILATION);
601 }
602
603 /*! Spawns dialog on classification config options.
604 \sa ClassificationConfigForm */
on_classificationOptionsButton_clicked()605 void mainForm::on_classificationOptionsButton_clicked()
606 {
607 ClassificationConfigForm* optionsDialog =
608 new ClassificationConfigForm(this);
609 optionsDialog->learnMethod = settings.getInt("learnMethod");
610 optionsDialog->colorSpace = settings.getInt("colorSpace");
611 optionsDialog->p = settings.getDouble("p");
612 optionsDialog->E = settings.getInt("E");
613 optionsDialog->Q = settings.getDouble("q");
614 optionsDialog->initAlpha = settings.getDouble("initAlpha");
615 optionsDialog->minAlpha = settings.getDouble("minAlpha");
616 optionsDialog->setValues();
617 optionsDialog->exec();
618 if (optionsDialog->result() == ClassificationConfigForm::Accepted)
619 {
620 settings.setInt("E", optionsDialog->E);
621 settings.setDouble("q", optionsDialog->Q);
622 settings.setDouble("initAlpha", optionsDialog->initAlpha);
623 settings.setDouble("minAlpha", optionsDialog->minAlpha);
624 settings.setDouble("learnMethod", optionsDialog->learnMethod);
625 settings.setDouble("colorSpace", optionsDialog->colorSpace);
626 settings.setDouble("p", optionsDialog->p);
627 }
628 delete optionsDialog;
629 }
630
631 /*! Spawns initial colors selection dialog.
632 \sa ColorsEditForm */
on_initialColorsButton_clicked()633 void mainForm::on_initialColorsButton_clicked()
634 {
635 ColorsEditForm* optionsDialog = new ColorsEditForm(this);
636 int settingsNColors = settings.getInt("nColors");
637 std::vector<QString> comments;
638 auto colors = settings.getInitColors(comments);
639 auto nColors = colors.size();
640
641 if (colors.empty())
642 {
643 // There are no settings in the store
644 nColors = settingsNColors;
645 colors = std::vector<QRgb>(nColors);
646 comments = std::vector<QString>(nColors);
647 for (std::size_t i = 0; i < nColors; i++)
648 {
649 colors[i] = qRgb(127, 127, 127);
650 comments[i] = QString(tr("new color"));
651 }
652 }
653 else if (nColors < settingsNColors)
654 {
655 // Existing settings only partially cover the higher number of colors
656 colors.resize(settingsNColors, qRgb(255, 255, 255));
657 comments.resize(settingsNColors, QString(tr("new color")));
658 }
659
660 optionsDialog->setColors(colors, comments);
661 optionsDialog->setColorsSource(static_cast<ColorsEditForm::ColorsSource>(
662 settings.getInt("initColorsSource")));
663 optionsDialog->exec();
664 if (optionsDialog->result() == ColorsEditForm::Accepted)
665 {
666 settings.setInt("initColorsSource", optionsDialog->getColorsSource());
667 settings.setInitColors(optionsDialog->getColors(),
668 optionsDialog->getComments());
669 }
670 delete optionsDialog;
671 }
672
673 //! Stores the value of number of colors spinbox located at colors tab.
on_howManyColorsSpinBox_valueChanged(int n)674 void mainForm::on_howManyColorsSpinBox_valueChanged(int n)
675 {
676 settings.setInt("nColors", n);
677 }
678
679 //! Spawns dialog on vectorization options.
on_setVectorizationOptionsButton_clicked()680 void mainForm::on_setVectorizationOptionsButton_clicked()
681 {
682 VectorizationConfigForm optionsDialog(this);
683 optionsDialog.speckleSize = settings.getDouble("speckleSize");
684 optionsDialog.doConnections = settings.getInt("doConnections");
685 optionsDialog.joinDistance = settings.getDouble("joinDistance");
686 optionsDialog.simpleConnectionsOnly =
687 settings.getInt("simpleConnectionsOnly");
688 optionsDialog.distDirBalance = settings.getDouble("distDirBalance");
689 optionsDialog.cornerMin = settings.getDouble("cornerMin");
690 optionsDialog.optTolerance = settings.getDouble("optTolerance");
691 optionsDialog.exec();
692 if (optionsDialog.result() == ClassificationConfigForm::Accepted)
693 {
694 settings.setDouble("speckleSize", optionsDialog.speckleSize);
695 settings.setDouble("joinDistance", optionsDialog.joinDistance);
696 settings.setInt("doConnections", optionsDialog.doConnections);
697 settings.setInt("simpleConnectionsOnly",
698 optionsDialog.simpleConnectionsOnly);
699 settings.setDouble("distDirBalance", optionsDialog.distDirBalance);
700 settings.setDouble("cornerMin", optionsDialog.cornerMin);
701 settings.setDouble("optTolerance", optionsDialog.optTolerance);
702 }
703 }
704
705 //! Creates polygons from current bwImage.
on_createVectorsButton_clicked()706 void mainForm::on_createVectorsButton_clicked()
707 {
708 if (!bwBitmapVectorizable)
709 QMessageBox::warning(this, tr("Perform thinning before vectorization"),
710 tr("It seems that thinning was not performed on"
711 " this B/W image. Vectorization can process"
712 " only one-pixel wide lines. Thicker lines"
713 " will not be converted to vectors."));
714
715 Polygons p;
716 p.setSpeckleSize(settings.getInt("speckleSize"));
717 p.setMaxDistance(settings.getInt("doConnections")
718 ? settings.getDouble("joinDistance")
719 : 0);
720 p.setSimpleOnly(settings.getInt("simpleConnectionsOnly"));
721 p.setDistDirRatio(settings.getDouble("distDirBalance"));
722 UIProgressDialog progressDialog (tr("Vectorizing"), tr("Cancel"), this);
723 auto q = p.createPolygonsFromImage(bwBitmap, &progressDialog);
724 ui.saveVectorsButton->setEnabled(!q.empty());
725 ui.bwImageView->setPolygons(std::move(q));
726 }
727
728 //! Transfers traced polygons back to the map.
on_saveVectorsButton_clicked()729 void mainForm::on_saveVectorsButton_clicked()
730 {
731 const PolygonList& polys = ui.bwImageView->polygons();
732 if (polys.empty())
733 return;
734
735 ooMap->clearObjectSelection(false);
736
737 auto* symbol = ui.symbolComboBox->symbol();
738 if (!symbol)
739 symbol = ooMap->getUndefinedLine();
740
741 // transform from template coordinates to map coordinates
742 auto const offset = QPointF { -0.5 * (ui.bwImageView->image()->width() - 1),
743 -0.5 * (ui.bwImageView->image()->height() - 1) };
744 auto const transform = [this, offset](const QPointF& point) -> OpenOrienteering::MapCoord {
745 return OpenOrienteering::MapCoord(ooTempl->templateToMap(point + offset));
746 };
747
748 std::vector<OpenOrienteering::Object*> result;
749 result.reserve(polys.size());
750
751 for (const auto& polygon : polys)
752 {
753 auto coords = OpenOrienteering::MapCoordVector {};
754 coords.reserve(polygon.size() + 1); // One extra slot, for some closed paths.
755 std::transform(begin(polygon), end(polygon), std::back_inserter(coords), transform);
756
757 auto* newOOPolygon = new OpenOrienteering::PathObject(symbol, std::move(coords));
758 if (polygon.isClosed())
759 newOOPolygon->closeAllParts();
760 newOOPolygon->setTag(QStringLiteral("generator"), QStringLiteral("cove")); /// \todo Configuration of tag
761 ooMap->addObject(newOOPolygon);
762 ooMap->addObjectToSelection(newOOPolygon, false);
763 result.push_back(newOOPolygon);
764 }
765
766 auto const* part = ooMap->getCurrentPart();
767 auto* undo_step = new OpenOrienteering::DeleteObjectsUndoStep(ooMap);
768 for (auto const* object : result)
769 undo_step->addObject(part->findObjectIndex(object));
770 ooMap->push(undo_step);
771
772 ooMap->setObjectsDirty();
773 ooMap->emitSelectionChanged();
774 }
775
776 //! Saves current bwImage to an image file.
on_bwImageSaveButton_clicked()777 void mainForm::on_bwImageSaveButton_clicked()
778 {
779 QString filter;
780 QList<QByteArray> extensions = QImageReader::supportedImageFormats();
781 QList<QByteArray>::iterator it;
782
783 for (it = extensions.begin(); it != extensions.end(); ++it)
784 {
785 filter.append(QString(*it).toUpper());
786 filter.append(QString(*it).prepend(" (*.").append(");;"));
787 }
788
789 filter.append(tr("All files"));
790 filter.append("(*)");
791
792 QString selectedFilter("PNG (*.png)");
793 QString newfilename = QFileDialog::getSaveFileName(
794 this, QString::null, QFileInfo(imageFileName).dir().path(), filter,
795 &selectedFilter);
796
797 if (!newfilename.isEmpty())
798 {
799 QString suffix = selectedFilter.section(" ", 1, 1);
800 suffix = suffix.mid(2, suffix.size() - 3);
801 if (!newfilename.endsWith(suffix, Qt::CaseInsensitive))
802 newfilename.append(suffix);
803 bwBitmap.save(newfilename,
804 selectedFilter.section(" ", 0, 0).toLatin1().constData());
805 }
806 }
807
808 //! Convenience method for enabling/disabling an UI tab.
setTabEnabled(QWidget * tab,bool state)809 void mainForm::setTabEnabled(QWidget* tab, bool state)
810 {
811 int tabindex = ui.mainTabWidget->indexOf(tab);
812 ui.mainTabWidget->setTabEnabled(tabindex, state);
813 }
814
815 /*! Applies FIR filter onto \a imageBitmap.
816 \sa FIRFilter */
on_applyFIRFilterPushButton_clicked()817 void mainForm::on_applyFIRFilterPushButton_clicked()
818 {
819 if (imageBitmap.isNull()) return;
820 FIRFilter f;
821 switch (ui.firFilterComboBox->currentIndex())
822 {
823 case 0:
824 f = FIRFilter(2).binomic();
825 break;
826 case 1:
827 f = FIRFilter(3).binomic();
828 break;
829 case 2:
830 f = FIRFilter(2).box();
831 break;
832 case 3:
833 f = FIRFilter(3).box();
834 break;
835 }
836
837 UIProgressDialog progressDialog(tr("Applying FIR Filter on image"),
838 tr("Cancel"), this);
839 auto functor = [&f](const QImage& source_image, ProgressObserver& progress_observer) -> QImage {
840 return f.apply(source_image, qRgb(127, 127, 127), &progress_observer);
841 };
842 QImage newImageBitmap = Concurrency::process<QImage>(&progressDialog, functor, imageBitmap);
843 if (!newImageBitmap.isNull()) imageBitmap = newImageBitmap;
844 ui.imageView->setImage(&imageBitmap);
845 }
846
847 /* \class mainForm::BwBitmapUndoStep
848 * \brief Embedded struct holding undo data.
849 */
850
BwBitmapUndoStep(mainForm & form,QImage image,bool vectorizable)851 mainForm::BwBitmapUndoStep::BwBitmapUndoStep(mainForm& form, QImage image, bool vectorizable)
852 : form { form }
853 , image { image }
854 , suitableForVectorization { vectorizable }
855 {
856 // nothing
857 }
858
redo()859 void mainForm::BwBitmapUndoStep::redo()
860 {
861 undo(); // the same data swap
862 }
863
undo()864 void mainForm::BwBitmapUndoStep::undo()
865 {
866 using std::swap;
867 swap(form.bwBitmap, image);
868 swap(form.bwBitmapVectorizable, suitableForVectorization);
869 form.ui.bwImageView->setImage(&form.bwBitmap);
870 }
871 } // cove
872
873 //@}
874