77f55ac2abbcff14f0f7b98afd079abde526fbc0
[gencolormap.git] / gui.cpp
1 /*
2  * Copyright (C) 2015 Computer Graphics Group, University of Siegen
3  * Written by Martin Lambers <martin.lambers@uni-siegen.de>
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to deal
7  * in the Software without restriction, including without limitation the rights
8  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9  * copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23
24 #include <cmath>
25
26 #include "gui.hpp"
27
28 #include <QApplication>
29 #include <QGridLayout>
30 #include <QLabel>
31 #include <QRadioButton>
32 #include <QSlider>
33 #include <QDoubleSpinBox>
34 #include <QMenu>
35 #include <QMenuBar>
36 #include <QImage>
37 #include <QPixmap>
38 #include <QFileDialog>
39 #include <QClipboard>
40 #include <QTextStream>
41 #include <QMessageBox>
42
43 #include "colormap.hpp"
44
45
46 CombinedSliderSpinBox::CombinedSliderSpinBox(float minval, float maxval, float step) :
47     _update_lock(false),
48     minval(minval), maxval(maxval), step(step)
49 {
50     slider = new QSlider(Qt::Horizontal);
51     slider->setMinimum(0);
52     slider->setMaximum((maxval - minval) / step);
53     slider->setSingleStep(step);
54
55     spinbox = new QDoubleSpinBox();
56     spinbox->setRange(minval, maxval);
57     spinbox->setSingleStep(step);
58     spinbox->setDecimals(std::log10(1.0f / step));
59
60     connect(slider, SIGNAL(valueChanged(int)), this, SLOT(slider_changed()));
61     connect(spinbox, SIGNAL(valueChanged(double)), this, SLOT(spinbox_changed()));
62 }
63
64 float CombinedSliderSpinBox::value() const
65 {
66     return spinbox->value();
67 }
68
69 void CombinedSliderSpinBox::setValue(float v)
70 {
71     _update_lock = true;
72     spinbox->setValue(v);
73     slider->setValue((v - minval) / step);
74     _update_lock = false;
75 }
76
77 void CombinedSliderSpinBox::slider_changed()
78 {
79     if (!_update_lock) {
80         _update_lock = true;
81         int i = slider->value();
82         float v = i * step + minval;
83         spinbox->setValue(v);
84         _update_lock = false;
85         emit valueChanged(value());
86     }
87 }
88
89 void CombinedSliderSpinBox::spinbox_changed()
90 {
91     if (!_update_lock) {
92         _update_lock = true;
93         float v = spinbox->value();
94         int i = (v - minval) / step;
95         slider->setValue(i);
96         _update_lock = false;
97         emit valueChanged(value());
98     }
99 }
100
101 GUI::GUI()
102 {
103     setWindowTitle("Generate Color Map");
104     setWindowIcon(QIcon(":cg-logo.png"));
105     QWidget *widget = new QWidget;
106     QGridLayout *layout = new QGridLayout;
107
108     QLabel* type_label = new QLabel("Type:");
109     layout->addWidget(type_label, 0, 0);
110     type_seq_btn = new QRadioButton("Sequential");
111     layout->addWidget(type_seq_btn, 0, 1);
112     type_div_btn = new QRadioButton("Diverging");
113     layout->addWidget(type_div_btn, 0, 2);
114     QRadioButton* type_qual_btn = new QRadioButton("Qualitative");
115     layout->addWidget(type_qual_btn, 0, 3);
116
117     QLabel* n_label = new QLabel("Colors:");
118     layout->addWidget(n_label, 1, 0);
119     n_spinbox = new QSpinBox();
120     n_spinbox->setRange(2, 1024);
121     n_spinbox->setSingleStep(1);
122     layout->addWidget(n_spinbox, 1, 1, 1, 3);
123
124     QLabel* hue_label = new QLabel("Hue:");
125     layout->addWidget(hue_label, 2, 0);
126     hue_changer = new CombinedSliderSpinBox(0, 360, 1);
127     layout->addWidget(hue_changer->slider, 2, 1, 1, 2);
128     layout->addWidget(hue_changer->spinbox, 2, 3);
129
130     divergence_label = new QLabel("Divergence:");
131     layout->addWidget(divergence_label, 3, 0);
132     divergence_changer = new CombinedSliderSpinBox(0, 360, 1);
133     layout->addWidget(divergence_changer->slider, 3, 1, 1, 2);
134     layout->addWidget(divergence_changer->spinbox, 3, 3);
135
136     warmth_label = new QLabel("Warmth:");
137     layout->addWidget(warmth_label, 4, 0);
138     warmth_changer = new CombinedSliderSpinBox(0, 1, 0.01f);
139     layout->addWidget(warmth_changer->slider, 4, 1, 1, 2);
140     layout->addWidget(warmth_changer->spinbox, 4, 3);
141
142     QLabel* contrast_label = new QLabel("Contrast:");
143     layout->addWidget(contrast_label, 5, 0);
144     contrast_changer = new CombinedSliderSpinBox(0, 1, 0.01f);
145     layout->addWidget(contrast_changer->slider, 5, 1, 1, 2);
146     layout->addWidget(contrast_changer->spinbox, 5, 3);
147
148     QLabel* saturation_label = new QLabel("Saturation:");
149     layout->addWidget(saturation_label, 6, 0);
150     saturation_changer = new CombinedSliderSpinBox(0, 1, 0.01f);
151     layout->addWidget(saturation_changer->slider, 6, 1, 1, 2);
152     layout->addWidget(saturation_changer->spinbox, 6, 3);
153
154     QLabel* brightness_label = new QLabel("Brightness:");
155     layout->addWidget(brightness_label, 7, 0);
156     brightness_changer = new CombinedSliderSpinBox(0, 1, 0.01f);
157     layout->addWidget(brightness_changer->slider, 7, 1, 1, 2);
158     layout->addWidget(brightness_changer->spinbox, 7, 3);
159
160     connect(type_seq_btn, SIGNAL(toggled(bool)), this, SLOT(update()));
161     connect(n_spinbox, SIGNAL(valueChanged(int)), this, SLOT(update()));
162     connect(type_div_btn, SIGNAL(toggled(bool)), this, SLOT(update()));
163     connect(hue_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
164     connect(divergence_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
165     connect(warmth_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
166     connect(contrast_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
167     connect(saturation_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
168     connect(brightness_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
169
170     colormap_label = new QLabel();
171     colormap_label->setScaledContents(true);
172     layout->addWidget(colormap_label, 0, 4, 8, 1);
173
174     layout->setColumnStretch(4, 1);
175     widget->setLayout(layout);
176     setCentralWidget(widget);
177
178     QMenu* file_menu = menuBar()->addMenu("&File");
179     QAction* file_export_png_act = new QAction("Export as &PNG...", this);
180     connect(file_export_png_act, SIGNAL(triggered()), this, SLOT(file_export_png()));
181     file_menu->addAction(file_export_png_act);
182     QAction* file_export_csv_act = new QAction("Export as &CSV...", this);
183     connect(file_export_csv_act, SIGNAL(triggered()), this, SLOT(file_export_csv()));
184     file_menu->addAction(file_export_csv_act);
185     file_menu->addSeparator();
186     QAction* quit_act = new QAction("&Quit...", this);
187     quit_act->setShortcut(QKeySequence::Quit);
188     connect(quit_act, SIGNAL(triggered()), this, SLOT(close()));
189     file_menu->addAction(quit_act);
190     QMenu* edit_menu = menuBar()->addMenu("&Edit");
191     QAction* edit_reset_act = new QAction("&Reset", this);
192     connect(edit_reset_act, SIGNAL(triggered()), this, SLOT(edit_reset()));
193     edit_menu->addAction(edit_reset_act);
194     QAction* edit_copy_as_img_act = new QAction("Copy as &image", this);
195     connect(edit_copy_as_img_act, SIGNAL(triggered()), this, SLOT(edit_copy_as_img()));
196     edit_menu->addAction(edit_copy_as_img_act);
197     QAction* edit_copy_as_txt_act = new QAction("Copy as &text", this);
198     connect(edit_copy_as_txt_act, SIGNAL(triggered()), this, SLOT(edit_copy_as_txt()));
199     edit_copy_as_txt_act->setShortcut(QKeySequence::Copy);
200     edit_menu->addAction(edit_copy_as_txt_act);
201     QMenu* help_menu = menuBar()->addMenu("&Help");
202     QAction* help_about_act = new QAction("&About", this);
203     connect(help_about_act, SIGNAL(triggered()), this, SLOT(help_about()));
204     help_menu->addAction(help_about_act);
205
206     show();
207     edit_reset();
208 }
209
210 GUI::~GUI()
211 {
212 }
213
214 void GUI::get_params(int& type, int& n, float& hue, float& divergence,
215         float& contrast, float& saturation, float& brightness,
216         float& warmth)
217 {
218     type = type_seq_btn->isChecked() ? 0 : type_div_btn->isChecked() ? 1 : 2;
219     n = n_spinbox->value();
220     hue = hue_changer->value() / 180.0f * static_cast<float>(M_PI);
221     divergence = divergence_changer->value() / 180.0f * static_cast<float>(M_PI);
222     contrast = contrast_changer->value();
223     saturation = saturation_changer->value();
224     brightness = brightness_changer->value();
225     warmth = warmth_changer->value();
226 }
227
228 std::vector<unsigned char> GUI::get_map(int type, int n, float hue, float divergence,
229         float contrast, float saturation, float brightness,
230         float warmth)
231 {
232     std::vector<unsigned char> colormap(3 * n);
233     if (type == 0) {
234         ColorMap::Sequential(n, &(colormap[0]), hue,
235                 contrast, saturation, brightness, warmth);
236     } else if (type == 1) {
237         ColorMap::Diverging(n, &(colormap[0]), hue, divergence,
238                 contrast, saturation, brightness, warmth);
239     } else {
240         ColorMap::Qualitative(n, &(colormap[0]), hue, divergence,
241                 contrast, saturation, brightness);
242     }
243     return colormap;
244 }
245
246 void GUI::update()
247 {
248     if (update_lock)
249         return;
250
251     int type, n;
252     float hue, divergence, contrast, saturation, brightness, warmth;
253     get_params(type, n, hue, divergence, contrast, saturation, brightness, warmth);
254
255     divergence_label->setEnabled(type >= 1);
256     divergence_changer->slider->setEnabled(type >= 1);
257     divergence_changer->spinbox->setEnabled(type >= 1);
258     warmth_label->setEnabled(type <= 1);
259     warmth_changer->slider->setEnabled(type <= 1);
260     warmth_changer->spinbox->setEnabled(type <= 1);
261
262     const int img_width = 32;
263     const int img_height = colormap_label->height();
264     if (img_height < n)
265         n = img_height;
266     QImage img(img_width, img_height, QImage::Format_RGB32);
267
268     std::vector<unsigned char> colormap(3 * n);
269     if (type == 0) {
270         ColorMap::Sequential(n, &(colormap[0]), hue,
271                 contrast, saturation, brightness, warmth);
272     } else if (type == 1) {
273         ColorMap::Diverging(n, &(colormap[0]), hue, divergence,
274                 contrast, saturation, brightness, warmth);
275     } else {
276         ColorMap::Qualitative(n, &(colormap[0]), hue, divergence,
277                 contrast, saturation, brightness);
278     }
279     for (int y = 0; y < img_height; y++) {
280         float entry_height = img_height / static_cast<float>(n);
281         int i = y / entry_height;
282         QRgb rgb = QColor(colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2]).rgb();
283         QRgb* scanline = reinterpret_cast<QRgb*>(img.scanLine(y));
284         for (int x = 0; x < img_width; x++)
285             scanline[x] = rgb;
286     }
287     colormap_label->setPixmap(QPixmap::fromImage(img));
288 }
289
290 void GUI::file_export_png()
291 {
292     QString name = QFileDialog::getSaveFileName();
293     if (!name.isEmpty()) {
294         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
295         int type, n;
296         float hue, divergence, contrast, saturation, brightness, warmth;
297         get_params(type, n, hue, divergence, contrast, saturation, brightness, warmth);
298         std::vector<unsigned char> colormap = get_map(type, n, hue, divergence,
299                 contrast, saturation, brightness, warmth);
300         QImage img(n, 1, QImage::Format_RGB32);
301         QRgb* scanline = reinterpret_cast<QRgb*>(img.scanLine(0));
302         for (int i = 0; i < n; i++) {
303             scanline[i] = QColor(colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2]).rgb();
304         }
305         img.save(name, "png");
306         QApplication::restoreOverrideCursor();
307     }
308 }
309
310 void GUI::file_export_csv()
311 {
312     QString name = QFileDialog::getSaveFileName();
313     if (!name.isEmpty()) {
314         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
315         QFile file(name);
316         if (file.open(QIODevice::ReadWrite)) {
317             int type, n;
318             float hue, divergence, contrast, saturation, brightness, warmth;
319             get_params(type, n, hue, divergence, contrast, saturation, brightness, warmth);
320             std::vector<unsigned char> colormap = get_map(type, n, hue, divergence,
321                     contrast, saturation, brightness, warmth);
322             QTextStream stream(&file);
323             for (int i = 0; i < n; i++) {
324                 stream << colormap[3 * i + 0] << ", "
325                        << colormap[3 * i + 1] << ", "
326                        << colormap[3 * i + 2] << endl;
327             }
328         }
329         QApplication::restoreOverrideCursor();
330     }
331 }
332
333 void GUI::edit_reset()
334 {
335     update_lock = true;
336     type_seq_btn->setChecked(true);
337     n_spinbox->setValue(301);
338     hue_changer->setValue(0);
339     divergence_changer->setValue(240);
340     warmth_changer->setValue(ColorMap::DefaultWarmth);
341     contrast_changer->setValue(ColorMap::DefaultContrast);
342     saturation_changer->setValue(ColorMap::DefaultSaturation);
343     brightness_changer->setValue(ColorMap::DefaultBrightness);
344     update_lock = false;
345     update();
346 }
347
348 void GUI::edit_copy_as_img()
349 {
350     int type, n;
351     float hue, divergence, contrast, saturation, brightness, warmth;
352     get_params(type, n, hue, divergence, contrast, saturation, brightness, warmth);
353     std::vector<unsigned char> colormap = get_map(type, n, hue, divergence,
354             contrast, saturation, brightness, warmth);
355     QImage img(n, 1, QImage::Format_RGB32);
356     QRgb* scanline = reinterpret_cast<QRgb*>(img.scanLine(0));
357     for (int i = 0; i < n; i++) {
358         scanline[i] = QColor(colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2]).rgb();
359     }
360     QApplication::clipboard()->setImage(img);
361 }
362
363 void GUI::edit_copy_as_txt()
364 {
365     int type, n;
366     float hue, divergence, contrast, saturation, brightness, warmth;
367     get_params(type, n, hue, divergence, contrast, saturation, brightness, warmth);
368     std::vector<unsigned char> colormap = get_map(type, n, hue, divergence,
369             contrast, saturation, brightness, warmth);
370     QString string;
371     QTextStream stream(&string);
372     for (int i = 0; i < n; i++) {
373         stream << colormap[3 * i + 0] << ", "
374                << colormap[3 * i + 1] << ", "
375                << colormap[3 * i + 2] << endl;
376     }
377     QApplication::clipboard()->setText(string);
378 }
379
380 void GUI::help_about()
381 {
382     QMessageBox::about(this, "About",
383                 "<p>gencolormap version 0.1</p>"
384                 "<p>Copyright (C) 2015<br>"
385                 "   <a href=\"http://www.cg.informatik.uni-siegen.de/\">"
386                 "   Computer Graphics Group, University of Siegen</a>.<br>"
387                 "   Written by <a href=\"http://www.cg.informatik.uni-siegen.de/lambers-martin\">Martin Lambers</a>.<br>"
388                 "   This is free software under the terms of the "
389                     "<a href=\"https://www.debian.org/legal/licenses/mit\">MIT/Expat License</a>. "
390                 "   There is NO WARRANTY, to the extent permitted by law."
391                 "</p>"
392                 "<p>This program implements the color map generation techniques described in<br>"
393                 "   M. Wijffelaars, R. Vliegen, J.J. van Wijk, E.-J. van der Linden."
394                 "   Generating color palettes using intuitive parameters. "
395                 "   In Computer Graphics Forum, vol. 27, no. 3, pp. 743-750, 2008."
396                 "</p>");
397 }
398
399 int main(int argc, char* argv[])
400 {
401     QApplication app(argc, argv);
402     GUI gui;
403     gui.show();
404     return app.exec();
405 }