-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmainwindow.cpp
1257 lines (1094 loc) · 65.9 KB
/
mainwindow.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*#-------------------------------------------------
#
# Dominant colors from image with openCV
# in 3D color spaces
#
# by AbsurdePhoton - www.absurdephoton.fr
#
# v2.3 - 2023/03/10
#
#-------------------------------------------------*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QSizeGrip>
#include <QGridLayout>
//#include <QDesktopWidget>
#include <QCursor>
#include <QMouseEvent>
#include <QWhatsThis>
#include <fstream>
#include <thread>
#include <omp.h>
#include "widgets/file-dialog.h"
#include "lib/dominant-colors.h"
#include "lib/image-transform.h"
#include "lib/image-color.h"
#include "lib/color-spaces.h"
#include "lib/image-utils.h"
#include "lib/image-lut.h"
using namespace cv;
using namespace cv::ximgproc;
using namespace std;
/////////////////// Window init //////////////////////
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// numeric dot vs comma
std::setlocale(LC_NUMERIC,"C"); // dot as numeric separator for decimal values - some countries use commas
// number of processors
int processor_threads = std::thread::hardware_concurrency(); // find how many processor threads in the system
omp_set_num_threads(processor_threads/* / 2*/ - 1); // set usable threads for OMP
// window
setWindowFlags((((windowFlags() | Qt::CustomizeWindowHint)
& ~Qt::WindowCloseButtonHint) | Qt::WindowMinMaxButtonsHint)); // don't show buttons in title bar
this->setWindowState(Qt::WindowMaximized); // maximize window
setFocusPolicy(Qt::StrongFocus); // catch keyboard and mouse in priority
statusBar()->setVisible(false); // no status bar
// add size grip to openGL widget
ui->openGLWidget_3d->setWindowFlags(Qt::SubWindow);
QSizeGrip * sizeGrip = new QSizeGrip(ui->openGLWidget_3d);
QGridLayout * layout = new QGridLayout(ui->openGLWidget_3d);
layout->addWidget(sizeGrip, 0,0,1,1,Qt::AlignBottom | Qt::AlignRight);
// populate color space combobox
ui->comboBox_color_space->blockSignals(true); // don't launch automatic update of 3D view
ui->comboBox_color_space->addItem(tr("RGB"));
ui->comboBox_color_space->addItem(tr("Wheel"));
ui->comboBox_color_space->addItem(tr("HSV"));
ui->comboBox_color_space->addItem(tr("HWB"));
ui->comboBox_color_space->addItem(tr("CIE L*a*b*"));
ui->comboBox_color_space->addItem(tr("CIE L*u*v*"));
ui->comboBox_color_space->addItem(tr("OKLAB"));
ui->comboBox_color_space->addItem(tr("----------"));
ui->comboBox_color_space->addItem(tr("RGB Triangle"));
ui->comboBox_color_space->addItem(tr("HCV"));
ui->comboBox_color_space->addItem(tr("HSL"));
ui->comboBox_color_space->addItem(tr("HCL"));
ui->comboBox_color_space->addItem(tr("CIE XYZ"));
ui->comboBox_color_space->addItem(tr("CIE xyY"));
ui->comboBox_color_space->addItem(tr("LMS"));
ui->comboBox_color_space->addItem(tr("Hunter Lab"));
//ui->comboBox_color_space->addItem(tr("CIE LCHab")); // same as CIE L*a*b*
//ui->comboBox_color_space->addItem(tr("CIE LCHuv")); // same as CIE L*u*v*
ui->comboBox_color_space->blockSignals(false); // return to normal behavior
// populate sort combobox
ui->comboBox_sort->blockSignals(true); // don't launch automatic update of palette
ui->comboBox_sort->addItem("Percentage");
ui->comboBox_sort->addItem("Hue");
ui->comboBox_sort->addItem("Chroma");
ui->comboBox_sort->addItem("Saturation");
ui->comboBox_sort->addItem("Value");
ui->comboBox_sort->addItem("Lightness");
ui->comboBox_sort->addItem("Luminance");
ui->comboBox_sort->addItem("Distance");
ui->comboBox_sort->addItem("Whiteness");
ui->comboBox_sort->addItem("Blackness");
ui->comboBox_sort->addItem("RGB");
ui->comboBox_sort->addItem("Luma");
ui->comboBox_sort->addItem("Rainbow6");
ui->comboBox_sort->blockSignals(false); // return to normal behavior
// fullscreen button
ui->button_3d_exit_fullscreen->setVisible(false); // hide 3D fullscreen exit button
// Timer
ui->timer->setPalette(Qt::red); // red !
ui->timer->display("-------");
// initial variable values
InitializeValues();
}
MainWindow::~MainWindow()
{
delete ui;
}
/////////////////// GUI //////////////////////
void MainWindow::InitializeValues() // Global variables init
{
loaded = false; // main image NOT loaded
computed = false; // dominant colors NOT computed
converted = "No color to convert"; // convert string for displaying color values in a QMessageBox
// save and load initialization
basedirinifile = QDir::currentPath().toUtf8().constData(); // where to store the folder ini file
basedirinifile += "/dir.ini";
cv::FileStorage fs(basedirinifile, FileStorage::READ); // open dir ini file
if (fs.isOpened()) {
fs["BaseDir"] >> basedir; // load default dir
}
else basedir = "/home/"; // default base path and file
basefile = "example";
quantized = cv::Mat();
palette = cv::Mat();
// read color names from .csv file
std::string line; // line to read in text file
ifstream names; // file to read
names.open("color-names.csv"); // read color names file
if (names) { // if successfully read
nb_color_names = -1; // index of color names array
size_t pos; // index for find function
std::string s; // used for item extraction
getline(names, line); // read first line (header)
while (getline(names, line)) { // read each line of text file: R G B name
pos = 0; // find index at the beginning of the line
nb_color_names++; // current index in color names array
int pos2 = line.find(";", pos); // find first semicolon char
s = line.substr(pos, pos2 - pos); // extract R value
color_names[nb_color_names].R = std::stoi(s); // R in array
pos = pos2 + 1; // next char
pos2 = line.find(";", pos); // find second semicolon char
s = line.substr(pos, pos2 - pos); // extract G value
color_names[nb_color_names].G = std::stoi(s); // G in array
pos = pos2 + 1; // next char
pos2 = line.find(";", pos); // find third semicolon char
s = line.substr(pos, pos2 - pos); // extract B value
color_names[nb_color_names].B = std::stoi(s); // B in array
s = line.substr(pos2 + 1, line.length() - pos2); // color name is at the end of the line
color_names[nb_color_names].name = QString::fromStdString(s); // color name in array
}
names.close(); // close text file
}
else {
QMessageBox::critical(this, "Colors CSV file not found!", "You forgot to put 'color-names.csv' in the same folder as the executable! This tool will crash as soon as you quantize an image...");
}
}
void MainWindow::on_button_whats_this_clicked() // What's this function
{
QWhatsThis::enterWhatsThisMode();
}
void MainWindow::on_button_quit_clicked() // quit GUI
{
int quit = QMessageBox::question(this, "Quit this wonderful program", "Are you sure you want to quit?", QMessageBox::Yes|QMessageBox::No); // quit, are you sure ?
if (quit == QMessageBox::No) // don't quit !
return;
QCoreApplication::quit(); // quit
}
void MainWindow::on_button_compute_clicked() // compute dominant colors and result images
{
Compute(); // yes do it !
}
void MainWindow::on_button_3d_reset_clicked() // recenter position & zoom for 3D scene for each color space
{
ui->openGLWidget_3d->zoom3D = 4; // zoom coefficient
if (ui->openGLWidget_3d->color_space == "Wheel") {
ui->openGLWidget_3d->SetXRotation(180);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(-90);
}
if (ui->openGLWidget_3d->color_space == "RGB") {
ui->openGLWidget_3d->SetXRotation(287);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(300);
}
if (ui->openGLWidget_3d->color_space == "CIE XYZ") {
ui->openGLWidget_3d->SetXRotation(287);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(280);
}
if (ui->openGLWidget_3d->color_space == "LMS") {
ui->openGLWidget_3d->SetXRotation(287);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(300);
}
if (ui->openGLWidget_3d->color_space == "RGB Triangle") {
ui->openGLWidget_3d->SetXRotation(26);
ui->openGLWidget_3d->SetYRotation(30);
ui->openGLWidget_3d->SetZRotation(180);
}
if ((ui->openGLWidget_3d->color_space == "HSV") or (ui->openGLWidget_3d->color_space == "HCV")
or (ui->openGLWidget_3d->color_space == "HSL") or (ui->openGLWidget_3d->color_space == "HCL")) {
ui->openGLWidget_3d->SetXRotation(280);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(90);
}
if (ui->openGLWidget_3d->color_space == "HWB") {
ui->openGLWidget_3d->SetXRotation(100);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(-90);
}
if ((ui->openGLWidget_3d->color_space == "CIE L*a*b*") or (ui->openGLWidget_3d->color_space == "Hunter Lab") or (ui->openGLWidget_3d->color_space == "OKLAB")) {
ui->openGLWidget_3d->SetXRotation(290);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(120);
}
if (ui->openGLWidget_3d->color_space == "CIE xyY") {
ui->openGLWidget_3d->SetXRotation(210);
ui->openGLWidget_3d->SetYRotation(240);
ui->openGLWidget_3d->SetZRotation(270);
}
if (ui->openGLWidget_3d->color_space == "CIE L*u*v*") {
ui->openGLWidget_3d->SetXRotation(280);
ui->openGLWidget_3d->SetYRotation(0);
ui->openGLWidget_3d->SetZRotation(120);
}
ui->openGLWidget_3d->SetXShift(0); // initial (x,y) position
ui->openGLWidget_3d->SetYShift(0);
}
void MainWindow::on_checkBox_3d_light_clicked() // light on/off in 3D scene
{
ui->openGLWidget_3d->lightEnabled = ui->checkBox_3d_light->isChecked(); // set value
ui->openGLWidget_3d->update(); // view 3D scene
}
void MainWindow::on_checkBox_3d_fullscreen_clicked() // view 3D scene fullscreen, <ESC> to return from it
{
saveXOpenGL = ui->openGLWidget_3d->x(); // save openGL widget position and size
saveYOpenGL = ui->openGLWidget_3d->y();
saveWidthOpenGL = ui->openGLWidget_3d->width();
saveHeightOpenGL = ui->openGLWidget_3d->height();
saveXButtonSave = ui->button_save_3d->x(); // save button position
saveYButtonSave = ui->button_save_3d->y();
ui->openGLWidget_3d->raise(); // bring the 3d widget to front, above all other objects
ui->button_save_3d->setGeometry(4, 40, ui->button_save_3d->width(), ui->button_save_3d->height()); // set it to upper-left corner
ui->button_save_3d->raise(); // bring this button to front, above all other objects
ui->button_3d_exit_fullscreen->setVisible(true); // show exit button
ui->button_3d_exit_fullscreen->raise(); // bring this button to front, above all other objects
QRect screenSize = QGuiApplication::primaryScreen()->geometry(); // get screen size in which app is run
int newW = screenSize.width(); // set 3D widget size to screen
int newH = screenSize.height();
setWindowFlags(Qt::Window | Qt::FramelessWindowHint); // window without frame
show();
ui->openGLWidget_3d->move(QPoint(0, 0)); // move widget to upper-left position in window
ui->openGLWidget_3d->resize(QSize(newW, newH)); // resize openGL widget
}
void MainWindow::on_button_3d_exit_fullscreen_clicked() // exit fullscreen view of 3d scene
{
ui->button_save_3d->setGeometry(saveXButtonSave,saveYButtonSave, ui->button_save_3d->width(), ui->button_save_3d->height()); // move back save image button
this->setWindowFlags((((windowFlags() | Qt::CustomizeWindowHint)
& ~Qt::WindowCloseButtonHint) | Qt::WindowMinMaxButtonsHint)); // window buttons
show(); // show window
ui->openGLWidget_3d->move(QPoint(saveXOpenGL, saveYOpenGL)); // restore openGL widget to its previous position
ui->openGLWidget_3d->resize(QSize(saveWidthOpenGL, saveHeightOpenGL)); // ... and size
ui->checkBox_3d_fullscreen->setChecked(false); // uncheck fullscreen button
ui->button_3d_exit_fullscreen->setVisible(false); // hide exit button
ui->spinBox_3D_rotate_x->raise(); // bring back 3D controls over 3D view
ui->spinBox_3D_rotate_y->raise();
ui->spinBox_3D_rotate_z->raise();
ui->horizontalSlider_3D_rotate_y->raise();
ui->verticalSlider_3D_rotate_x->raise();
ui->button_3d_reset->raise();
ui->checkBox_3d_light->raise();
ui->checkBox_3d_fullscreen->raise();
}
void MainWindow::on_button_3d_reset_flags_clicked() // reset visibility and selection flags in 3D view
{
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) {
ui->openGLWidget_3d->palettes[n].selected = false;
ui->openGLWidget_3d->palettes[n].visible = true;
}
ui->openGLWidget_3d->update();
ShowImages();
}
void MainWindow::on_comboBox_color_space_currentIndexChanged(int index) // change color space
{
ui->openGLWidget_3d->color_space = ui->comboBox_color_space->currentText().toStdString(); // set value
ui->openGLWidget_3d->update(); // view 3D scene
}
void MainWindow::on_comboBox_sort_currentIndexChanged(int index) // sort palette
{
SortPalettes(); // call sorting method and palette image reconstruction
}
void MainWindow::on_button_save_3d_clicked() // save current view of 3D color space
{
QString filename = QFileDialog::getSaveFileName(this, "Save image file",
QString::fromStdString(basedir + basefile + "-color-space-" + ui->openGLWidget_3d->color_space + ".png"),
tr("PNG (*.png")); // image filename
if (filename.isNull() || filename.isEmpty()) // cancel ?
return;
ChangeBaseDir(filename); // save current path to ini file
ui->openGLWidget_3d->Capture(); // capture current 3D scene
cv::imwrite(filename.toStdString(), QImage2Mat(ui->openGLWidget_3d->capture3D)); // write image file
}
void MainWindow::on_button_values_clicked() // show converted color in several color spaces
{
QMessageBox::information(this, "Color conversion", converted); // show message box with info
}
/////////////////// Keyboard events //////////////////////
void MainWindow::keyPressEvent(QKeyEvent *keyEvent) // keyboard events
{
if ((keyEvent->key() == Qt::Key_Escape) & (ui->checkBox_3d_fullscreen->isChecked())) { // only way to get out of fullscreen view
ui->button_save_3d->setGeometry(saveXButtonSave,saveYButtonSave, ui->button_save_3d->width(), ui->button_save_3d->height()); // move back save image button
this->setWindowFlags((((windowFlags() | Qt::CustomizeWindowHint)
& ~Qt::WindowCloseButtonHint) | Qt::WindowMinMaxButtonsHint)); // window buttons
show(); // show window
ui->openGLWidget_3d->move(QPoint(saveXOpenGL, saveYOpenGL)); // restore openGL widget to its previous position
ui->openGLWidget_3d->resize(QSize(saveWidthOpenGL, saveHeightOpenGL)); // ... and size
ui->checkBox_3d_fullscreen->setChecked(false); // uncheck fullscreen button
ui->button_3d_exit_fullscreen->setVisible(false); // hide exit button
ui->spinBox_3D_rotate_x->raise(); // bring back 3D controls over 3D view
ui->spinBox_3D_rotate_y->raise();
ui->spinBox_3D_rotate_z->raise();
ui->horizontalSlider_3D_rotate_y->raise();
ui->verticalSlider_3D_rotate_x->raise();
ui->button_3d_reset->raise();
ui->checkBox_3d_light->raise();
ui->checkBox_3d_fullscreen->raise();
}
// controls for 3D view
if (keyEvent->key() == Qt::Key_Left) // move left
ui->openGLWidget_3d->SetShiftLeft();
if (keyEvent->key() == Qt::Key_Right) // move right
ui->openGLWidget_3d->SetShiftRight();
if (keyEvent->key() == Qt::Key_Up) // move up
ui->openGLWidget_3d->SetShiftUp();
if (keyEvent->key() == Qt::Key_Down) // move down
ui->openGLWidget_3d->SetShiftDown();
if (keyEvent->key() == Qt::Key_PageUp) // vertical rotation up
ui->openGLWidget_3d->SetAngleXMinus();
if (keyEvent->key() == Qt::Key_PageDown) // vertical rotation down
ui->openGLWidget_3d->SetAngleXPlus();
if (keyEvent->key() == Qt::Key_Home) // horizontal rotation left
ui->openGLWidget_3d->SetAngleYMinus();
if (keyEvent->key() == Qt::Key_End) // horizontal rotation right
ui->openGLWidget_3d->SetAngleYPlus();
if (keyEvent->key() == Qt::Key_Insert) // z rotation +
ui->openGLWidget_3d->SetAngleZMinus();
if (keyEvent->key() == Qt::Key_Delete) // z rotation -
ui->openGLWidget_3d->SetAngleZPlus();
}
/////////////////// Mouse events //////////////////////
void MainWindow::mousePressEvent(QMouseEvent *eventPress) // event triggered by a mouse click
{
mouseButton = eventPress->button(); // mouse button value
bool key_control = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier); // modifier ctrl key pressed ?
bool key_alt = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier); // modifier alt key pressed ?
bool color_found = false; // valid color found ?
Vec3b color; // BGR values of picked color
if (ui->label_palette->underMouse()) { // mouse over palette ?
mouse_pos = ui->label_palette->mapFromGlobal(QCursor::pos()); // mouse position
if ((mouseButton == Qt::LeftButton) and (!palette.empty())) { // mouse left button clicked
const QPixmap q = ui->label_palette->pixmap(); // stored reduced quantized image in GUI
int x = round(palette.cols * double(mouse_pos.x() - (ui->label_palette->width() - q.width()) / 2) / double(q.width())); // real x position in palette image
int y = round(palette.rows * double(mouse_pos.y() - (ui->label_palette->height() - q.height()) / 2) / double(q.height())); // real y position in palette image
if ((x > 0) and (x < palette.cols) and (y > 0) and (y < palette.rows)) {
color = palette.at<Vec3b>(0, x); // pick color in palette
color_found = true; // found !
}
else
color_found = false;
}
}
if (ui->label_quantized->underMouse()) { // mouse over quantized image ?
mouse_pos = ui->label_quantized->mapFromGlobal(QCursor::pos()); // mouse position
if ((mouseButton == Qt::LeftButton) and (!quantized.empty())) { // mouse left button clicked
const QPixmap q = ui->label_quantized->pixmap(); // stored reduced quantized image in GUI
double percentX = double(mouse_pos.x() - (ui->label_quantized->width() - q.width()) / 2) / double(q.width()); // real x and y position in quantized image
double percentY = double(mouse_pos.y() - (ui->label_quantized->height() - q.height()) / 2) / double(q.height());
if ((percentX >= 0) and (percentX < 1) and (percentY >= 0) and (percentY < 1)) {
color = quantized.at<Vec3b>(round(percentY * quantized.rows), round(percentX * quantized.cols)); // pick color in quantized image at x,y
color_found = true; // found !
}
}
}
if (color_found) { // color picked ?
// RGB values
int R = color[2];
int G = color[1];
int B = color[0];
// display color, RGB values
Mat bar = cv::Mat::zeros(cv::Size(1,1), CV_8UC3); // 1 pixel image
bar = Vec3b(B,G,R); // set it to picked color
ui->label_color_bar->setPixmap(Mat2QPixmapResized(bar, ui->label_color_bar->width(), ui->label_color_bar->height(), false)); // show picked color
ui->label_color_r->setText(QString::number(R)); // show RGB values
ui->label_color_g->setText(QString::number(G));
ui->label_color_b->setText(QString::number(B));
// find color in palette
bool found = false; // picked color found in palette ?
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) { // search in palette
if ((round(ui->openGLWidget_3d->palettes[n].RGB.R * 255.0) == R)
and (round(ui->openGLWidget_3d->palettes[n].RGB.G * 255.0) == G)
and (round(ui->openGLWidget_3d->palettes[n].RGB.B * 255.0) == B)) { // identical RGB values found
QString value = QString::number(ui->openGLWidget_3d->palettes[n].percentage * 100, 'f', 2) + "%"; // picked color percentage in quantized image
ui->label_color_percentage->setText(value); // display percentage
ui->label_color_name->setText(QString::fromStdString(ui->openGLWidget_3d->palettes[n].name));
ui->label_color_hex->setText(QString::fromStdString(ui->openGLWidget_3d->palettes[n].hexa)); // show hexa
found = true; // color found in palette
if (key_control) {
ui->openGLWidget_3d->palettes[n].selected = !ui->openGLWidget_3d->palettes[n].selected; // ctrl key = switch selected state for this color in 3D scene
ShowImages();
}
if (key_alt)
ui->openGLWidget_3d->palettes[n].visible = !ui->openGLWidget_3d->palettes[n].visible; // alt key = switch visibility state for this color in 3D scene
converted = ConvertColor(ui->openGLWidget_3d->palettes[n].RGB.R, ui->openGLWidget_3d->palettes[n].RGB.G, ui->openGLWidget_3d->palettes[n].RGB.B); // compute string with values in most known color spaces
ui->openGLWidget_3d->update(); // update 3D view
break; // get out of loop
}
}
if (!found) { // picked color not found in palette
ui->label_color_percentage->setText(""); // no percentage displayed
ui->label_color_name->setText(""); // no color name
ui->label_color_hex->setText(""); // no hexa
}
}
}
/////////////////// Save and load //////////////////////
void MainWindow::SaveDirBaseFile() // write current folder name in ini file
{
cv::FileStorage fs(basedirinifile, cv::FileStorage::WRITE); // open dir ini file for writing
fs << "BaseDir" << basedir; // write folder name
fs.release(); // close file
}
void MainWindow::ChangeBaseDir(QString filename) // set base dir and file
{
basefile = filename.toUtf8().constData(); // base file name and dir are used after to save other files
// Remove extension if present
size_t period_idx = basefile.rfind('.'); // find final dot in filename
if (std::string::npos != period_idx)
basefile.erase(period_idx);
basedir = basefile;
size_t found = basefile.find_last_of("\\/"); // find last directory
std::string separator = basefile.substr(found,1); // copy path separator (Linux <> Windows)
basedir = basedir.substr(0,found) + separator; // define base path
basefile = basefile.substr(found+1); // delete path in base file
SaveDirBaseFile(); // Save current path to ini file
}
void MainWindow::on_button_load_image_clicked() // load image to analyze
{
PreviewFileDialog* mpOpenDialog = new PreviewFileDialog(this, "Load image...", QString::fromStdString(basedir), tr("Images (*.jpg *.jpeg *.jp2 *.png *.tif *.tiff *.webp)"));
QString filename = mpOpenDialog->GetSelectedFile();
/*QString filename = QFileDialog::getOpenFileName(this, "Load image...", QString::fromStdString(basedir),
tr("Images (*.jpg *.jpeg *.jp2 *.png *.tif *.tiff)")); // image file name*/
if (filename.isNull() || filename.isEmpty()) // cancel ?
return;
ChangeBaseDir(filename); // save current path to ini file
std::string filesession = filename.toUtf8().constData(); // base file name
image = cv::imread(filesession); // load image
if (image.empty()) {
QMessageBox::critical(this, "File error", "There was a problem reading the image file");
return;
}
if (ui->checkBox_gaussian_blur->isChecked()) cv::GaussianBlur(image, image, Size(3,3), 0, 0); // gaussian blur
if (ui->checkBox_reduce_size->isChecked()) {
if ((image.rows > 512) or (image.cols > 512)) image = ResizeImageAspectRatio(image, cv::Size(512,512)); // resize image to 512 pixels
}
loaded = true; // loaded successfully !
ui->label_filename->setText(filename); // display filename in ui
thumbnail = ResizeImageAspectRatio(image, cv::Size(ui->label_thumbnail->width(),ui->label_thumbnail->height())); // create thumbnail
quantized.release(); // no quantized image yet
palette.release(); // no palette image yet
//classification.release();
ShowImages(); // show images in GUI
ui->openGLWidget_3d->nb_palettes = -1; // no palette to show yet
ui->openGLWidget_3d->update(); // show empty 3D view
ui->timer->display("-------"); // reset timer in GUI
ui->spinBox_nb_palettes->setStyleSheet("QSpinBox{color:black;}"); // number of colors in black = no error
ui->label_color_bar->setPixmap(QPixmap()); // show picked color
ui->label_color_r->setText(""); // show RGB values
ui->label_color_g->setText("");
ui->label_color_b->setText("");
ui->label_color_hex->setText("");
ui->label_color_percentage->setText("");
ui->label_color_name->setText("");
}
void MainWindow::on_button_load_lut_clicked() // load .cube LUT to analyze
{
QString filename = QFileDialog::getOpenFileName(this, "Load Cube LUT...", QString::fromStdString(basedir),
tr("LUT (*.cube *.CUBE)"), NULL, QFileDialog::DontUseNativeDialog); // .cube LUT file name
if (filename.isNull() || filename.isEmpty()) // cancel ?
return;
ChangeBaseDir(filename); // save current path to ini file
std::string filesession = filename.toUtf8().constData(); // base file name
// Cube LUT
CubeLUT cube; // new cube
std::ifstream cubeFile; // file for this cube
cubeFile.open(filesession, std::ifstream::in); // open cube file
bool ok = cubeFile.is_open(); // indicator
if (ok) { // file loaded successfully
CubeLUT::LUTState state = cube.LoadCubeFile(cubeFile);
if (state == CubeLUT::OK) {
QApplication::setOverrideCursor(Qt::WaitCursor); // wait cursor
timer.start(); // start of elapsed time
ShowTimer(true); // show elapsed time
qApp->processEvents();
if (!cube.LUT1D.empty()) {
image = cv::Mat::zeros(1, cube.LUT1D.size(), CV_8UC3);
cv::Vec3b* imageP = image.ptr<cv::Vec3b>(0);
for (int n = 0; n < int(cube.LUT1D.size()); n++) {
imageP[n] = cv::Vec3b(cube.LUT1D[n][2] * 255.0, cube.LUT1D[n][1] * 255.0, cube.LUT1D[n][0] * 255.0);
}
}
else if (!cube.LUT3D.empty()) {
image = cv::Mat::zeros(1, cube.LUT3D[0].size() * cube.LUT3D[0].size() * cube.LUT3D[0].size(), CV_8UC3);
cv::Vec3b* imageP = image.ptr<cv::Vec3b>(0);
int n = 0;
for (int x = 0; x < int(cube.LUT3D[0].size()); x++) {
for (int y = 0; y < int(cube.LUT3D[1].size()); y++) {
for (int z = 0; z < int(cube.LUT3D[2].size()); z++) {
imageP[n] = cv::Vec3b(cube.LUT3D[x][y][z][2] * 255.0, cube.LUT3D[x][y][z][1] * 255.0, cube.LUT3D[x][y][z][0] * 255.0);
n++;
}
}
}
}
else {
ShowTimer(false); // show elapsed time
QApplication::restoreOverrideCursor(); // Restore cursor
loaded = false;
QMessageBox::information(this, "Error", "Cube LUT bad format");
return;
}
ShowTimer(false); // show elapsed time
QApplication::restoreOverrideCursor(); // Restore cursor
}
}
else {
loaded = false;
QMessageBox::information(this, "Error", "Problem loading Cube LUT");
return;
}
loaded = true; // loaded successfully !
ui->label_filename->setText(filename); // display filename in ui
//thumbnail = ResizeImageAspectRatio(image, cv::Size(ui->label_thumbnail->width(),ui->label_thumbnail->height())); // create thumbnail
thumbnail = cv::Mat::zeros(3, 3, CV_8UC3);
quantized.release(); // no quantized image yet
palette.release(); // no palette image yet
//classification.release();
ShowImages(); // show images in GUI
ui->openGLWidget_3d->nb_palettes = -1; // no palette to show yet
ui->openGLWidget_3d->update(); // show empty 3D view
ui->timer->display("-------"); // reset timer in GUI
ui->spinBox_nb_palettes->setStyleSheet("QSpinBox{color:black;}"); // number of colors in black = no error
ui->label_color_bar->setPixmap(QPixmap()); // show picked color
ui->label_color_r->setText(""); // show RGB values
ui->label_color_g->setText("");
ui->label_color_b->setText("");
ui->label_color_hex->setText("");
ui->label_color_percentage->setText("");
ui->label_color_name->setText("");
// set special options for LUTs
ui->checkBox_filter_grays->setChecked(false); // don't filter grays
ui->checkBox_filter_percent->setChecked(false); // don't filter low values
ui->spinBox_nb_palettes->setValue(512); // 512 levels of color
Compute();
}
void MainWindow::on_button_save_clicked() // save dominant colors results
{
QString filename = QFileDialog::getSaveFileName(this, "Save image file", QString::fromStdString(basedir + basefile + ".png"), tr("PNG (*.png *.PNG)")); // image filename
if (filename.isNull() || filename.isEmpty()) // cancel ?
return;
ChangeBaseDir(filename); // save current path to ini file
// if image not empty save it with base name + type .PNG
if (!quantized.empty())
cv::imwrite(basedir + basefile + "-quantized.png", quantized);
if (!palette.empty())
cv::imwrite(basedir + basefile + "-palette.png", palette);
ui->openGLWidget_3d->Capture(); // capture current 3D scene
cv::imwrite(basedir + basefile + "-color-space-" + ui->openGLWidget_3d->color_space + ".png", QImage2Mat(ui->openGLWidget_3d->capture3D)); // save 3D scene image file
/*if (!classification.empty())
cv::imwrite(basedir + basefile + "-classification.png", classification);*/
// palette save to CSV file
ofstream save; // file to save
save.open(basedir + basefile + "-palette.csv"); // save palette file
if (save) { // if successfully open
setlocale(LC_ALL, "C"); // force numeric separator=dot instead of comma (I'm French) when using std functions
save << "Name;RGB.R;RGB.G;RGB.B;RGB.R normalized;RGB.G normalized;RGB.B normalized;RGB hexadecimal;HSV.H °;HSV.S;HSV.V;HSV.C;HSL.H °;HSL.S;HSL.L;HSL.C;HWB.H °;HWB.W;HWB.B;XYZ.X;XYZ.Y;XYZ.Z;xyY.x;xyY.y;xyY.Y;L*u*v*.L;L*u*v*.u;L*u*v*.v;LCHuv.L;LCHuv.C;LCHuv.H °;L*A*B*.L;L*A*B*.a signed;L*A*B*.b signed;LCHab.L;LCHab.C;LCHab.H °;Hunter LAB.L;Hunter LAB.a signed;Hunter LAB.b signed;LMS.L;LMS.M;LMS.S;CMYK.C;CMYK.M;CMYK.Y;CMYK.K;OKLAB.L;OKLAB.a signed;OKLAB.b signed;OKLCH.L;OKLCH.C;OKLCH.H °;Percentage\n"; // CSV header
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) { // read palette
save << ui->openGLWidget_3d->palettes[n].name << ";";
// RGB [0..255]
save << ui->openGLWidget_3d->palettes[n].RGB.R * 255.0 << ";";
save << ui->openGLWidget_3d->palettes[n].RGB.G * 255.0 << ";";
save << ui->openGLWidget_3d->palettes[n].RGB.B * 255.0 << ";";
// RGB [0..1]
save << ui->openGLWidget_3d->palettes[n].RGB.R << ";";
save << ui->openGLWidget_3d->palettes[n].RGB.G << ";";
save << ui->openGLWidget_3d->palettes[n].RGB.B << ";";
// RGB hexa
save << ui->openGLWidget_3d->palettes[n].hexa << ";";
// HSV+C
save << ui->openGLWidget_3d->palettes[n].HSV.H * 360.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HSV.S * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HSV.V * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HSV.C * 100.0 << ";";
// HSL+C
save << ui->openGLWidget_3d->palettes[n].HSL.H * 360.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HSL.S * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HSL.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HSL.C * 100.0 << ";";
// HWB
save << ui->openGLWidget_3d->palettes[n].HWB.H * 360.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HWB.W * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HWB.B * 100.0 << ";";
// CIE XYZ
save << ui->openGLWidget_3d->palettes[n].XYZ.X * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].XYZ.Y * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].XYZ.Z * 100.0 << ";";
// CIE xyY
save << ui->openGLWidget_3d->palettes[n].XYY.x << ";";
save << ui->openGLWidget_3d->palettes[n].XYY.y << ";";
save << ui->openGLWidget_3d->palettes[n].XYY.Y * 100.0 << ";";
// CIE Luv
save << ui->openGLWidget_3d->palettes[n].LUV.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LUV.u * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LUV.v * 100.0 << ";";
// CIE LCHuv
save << ui->openGLWidget_3d->palettes[n].LCHUV.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LCHUV.C * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LCHUV.H * 360.0 << ";";
// CIE L*a*b*
save << ui->openGLWidget_3d->palettes[n].CIELAB.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].CIELAB.A * 127.0 << ";";
save << ui->openGLWidget_3d->palettes[n].CIELAB.B * 127.0 << ";";
// CIE LCHab
save << ui->openGLWidget_3d->palettes[n].LCHAB.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LCHAB.C * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LCHAB.H * 360.0 << ";";
// Hunter LAB
save << ui->openGLWidget_3d->palettes[n].HLAB.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HLAB.A * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].HLAB.B * 100.0 << ";";
// CIE LMS
save << ui->openGLWidget_3d->palettes[n].LMS.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LMS.M * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].LMS.S * 100.0 << ";";
// CMYK
save << ui->openGLWidget_3d->palettes[n].CMYK.C * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].CMYK.M * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].CMYK.Y * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].CMYK.K * 100.0 << ";";
// OKLAB
save << ui->openGLWidget_3d->palettes[n].OKLAB.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].OKLAB.A * 127.0 << ";";
save << ui->openGLWidget_3d->palettes[n].OKLAB.B * 127.0 << ";";
// OKLCH
save << ui->openGLWidget_3d->palettes[n].OKLCH.L * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].OKLCH.C * 100.0 << ";";
save << ui->openGLWidget_3d->palettes[n].OKLCH.H * 360.0 << ";";
// percentage
save << ui->openGLWidget_3d->palettes[n].percentage << "\n";
}
save.close(); // close text file
}
// palette .ACT file (Adobe Photoshop and Illustrator)
char buffer[771] = {0}; // .ACT files are 772 bytes long
ofstream saveACT (basedir + basefile + "-palette-adobe.act", ios::out | ios::binary); // open stream
int nbValues = ui->openGLWidget_3d->nb_palettes; // number of values to write
if (nbValues > 256) // 256 values max !
nbValues = 256; // so we'll save first 256 values
for (int n = 0; n < nbValues; n++) { // palette values to buffer
buffer[n * 3 + 0] = round(ui->openGLWidget_3d->palettes[n].RGB.R * 255.0);
buffer[n * 3 + 1] = round(ui->openGLWidget_3d->palettes[n].RGB.G * 255.0);
buffer[n * 3 + 2] = round(ui->openGLWidget_3d->palettes[n].RGB.B * 255.0);
}
buffer[768] = (unsigned short) nbValues; // last second 16-bit value : number of colors in palette
buffer[770] = (unsigned short) 255; // last 16-bit value : which color is transparency
saveACT.write(buffer, 772); // write 772 bytes from buffer
saveACT.close(); // close binary file
// palette .PAL file (text JASC-PAL for PaintShop Pro)
ofstream saveJASC; // file to save
saveJASC.open(basedir + basefile + "-palette-paintshoppro.pal"); // save palette file
if (saveJASC) { // if successfully open
saveJASC << "JASC-PAL\n0100\n";
saveJASC << ui->openGLWidget_3d->nb_palettes << "\n";
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) { // read palette
saveJASC << round(ui->openGLWidget_3d->palettes[n].RGB.R * 255.0) << " ";
saveJASC << round(ui->openGLWidget_3d->palettes[n].RGB.G * 255.0) << " ";
saveJASC << round(ui->openGLWidget_3d->palettes[n].RGB.B * 255.0) << "\n";
}
saveJASC.close(); // close text file
}
// palette .PAL file (text with CMYK values for CorelDraw)
ofstream saveCOREL; // file to save
saveCOREL.open(basedir + basefile + "-palette-coreldraw.pal"); // save palette file
if (saveCOREL) { // if successfully open
double C, M, Y, K;
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) { // read palette
RGBtoCMYK(ui->openGLWidget_3d->palettes[n].RGB.R, ui->openGLWidget_3d->palettes[n].RGB.G, ui->openGLWidget_3d->palettes[n].RGB.B, C, M, Y, K);
saveCOREL << '"' << ui->openGLWidget_3d->palettes[n].name << '"' << " " << int(round(C * 100.0)) << " " << int(round(M * 100.0)) << " " << int(round(Y * 100.0)) << " " << int(round(K * 100.0)) << "\n";
}
saveCOREL.close(); // close text file
}
QMessageBox::information(this, "Results saved", "Your results were saved with base file name:\n" + QString::fromStdString(basedir + basefile));
}
/////////////////// Core functions //////////////////////
void MainWindow::Compute() // analyze image dominant colors
{
if (!loaded) { // nothing loaded yet = get out
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor); // wait cursor
timer.start(); // start of elapsed time
ShowTimer(true); // show elapsed time
qApp->processEvents();
Mat imageCopy; // work on a copy of the image, because gray colors can be filtered
image.copyTo(imageCopy);
if (ui->checkBox_filter_grays->isChecked()) { // filter whites, blacks and greys
Vec3b RGB;
double H, S, L, C;
for (int x = 0; x < imageCopy.cols; x++) // parse temp image
for (int y = 0; y < imageCopy.rows; y++) {
RGB = imageCopy.at<Vec3b>(y, x); // current pixel color
RGBtoHSL(double(RGB[2]) / 255.0, double(RGB[1]) / 255.0, double(RGB[0]) / 255.0, H, S, L, C); // convert it to HSL
if ((S < 0.25) or (L < 0.15) or (L > 0.8)) // white or black or grey pixel ?
imageCopy.at<Vec3b>(y, x) = Vec3b(0, 0, 0); // replace it with black
}
}
ui->openGLWidget_3d->nb_palettes= ui->spinBox_nb_palettes->value(); // how many dominant colors
int nb_palettes_asked = ui->openGLWidget_3d->nb_palettes; // save asked number of colors for later
ui->spinBox_nb_palettes->setStyleSheet("QSpinBox{color:black;}"); // show number of colors in black (in case it was red before)
if (ui->checkBox_filter_grays->isChecked()) { // if grays and blacks and whites filtered
Mat1b black_mask;
inRange(imageCopy, Vec3b(0, 0, 0), Vec3b(0, 0, 0), black_mask); // extract black pixels from image
if ((cv::sum(black_mask) != Scalar(0,0,0))) // image contains black pixels ?
ui->openGLWidget_3d->nb_palettes++; // add one color to asked number of colors in palette, to remove it later and get only colors
}
// set palette values to 0;
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) {
ui->openGLWidget_3d->palettes[n].RGB.R = 0.0;
ui->openGLWidget_3d->palettes[n].RGB.G = 0.0;
ui->openGLWidget_3d->palettes[n].RGB.B = 0.0;
ui->openGLWidget_3d->palettes[n].count = 0;
ui->openGLWidget_3d->palettes[n].percentage = 0.0;
ui->openGLWidget_3d->palettes[n].selected = false; // color not selected
ui->openGLWidget_3d->palettes[n].visible = true; // color shown
}
if (ui->radioButton_eigenvectors->isChecked()) { // eigen method
cv::Mat cielab = ConvertImageRGBtoCIELab(imageCopy);
std::vector<cv::Vec3d> palette_vec = DominantColorsEigen(cielab, ui->openGLWidget_3d->nb_palettes, quantized); // get dominant palette, palette image and quantized image
quantized = ConvertImageCIELabToRGB(quantized);
/*for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) // store palette values in structured array
{
double R, G, B;
CIELabToRGB(palette_vec[n][0], palette_vec[n][1], palette_vec[n][2], R, G, B);
// RGB
ui->openGLWidget_3d->palettes[n].RGB.R = R;
ui->openGLWidget_3d->palettes[n].RGB.G = G;
ui->openGLWidget_3d->palettes[n].RGB.B = B;
}*/
// palette from quantized image
cv::Vec3b color[ui->openGLWidget_3d->nb_palettes]; // temp palette
int nbColor = 0; // current color
for (int x = 0; x < quantized.cols; x++) // parse entire image
for (int y = 0; y < quantized.rows; y++) {
cv::Vec3b col = quantized.at<cv::Vec3b>(y, x); // current pixel color
bool found = false;
for (int i = 0; i < nbColor; i++) // look into temp palette
if (col == color[i]) { // if color already exists
found = true; // found, don't add it
break;
}
if (!found) { // color not already in temp palette
color[nbColor] = col; // save new color
ui->openGLWidget_3d->palettes[nbColor].RGB.R = col[2] / 255.0; // copy RGB values to global palette
ui->openGLWidget_3d->palettes[nbColor].RGB.G = col[1] / 255.0;
ui->openGLWidget_3d->palettes[nbColor].RGB.B = col[0] / 255.0;
nbColor++; // one more color
}
}
ui->openGLWidget_3d->ConvertPaletteFromRGB(); // convert RGB to other values
}
else if (ui->radioButton_k_means->isChecked()) { // K-means method
cv::Mat1f colors; // to store palette from K-means
quantized = DominantColorsKMeansRGB(imageCopy, ui->spinBox_nb_palettes->value(), colors); // get quantized image and palette
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) // store palette in structured array
{
// RGB
ui->openGLWidget_3d->palettes[n].RGB.R = double(colors(n, 2)) / 255.0;
ui->openGLWidget_3d->palettes[n].RGB.G = double(colors(n, 1)) / 255.0;
ui->openGLWidget_3d->palettes[n].RGB.B = double(colors(n, 0)) / 255.0;
}
ui->openGLWidget_3d->ConvertPaletteFromRGB(); // convert RGB to other values
//classification.release();
}
// compute HSL values from RGB + compute hexa
for (int n = 0; n < ui->openGLWidget_3d->nb_palettes; n++) {
// HSL value
double C;
RGBtoHSL(ui->openGLWidget_3d->palettes[n].RGB.R, ui->openGLWidget_3d->palettes[n].RGB.G, ui->openGLWidget_3d->palettes[n].RGB.B,
ui->openGLWidget_3d->palettes[n].HSL.H, ui->openGLWidget_3d->palettes[n].HSL.S, ui->openGLWidget_3d->palettes[n].HSL.L, C); // convert current color to HSL
// hexadecimal value
QString hex = QString(" %1").arg(((int(round(ui->openGLWidget_3d->palettes[n].RGB.R * 255.0)) & 0xff) << 16)
+ ((int(round(ui->openGLWidget_3d->palettes[n].RGB.G * 255.0)) & 0xff) << 8)
+ (int(round(ui->openGLWidget_3d->palettes[n].RGB.B * 255.0)) & 0xff)
, 6, 16, QChar('0')).trimmed(); // compute hexa RGB value
hex = "#" + hex;
ui->openGLWidget_3d->palettes[n].hexa = hex.toUpper().toUtf8().constData();
}
// clean palette : number of asked colors may be superior to number of colors found
int n = CountRGBUniqueValues(quantized); // how many colors in quantized image, really ?
if (n < ui->openGLWidget_3d->nb_palettes) { // if asked number of colors exceeds total number of colors in image
std::sort(ui->openGLWidget_3d->palettes, ui->openGLWidget_3d->palettes + ui->openGLWidget_3d->nb_palettes,
[](const struct_palette& a, const struct_palette& b) {return a.hexa > b.hexa;}); // sort palette by hexa value, decreasing values
if ((ui->openGLWidget_3d->palettes[0].RGB.R == ui->openGLWidget_3d->palettes[1].RGB.R)
and (ui->openGLWidget_3d->palettes[0].RGB.G == ui->openGLWidget_3d->palettes[1].RGB.G)
and (ui->openGLWidget_3d->palettes[0].RGB.B == ui->openGLWidget_3d->palettes[1].RGB.B)) // if first color in palette is equal to second we have to reverse sort by percentage
std::sort(ui->openGLWidget_3d->palettes, ui->openGLWidget_3d->palettes + ui->openGLWidget_3d->nb_palettes,
[](const struct_palette& a, const struct_palette& b) {return a.hexa > b.hexa;}); // sort the palette, this time by increasing hexa values
ui->openGLWidget_3d->nb_palettes = n; // new number of colors in palette
ui->spinBox_nb_palettes->setValue(ui->openGLWidget_3d->nb_palettes); // show new number of colors
}
int total = quantized.rows * quantized.cols; // size of quantized image in pixels
// delete blacks in palette if needed
bool black_found = false;
if (ui->checkBox_filter_grays->isChecked()) { // delete last "black" values in palette
std::sort(ui->openGLWidget_3d->palettes, ui->openGLWidget_3d->palettes + ui->openGLWidget_3d->nb_palettes,
[](const struct_palette& a, const struct_palette& b) {return a.HSL.L > b.HSL.L;}); // sort palette by lightness value
while (ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].HSL.L < 0.15) { // at the end of palette, find black colors
Mat1b black_mask;
inRange(quantized, Vec3b(int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.B * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.G * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.R * 255.0))),
Vec3b(int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.B * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.G * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.R * 255.0))),
black_mask); // extract black color from image
int c = countNonZero(black_mask);
total = total - c; // update total pixel count
ui->openGLWidget_3d->nb_palettes--; // exclude this black color from palette
if (c > 0) // really found black color ?
black_found = true; // black color found !
}
if (black_found) { // if black color found
ui->spinBox_nb_palettes->setValue(ui->openGLWidget_3d->nb_palettes); // show new number of colors without black values
}
}
// compute percentages
for (int n = 0;n < ui->openGLWidget_3d->nb_palettes; n++) { // for each color in palette
Mat1b mask; // current color mask
inRange(quantized, Vec3b(int(round(ui->openGLWidget_3d->palettes[n].RGB.B * 255.0)), int(round(ui->openGLWidget_3d->palettes[n].RGB.G * 255.0)), int(round(ui->openGLWidget_3d->palettes[n].RGB.R * 255.0))),
Vec3b(int(round(ui->openGLWidget_3d->palettes[n].RGB.B * 255.0)), int(round(ui->openGLWidget_3d->palettes[n].RGB.G * 255.0)), int(round(ui->openGLWidget_3d->palettes[n].RGB.R * 255.0))),
mask); // create mask for current color
ui->openGLWidget_3d->palettes[n].count = cv::countNonZero(mask); // count pixels in this mask
ui->openGLWidget_3d->palettes[n].percentage = double(ui->openGLWidget_3d->palettes[n].count) / double(total); // compute color use percentage
}
// delete non significant values in palette by percentage
if (ui->checkBox_filter_percent->isChecked()) { // filter by x% ?
bool cleaning_found = false; // indicator
std::sort(ui->openGLWidget_3d->palettes, ui->openGLWidget_3d->palettes + ui->openGLWidget_3d->nb_palettes,
[](const struct_palette& a, const struct_palette& b) {return a.percentage > b.percentage;}); // sort palette by percentage
while (double(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].count) / double(total) < double(ui->spinBox_nb_percentage->value()) / 100.0) { // at the end of palette, find values < x%
Mat1b cleaning_mask;
inRange(quantized, Vec3b(int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.B * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.G * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.R * 255.0))),
Vec3b(int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.B * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.G * 255.0)), int(round(ui->openGLWidget_3d->palettes[ui->openGLWidget_3d->nb_palettes - 1].RGB.R * 255.0))),
cleaning_mask); // extract color from image
int c = countNonZero(cleaning_mask); // count occurences
total = total - c; // update total pixel count
ui->openGLWidget_3d->nb_palettes--; // exclude this color from palette
if (c > 0) // really found this color ?
cleaning_found = true; // nb_palettes has to change
}
if (cleaning_found) { // if cleaning found
ui->spinBox_nb_palettes->setValue(ui->openGLWidget_3d->nb_palettes); // show new number of colors without cleaned values
// re-compute percentages