annotate layer/ColourMapper.cpp @ 1199:73d43e410a6b levelpanwidget

Add swatches to colour map combo (optionally, as it turns out they are quite visually distracting)
author Chris Cannam
date Fri, 16 Dec 2016 15:55:59 +0000
parents 65b183494331
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