annotate layer/ColourMapper.cpp @ 1245:f0e291fa7b9c

Use Range01 normalisation in Colour 3D Plot. This gives us the same column normalisation behaviour as in 2.5 (better than the Max1 option).
author Chris Cannam
date Tue, 28 Feb 2017 14:06:24 +0000
parents 73d43e410a6b
children 6e724c81f18f
rev   line source
Chris@376 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@376 2
Chris@376 3 /*
Chris@376 4 Sonic Visualiser
Chris@376 5 An audio file viewer and annotation editor.
Chris@376 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1199 7 This file copyright 2006-2016 Chris Cannam and QMUL.
Chris@376 8
Chris@376 9 This program is free software; you can redistribute it and/or
Chris@376 10 modify it under the terms of the GNU General Public License as
Chris@376 11 published by the Free Software Foundation; either version 2 of the
Chris@376 12 License, or (at your option) any later version. See the file
Chris@376 13 COPYING included with this distribution for more information.
Chris@376 14 */
Chris@376 15
Chris@376 16 #include "ColourMapper.h"
Chris@376 17
Chris@376 18 #include <iostream>
Chris@376 19
Chris@376 20 #include <cmath>
Chris@376 21
Chris@682 22 #include "base/Debug.h"
Chris@682 23
Chris@1012 24 #include <vector>
Chris@1012 25
Chris@1199 26 #include <QPainter>
Chris@1199 27
Chris@1012 28 using namespace std;
Chris@1012 29
Chris@1012 30 static vector<QColor> convertStrings(const vector<QString> &strs)
Chris@1012 31 {
Chris@1012 32 vector<QColor> converted;
Chris@1012 33 for (const auto &s: strs) converted.push_back(QColor(s));
Chris@1012 34 reverse(converted.begin(), converted.end());
Chris@1012 35 return converted;
Chris@1012 36 }
Chris@1012 37
Chris@1017 38 static vector<QColor> ice = convertStrings({
Chris@1015 39 // Based on ColorBrewer ylGnBu
Chris@1015 40 "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5",
Chris@1015 41 "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040"
Chris@1013 42 });
Chris@1012 43
Chris@1017 44 static vector<QColor> cherry = convertStrings({
Chris@1016 45 "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497",
Chris@1016 46 "#ae017e", "#7a0177", "#49006a"
Chris@1015 47 });
Chris@1015 48
Chris@1012 49 static void
Chris@1012 50 mapDiscrete(double norm, vector<QColor> &colours, double &r, double &g, double &b)
Chris@1012 51 {
Chris@1015 52 int n = int(colours.size());
Chris@1012 53 double m = norm * (n-1);
Chris@1012 54 if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; }
Chris@1012 55 if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; }
Chris@1012 56 int base(int(floor(m)));
Chris@1012 57 double prop0 = (base + 1.0) - m, prop1 = m - base;
Chris@1012 58 QColor c0(colours[base]), c1(colours[base+1]);
Chris@1012 59 r = c0.redF() * prop0 + c1.redF() * prop1;
Chris@1012 60 g = c0.greenF() * prop0 + c1.greenF() * prop1;
Chris@1012 61 b = c0.blueF() * prop0 + c1.blueF() * prop1;
Chris@1012 62 }
Chris@1012 63
Chris@902 64 ColourMapper::ColourMapper(int map, double min, double max) :
Chris@376 65 m_map(map),
Chris@376 66 m_min(min),
Chris@376 67 m_max(max)
Chris@376 68 {
Chris@376 69 if (m_min == m_max) {
Chris@682 70 cerr << "WARNING: ColourMapper: min == max (== " << m_min
Chris@682 71 << "), adjusting" << endl;
Chris@376 72 m_max = m_min + 1;
Chris@376 73 }
Chris@376 74 }
Chris@376 75
Chris@376 76 ColourMapper::~ColourMapper()
Chris@376 77 {
Chris@376 78 }
Chris@376 79
Chris@376 80 int
Chris@376 81 ColourMapper::getColourMapCount()
Chris@376 82 {
Chris@1015 83 return 12;
Chris@376 84 }
Chris@376 85
Chris@376 86 QString
Chris@376 87 ColourMapper::getColourMapName(int n)
Chris@376 88 {
Chris@1071 89 if (n >= getColourMapCount()) return QObject::tr("<unknown>");
Chris@376 90 StandardMap map = (StandardMap)n;
Chris@376 91
Chris@376 92 switch (map) {
Chris@1071 93 case Green: return QObject::tr("Green");
Chris@1071 94 case WhiteOnBlack: return QObject::tr("White on Black");
Chris@1071 95 case BlackOnWhite: return QObject::tr("Black on White");
Chris@1071 96 case Cherry: return QObject::tr("Cherry");
Chris@1071 97 case Wasp: return QObject::tr("Wasp");
Chris@1071 98 case Ice: return QObject::tr("Ice");
Chris@1071 99 case Sunset: return QObject::tr("Sunset");
Chris@1071 100 case FruitSalad: return QObject::tr("Fruit Salad");
Chris@1071 101 case Banded: return QObject::tr("Banded");
Chris@1071 102 case Highlight: return QObject::tr("Highlight");
Chris@1071 103 case Printer: return QObject::tr("Printer");
Chris@1071 104 case HighGain: return QObject::tr("High Gain");
Chris@376 105 }
Chris@376 106
Chris@1071 107 return QObject::tr("<unknown>");
Chris@376 108 }
Chris@376 109
Chris@376 110 QColor
Chris@902 111 ColourMapper::map(double value) const
Chris@376 112 {
Chris@902 113 double norm = (value - m_min) / (m_max - m_min);
Chris@902 114 if (norm < 0.0) norm = 0.0;
Chris@902 115 if (norm > 1.0) norm = 1.0;
Chris@376 116
Chris@902 117 double h = 0.0, s = 0.0, v = 0.0, r = 0.0, g = 0.0, b = 0.0;
Chris@376 118 bool hsv = true;
Chris@376 119
Chris@911 120 double blue = 0.6666, pieslice = 0.3333;
Chris@376 121
Chris@376 122 if (m_map >= getColourMapCount()) return Qt::black;
Chris@376 123 StandardMap map = (StandardMap)m_map;
Chris@376 124
Chris@376 125 switch (map) {
Chris@376 126
Chris@1017 127 case Green:
Chris@902 128 h = blue - norm * 2.0 * pieslice;
Chris@902 129 s = 0.5f + norm/2.0;
Chris@376 130 v = norm;
Chris@376 131 break;
Chris@376 132
Chris@376 133 case WhiteOnBlack:
Chris@376 134 r = g = b = norm;
Chris@376 135 hsv = false;
Chris@376 136 break;
Chris@376 137
Chris@376 138 case BlackOnWhite:
Chris@902 139 r = g = b = 1.0 - norm;
Chris@376 140 hsv = false;
Chris@376 141 break;
Chris@376 142
Chris@1017 143 case Cherry:
Chris@1015 144 hsv = false;
Chris@1017 145 mapDiscrete(norm, cherry, r, g, b);
Chris@376 146 break;
Chris@376 147
Chris@1017 148 case Wasp:
Chris@902 149 h = 0.15;
Chris@902 150 s = 1.0;
Chris@376 151 v = norm;
Chris@376 152 break;
Chris@376 153
Chris@376 154 case Sunset:
Chris@902 155 r = (norm - 0.24) * 2.38;
Chris@902 156 if (r > 1.0) r = 1.0;
Chris@902 157 if (r < 0.0) r = 0.0;
Chris@902 158 g = (norm - 0.64) * 2.777;
Chris@902 159 if (g > 1.0) g = 1.0;
Chris@902 160 if (g < 0.0) g = 0.0;
Chris@376 161 b = (3.6f * norm);
Chris@902 162 if (norm > 0.277) b = 2.0 - b;
Chris@902 163 if (b > 1.0) b = 1.0;
Chris@902 164 if (b < 0.0) b = 0.0;
Chris@376 165 hsv = false;
Chris@376 166 break;
Chris@376 167
Chris@376 168 case FruitSalad:
Chris@902 169 h = blue + (pieslice/6.0) - norm;
Chris@902 170 if (h < 0.0) h += 1.0;
Chris@902 171 s = 1.0;
Chris@902 172 v = 1.0;
Chris@376 173 break;
Chris@376 174
Chris@376 175 case Banded:
Chris@376 176 if (norm < 0.125) return Qt::darkGreen;
Chris@376 177 else if (norm < 0.25) return Qt::green;
Chris@376 178 else if (norm < 0.375) return Qt::darkBlue;
Chris@376 179 else if (norm < 0.5) return Qt::blue;
Chris@376 180 else if (norm < 0.625) return Qt::darkYellow;
Chris@376 181 else if (norm < 0.75) return Qt::yellow;
Chris@376 182 else if (norm < 0.875) return Qt::darkRed;
Chris@376 183 else return Qt::red;
Chris@376 184 break;
Chris@376 185
Chris@376 186 case Highlight:
Chris@376 187 if (norm > 0.99) return Qt::white;
Chris@376 188 else return Qt::darkBlue;
Chris@376 189
Chris@376 190 case Printer:
Chris@376 191 if (norm > 0.8) {
Chris@902 192 r = 1.0;
Chris@376 193 } else if (norm > 0.7) {
Chris@902 194 r = 0.9;
Chris@376 195 } else if (norm > 0.6) {
Chris@902 196 r = 0.8;
Chris@376 197 } else if (norm > 0.5) {
Chris@902 198 r = 0.7;
Chris@376 199 } else if (norm > 0.4) {
Chris@902 200 r = 0.6;
Chris@376 201 } else if (norm > 0.3) {
Chris@902 202 r = 0.5;
Chris@376 203 } else if (norm > 0.2) {
Chris@902 204 r = 0.4;
Chris@376 205 } else {
Chris@902 206 r = 0.0;
Chris@376 207 }
Chris@902 208 r = g = b = 1.0 - r;
Chris@376 209 hsv = false;
Chris@376 210 break;
Chris@536 211
Chris@536 212 case HighGain:
Chris@902 213 if (norm <= 1.0 / 256.0) {
Chris@902 214 norm = 0.0;
Chris@536 215 } else {
Chris@904 216 norm = 0.1f + (pow(((norm - 0.5) * 2.0), 3.0) + 1.0) / 2.081;
Chris@536 217 }
Chris@536 218 // now as for Sunset
Chris@902 219 r = (norm - 0.24) * 2.38;
Chris@902 220 if (r > 1.0) r = 1.0;
Chris@902 221 if (r < 0.0) r = 0.0;
Chris@902 222 g = (norm - 0.64) * 2.777;
Chris@902 223 if (g > 1.0) g = 1.0;
Chris@902 224 if (g < 0.0) g = 0.0;
Chris@536 225 b = (3.6f * norm);
Chris@902 226 if (norm > 0.277) b = 2.0 - b;
Chris@902 227 if (b > 1.0) b = 1.0;
Chris@902 228 if (b < 0.0) b = 0.0;
Chris@536 229 hsv = false;
Chris@536 230 /*
Chris@902 231 if (r > 1.0) r = 1.0;
Chris@902 232 r = g = b = 1.0 - r;
Chris@536 233 hsv = false;
Chris@536 234 */
Chris@536 235 break;
Chris@1012 236
Chris@1017 237 case Ice:
Chris@1012 238 hsv = false;
Chris@1017 239 mapDiscrete(norm, ice, r, g, b);
Chris@376 240 }
Chris@376 241
Chris@376 242 if (hsv) {
Chris@376 243 return QColor::fromHsvF(h, s, v);
Chris@376 244 } else {
Chris@376 245 return QColor::fromRgbF(r, g, b);
Chris@376 246 }
Chris@376 247 }
Chris@376 248
Chris@376 249 QColor
Chris@376 250 ColourMapper::getContrastingColour() const
Chris@376 251 {
Chris@376 252 if (m_map >= getColourMapCount()) return Qt::white;
Chris@376 253 StandardMap map = (StandardMap)m_map;
Chris@376 254
Chris@376 255 switch (map) {
Chris@376 256
Chris@1017 257 case Green:
Chris@376 258 return QColor(255, 150, 50);
Chris@376 259
Chris@376 260 case WhiteOnBlack:
Chris@376 261 return Qt::red;
Chris@376 262
Chris@376 263 case BlackOnWhite:
Chris@376 264 return Qt::darkGreen;
Chris@376 265
Chris@1017 266 case Cherry:
Chris@376 267 return Qt::green;
Chris@376 268
Chris@1017 269 case Wasp:
Chris@376 270 return QColor::fromHsv(240, 255, 255);
Chris@376 271
Chris@1017 272 case Ice:
Chris@376 273 return Qt::red;
Chris@376 274
Chris@376 275 case Sunset:
Chris@376 276 return Qt::white;
Chris@376 277
Chris@376 278 case FruitSalad:
Chris@376 279 return Qt::white;
Chris@376 280
Chris@376 281 case Banded:
Chris@376 282 return Qt::cyan;
Chris@376 283
Chris@376 284 case Highlight:
Chris@376 285 return Qt::red;
Chris@376 286
Chris@376 287 case Printer:
Chris@376 288 return Qt::red;
Chris@536 289
Chris@536 290 case HighGain:
Chris@536 291 return Qt::red;
Chris@376 292 }
Chris@376 293
Chris@376 294 return Qt::white;
Chris@376 295 }
Chris@376 296
Chris@376 297 bool
Chris@376 298 ColourMapper::hasLightBackground() const
Chris@376 299 {
Chris@376 300 if (m_map >= getColourMapCount()) return false;
Chris@376 301 StandardMap map = (StandardMap)m_map;
Chris@376 302
Chris@376 303 switch (map) {
Chris@376 304
Chris@376 305 case BlackOnWhite:
Chris@376 306 case Printer:
Chris@536 307 case HighGain:
Chris@376 308 return true;
Chris@376 309
Chris@1017 310 case Green:
Chris@805 311 case Sunset:
Chris@805 312 case WhiteOnBlack:
Chris@1017 313 case Cherry:
Chris@1017 314 case Wasp:
Chris@1017 315 case Ice:
Chris@805 316 case FruitSalad:
Chris@805 317 case Banded:
Chris@805 318 case Highlight:
Chris@805 319
Chris@376 320 default:
Chris@376 321 return false;
Chris@376 322 }
Chris@376 323 }
Chris@376 324
Chris@1199 325 QPixmap
Chris@1199 326 ColourMapper::getExamplePixmap(QSize size) const
Chris@1199 327 {
Chris@1199 328 QPixmap pmap(size);
Chris@1199 329 pmap.fill(Qt::white);
Chris@1199 330 QPainter paint(&pmap);
Chris@376 331
Chris@1199 332 int w = size.width(), h = size.height();
Chris@1199 333
Chris@1199 334 int margin = 2;
Chris@1199 335 if (w < 4 || h < 4) margin = 0;
Chris@1199 336 else if (w < 8 || h < 8) margin = 1;
Chris@1199 337
Chris@1199 338 int n = w - margin*2;
Chris@1199 339
Chris@1199 340 for (int x = 0; x < n; ++x) {
Chris@1199 341 double value = m_min + ((m_max - m_min) * x) / (n-1);
Chris@1199 342 QColor colour(map(value));
Chris@1199 343 paint.setPen(colour);
Chris@1199 344 paint.drawLine(x + margin, margin, x + margin, h - margin);
Chris@1199 345 }
Chris@1199 346
Chris@1199 347 return pmap;
Chris@1199 348 }
Chris@1199 349
Chris@1199 350