Search code examples
c++qttostringqcolor

QColor to human readable string


I have a colour object in QML which I think is an instance of QColor, using the debugger I can see the colour object contains:

    a                1
    b                0
    g                0
    hslHue           -1
    hslLightness     0
    hslSaturation    0
    hsvHue           -1
    hsvSaturation    0
    hsvValue         0
    r                0

Is there a way to translate this into a human readable string, e.g Red ?


Solution

  • Colors are a difficult topic. (If you ever connected two screens to your computer and tried to configure them for identical color profiles you know what I mean.)

    Nevertheless, I believe the intention of OP is reasonable. If Qt can process human-readable color names why shouldn't this reversible?

    At first, I had a look into the manual – QColor:

    A color can be set by passing an RGB string (such as "#112233"), or an ARGB string (such as "#ff112233") or a color name (such as "blue"), to the setNamedColor() function. The color names are taken from the SVG 1.0 color names. The name() function returns the name of the color in the format "#RRGGBB".

    The statement about QColor::name() sounds precisely like what OP described. To convince myself completely, I made an MCVE but it didn't change anything.

    OK. So, it's not a bug – it's a feature.

    If it's missing in Qt how this could be added? Qt seems to “know” all the names. So, it would be annoying if this cannot be exploited somehow.

    I clicked a bit through the doc. and found e.g. in the doc. of QColor::setNamedColor a link for the table of the SVG Color Names.

    So, I considered for a second just to copy it.

    I also found the SVG Colors. Please, note, that it's remarked with

    Since: Qt 5.14

    and still part of the qt5-dev doc. (at the time of this writing).

    On woboq.org, I stumbled into the responsible source code for the color names in qtbase/src/gui/painting/qcolor.cpp:

    #ifndef QT_NO_COLORNAMES
    /*
      CSS color names = SVG 1.0 color names + transparent (rgba(0,0,0,0))
    */
    #ifdef rgb
    #  undef rgb
    #endif
    #define rgb(r,g,b) (0xff000000 | (r << 16) |  (g << 8) | b)
    static const struct RGBData {
        const char name[21];
        uint  value;
    } rgbTbl[] = {
        { "aliceblue", rgb(240, 248, 255) },
        { "antiquewhite", rgb(250, 235, 215) },
        { "aqua", rgb( 0, 255, 255) },
    

        { "yellow", rgb(255, 255, 0) },
        { "yellowgreen", rgb(154, 205, 50) }
    };
    static const int rgbTblSize = sizeof(rgbTbl) / sizeof(RGBData);
    #undef rgb
    

    And, finally, I ended up in QColor::colorNames():

    QStringList QColor::colorNames() [static]

    Returns a QStringList containing the color names Qt knows about.

    See also Predefined Colors.

    With a list of all color names (which Qt can recognize) it's easy to build a reverse mapping table.

    I made an MCVE testQColorName.cc to demonstrate this:

    #include <functional>
    #include <unordered_map>
    
    // Qt header:
    #include <QtWidgets>
    
    namespace std {
    
    template <> struct hash<QColor> {
      size_t operator()(const QColor &color) const
      {
        return std::hash<QRgb>()(color.rgb());
      }
    };
    
    } // namespace std
    
    typedef std::unordered_map<QColor, QString> ColorNameMap;
    
    class ColorButton: public QPushButton {
      private:
        QColor _qColor;
    
      public:
        explicit ColorButton(
          const QString &text = QString(), const QColor &qColor = Qt::black,
          QWidget *pQParent = nullptr):
          QPushButton(text, pQParent)
        {
          setColor(qColor);
        }
        virtual ~ColorButton() = default;
        ColorButton(const ColorButton&) = delete;
        ColorButton& operator=(const ColorButton&) = delete;
    
        const QColor& color() const { return _qColor; }
        void setColor(const QColor &qColor)
        {
          _qColor = qColor;
          QFontMetrics qFontMetrics(font());
          const int h = qFontMetrics.height();
          QPixmap qPixmap(h, h);
          qPixmap.fill(_qColor);
          setIcon(qPixmap);
        }
    
        QColor chooseColor()
        {
          setColor(QColorDialog::getColor(_qColor, this, text()));
          return _qColor;
        }
    };
    
    // main application
    int main(int argc, char **argv)
    {
      qDebug() << "Qt Version:" << QT_VERSION_STR;
      QApplication app(argc, argv);
      // setup data
      const ColorNameMap qMapColorNames = []() {
        ColorNameMap qMapColorNames;
        const QStringList qColorNames = QColor::colorNames();
        for (const QString &qColorName : qColorNames) {
          qMapColorNames[QColor(qColorName)] = qColorName;
          qDebug() << qColorName;
        }
        return qMapColorNames;
      }();
      // setup GUI
      QWidget qWinMain;
      qWinMain.setWindowTitle(QString::fromUtf8("Test Color Name"));
      QHBoxLayout qHBox;
      QLabel qLblColor(QString::fromUtf8("Color:"));
      qHBox.addWidget(&qLblColor);
      QLineEdit qEditColor;
      qHBox.addWidget(&qEditColor, 1);
      ColorButton qBtnColor;
      qHBox.addWidget(&qBtnColor);
      qWinMain.setLayout(&qHBox);
      qWinMain.show();
      qEditColor.setText(qBtnColor.color().name());
      // install signal handlers
      QObject::connect(&qBtnColor, &QPushButton::clicked,
        [&]() {
          const QColor qColor = qBtnColor.chooseColor();
          const ColorNameMap::const_iterator iter = qMapColorNames.find(qColor);
          qEditColor.setText(iter != qMapColorNames.end() ? iter->second : qColor.name());
        });
      QObject::connect(&qEditColor, &QLineEdit::textEdited,
        [&](const QString &text) {
          const QColor qColor(text);
          qBtnColor.setColor(qColor);
        });
      // runtime loop
      return app.exec();
    }
    

    Output:

    Snapshot of testQColorName (after text input)

    Snapshot of testQColorName (after chosing color #ff0000 with color button)

    Console Output:

    Qt Version: 5.13.0
    "aliceblue"
    "antiquewhite"
    "aqua"
    "aquamarine"
    "azure"
    "beige"
    "bisque"
    "black"
    "blanchedalmond"
    "blue"
    "blueviolet"
    "brown"
    "burlywood"
    "cadetblue"
    "chartreuse"
    "chocolate"
    "coral"
    "cornflowerblue"
    "cornsilk"
    "crimson"
    "cyan"
    "darkblue"
    "darkcyan"
    "darkgoldenrod"
    "darkgray"
    "darkgreen"
    "darkgrey"
    "darkkhaki"
    "darkmagenta"
    "darkolivegreen"
    "darkorange"
    "darkorchid"
    "darkred"
    "darksalmon"
    "darkseagreen"
    "darkslateblue"
    "darkslategray"
    "darkslategrey"
    "darkturquoise"
    "darkviolet"
    "deeppink"
    "deepskyblue"
    "dimgray"
    "dimgrey"
    "dodgerblue"
    "firebrick"
    "floralwhite"
    "forestgreen"
    "fuchsia"
    "gainsboro"
    "ghostwhite"
    "gold"
    "goldenrod"
    "gray"
    "green"
    "greenyellow"
    "grey"
    "honeydew"
    "hotpink"
    "indianred"
    "indigo"
    "ivory"
    "khaki"
    "lavender"
    "lavenderblush"
    "lawngreen"
    "lemonchiffon"
    "lightblue"
    "lightcoral"
    "lightcyan"
    "lightgoldenrodyellow"
    "lightgray"
    "lightgreen"
    "lightgrey"
    "lightpink"
    "lightsalmon"
    "lightseagreen"
    "lightskyblue"
    "lightslategray"
    "lightslategrey"
    "lightsteelblue"
    "lightyellow"
    "lime"
    "limegreen"
    "linen"
    "magenta"
    "maroon"
    "mediumaquamarine"
    "mediumblue"
    "mediumorchid"
    "mediumpurple"
    "mediumseagreen"
    "mediumslateblue"
    "mediumspringgreen"
    "mediumturquoise"
    "mediumvioletred"
    "midnightblue"
    "mintcream"
    "mistyrose"
    "moccasin"
    "navajowhite"
    "navy"
    "oldlace"
    "olive"
    "olivedrab"
    "orange"
    "orangered"
    "orchid"
    "palegoldenrod"
    "palegreen"
    "paleturquoise"
    "palevioletred"
    "papayawhip"
    "peachpuff"
    "peru"
    "pink"
    "plum"
    "powderblue"
    "purple"
    "red"
    "rosybrown"
    "royalblue"
    "saddlebrown"
    "salmon"
    "sandybrown"
    "seagreen"
    "seashell"
    "sienna"
    "silver"
    "skyblue"
    "slateblue"
    "slategray"
    "slategrey"
    "snow"
    "springgreen"
    "steelblue"
    "tan"
    "teal"
    "thistle"
    "tomato"
    "transparent"
    "turquoise"
    "violet"
    "wheat"
    "white"
    "whitesmoke"
    "yellow"
    "yellowgreen"
    

    Note:

    It seems that QColor does provide neither a less (to allow usage as key in std::map) nor a specialization of std::hash (to allow usage as key in std::unordered_map). So, I had to specialize std::hash myself for QColor.

    SO: How to specialize std::hash::operator() for user-defined type in unordered containers?)