Chris@376: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@376: Chris@376: /* Chris@376: Sonic Visualiser Chris@376: An audio file viewer and annotation editor. Chris@376: Centre for Digital Music, Queen Mary, University of London. Chris@376: This file copyright 2006-2007 Chris Cannam and QMUL. Chris@376: Chris@376: This program is free software; you can redistribute it and/or Chris@376: modify it under the terms of the GNU General Public License as Chris@376: published by the Free Software Foundation; either version 2 of the Chris@376: License, or (at your option) any later version. See the file Chris@376: COPYING included with this distribution for more information. Chris@376: */ Chris@376: Chris@376: #include "ColourMapper.h" Chris@376: Chris@376: #include Chris@376: Chris@376: #include Chris@376: Chris@682: #include "base/Debug.h" Chris@682: Chris@1012: #include Chris@1012: Chris@1012: using namespace std; Chris@1012: Chris@1012: static vector convertStrings(const vector &strs) Chris@1012: { Chris@1012: vector converted; Chris@1012: for (const auto &s: strs) converted.push_back(QColor(s)); Chris@1012: reverse(converted.begin(), converted.end()); Chris@1012: return converted; Chris@1012: } Chris@1012: Chris@1017: static vector ice = convertStrings({ Chris@1015: // Based on ColorBrewer ylGnBu Chris@1015: "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", Chris@1015: "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040" Chris@1013: }); Chris@1012: Chris@1017: static vector cherry = convertStrings({ Chris@1016: "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497", Chris@1016: "#ae017e", "#7a0177", "#49006a" Chris@1015: }); Chris@1015: Chris@1012: static void Chris@1012: mapDiscrete(double norm, vector &colours, double &r, double &g, double &b) Chris@1012: { Chris@1015: int n = int(colours.size()); Chris@1012: double m = norm * (n-1); Chris@1012: if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; } Chris@1012: if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; } Chris@1012: int base(int(floor(m))); Chris@1012: double prop0 = (base + 1.0) - m, prop1 = m - base; Chris@1012: QColor c0(colours[base]), c1(colours[base+1]); Chris@1012: r = c0.redF() * prop0 + c1.redF() * prop1; Chris@1012: g = c0.greenF() * prop0 + c1.greenF() * prop1; Chris@1012: b = c0.blueF() * prop0 + c1.blueF() * prop1; Chris@1012: } Chris@1012: Chris@902: ColourMapper::ColourMapper(int map, double min, double max) : Chris@376: QObject(), Chris@376: m_map(map), Chris@376: m_min(min), Chris@376: m_max(max) Chris@376: { Chris@376: if (m_min == m_max) { Chris@682: cerr << "WARNING: ColourMapper: min == max (== " << m_min Chris@682: << "), adjusting" << endl; Chris@376: m_max = m_min + 1; Chris@376: } Chris@376: } Chris@376: Chris@376: ColourMapper::~ColourMapper() Chris@376: { Chris@376: } Chris@376: Chris@376: int Chris@376: ColourMapper::getColourMapCount() Chris@376: { Chris@1015: return 12; Chris@376: } Chris@376: Chris@376: QString Chris@376: ColourMapper::getColourMapName(int n) Chris@376: { Chris@376: if (n >= getColourMapCount()) return tr(""); Chris@376: StandardMap map = (StandardMap)n; Chris@376: Chris@376: switch (map) { Chris@1017: case Green: return tr("Green"); Chris@376: case WhiteOnBlack: return tr("White on Black"); Chris@376: case BlackOnWhite: return tr("Black on White"); Chris@1017: case Cherry: return tr("Cherry"); Chris@1017: case Wasp: return tr("Wasp"); Chris@1017: case Ice: return tr("Ice"); Chris@376: case Sunset: return tr("Sunset"); Chris@376: case FruitSalad: return tr("Fruit Salad"); Chris@376: case Banded: return tr("Banded"); Chris@376: case Highlight: return tr("Highlight"); Chris@376: case Printer: return tr("Printer"); Chris@536: case HighGain: return tr("High Gain"); Chris@376: } Chris@376: Chris@376: return tr(""); Chris@376: } Chris@376: Chris@376: QColor Chris@902: ColourMapper::map(double value) const Chris@376: { Chris@902: double norm = (value - m_min) / (m_max - m_min); Chris@902: if (norm < 0.0) norm = 0.0; Chris@902: if (norm > 1.0) norm = 1.0; Chris@376: Chris@902: double h = 0.0, s = 0.0, v = 0.0, r = 0.0, g = 0.0, b = 0.0; Chris@376: bool hsv = true; Chris@376: Chris@911: double blue = 0.6666, pieslice = 0.3333; Chris@376: Chris@376: if (m_map >= getColourMapCount()) return Qt::black; Chris@376: StandardMap map = (StandardMap)m_map; Chris@376: Chris@376: switch (map) { Chris@376: Chris@1017: case Green: Chris@902: h = blue - norm * 2.0 * pieslice; Chris@902: s = 0.5f + norm/2.0; Chris@376: v = norm; Chris@376: break; Chris@376: Chris@376: case WhiteOnBlack: Chris@376: r = g = b = norm; Chris@376: hsv = false; Chris@376: break; Chris@376: Chris@376: case BlackOnWhite: Chris@902: r = g = b = 1.0 - norm; Chris@376: hsv = false; Chris@376: break; Chris@376: Chris@1017: case Cherry: Chris@1015: hsv = false; Chris@1017: mapDiscrete(norm, cherry, r, g, b); Chris@376: break; Chris@376: Chris@1017: case Wasp: Chris@902: h = 0.15; Chris@902: s = 1.0; Chris@376: v = norm; Chris@376: break; Chris@376: Chris@376: case Sunset: Chris@902: r = (norm - 0.24) * 2.38; Chris@902: if (r > 1.0) r = 1.0; Chris@902: if (r < 0.0) r = 0.0; Chris@902: g = (norm - 0.64) * 2.777; Chris@902: if (g > 1.0) g = 1.0; Chris@902: if (g < 0.0) g = 0.0; Chris@376: b = (3.6f * norm); Chris@902: if (norm > 0.277) b = 2.0 - b; Chris@902: if (b > 1.0) b = 1.0; Chris@902: if (b < 0.0) b = 0.0; Chris@376: hsv = false; Chris@376: break; Chris@376: Chris@376: case FruitSalad: Chris@902: h = blue + (pieslice/6.0) - norm; Chris@902: if (h < 0.0) h += 1.0; Chris@902: s = 1.0; Chris@902: v = 1.0; Chris@376: break; Chris@376: Chris@376: case Banded: Chris@376: if (norm < 0.125) return Qt::darkGreen; Chris@376: else if (norm < 0.25) return Qt::green; Chris@376: else if (norm < 0.375) return Qt::darkBlue; Chris@376: else if (norm < 0.5) return Qt::blue; Chris@376: else if (norm < 0.625) return Qt::darkYellow; Chris@376: else if (norm < 0.75) return Qt::yellow; Chris@376: else if (norm < 0.875) return Qt::darkRed; Chris@376: else return Qt::red; Chris@376: break; Chris@376: Chris@376: case Highlight: Chris@376: if (norm > 0.99) return Qt::white; Chris@376: else return Qt::darkBlue; Chris@376: Chris@376: case Printer: Chris@376: if (norm > 0.8) { Chris@902: r = 1.0; Chris@376: } else if (norm > 0.7) { Chris@902: r = 0.9; Chris@376: } else if (norm > 0.6) { Chris@902: r = 0.8; Chris@376: } else if (norm > 0.5) { Chris@902: r = 0.7; Chris@376: } else if (norm > 0.4) { Chris@902: r = 0.6; Chris@376: } else if (norm > 0.3) { Chris@902: r = 0.5; Chris@376: } else if (norm > 0.2) { Chris@902: r = 0.4; Chris@376: } else { Chris@902: r = 0.0; Chris@376: } Chris@902: r = g = b = 1.0 - r; Chris@376: hsv = false; Chris@376: break; Chris@536: Chris@536: case HighGain: Chris@902: if (norm <= 1.0 / 256.0) { Chris@902: norm = 0.0; Chris@536: } else { Chris@904: norm = 0.1f + (pow(((norm - 0.5) * 2.0), 3.0) + 1.0) / 2.081; Chris@536: } Chris@536: // now as for Sunset Chris@902: r = (norm - 0.24) * 2.38; Chris@902: if (r > 1.0) r = 1.0; Chris@902: if (r < 0.0) r = 0.0; Chris@902: g = (norm - 0.64) * 2.777; Chris@902: if (g > 1.0) g = 1.0; Chris@902: if (g < 0.0) g = 0.0; Chris@536: b = (3.6f * norm); Chris@902: if (norm > 0.277) b = 2.0 - b; Chris@902: if (b > 1.0) b = 1.0; Chris@902: if (b < 0.0) b = 0.0; Chris@536: hsv = false; Chris@536: /* Chris@902: if (r > 1.0) r = 1.0; Chris@902: r = g = b = 1.0 - r; Chris@536: hsv = false; Chris@536: */ Chris@536: break; Chris@1012: Chris@1017: case Ice: Chris@1012: hsv = false; Chris@1017: mapDiscrete(norm, ice, r, g, b); Chris@376: } Chris@376: Chris@376: if (hsv) { Chris@376: return QColor::fromHsvF(h, s, v); Chris@376: } else { Chris@376: return QColor::fromRgbF(r, g, b); Chris@376: } Chris@376: } Chris@376: Chris@376: QColor Chris@376: ColourMapper::getContrastingColour() const Chris@376: { Chris@376: if (m_map >= getColourMapCount()) return Qt::white; Chris@376: StandardMap map = (StandardMap)m_map; Chris@376: Chris@376: switch (map) { Chris@376: Chris@1017: case Green: Chris@376: return QColor(255, 150, 50); Chris@376: Chris@376: case WhiteOnBlack: Chris@376: return Qt::red; Chris@376: Chris@376: case BlackOnWhite: Chris@376: return Qt::darkGreen; Chris@376: Chris@1017: case Cherry: Chris@376: return Qt::green; Chris@376: Chris@1017: case Wasp: Chris@376: return QColor::fromHsv(240, 255, 255); Chris@376: Chris@1017: case Ice: Chris@376: return Qt::red; Chris@376: Chris@376: case Sunset: Chris@376: return Qt::white; Chris@376: Chris@376: case FruitSalad: Chris@376: return Qt::white; Chris@376: Chris@376: case Banded: Chris@376: return Qt::cyan; Chris@376: Chris@376: case Highlight: Chris@376: return Qt::red; Chris@376: Chris@376: case Printer: Chris@376: return Qt::red; Chris@536: Chris@536: case HighGain: Chris@536: return Qt::red; Chris@376: } Chris@376: Chris@376: return Qt::white; Chris@376: } Chris@376: Chris@376: bool Chris@376: ColourMapper::hasLightBackground() const Chris@376: { Chris@376: if (m_map >= getColourMapCount()) return false; Chris@376: StandardMap map = (StandardMap)m_map; Chris@376: Chris@376: switch (map) { Chris@376: Chris@376: case BlackOnWhite: Chris@376: case Printer: Chris@536: case HighGain: Chris@376: return true; Chris@376: Chris@1017: case Green: Chris@805: case Sunset: Chris@805: case WhiteOnBlack: Chris@1017: case Cherry: Chris@1017: case Wasp: Chris@1017: case Ice: Chris@805: case FruitSalad: Chris@805: case Banded: Chris@805: case Highlight: Chris@805: Chris@376: default: Chris@376: return false; Chris@376: } Chris@376: } Chris@376: Chris@376: