| 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<QString> ylGnBuS { | 
| Chris@1012 | 29 // this is ylGnBu 9 | 
| Chris@1012 | 30 //    "#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081" | 
| Chris@1012 | 31 | 
| Chris@1012 | 32     "#ffffff", "#ffff00", "#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081","#042040" | 
| Chris@1012 | 33 | 
| Chris@1012 | 34 // this is PuOr 11 | 
| Chris@1012 | 35 //    "#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b" | 
| Chris@1012 | 36         }; | 
| Chris@1012 | 37 | 
| Chris@1012 | 38 | 
| Chris@1012 | 39 static vector<QColor> convertStrings(const vector<QString> &strs) | 
| Chris@1012 | 40 { | 
| Chris@1012 | 41     vector<QColor> converted; | 
| Chris@1012 | 42     for (const auto &s: strs) converted.push_back(QColor(s)); | 
| Chris@1012 | 43     reverse(converted.begin(), converted.end()); | 
| Chris@1012 | 44     return converted; | 
| Chris@1012 | 45 } | 
| Chris@1012 | 46 | 
| Chris@1012 | 47 static vector<QColor> ylGnBu = convertStrings(ylGnBuS); | 
| Chris@1012 | 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@1012 | 52     int n = 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     cerr << "mapDiscrete: norm " << norm << " -> " << r << "," << g << "," << b << endl; | 
| Chris@1012 | 63 } | 
| Chris@1012 | 64 | 
| Chris@902 | 65 ColourMapper::ColourMapper(int map, double min, double max) : | 
| Chris@376 | 66     QObject(), | 
| Chris@376 | 67     m_map(map), | 
| Chris@376 | 68     m_min(min), | 
| Chris@376 | 69     m_max(max) | 
| Chris@376 | 70 { | 
| Chris@376 | 71     if (m_min == m_max) { | 
| Chris@682 | 72         cerr << "WARNING: ColourMapper: min == max (== " << m_min | 
| Chris@682 | 73                   << "), adjusting" << endl; | 
| Chris@376 | 74         m_max = m_min + 1; | 
| Chris@376 | 75     } | 
| Chris@376 | 76 } | 
| Chris@376 | 77 | 
| Chris@376 | 78 ColourMapper::~ColourMapper() | 
| Chris@376 | 79 { | 
| Chris@376 | 80 } | 
| Chris@376 | 81 | 
| Chris@376 | 82 int | 
| Chris@376 | 83 ColourMapper::getColourMapCount() | 
| Chris@376 | 84 { | 
| Chris@1012 | 85     return 13; | 
| Chris@376 | 86 } | 
| Chris@376 | 87 | 
| Chris@376 | 88 QString | 
| Chris@376 | 89 ColourMapper::getColourMapName(int n) | 
| Chris@376 | 90 { | 
| Chris@376 | 91     if (n >= getColourMapCount()) return tr("<unknown>"); | 
| Chris@376 | 92     StandardMap map = (StandardMap)n; | 
| Chris@376 | 93 | 
| Chris@376 | 94     switch (map) { | 
| Chris@376 | 95     case DefaultColours:   return tr("Default"); | 
| Chris@376 | 96     case WhiteOnBlack:     return tr("White on Black"); | 
| Chris@376 | 97     case BlackOnWhite:     return tr("Black on White"); | 
| Chris@376 | 98     case RedOnBlue:        return tr("Red on Blue"); | 
| Chris@376 | 99     case YellowOnBlack:    return tr("Yellow on Black"); | 
| Chris@376 | 100     case BlueOnBlack:      return tr("Blue on Black"); | 
| Chris@376 | 101     case Sunset:           return tr("Sunset"); | 
| Chris@376 | 102     case FruitSalad:       return tr("Fruit Salad"); | 
| Chris@376 | 103     case Banded:           return tr("Banded"); | 
| Chris@376 | 104     case Highlight:        return tr("Highlight"); | 
| Chris@376 | 105     case Printer:          return tr("Printer"); | 
| Chris@536 | 106     case HighGain:         return tr("High Gain"); | 
| Chris@1012 | 107     case YlGnBu:           return tr("YlGnBu"); | 
| Chris@376 | 108     } | 
| Chris@376 | 109 | 
| Chris@376 | 110     return tr("<unknown>"); | 
| Chris@376 | 111 } | 
| Chris@376 | 112 | 
| Chris@376 | 113 QColor | 
| Chris@902 | 114 ColourMapper::map(double value) const | 
| Chris@376 | 115 { | 
| Chris@902 | 116     double norm = (value - m_min) / (m_max - m_min); | 
| Chris@902 | 117     if (norm < 0.0) norm = 0.0; | 
| Chris@902 | 118     if (norm > 1.0) norm = 1.0; | 
| Chris@376 | 119 | 
| Chris@902 | 120     double h = 0.0, s = 0.0, v = 0.0, r = 0.0, g = 0.0, b = 0.0; | 
| Chris@376 | 121     bool hsv = true; | 
| Chris@376 | 122 | 
| Chris@911 | 123     double blue = 0.6666, pieslice = 0.3333; | 
| Chris@376 | 124 | 
| Chris@376 | 125     if (m_map >= getColourMapCount()) return Qt::black; | 
| Chris@376 | 126     StandardMap map = (StandardMap)m_map; | 
| Chris@376 | 127 | 
| Chris@376 | 128     switch (map) { | 
| Chris@376 | 129 | 
| Chris@376 | 130     case DefaultColours: | 
| Chris@902 | 131         h = blue - norm * 2.0 * pieslice; | 
| Chris@902 | 132         s = 0.5f + norm/2.0; | 
| Chris@376 | 133         v = norm; | 
| Chris@376 | 134         break; | 
| Chris@376 | 135 | 
| Chris@376 | 136     case WhiteOnBlack: | 
| Chris@376 | 137         r = g = b = norm; | 
| Chris@376 | 138         hsv = false; | 
| Chris@376 | 139         break; | 
| Chris@376 | 140 | 
| Chris@376 | 141     case BlackOnWhite: | 
| Chris@902 | 142         r = g = b = 1.0 - norm; | 
| Chris@376 | 143         hsv = false; | 
| Chris@376 | 144         break; | 
| Chris@376 | 145 | 
| Chris@376 | 146     case RedOnBlue: | 
| Chris@902 | 147         h = blue - pieslice/4.0 + norm * (pieslice + pieslice/4.0); | 
| Chris@902 | 148         s = 1.0; | 
| Chris@376 | 149         v = norm; | 
| Chris@376 | 150         break; | 
| Chris@376 | 151 | 
| Chris@376 | 152     case YellowOnBlack: | 
| Chris@902 | 153         h = 0.15; | 
| Chris@902 | 154         s = 1.0; | 
| Chris@376 | 155         v = norm; | 
| Chris@376 | 156         break; | 
| Chris@376 | 157 | 
| Chris@376 | 158     case BlueOnBlack: | 
| Chris@376 | 159         h = blue; | 
| Chris@902 | 160         s = 1.0; | 
| Chris@902 | 161         v = norm * 2.0; | 
| Chris@902 | 162         if (v > 1.0) { | 
| Chris@902 | 163             v = 1.0; | 
| Chris@904 | 164             s = 1.0 - (sqrt(norm) - 0.707) * 3.413; | 
| Chris@902 | 165             if (s < 0.0) s = 0.0; | 
| Chris@902 | 166             if (s > 1.0) s = 1.0; | 
| Chris@376 | 167         } | 
| Chris@376 | 168         break; | 
| Chris@376 | 169 | 
| Chris@376 | 170     case Sunset: | 
| Chris@902 | 171         r = (norm - 0.24) * 2.38; | 
| Chris@902 | 172         if (r > 1.0) r = 1.0; | 
| Chris@902 | 173         if (r < 0.0) r = 0.0; | 
| Chris@902 | 174         g = (norm - 0.64) * 2.777; | 
| Chris@902 | 175         if (g > 1.0) g = 1.0; | 
| Chris@902 | 176         if (g < 0.0) g = 0.0; | 
| Chris@376 | 177         b = (3.6f * norm); | 
| Chris@902 | 178         if (norm > 0.277) b = 2.0 - b; | 
| Chris@902 | 179         if (b > 1.0) b = 1.0; | 
| Chris@902 | 180         if (b < 0.0) b = 0.0; | 
| Chris@376 | 181         hsv = false; | 
| Chris@376 | 182         break; | 
| Chris@376 | 183 | 
| Chris@376 | 184     case FruitSalad: | 
| Chris@902 | 185         h = blue + (pieslice/6.0) - norm; | 
| Chris@902 | 186         if (h < 0.0) h += 1.0; | 
| Chris@902 | 187         s = 1.0; | 
| Chris@902 | 188         v = 1.0; | 
| Chris@376 | 189         break; | 
| Chris@376 | 190 | 
| Chris@376 | 191     case Banded: | 
| Chris@376 | 192         if      (norm < 0.125) return Qt::darkGreen; | 
| Chris@376 | 193         else if (norm < 0.25)  return Qt::green; | 
| Chris@376 | 194         else if (norm < 0.375) return Qt::darkBlue; | 
| Chris@376 | 195         else if (norm < 0.5)   return Qt::blue; | 
| Chris@376 | 196         else if (norm < 0.625) return Qt::darkYellow; | 
| Chris@376 | 197         else if (norm < 0.75)  return Qt::yellow; | 
| Chris@376 | 198         else if (norm < 0.875) return Qt::darkRed; | 
| Chris@376 | 199         else                   return Qt::red; | 
| Chris@376 | 200         break; | 
| Chris@376 | 201 | 
| Chris@376 | 202     case Highlight: | 
| Chris@376 | 203         if (norm > 0.99) return Qt::white; | 
| Chris@376 | 204         else return Qt::darkBlue; | 
| Chris@376 | 205 | 
| Chris@376 | 206     case Printer: | 
| Chris@376 | 207         if (norm > 0.8) { | 
| Chris@902 | 208             r = 1.0; | 
| Chris@376 | 209         } else if (norm > 0.7) { | 
| Chris@902 | 210             r = 0.9; | 
| Chris@376 | 211         } else if (norm > 0.6) { | 
| Chris@902 | 212             r = 0.8; | 
| Chris@376 | 213         } else if (norm > 0.5) { | 
| Chris@902 | 214             r = 0.7; | 
| Chris@376 | 215         } else if (norm > 0.4) { | 
| Chris@902 | 216             r = 0.6; | 
| Chris@376 | 217         } else if (norm > 0.3) { | 
| Chris@902 | 218             r = 0.5; | 
| Chris@376 | 219         } else if (norm > 0.2) { | 
| Chris@902 | 220             r = 0.4; | 
| Chris@376 | 221         } else { | 
| Chris@902 | 222             r = 0.0; | 
| Chris@376 | 223         } | 
| Chris@902 | 224         r = g = b = 1.0 - r; | 
| Chris@376 | 225         hsv = false; | 
| Chris@376 | 226         break; | 
| Chris@536 | 227 | 
| Chris@536 | 228     case HighGain: | 
| Chris@902 | 229         if (norm <= 1.0 / 256.0) { | 
| Chris@902 | 230             norm = 0.0; | 
| Chris@536 | 231         } else { | 
| Chris@904 | 232             norm = 0.1f + (pow(((norm - 0.5) * 2.0), 3.0) + 1.0) / 2.081; | 
| Chris@536 | 233         } | 
| Chris@536 | 234         // now as for Sunset | 
| Chris@902 | 235         r = (norm - 0.24) * 2.38; | 
| Chris@902 | 236         if (r > 1.0) r = 1.0; | 
| Chris@902 | 237         if (r < 0.0) r = 0.0; | 
| Chris@902 | 238         g = (norm - 0.64) * 2.777; | 
| Chris@902 | 239         if (g > 1.0) g = 1.0; | 
| Chris@902 | 240         if (g < 0.0) g = 0.0; | 
| Chris@536 | 241         b = (3.6f * norm); | 
| Chris@902 | 242         if (norm > 0.277) b = 2.0 - b; | 
| Chris@902 | 243         if (b > 1.0) b = 1.0; | 
| Chris@902 | 244         if (b < 0.0) b = 0.0; | 
| Chris@536 | 245         hsv = false; | 
| Chris@536 | 246 /* | 
| Chris@902 | 247         if (r > 1.0) r = 1.0; | 
| Chris@902 | 248         r = g = b = 1.0 - r; | 
| Chris@536 | 249         hsv = false; | 
| Chris@536 | 250 */ | 
| Chris@536 | 251         break; | 
| Chris@1012 | 252 | 
| Chris@1012 | 253     case YlGnBu: | 
| Chris@1012 | 254         hsv = false; | 
| Chris@1012 | 255         mapDiscrete(norm, ylGnBu, r, g, b); | 
| Chris@376 | 256     } | 
| Chris@376 | 257 | 
| Chris@376 | 258     if (hsv) { | 
| Chris@376 | 259         return QColor::fromHsvF(h, s, v); | 
| Chris@376 | 260     } else { | 
| Chris@376 | 261         return QColor::fromRgbF(r, g, b); | 
| Chris@376 | 262     } | 
| Chris@376 | 263 } | 
| Chris@376 | 264 | 
| Chris@376 | 265 QColor | 
| Chris@376 | 266 ColourMapper::getContrastingColour() const | 
| Chris@376 | 267 { | 
| Chris@376 | 268     if (m_map >= getColourMapCount()) return Qt::white; | 
| Chris@376 | 269     StandardMap map = (StandardMap)m_map; | 
| Chris@376 | 270 | 
| Chris@376 | 271     switch (map) { | 
| Chris@376 | 272 | 
| Chris@376 | 273     case DefaultColours: | 
| Chris@376 | 274         return QColor(255, 150, 50); | 
| Chris@376 | 275 | 
| Chris@376 | 276     case WhiteOnBlack: | 
| Chris@376 | 277         return Qt::red; | 
| Chris@376 | 278 | 
| Chris@376 | 279     case BlackOnWhite: | 
| Chris@376 | 280         return Qt::darkGreen; | 
| Chris@376 | 281 | 
| Chris@376 | 282     case RedOnBlue: | 
| Chris@376 | 283         return Qt::green; | 
| Chris@376 | 284 | 
| Chris@376 | 285     case YellowOnBlack: | 
| Chris@376 | 286         return QColor::fromHsv(240, 255, 255); | 
| Chris@376 | 287 | 
| Chris@376 | 288     case BlueOnBlack: | 
| Chris@376 | 289         return Qt::red; | 
| Chris@376 | 290 | 
| Chris@376 | 291     case Sunset: | 
| Chris@376 | 292         return Qt::white; | 
| Chris@376 | 293 | 
| Chris@376 | 294     case FruitSalad: | 
| Chris@376 | 295         return Qt::white; | 
| Chris@376 | 296 | 
| Chris@376 | 297     case Banded: | 
| Chris@376 | 298         return Qt::cyan; | 
| Chris@376 | 299 | 
| Chris@376 | 300     case Highlight: | 
| Chris@376 | 301         return Qt::red; | 
| Chris@376 | 302 | 
| Chris@376 | 303     case Printer: | 
| Chris@376 | 304         return Qt::red; | 
| Chris@536 | 305 | 
| Chris@536 | 306     case HighGain: | 
| Chris@536 | 307         return Qt::red; | 
| Chris@376 | 308     } | 
| Chris@376 | 309 | 
| Chris@376 | 310     return Qt::white; | 
| Chris@376 | 311 } | 
| Chris@376 | 312 | 
| Chris@376 | 313 bool | 
| Chris@376 | 314 ColourMapper::hasLightBackground() const | 
| Chris@376 | 315 { | 
| Chris@376 | 316     if (m_map >= getColourMapCount()) return false; | 
| Chris@376 | 317     StandardMap map = (StandardMap)m_map; | 
| Chris@376 | 318 | 
| Chris@376 | 319     switch (map) { | 
| Chris@376 | 320 | 
| Chris@376 | 321     case BlackOnWhite: | 
| Chris@376 | 322     case Printer: | 
| Chris@536 | 323     case HighGain: | 
| Chris@376 | 324         return true; | 
| Chris@376 | 325 | 
| Chris@805 | 326     case DefaultColours: | 
| Chris@805 | 327     case Sunset: | 
| Chris@805 | 328     case WhiteOnBlack: | 
| Chris@805 | 329     case RedOnBlue: | 
| Chris@805 | 330     case YellowOnBlack: | 
| Chris@805 | 331     case BlueOnBlack: | 
| Chris@805 | 332     case FruitSalad: | 
| Chris@805 | 333     case Banded: | 
| Chris@805 | 334     case Highlight: | 
| Chris@805 | 335 | 
| Chris@376 | 336     default: | 
| Chris@376 | 337         return false; | 
| Chris@376 | 338     } | 
| Chris@376 | 339 } | 
| Chris@376 | 340 | 
| Chris@376 | 341 |