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@1265
|
70 SVCERR << "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
|