Implement McNames color maps, but discourage their use.
authorMartin Lambers <marlam@marlam.de>
Mon, 8 Feb 2016 10:33:10 +0000 (11:33 +0100)
committerMartin Lambers <marlam@marlam.de>
Mon, 8 Feb 2016 10:33:10 +0000 (11:33 +0100)
cmdline.cpp
colormap.cpp
colormap.hpp
colormapwidgets.cpp
colormapwidgets.hpp
gui.cpp
gui.hpp

index 497547a..7eef62f 100644 (file)
@@ -38,7 +38,8 @@ enum type {
     brewer_diverging = 1,
     brewer_qualitative = 2,
     cubehelix = 3,
-    morelanddiverging = 4
+    morelanddiverging = 4,
+    mcnamessequential = 5
 };
 
 int main(int argc, char* argv[])
@@ -59,6 +60,7 @@ int main(int argc, char* argv[])
     unsigned char color0[3];
     bool have_color1 = false;
     unsigned char color1[3];
+    float periods = NAN;
     struct option options[] = {
         { "version",    no_argument,       0, 'v' },
         { "help",       no_argument,       0, 'H' },
@@ -74,11 +76,12 @@ int main(int argc, char* argv[])
         { "gamma",      required_argument, 0, 'g' },
         { "color0",     required_argument, 0, 'A' },
         { "color1",     required_argument, 0, 'O' },
+        { "periods",    required_argument, 0, 'p' },
         { 0, 0, 0, 0 }
     };
 
     for (;;) {
-        int c = getopt_long(argc, argv, "vHt:n:h:d:c:s:b:w:r:g:A:O:", options, NULL);
+        int c = getopt_long(argc, argv, "vHt:n:h:d:c:s:b:w:r:g:A:O:p:", options, NULL);
         if (c == -1)
             break;
         switch (c) {
@@ -94,6 +97,7 @@ int main(int argc, char* argv[])
                     : strcmp(optarg, "brewer-qualitative") == 0 ? brewer_qualitative
                     : strcmp(optarg, "cubehelix") == 0 ? cubehelix
                     : strcmp(optarg, "morelanddiverging") == 0 ? morelanddiverging
+                    : strcmp(optarg, "mcnamessequential") == 0 ? mcnamessequential
                     : -2);
             break;
         case 'n':
@@ -131,6 +135,9 @@ int main(int argc, char* argv[])
             std::sscanf(optarg, "%hhu,%hhu,%hhu", color1 + 0, color1 + 1, color1 + 2);
             have_color1 = true;
             break;
+        case 'p':
+            periods = atof(optarg);
+            break;
         default:
             return 1;
         }
@@ -164,10 +171,13 @@ int main(int argc, char* argv[])
                 "    [-r|--rotations=R]            Set number of rotations, in (-infty,infty)\n"
                 "    [-s|--saturation=S]           Set saturation, in [0,1]\n"
                 "    [-g|--gamma=G]                Set gamma correction, in (0,infty)\n"
-                "  MorelandDiverging color maps:\n"
+                "  Moreland diverging color maps:\n"
                 "    -t|--type=morelanddiverging   Generate a Moreland diverging color map\n"
                 "    [-A|--color0=sr,sg,sb         Set the first color as sRGB values in [0,255]\n"
                 "    [-O|--color1=sr,sg,sb         Set the last color as sRGB values in [0,255]\n"
+                "  McNames sequential color maps:\n"
+                "    -t|--type=mcnamessequential   Generate a McNames sequential color map\n"
+                "    [-p|--periods=p]              Set the number of periods in (0, infty)\n"
                 "Generates a color map and prints it to standard output as sRGB triplets.\n"
                 "Report bugs to <martin.lambers@uni-siegen.de>.\n", argv[0]);
         return 0;
@@ -253,6 +263,10 @@ int main(int argc, char* argv[])
             color1[2] = ColorMap::MorelandDivergingDefaultB1;
         }
     }
+    if (std::isnan(periods)) {
+        if (type == mcnamessequential)
+            periods = ColorMap::McNamesSequentialDefaultPeriods;
+    }
 
     std::vector<unsigned char> colormap(3 * n);
     switch (type) {
@@ -273,6 +287,9 @@ int main(int argc, char* argv[])
                 color0[0], color0[1], color0[2],
                 color1[0], color1[1], color1[2]);
         break;
+    case mcnamessequential:
+        ColorMap::McNamesSequential(n, &(colormap[0]), periods);
+        break;
     }
 
     for (int i = 0; i < n; i++) {
index 4357ef0..5f5c829 100644 (file)
@@ -684,4 +684,66 @@ void MorelandDiverging(int n, unsigned char* colormap,
     }
 }
 
+/* McNames */
+
+static void cart2pol(float x, float y, float* theta, float* rho)
+{
+    *theta = std::atan2(y, x);
+    *rho = std::hypot(x, y);
+}
+
+static void pol2cart(float theta, float rho, float* x, float* y)
+{
+    *x = rho * std::cos(theta);
+    *y = rho * std::sin(theta);
+}
+
+static float windowfunc(float t)
+{
+    static const float ww = std::sqrt(3.0f / 8.0f);
+    /* triangular window function: */
+#if 0
+    if (t <= 0.5f) {
+        return ww * 2.0f * t;
+    } else {
+        return ww * 2.0f * (1.0f - t);
+    }
+#endif
+    /* window function based on cosh: */
+#if 1
+    static const float acosh2 = std::acosh(2.0f);
+    return 0.95f * ww * (2.0f - std::cosh(acosh2 * (2.0f * t - 1.0f)));
+#endif
+}
+
+void McNamesSequential(int n, unsigned char* colormap, float periods)
+{
+    static const float sqrt3 = std::sqrt(3.0f);
+    static const float a12 = std::asin(1.0f / sqrt3);
+    static const float a23 = pi / 4.0f;
+
+    for (int i = 0; i < n; i++) {
+        float t = 1.0f - i / (n - 1.0f);
+        float w = windowfunc(t);
+        float tt = (1.0f - t) * sqrt3;
+        float ttt = (tt - sqrt3 / 2.0f) * periods * twopi / sqrt3;
+
+        float r0, g0, b0, r1, g1, b1, r2, g2, b2;
+        float ag, rd;
+        r0 = tt;
+        g0 = w * std::cos(ttt);
+        b0 = w * std::sin(ttt);
+        cart2pol(r0, g0, &ag, &rd);
+        pol2cart(ag + a12, rd, &r1, &g1);
+        b1 = b0;
+        cart2pol(r1, b1, &ag, &rd);
+        pol2cart(ag + a23, rd, &r2, &b2);
+        g2 = g1;
+
+        colormap[3 * i + 0] = float_to_uchar(clamp(r2, 0.0f, 1.0f));
+        colormap[3 * i + 1] = float_to_uchar(clamp(g2, 0.0f, 1.0f));
+        colormap[3 * i + 2] = float_to_uchar(clamp(b2, 0.0f, 1.0f));
+    }
+}
+
 }
index f6448aa..bc3a8bd 100644 (file)
@@ -147,6 +147,21 @@ void MorelandDiverging(int n, unsigned char* colormap,
         unsigned char sg1 = MorelandDivergingDefaultG1,
         unsigned char sb1 = MorelandDivergingDefaultB1);
 
+/*
+ * McNamesSequential color maps, as described in
+ * J. McNames, An Effective Color Scale for Simultaneous Color and Gray-Scale Publications,
+ * IEEE Signal Processing Magazine 23(1), January 2006, DOI 10.1109/MSP.2006.1593340.
+ *
+ * Note: Use CubeHelix instead! The McNames color maps are perceptually not linear in luminance!
+ */
+
+// Create a McNamesSequential colormap with n colors. Specify the number of
+// periods.
+const float McNamesSequentialDefaultPeriods = 2.0f;
+
+void McNamesSequential(int n, unsigned char* colormap,
+        float periods = McNamesSequentialDefaultPeriods);
+
 }
 
 #endif
index ee7a42e..3514997 100644 (file)
@@ -53,6 +53,11 @@ static QString moreland_reference = QString("Relevant paper: "
         "<a href=\"http://www.kennethmoreland.com/color-maps/\">Diverging Color Maps for Scientific Visualization</a>, "
         "Proc. Int. Symp. Visual Computing, December 2009."); // DOI 10.1007/978-3-642-10520-3_9.
 
+static QString mcnames_references = QString("Relevant paper: "
+        "J. McNames, "
+        "<a href=\"http://dx.doi.org/10.1109/MSP.2006.1593340\">An Effective Color Scale for Simultaneous Color and Gray-Scale Publications</a>, "
+        "IEEE Signal Processing Magazine 23(1), January 2006.");
+
 /* ColorMapCombinedSliderSpinBox */
 
 ColorMapCombinedSliderSpinBox::ColorMapCombinedSliderSpinBox(float minval, float maxval, float step) :
@@ -727,3 +732,71 @@ void ColorMapMorelandDivergingWidget::update()
     if (!_update_lock)
         emit colorMapChanged();
 }
+
+ColorMapMcNamesSequentialWidget::ColorMapMcNamesSequentialWidget() :
+    _update_lock(false)
+{
+    QGridLayout *layout = new QGridLayout;
+
+    QLabel* n_label = new QLabel("Colors:");
+    layout->addWidget(n_label, 1, 0);
+    _n_spinbox = new QSpinBox();
+    _n_spinbox->setRange(2, 1024);
+    _n_spinbox->setSingleStep(1);
+    layout->addWidget(_n_spinbox, 1, 1, 1, 3);
+
+    QLabel* periods_label = new QLabel("Periods:");
+    layout->addWidget(periods_label, 2, 0);
+    _periods_changer = new ColorMapCombinedSliderSpinBox(0.1f, 5.0f, 0.1f);
+    layout->addWidget(_periods_changer->slider, 2, 1, 1, 2);
+    layout->addWidget(_periods_changer->spinbox, 2, 3);
+
+    layout->setColumnStretch(1, 1);
+    layout->addItem(new QSpacerItem(0, 0), 3, 0, 1, 4);
+    layout->setRowStretch(3, 1);
+    setLayout(layout);
+
+    connect(_n_spinbox, SIGNAL(valueChanged(int)), this, SLOT(update()));
+    connect(_periods_changer, SIGNAL(valueChanged(float)), this, SLOT(update()));
+    reset();
+}
+
+ColorMapMcNamesSequentialWidget::~ColorMapMcNamesSequentialWidget()
+{
+}
+
+void ColorMapMcNamesSequentialWidget::reset()
+{
+    _update_lock = true;
+    _n_spinbox->setValue(256);
+    _periods_changer->setValue(ColorMap::McNamesSequentialDefaultPeriods);
+    _update_lock = false;
+    update();
+}
+
+QVector<QColor> ColorMapMcNamesSequentialWidget::colorMap() const
+{
+    int n;
+    float p;
+    parameters(n, p);
+    QVector<unsigned char> colormap(3 * n);
+    ColorMap::McNamesSequential(n, colormap.data(), p);
+    return toQColor(colormap);
+}
+
+QString ColorMapMcNamesSequentialWidget::reference() const
+{
+    return mcnames_references;
+}
+
+void ColorMapMcNamesSequentialWidget::parameters(int& n, float& p) const
+{
+    n = _n_spinbox->value();
+    p = _periods_changer->value();
+}
+
+void ColorMapMcNamesSequentialWidget::update()
+{
+    if (!_update_lock)
+        emit colorMapChanged();
+}
index 4c71243..b2516d6 100644 (file)
@@ -209,4 +209,24 @@ public:
             unsigned char& r1, unsigned char& g1, unsigned char& b1) const;
 };
 
+class ColorMapMcNamesSequentialWidget : public ColorMapWidget
+{
+Q_OBJECT
+private:
+    bool _update_lock;
+    QSpinBox* _n_spinbox;
+    ColorMapCombinedSliderSpinBox* _periods_changer;
+private slots:
+    void update();
+
+public:
+    ColorMapMcNamesSequentialWidget();
+    ~ColorMapMcNamesSequentialWidget();
+
+    void reset() override;
+    QVector<QColor> colorMap() const override;
+    QString reference() const override;
+    void parameters(int& n, float& p) const;
+};
+
 #endif
diff --git a/gui.cpp b/gui.cpp
index 2e2e5e8..33e75f2 100644 (file)
--- a/gui.cpp
+++ b/gui.cpp
@@ -48,11 +48,13 @@ GUI::GUI()
     _brewerqual_widget = new ColorMapBrewerQualitativeWidget;
     _cubehelix_widget = new ColorMapCubeHelixWidget;
     _morelanddiv_widget = new ColorMapMorelandDivergingWidget;
+    _mcnamesseq_widget = new ColorMapMcNamesSequentialWidget;
     connect(_brewerseq_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
     connect(_brewerdiv_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
     connect(_brewerqual_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
     connect(_cubehelix_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
     connect(_morelanddiv_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
+    connect(_mcnamesseq_widget, SIGNAL(colorMapChanged()), this, SLOT(update()));
 
     QWidget *widget = new QWidget;
     widget->setMinimumWidth(384 * qApp->devicePixelRatio());
@@ -62,6 +64,7 @@ GUI::GUI()
     _category_seq_widget = new QTabWidget();
     _category_seq_widget->addTab(_brewerseq_widget, "Brewer-like");
     _category_seq_widget->addTab(_cubehelix_widget, "CubeHelix");
+    //_category_seq_widget->addTab(_mcnamesseq_widget, "McNames");
     connect(_category_seq_widget, SIGNAL(currentChanged(int)), this, SLOT(update()));
     _category_widget->addTab(_category_seq_widget, "Sequential");
     _category_div_widget = new QTabWidget();
diff --git a/gui.hpp b/gui.hpp
index 9d9b1a1..15afc05 100644 (file)
--- a/gui.hpp
+++ b/gui.hpp
@@ -32,6 +32,7 @@ class ColorMapBrewerDivergingWidget;
 class ColorMapBrewerQualitativeWidget;
 class ColorMapCubeHelixWidget;
 class ColorMapMorelandDivergingWidget;
+class ColorMapMcNamesSequentialWidget;
 class QTabWidget;
 class QLabel;
 
@@ -50,6 +51,7 @@ private:
     ColorMapBrewerQualitativeWidget* _brewerqual_widget;
     ColorMapCubeHelixWidget* _cubehelix_widget;
     ColorMapMorelandDivergingWidget* _morelanddiv_widget;
+    ColorMapMcNamesSequentialWidget* _mcnamesseq_widget;
     QTabWidget* _category_widget;
     QTabWidget* _category_seq_widget;
     QTabWidget* _category_div_widget;