annotate layer/ColourMapper.cpp @ 1127:9fb8dfd7ce4c spectrogram-minor-refactor

Fix threshold in spectrogram -- it wasn't working in the last release. There is a new protocol for this. Formerly the threshold parameter had a range from -50dB to 0 with the default at -50, and -50 treated internally as "no threshold". However, there was a hardcoded, hidden internal threshold for spectrogram colour mapping at -80dB with anything below this being rounded to zero. Now the threshold parameter has range -81 to -1 with the default at -80, -81 is treated internally as "no threshold", and there is no hidden internal threshold. So the default behaviour is the same as before, an effective -80dB threshold, but it is now possible to change this in both directions. Sessions reloaded from prior versions may look slightly different because, if the session says there should be no threshold, there will now actually be no threshold instead of having the hidden internal one. Still need to do something in the UI to make it apparent that the -81dB setting removes the threshold entirely. This is at least no worse than the previous, also obscured, magic -50dB setting.
author Chris Cannam
date Mon, 01 Aug 2016 16:21:01 +0100
parents 65b183494331
children 73d43e410a6b
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@376 7 This file copyright 2006-2007 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@1012 26 using namespace std;
Chris@1012 27
Chris@1012 28 static vector<QColor> convertStrings(const vector<QString> &strs)
Chris@1012 29 {
Chris@1012 30 vector<QColor> converted;
Chris@1012 31 for (const auto &s: strs) converted.push_back(QColor(s));
Chris@1012 32 reverse(converted.begin(), converted.end());
Chris@1012 33 return converted;
Chris@1012 34 }
Chris@1012 35
Chris@1017 36 static vector<QColor> ice = convertStrings({
Chris@1015 37 // Based on ColorBrewer ylGnBu
Chris@1015 38 "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5",
Chris@1015 39 "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040"
Chris@1013 40 });
Chris@1012 41
Chris@1017 42 static vector<QColor> cherry = convertStrings({
Chris@1016 43 "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497",
Chris@1016 44 "#ae017e", "#7a0177", "#49006a"
Chris@1015 45 });
Chris@1015 46
Chris@1012 47 static void
Chris@1012 48 mapDiscrete(double norm, vector<QColor> &colours, double &r, double &g, double &b)
Chris@1012 49 {
Chris@1015 50 int n = int(colours.size());
Chris@1012 51 double m = norm * (n-1);
Chris@1012 52 if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; }
Chris@1012 53 if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; }
Chris@1012 54 int base(int(floor(m)));
Chris@1012 55 double prop0 = (base + 1.0) - m, prop1 = m - base;
Chris@1012 56 QColor c0(colours[base]), c1(colours[base+1]);
Chris@1012 57 r = c0.redF() * prop0 + c1.redF() * prop1;
Chris@1012 58 g = c0.greenF() * prop0 + c1.greenF() * prop1;
Chris@1012 59 b = c0.blueF() * prop0 + c1.blueF() * prop1;
Chris@1012 60 }
Chris@1012 61
Chris@902 62 ColourMapper::ColourMapper(int map, double min, double max) :
Chris@376 63 m_map(map),
Chris@376 64 m_min(min),
Chris@376 65 m_max(max)
Chris@376 66 {
Chris@376 67 if (m_min == m_max) {
Chris@682 68 cerr << "WARNING: ColourMapper: min == max (== " << m_min
Chris@682 69 << "), adjusting" << endl;
Chris@376 70 m_max = m_min + 1;
Chris@376 71 }
Chris@376 72 }
Chris@376 73
Chris@376 74 ColourMapper::~ColourMapper()
Chris@376 75 {
Chris@376 76 }
Chris@376 77
Chris@376 78 int
Chris@376 79 ColourMapper::getColourMapCount()
Chris@376 80 {
Chris@1015 81 return 12;
Chris@376 82 }
Chris@376 83
Chris@376 84 QString
Chris@376 85 ColourMapper::getColourMapName(int n)
Chris@376 86 {
Chris@1071 87 if (n >= getColourMapCount()) return QObject::tr("<unknown>");
Chris@376 88 StandardMap map = (StandardMap)n;
Chris@376 89
Chris@376 90 switch (map) {
Chris@1071 91 case Green: return QObject::tr("Green");
Chris@1071 92 case WhiteOnBlack: return QObject::tr("White on Black");
Chris@1071 93 case BlackOnWhite: return QObject::tr("Black on White");
Chris@1071 94 case Cherry: return QObject::tr("Cherry");
Chris@1071 95 case Wasp: return QObject::tr("Wasp");
Chris@1071 96 case Ice: return QObject::tr("Ice");
Chris@1071 97 case Sunset: return QObject::tr("Sunset");
Chris@1071 98 case FruitSalad: return QObject::tr("Fruit Salad");
Chris@1071 99 case Banded: return QObject::tr("Banded");
Chris@1071 100 case Highlight: return QObject::tr("Highlight");
Chris@1071 101 case Printer: return QObject::tr("Printer");
Chris@1071 102 case HighGain: return QObject::tr("High Gain");
Chris@376 103 }
Chris@376 104
Chris@1071 105 return QObject::tr("<unknown>");
Chris@376 106 }
Chris@376 107
Chris@376 108 QColor
Chris@902 109 ColourMapper::map(double value) const
Chris@376 110 {
Chris@902 111 double norm = (value - m_min) / (m_max - m_min);
Chris@902 112 if (norm < 0.0) norm = 0.0;
Chris@902 113 if (norm > 1.0) norm = 1.0;
Chris@376 114
Chris@902 115 double h = 0.0, s = 0.0, v = 0.0, r = 0.0, g = 0.0, b = 0.0;
Chris@376 116 bool hsv = true;
Chris@376 117
Chris@911 118 double blue = 0.6666, pieslice = 0.3333;
Chris@376 119
Chris@376 120 if (m_map >= getColourMapCount()) return Qt::black;
Chris@376 121 StandardMap map = (StandardMap)m_map;
Chris@376 122
Chris@376 123 switch (map) {
Chris@376 124
Chris@1017 125 case Green:
Chris@902 126 h = blue - norm * 2.0 * pieslice;
Chris@902 127 s = 0.5f + norm/2.0;
Chris@376 128 v = norm;
Chris@376 129 break;
Chris@376 130
Chris@376 131 case WhiteOnBlack:
Chris@376 132 r = g = b = norm;
Chris@376 133 hsv = false;
Chris@376 134 break;
Chris@376 135
Chris@376 136 case BlackOnWhite:
Chris@902 137 r = g = b = 1.0 - norm;
Chris@376 138 hsv = false;
Chris@376 139 break;
Chris@376 140
Chris@1017 141 case Cherry:
Chris@1015 142 hsv = false;
Chris@1017 143 mapDiscrete(norm, cherry, r, g, b);
Chris@376 144 break;
Chris@376 145
Chris@1017 146 case Wasp:
Chris@902 147 h = 0.15;
Chris@902 148 s = 1.0;
Chris@376 149 v = norm;
Chris@376 150 break;
Chris@376 151
Chris@376 152 case Sunset:
Chris@902 153 r = (norm - 0.24) * 2.38;
Chris@902 154 if (r > 1.0) r = 1.0;
Chris@902 155 if (r < 0.0) r = 0.0;
Chris@902 156 g = (norm - 0.64) * 2.777;
Chris@902 157 if (g > 1.0) g = 1.0;
Chris@902 158 if (g < 0.0) g = 0.0;
Chris@376 159 b = (3.6f * norm);
Chris@902 160 if (norm > 0.277) b = 2.0 - b;
Chris@902 161 if (b > 1.0) b = 1.0;
Chris@902 162 if (b < 0.0) b = 0.0;
Chris@376 163 hsv = false;
Chris@376 164 break;
Chris@376 165
Chris@376 166 case FruitSalad:
Chris@902 167 h = blue + (pieslice/6.0) - norm;
Chris@902 168 if (h < 0.0) h += 1.0;
Chris@902 169 s = 1.0;
Chris@902 170 v = 1.0;
Chris@376 171 break;
Chris@376 172
Chris@376 173 case Banded:
Chris@376 174 if (norm < 0.125) return Qt::darkGreen;
Chris@376 175 else if (norm < 0.25) return Qt::green;
Chris@376 176 else if (norm < 0.375) return Qt::darkBlue;
Chris@376 177 else if (norm < 0.5) return Qt::blue;
Chris@376 178 else if (norm < 0.625) return Qt::darkYellow;
Chris@376 179 else if (norm < 0.75) return Qt::yellow;
Chris@376 180 else if (norm < 0.875) return Qt::darkRed;
Chris@376 181 else return Qt::red;
Chris@376 182 break;
Chris@376 183
Chris@376 184 case Highlight:
Chris@376 185 if (norm > 0.99) return Qt::white;
Chris@376 186 else return Qt::darkBlue;
Chris@376 187
Chris@376 188 case Printer:
Chris@376 189 if (norm > 0.8) {
Chris@902 190 r = 1.0;
Chris@376 191 } else if (norm > 0.7) {
Chris@902 192 r = 0.9;
Chris@376 193 } else if (norm > 0.6) {
Chris@902 194 r = 0.8;
Chris@376 195 } else if (norm > 0.5) {
Chris@902 196 r = 0.7;
Chris@376 197 } else if (norm > 0.4) {
Chris@902 198 r = 0.6;
Chris@376 199 } else if (norm > 0.3) {
Chris@902 200 r = 0.5;
Chris@376 201 } else if (norm > 0.2) {
Chris@902 202 r = 0.4;
Chris@376 203 } else {
Chris@902 204 r = 0.0;
Chris@376 205 }
Chris@902 206 r = g = b = 1.0 - r;
Chris@376 207 hsv = false;
Chris@376 208 break;
Chris@536 209
Chris@536 210 case HighGain:
Chris@902 211 if (norm <= 1.0 / 256.0) {
Chris@902 212 norm = 0.0;
Chris@536 213 } else {
Chris@904 214 norm = 0.1f + (pow(((norm - 0.5) * 2.0), 3.0) + 1.0) / 2.081;
Chris@536 215 }
Chris@536 216 // now as for Sunset
Chris@902 217 r = (norm - 0.24) * 2.38;
Chris@902 218 if (r > 1.0) r = 1.0;
Chris@902 219 if (r < 0.0) r = 0.0;
Chris@902 220 g = (norm - 0.64) * 2.777;
Chris@902 221 if (g > 1.0) g = 1.0;
Chris@902 222 if (g < 0.0) g = 0.0;
Chris@536 223 b = (3.6f * norm);
Chris@902 224 if (norm > 0.277) b = 2.0 - b;
Chris@902 225 if (b > 1.0) b = 1.0;
Chris@902 226 if (b < 0.0) b = 0.0;
Chris@536 227 hsv = false;
Chris@536 228 /*
Chris@902 229 if (r > 1.0) r = 1.0;
Chris@902 230 r = g = b = 1.0 - r;
Chris@536 231 hsv = false;
Chris@536 232 */
Chris@536 233 break;
Chris@1012 234
Chris@1017 235 case Ice:
Chris@1012 236 hsv = false;
Chris@1017 237 mapDiscrete(norm, ice, r, g, b);
Chris@376 238 }
Chris@376 239
Chris@376 240 if (hsv) {
Chris@376 241 return QColor::fromHsvF(h, s, v);
Chris@376 242 } else {
Chris@376 243 return QColor::fromRgbF(r, g, b);
Chris@376 244 }
Chris@376 245 }
Chris@376 246
Chris@376 247 QColor
Chris@376 248 ColourMapper::getContrastingColour() const
Chris@376 249 {
Chris@376 250 if (m_map >= getColourMapCount()) return Qt::white;
Chris@376 251 StandardMap map = (StandardMap)m_map;
Chris@376 252
Chris@376 253 switch (map) {
Chris@376 254
Chris@1017 255 case Green:
Chris@376 256 return QColor(255, 150, 50);
Chris@376 257
Chris@376 258 case WhiteOnBlack:
Chris@376 259 return Qt::red;
Chris@376 260
Chris@376 261 case BlackOnWhite:
Chris@376 262 return Qt::darkGreen;
Chris@376 263
Chris@1017 264 case Cherry:
Chris@376 265 return Qt::green;
Chris@376 266
Chris@1017 267 case Wasp:
Chris@376 268 return QColor::fromHsv(240, 255, 255);
Chris@376 269
Chris@1017 270 case Ice:
Chris@376 271 return Qt::red;
Chris@376 272
Chris@376 273 case Sunset:
Chris@376 274 return Qt::white;
Chris@376 275
Chris@376 276 case FruitSalad:
Chris@376 277 return Qt::white;
Chris@376 278
Chris@376 279 case Banded:
Chris@376 280 return Qt::cyan;
Chris@376 281
Chris@376 282 case Highlight:
Chris@376 283 return Qt::red;
Chris@376 284
Chris@376 285 case Printer:
Chris@376 286 return Qt::red;
Chris@536 287
Chris@536 288 case HighGain:
Chris@536 289 return Qt::red;
Chris@376 290 }
Chris@376 291
Chris@376 292 return Qt::white;
Chris@376 293 }
Chris@376 294
Chris@376 295 bool
Chris@376 296 ColourMapper::hasLightBackground() const
Chris@376 297 {
Chris@376 298 if (m_map >= getColourMapCount()) return false;
Chris@376 299 StandardMap map = (StandardMap)m_map;
Chris@376 300
Chris@376 301 switch (map) {
Chris@376 302
Chris@376 303 case BlackOnWhite:
Chris@376 304 case Printer:
Chris@536 305 case HighGain:
Chris@376 306 return true;
Chris@376 307
Chris@1017 308 case Green:
Chris@805 309 case Sunset:
Chris@805 310 case WhiteOnBlack:
Chris@1017 311 case Cherry:
Chris@1017 312 case Wasp:
Chris@1017 313 case Ice:
Chris@805 314 case FruitSalad:
Chris@805 315 case Banded:
Chris@805 316 case Highlight:
Chris@805 317
Chris@376 318 default:
Chris@376 319 return false;
Chris@376 320 }
Chris@376 321 }
Chris@376 322
Chris@376 323