Implement CubeHelix color maps, and reorganize command line and GUI accordingly.
[gencolormap.git] / gui.cpp
1 /*
2  * Copyright (C) 2015, 2016 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 "gui.hpp"
25
26 #include <QApplication>
27 #include <QGuiApplication>
28 #include <QGridLayout>
29 #include <QLabel>
30 #include <QRadioButton>
31 #include <QSlider>
32 #include <QDoubleSpinBox>
33 #include <QMenu>
34 #include <QMenuBar>
35 #include <QImage>
36 #include <QPixmap>
37 #include <QFileDialog>
38 #include <QClipboard>
39 #include <QTextStream>
40 #include <QMessageBox>
41 #include <QtMath>
42
43 #include "colormap.hpp"
44
45
46 ColorMapCombinedSliderSpinBox::ColorMapCombinedSliderSpinBox(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(sliderChanged()));
61     connect(spinbox, SIGNAL(valueChanged(double)), this, SLOT(spinboxChanged()));
62 }
63
64 float ColorMapCombinedSliderSpinBox::value() const
65 {
66     return spinbox->value();
67 }
68
69 void ColorMapCombinedSliderSpinBox::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 ColorMapCombinedSliderSpinBox::sliderChanged()
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 ColorMapCombinedSliderSpinBox::spinboxChanged()
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 static void hideWidgetButPreserveSize(QWidget* widget)
102 {
103     QSizePolicy sp = widget->sizePolicy();
104     sp.setRetainSizeWhenHidden(true);
105     widget->setSizePolicy(sp);
106     widget->hide();
107 }
108
109 ColorMapBrewerSequentialWidget::ColorMapBrewerSequentialWidget() :
110     _update_lock(false)
111 {
112     QGridLayout *layout = new QGridLayout;
113
114     QLabel* n_label = new QLabel("Colors:");
115     layout->addWidget(n_label, 1, 0);
116     _n_spinbox = new QSpinBox();
117     _n_spinbox->setRange(2, 1024);
118     _n_spinbox->setSingleStep(1);
119     layout->addWidget(_n_spinbox, 1, 1, 1, 3);
120
121     QLabel* hue_label = new QLabel("Hue:");
122     layout->addWidget(hue_label, 2, 0);
123     _hue_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
124     layout->addWidget(_hue_changer->slider, 2, 1, 1, 2);
125     layout->addWidget(_hue_changer->spinbox, 2, 3);
126
127     QLabel* divergence_label = new QLabel("Divergence:");
128     layout->addWidget(divergence_label, 3, 0);
129     ColorMapCombinedSliderSpinBox* divergence_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
130     layout->addWidget(divergence_changer->slider, 3, 1, 1, 2);
131     layout->addWidget(divergence_changer->spinbox, 3, 3);
132     hideWidgetButPreserveSize(divergence_label);
133     hideWidgetButPreserveSize(divergence_changer->slider);
134     hideWidgetButPreserveSize(divergence_changer->spinbox);
135
136     QLabel* warmth_label = new QLabel("Warmth:");
137     layout->addWidget(warmth_label, 4, 0);
138     _warmth_changer = new ColorMapCombinedSliderSpinBox(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 ColorMapCombinedSliderSpinBox(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 ColorMapCombinedSliderSpinBox(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 ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
157     layout->addWidget(_brightness_changer->slider, 7, 1, 1, 2);
158     layout->addWidget(_brightness_changer->spinbox, 7, 3);
159
160     layout->addItem(new QSpacerItem(0, 0), 8, 0, 1, 4);
161
162     layout->setColumnStretch(1, 1);
163     layout->addItem(new QSpacerItem(0, 0), 8, 0, 1, 4);
164     layout->setRowStretch(8, 1);
165     setLayout(layout);
166
167     connect(_n_spinbox, SIGNAL(valueChanged(int)), this, SLOT(update()));
168     connect(_hue_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
169     connect(_warmth_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
170     connect(_contrast_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
171     connect(_saturation_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
172     connect(_brightness_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
173     reset();
174 }
175
176 ColorMapBrewerSequentialWidget::~ColorMapBrewerSequentialWidget()
177 {
178 }
179
180 void ColorMapBrewerSequentialWidget::reset()
181 {
182     _update_lock = true;
183     _n_spinbox->setValue(256);
184     _hue_changer->setValue(qRadiansToDegrees(ColorMap::BrewerSequentialDefaultHue));
185     _warmth_changer->setValue(ColorMap::BrewerSequentialDefaultWarmth);
186     _contrast_changer->setValue(ColorMap::BrewerSequentialDefaultContrast);
187     _saturation_changer->setValue(ColorMap::BrewerSequentialDefaultSaturation);
188     _brightness_changer->setValue(ColorMap::BrewerSequentialDefaultBrightness);
189     _update_lock = false;
190     update();
191 }
192
193 void ColorMapBrewerSequentialWidget::parameters(int& n, float& hue,
194         float& contrast, float& saturation, float& brightness, float& warmth)
195 {
196     n = _n_spinbox->value();
197     hue = qDegreesToRadians(_hue_changer->value());
198     contrast = _contrast_changer->value();
199     saturation = _saturation_changer->value();
200     brightness = _brightness_changer->value();
201     warmth = _warmth_changer->value();
202 }
203
204 void ColorMapBrewerSequentialWidget::recomputeColorMap()
205 {
206     int n;
207     float h, c, s, b, w;
208     parameters(n, h, c, s, b, w);
209     _colormap.resize(3 * n);
210     ColorMap::BrewerSequential(n, _colormap.data(), h, c, s, b, w);
211 }
212
213 void ColorMapBrewerSequentialWidget::update()
214 {
215     if (!_update_lock)
216         emit colorMapChanged();
217 }
218
219 ColorMapBrewerDivergingWidget::ColorMapBrewerDivergingWidget() :
220     _update_lock(false)
221 {
222     QGridLayout *layout = new QGridLayout;
223
224     QLabel* n_label = new QLabel("Colors:");
225     layout->addWidget(n_label, 1, 0);
226     _n_spinbox = new QSpinBox();
227     _n_spinbox->setRange(2, 1024);
228     _n_spinbox->setSingleStep(1);
229     layout->addWidget(_n_spinbox, 1, 1, 1, 3);
230
231     QLabel* hue_label = new QLabel("Hue:");
232     layout->addWidget(hue_label, 2, 0);
233     _hue_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
234     layout->addWidget(_hue_changer->slider, 2, 1, 1, 2);
235     layout->addWidget(_hue_changer->spinbox, 2, 3);
236
237     QLabel* divergence_label = new QLabel("Divergence:");
238     layout->addWidget(divergence_label, 3, 0);
239     _divergence_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
240     layout->addWidget(_divergence_changer->slider, 3, 1, 1, 2);
241     layout->addWidget(_divergence_changer->spinbox, 3, 3);
242
243     QLabel* warmth_label = new QLabel("Warmth:");
244     layout->addWidget(warmth_label, 4, 0);
245     _warmth_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
246     layout->addWidget(_warmth_changer->slider, 4, 1, 1, 2);
247     layout->addWidget(_warmth_changer->spinbox, 4, 3);
248
249     QLabel* contrast_label = new QLabel("Contrast:");
250     layout->addWidget(contrast_label, 5, 0);
251     _contrast_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
252     layout->addWidget(_contrast_changer->slider, 5, 1, 1, 2);
253     layout->addWidget(_contrast_changer->spinbox, 5, 3);
254
255     QLabel* saturation_label = new QLabel("Saturation:");
256     layout->addWidget(saturation_label, 6, 0);
257     _saturation_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
258     layout->addWidget(_saturation_changer->slider, 6, 1, 1, 2);
259     layout->addWidget(_saturation_changer->spinbox, 6, 3);
260
261     QLabel* brightness_label = new QLabel("Brightness:");
262     layout->addWidget(brightness_label, 7, 0);
263     _brightness_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
264     layout->addWidget(_brightness_changer->slider, 7, 1, 1, 2);
265     layout->addWidget(_brightness_changer->spinbox, 7, 3);
266
267     layout->setColumnStretch(1, 1);
268     layout->addItem(new QSpacerItem(0, 0), 8, 0, 1, 4);
269     layout->setRowStretch(8, 1);
270     setLayout(layout);
271
272     connect(_n_spinbox, SIGNAL(valueChanged(int)), this, SLOT(update()));
273     connect(_hue_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
274     connect(_divergence_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
275     connect(_warmth_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
276     connect(_contrast_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
277     connect(_saturation_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
278     connect(_brightness_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
279     reset();
280 }
281
282 ColorMapBrewerDivergingWidget::~ColorMapBrewerDivergingWidget()
283 {
284 }
285
286 void ColorMapBrewerDivergingWidget::reset()
287 {
288     _update_lock = true;
289     _n_spinbox->setValue(256);
290     _hue_changer->setValue(qRadiansToDegrees(ColorMap::BrewerDivergingDefaultHue));
291     _divergence_changer->setValue(qRadiansToDegrees(ColorMap::BrewerDivergingDefaultDivergence));
292     _warmth_changer->setValue(ColorMap::BrewerDivergingDefaultWarmth);
293     _contrast_changer->setValue(ColorMap::BrewerDivergingDefaultContrast);
294     _saturation_changer->setValue(ColorMap::BrewerDivergingDefaultSaturation);
295     _brightness_changer->setValue(ColorMap::BrewerDivergingDefaultBrightness);
296     _update_lock = false;
297     update();
298 }
299
300 void ColorMapBrewerDivergingWidget::parameters(int& n, float& hue, float& divergence,
301         float& contrast, float& saturation, float& brightness, float& warmth)
302 {
303     n = _n_spinbox->value();
304     hue = qDegreesToRadians(_hue_changer->value());
305     divergence = qDegreesToRadians(_divergence_changer->value());
306     contrast = _contrast_changer->value();
307     saturation = _saturation_changer->value();
308     brightness = _brightness_changer->value();
309     warmth = _warmth_changer->value();
310 }
311
312 void ColorMapBrewerDivergingWidget::recomputeColorMap()
313 {
314     int n;
315     float h, d, c, s, b, w;
316     parameters(n, h, d, c, s, b, w);
317     _colormap.resize(3 * n);
318     ColorMap::BrewerDiverging(n, _colormap.data(), h, d, c, s, b, w);
319 }
320
321 void ColorMapBrewerDivergingWidget::update()
322 {
323     if (!_update_lock)
324         emit colorMapChanged();
325 }
326
327 ColorMapBrewerQualitativeWidget::ColorMapBrewerQualitativeWidget() :
328     _update_lock(false)
329 {
330     QGridLayout *layout = new QGridLayout;
331
332     QLabel* n_label = new QLabel("Colors:");
333     layout->addWidget(n_label, 1, 0);
334     _n_spinbox = new QSpinBox();
335     _n_spinbox->setRange(2, 1024);
336     _n_spinbox->setSingleStep(1);
337     layout->addWidget(_n_spinbox, 1, 1, 1, 3);
338
339     QLabel* hue_label = new QLabel("Hue:");
340     layout->addWidget(hue_label, 2, 0);
341     _hue_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
342     layout->addWidget(_hue_changer->slider, 2, 1, 1, 2);
343     layout->addWidget(_hue_changer->spinbox, 2, 3);
344
345     QLabel* divergence_label = new QLabel("Divergence:");
346     layout->addWidget(divergence_label, 3, 0);
347     _divergence_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
348     layout->addWidget(_divergence_changer->slider, 3, 1, 1, 2);
349     layout->addWidget(_divergence_changer->spinbox, 3, 3);
350
351     QLabel* warmth_label = new QLabel("Warmth:");
352     layout->addWidget(warmth_label, 4, 0);
353     ColorMapCombinedSliderSpinBox* warmth_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
354     layout->addWidget(warmth_changer->slider, 4, 1, 1, 2);
355     layout->addWidget(warmth_changer->spinbox, 4, 3);
356     hideWidgetButPreserveSize(warmth_label);
357     hideWidgetButPreserveSize(warmth_changer->slider);
358     hideWidgetButPreserveSize(warmth_changer->spinbox);
359
360     QLabel* contrast_label = new QLabel("Contrast:");
361     layout->addWidget(contrast_label, 5, 0);
362     _contrast_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
363     layout->addWidget(_contrast_changer->slider, 5, 1, 1, 2);
364     layout->addWidget(_contrast_changer->spinbox, 5, 3);
365
366     QLabel* saturation_label = new QLabel("Saturation:");
367     layout->addWidget(saturation_label, 6, 0);
368     _saturation_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
369     layout->addWidget(_saturation_changer->slider, 6, 1, 1, 2);
370     layout->addWidget(_saturation_changer->spinbox, 6, 3);
371
372     QLabel* brightness_label = new QLabel("Brightness:");
373     layout->addWidget(brightness_label, 7, 0);
374     _brightness_changer = new ColorMapCombinedSliderSpinBox(0, 1, 0.01f);
375     layout->addWidget(_brightness_changer->slider, 7, 1, 1, 2);
376     layout->addWidget(_brightness_changer->spinbox, 7, 3);
377
378     layout->setColumnStretch(1, 1);
379     layout->addItem(new QSpacerItem(0, 0), 8, 0, 1, 4);
380     layout->setRowStretch(8, 1);
381     setLayout(layout);
382
383     connect(_n_spinbox, SIGNAL(valueChanged(int)), this, SLOT(update()));
384     connect(_hue_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
385     connect(_divergence_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
386     connect(_contrast_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
387     connect(_saturation_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
388     connect(_brightness_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
389     reset();
390 }
391
392 ColorMapBrewerQualitativeWidget::~ColorMapBrewerQualitativeWidget()
393 {
394 }
395
396 void ColorMapBrewerQualitativeWidget::reset()
397 {
398     _update_lock = true;
399     _n_spinbox->setValue(256);
400     _hue_changer->setValue(qRadiansToDegrees(ColorMap::BrewerQualitativeDefaultHue));
401     _divergence_changer->setValue(qRadiansToDegrees(ColorMap::BrewerQualitativeDefaultDivergence));
402     _contrast_changer->setValue(ColorMap::BrewerQualitativeDefaultContrast);
403     _saturation_changer->setValue(ColorMap::BrewerQualitativeDefaultSaturation);
404     _brightness_changer->setValue(ColorMap::BrewerQualitativeDefaultBrightness);
405     _update_lock = false;
406     update();
407 }
408
409 void ColorMapBrewerQualitativeWidget::parameters(int& n, float& hue, float& divergence,
410         float& contrast, float& saturation, float& brightness)
411 {
412     n = _n_spinbox->value();
413     hue = qDegreesToRadians(_hue_changer->value());
414     divergence = qDegreesToRadians(_divergence_changer->value());
415     contrast = _contrast_changer->value();
416     saturation = _saturation_changer->value();
417     brightness = _brightness_changer->value();
418 }
419
420 void ColorMapBrewerQualitativeWidget::recomputeColorMap()
421 {
422     int n;
423     float h, d, c, s, b;
424     parameters(n, h, d, c, s, b);
425     _colormap.resize(3 * n);
426     ColorMap::BrewerQualitative(n, _colormap.data(), h, d, c, s, b);
427 }
428
429 void ColorMapBrewerQualitativeWidget::update()
430 {
431     if (!_update_lock)
432         emit colorMapChanged();
433 }
434
435 ColorMapCubeHelixWidget::ColorMapCubeHelixWidget() :
436     _update_lock(false)
437 {
438     QGridLayout *layout = new QGridLayout;
439
440     QLabel* n_label = new QLabel("Colors:");
441     layout->addWidget(n_label, 1, 0);
442     _n_spinbox = new QSpinBox();
443     _n_spinbox->setRange(2, 1024);
444     _n_spinbox->setSingleStep(1);
445     layout->addWidget(_n_spinbox, 1, 1, 1, 3);
446
447     QLabel* hue_label = new QLabel("Hue:");
448     layout->addWidget(hue_label, 2, 0);
449     _hue_changer = new ColorMapCombinedSliderSpinBox(0, 360, 1);
450     layout->addWidget(_hue_changer->slider, 2, 1, 1, 2);
451     layout->addWidget(_hue_changer->spinbox, 2, 3);
452
453     QLabel* rotations_label = new QLabel("Rotations:");
454     layout->addWidget(rotations_label, 3, 0);
455     _rotations_changer = new ColorMapCombinedSliderSpinBox(-5.0f, +5.0f, 0.1f);
456     layout->addWidget(_rotations_changer->slider, 3, 1, 1, 2);
457     layout->addWidget(_rotations_changer->spinbox, 3, 3);
458
459     QLabel* saturation_label = new QLabel("Saturation:");
460     layout->addWidget(saturation_label, 4, 0);
461     _saturation_changer = new ColorMapCombinedSliderSpinBox(0.0f, 2.0f, 0.1f);
462     layout->addWidget(_saturation_changer->slider, 4, 1, 1, 2);
463     layout->addWidget(_saturation_changer->spinbox, 4, 3);
464
465     QLabel* gamma_label = new QLabel("Gamma:");
466     layout->addWidget(gamma_label, 5, 0);
467     _gamma_changer = new ColorMapCombinedSliderSpinBox(0.3f, 3.0f, 0.1f);
468     layout->addWidget(_gamma_changer->slider, 5, 1, 1, 2);
469     layout->addWidget(_gamma_changer->spinbox, 5, 3);
470
471     layout->setColumnStretch(1, 1);
472     layout->addItem(new QSpacerItem(0, 0), 6, 0, 1, 4);
473     layout->setRowStretch(6, 1);
474     setLayout(layout);
475
476     connect(_n_spinbox, SIGNAL(valueChanged(int)), this, SLOT(update()));
477     connect(_hue_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
478     connect(_rotations_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
479     connect(_saturation_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
480     connect(_gamma_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
481     reset();
482 }
483
484 ColorMapCubeHelixWidget::~ColorMapCubeHelixWidget()
485 {
486 }
487
488 void ColorMapCubeHelixWidget::reset()
489 {
490     _update_lock = true;
491     _n_spinbox->setValue(256);
492     _hue_changer->setValue(qRadiansToDegrees(ColorMap::CubeHelixDefaultHue));
493     _rotations_changer->setValue(ColorMap::CubeHelixDefaultRotations);
494     _saturation_changer->setValue(ColorMap::CubeHelixDefaultSaturation);
495     _gamma_changer->setValue(ColorMap::CubeHelixDefaultGamma);
496     _update_lock = false;
497     update();
498 }
499
500 void ColorMapCubeHelixWidget::parameters(int& n, float& hue,
501         float& rotations, float& saturation, float& gamma)
502 {
503     n = _n_spinbox->value();
504     hue = qDegreesToRadians(_hue_changer->value());
505     rotations = _rotations_changer->value();
506     saturation = _saturation_changer->value();
507     gamma = _gamma_changer->value();
508 }
509
510 void ColorMapCubeHelixWidget::recomputeColorMap()
511 {
512     int n;
513     float h, r, s, g;
514     parameters(n, h, r, s, g);
515     _colormap.resize(3 * n);
516     ColorMap::CubeHelix(n, _colormap.data(), h, r, s, g);
517 }
518
519 void ColorMapCubeHelixWidget::update()
520 {
521     if (!_update_lock)
522         emit colorMapChanged();
523 }
524
525
526 GUI::GUI()
527 {
528     setWindowTitle("Generate Color Map");
529     setWindowIcon(QIcon(":cg-logo.png"));
530
531     _brewerseq_widget = new ColorMapBrewerSequentialWidget;
532     _brewerdiv_widget = new ColorMapBrewerDivergingWidget;
533     _brewerqual_widget = new ColorMapBrewerQualitativeWidget;
534     _cubehelix_widget = new ColorMapCubeHelixWidget;
535     connect(_brewerseq_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
536     connect(_brewerdiv_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
537     connect(_brewerqual_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
538     connect(_cubehelix_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
539
540     QWidget *widget = new QWidget;
541     widget->setMinimumWidth(384 * qApp->devicePixelRatio());
542     QGridLayout *layout = new QGridLayout;
543
544     _tab_widget = new QTabWidget();
545     _tab_widget->addTab(_brewerseq_widget,  "Brewer-like Sequential");
546     _tab_widget->addTab(_brewerdiv_widget,  "Brewer-like Diverging");
547     _tab_widget->addTab(_brewerqual_widget, "Brewer-like Qualitative");
548     _tab_widget->addTab(_cubehelix_widget,  "CubeHelix");
549     connect(_tab_widget, SIGNAL(currentChanged(int)), this, SLOT(update()));
550     layout->addWidget(_tab_widget, 0, 0);
551
552     _colormap_label = new QLabel();
553     _colormap_label->setScaledContents(true);
554     layout->addWidget(_colormap_label, 0, 1);
555
556     layout->setColumnStretch(0, 1);
557     widget->setLayout(layout);
558     setCentralWidget(widget);
559
560     QMenu* file_menu = menuBar()->addMenu("&File");
561     QAction* file_export_png_act = new QAction("Export as &PNG...", this);
562     connect(file_export_png_act, SIGNAL(triggered()), this, SLOT(file_export_png()));
563     file_menu->addAction(file_export_png_act);
564     QAction* file_export_csv_act = new QAction("Export as &CSV...", this);
565     connect(file_export_csv_act, SIGNAL(triggered()), this, SLOT(file_export_csv()));
566     file_menu->addAction(file_export_csv_act);
567     file_menu->addSeparator();
568     QAction* quit_act = new QAction("&Quit...", this);
569     quit_act->setShortcut(QKeySequence::Quit);
570     connect(quit_act, SIGNAL(triggered()), this, SLOT(close()));
571     file_menu->addAction(quit_act);
572     QMenu* edit_menu = menuBar()->addMenu("&Edit");
573     QAction* edit_reset_act = new QAction("&Reset", this);
574     connect(edit_reset_act, SIGNAL(triggered()), this, SLOT(edit_reset()));
575     edit_menu->addAction(edit_reset_act);
576     QAction* edit_copy_as_img_act = new QAction("Copy as &image", this);
577     connect(edit_copy_as_img_act, SIGNAL(triggered()), this, SLOT(edit_copy_as_img()));
578     edit_menu->addAction(edit_copy_as_img_act);
579     QAction* edit_copy_as_txt_act = new QAction("Copy as &text", this);
580     connect(edit_copy_as_txt_act, SIGNAL(triggered()), this, SLOT(edit_copy_as_txt()));
581     edit_copy_as_txt_act->setShortcut(QKeySequence::Copy);
582     edit_menu->addAction(edit_copy_as_txt_act);
583     QMenu* help_menu = menuBar()->addMenu("&Help");
584     QAction* help_about_act = new QAction("&About", this);
585     connect(help_about_act, SIGNAL(triggered()), this, SLOT(help_about()));
586     help_menu->addAction(help_about_act);
587
588     show();
589     update();
590 }
591
592 GUI::~GUI()
593 {
594 }
595
596 void GUI::update()
597 {
598     ColorMapWidget* currentWidget = reinterpret_cast<ColorMapWidget*>(_tab_widget->currentWidget());
599     const QVector<unsigned char>& colormap = *currentWidget->colorMap();
600
601     int img_width = 32;
602     int img_height = _colormap_label->height();
603     QImage img(img_width, img_height, QImage::Format_RGB32);
604     for (int y = 0; y < img_height; y++) {
605         float entry_height = img_height / static_cast<float>(colormap.size() / 3);
606         int i = y / entry_height;
607         QRgb rgb = QColor(colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2]).rgb();
608         QRgb* scanline = reinterpret_cast<QRgb*>(img.scanLine(y));
609         for (int x = 0; x < img_width; x++)
610             scanline[x] = rgb;
611     }
612     _colormap_label->setPixmap(QPixmap::fromImage(img));
613 }
614
615 void GUI::file_export_png()
616 {
617     QString name = QFileDialog::getSaveFileName();
618     if (!name.isEmpty()) {
619         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
620         ColorMapWidget* currentWidget = reinterpret_cast<ColorMapWidget*>(_tab_widget->currentWidget());
621         const QVector<unsigned char>& colormap = *currentWidget->colorMap();
622         QImage img(colormap.size() / 3, 1, QImage::Format_RGB32);
623         QRgb* scanline = reinterpret_cast<QRgb*>(img.scanLine(0));
624         for (int i = 0; i < colormap.size() / 3; i++) {
625             scanline[i] = QColor(colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2]).rgb();
626         }
627         img.save(name, "png");
628         QApplication::restoreOverrideCursor();
629     }
630 }
631
632 void GUI::file_export_csv()
633 {
634     QString name = QFileDialog::getSaveFileName();
635     if (!name.isEmpty()) {
636         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
637         QFile file(name);
638         if (file.open(QIODevice::ReadWrite)) {
639             ColorMapWidget* currentWidget = reinterpret_cast<ColorMapWidget*>(_tab_widget->currentWidget());
640             const QVector<unsigned char>& colormap = *currentWidget->colorMap();
641             QTextStream stream(&file);
642             for (int i = 0; i < colormap.size() / 3; i++) {
643                 stream << colormap[3 * i + 0] << ", "
644                        << colormap[3 * i + 1] << ", "
645                        << colormap[3 * i + 2] << endl;
646             }
647         }
648         QApplication::restoreOverrideCursor();
649     }
650 }
651
652 void GUI::edit_reset()
653 {
654     ColorMapWidget* currentWidget = reinterpret_cast<ColorMapWidget*>(_tab_widget->currentWidget());
655     currentWidget->reset();
656 }
657
658 void GUI::edit_copy_as_img()
659 {
660     ColorMapWidget* currentWidget = reinterpret_cast<ColorMapWidget*>(_tab_widget->currentWidget());
661     const QVector<unsigned char>& colormap = *currentWidget->colorMap();
662     QImage img(colormap.size() / 3, 1, QImage::Format_RGB32);
663     QRgb* scanline = reinterpret_cast<QRgb*>(img.scanLine(0));
664     for (int i = 0; i < colormap.size() / 3; i++) {
665         scanline[i] = QColor(colormap[3 * i + 0], colormap[3 * i + 1], colormap[3 * i + 2]).rgb();
666     }
667     QApplication::clipboard()->setImage(img);
668 }
669
670 void GUI::edit_copy_as_txt()
671 {
672     ColorMapWidget* currentWidget = reinterpret_cast<ColorMapWidget*>(_tab_widget->currentWidget());
673     const QVector<unsigned char>& colormap = *currentWidget->colorMap();
674     QString string;
675     QTextStream stream(&string);
676     for (int i = 0; i < colormap.size() / 3; i++) {
677         stream << colormap[3 * i + 0] << ", "
678                << colormap[3 * i + 1] << ", "
679                << colormap[3 * i + 2] << endl;
680     }
681     QApplication::clipboard()->setText(string);
682 }
683
684 void GUI::help_about()
685 {
686     QMessageBox::about(this, "About",
687                 "<p>gencolormap version 0.2</p>"
688                 "<p>Copyright (C) 2016<br>"
689                 "   <a href=\"http://www.cg.informatik.uni-siegen.de/\">"
690                 "   Computer Graphics Group, University of Siegen</a>.<br>"
691                 "   Written by <a href=\"http://www.cg.informatik.uni-siegen.de/lambers-martin\">Martin Lambers</a>.<br>"
692                 "   This is free software under the terms of the "
693                     "<a href=\"https://www.debian.org/legal/licenses/mit\">MIT/Expat License</a>. "
694                 "   There is NO WARRANTY, to the extent permitted by law."
695                 "</p>"
696                 "<p>This program implements the color map generation techniques described in<br>"
697                 "   M. Wijffelaars, R. Vliegen, J.J. van Wijk, E.-J. van der Linden."
698                 "   Generating color palettes using intuitive parameters. "
699                 "   In Computer Graphics Forum, vol. 27, no. 3, pp. 743-750, 2008."
700                 "</p>");
701 }
702
703 int main(int argc, char* argv[])
704 {
705     QApplication app(argc, argv);
706     GUI gui;
707     gui.show();
708     return app.exec();
709 }