3

I create a color by name ("red", "green"), but later when I ask its name I get RGB info. Is there no way to retrieve's color name if any (obviously, they can't all have a name).

#include <QColor>
#include <iostream>

int main( int argc, char* argv[] )
{
    QColor color( "red" );
    std::cout << color.name().toStdString();
    return 0;
}

This outputs, "#ff0000", I would like it to output "red".

jpo38
  • 20,821
  • 10
  • 70
  • 151

2 Answers2

6

The only way I can see from the documentation is to iterate over all the named colors that Qt knows about (provided by QColor::colorNames()), converting each one to a QColor and checking if the colors match (there is an operator== for QColor available).

If you want to do this repeatedly, it is advisable to use some sort of map instead of doing linear searches constantly. QColor does not lend itself directly as map key (no operator< and no hash function), but we can use its underlying RGBA value. If we write custom code for this we might as well get the performance aspect right by avoiding the (for our purposes) inefficient map/unordered_map implementations and using binary search over sorted vectors instead:

// Lookup class that is only accessible from getColorName free function.
class NamedQColorLookup
{
private:
    NamedQColorLookup()
    {
        auto keyList = QColor::colorNames();

        // Simple implementation for filling _keys and _values using std::map.
        // Alternatively, sort two vectors at once, for example like
        // https://stackoverflow.com/questions/17074324/how-can-i-sort-two-vectors-in-the-same-way-with-criteria-that-uses-only-one-of
        // But that's less readable and (since it's only done once) has no meaningful performance impact.
        std::map<std::uint64_t, QString> colorMap;
        for (const auto& key : keyList)
            colorMap.emplace(QColor(key).rgba64(), key);

        // Convert to faster and smaller vector lookup.
        _keys.reserve(colorMap.size());
        _values.reserve(colorMap.size());
        for (const auto& [key, value] : colorMap)
        {
            _keys.emplace_back(key);
            _values.emplace_back(value);
        }
    }

    QString getName(const QColor& color) const
    {
        auto rgba = color.rgba64();
        // Binary search for the RGBA value.
        auto [notLessThan, greaterThan] = std::equal_range(_keys.begin(), _keys.end(), rgba);

        // If this is not a named color, return the RGB code instead.
        if (notLessThan == greaterThan)
            return color.name();

        // We found a matching ARGB value, obtain its index.
        auto index = std::distance(_keys.begin(), notLessThan);
        return _values[index];
    }

    std::vector<std::uint64_t> _keys;
    std::vector<QString> _values; // ...or some kind of string view if you want.

    friend QString getColorName(const QColor& color);
};

// The interface for color -> name lookups.
QString getColorName(const QColor& color)
{
    static NamedQColorLookup lookup;
    return lookup.getName(color);
}

We could use a std::vector<std::pair<std::uint64_t, QString>> instead of two separate vectors, but that would make the binary search slower (more cache misses).

Play with it here: https://godbolt.org/z/z1fgyc

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • Good point. You could just have the integer keys and string values in separate vectors (sorted by the integer key), binary search the keys and, if the key is found, use its index to retrieve the string from the second vector. (You could also store the pairs in one vector, sorted only by one pair element, but that'll make the binary search harder on the cache). – Max Langhof Aug 14 '19 at 09:39
  • @xception After lunch :) – Max Langhof Aug 14 '19 at 09:59
  • @xception Done. Please take a look and share your thoughts! – Max Langhof Aug 14 '19 at 11:19
  • nice, upvoted ... sorry for the time it took, had a few busy hours – xception Aug 14 '19 at 13:13
2

Seems to me this is what you're looking for

#include <QColor>
#include <iostream>

int main( int argc, char* argv[] )
{
    QColor color( "red" );
    QColor cmp;
    for(auto i : QColor::colorNames()) {
        cmp.setNamedColor(i);
        if(cmp == color)
            std::cout << i.toStdString();
    }
    return 0;
}

note that anything builtin that would get that color name would probably also use a similar loop.

As a side note you should consider using qDebug instead of cout since you're using Qt.

xception
  • 4,241
  • 1
  • 17
  • 27
  • 1
    Why using `cmp.rgba64() == color.rgba64()` instead of just `cmp == color`? – jpo38 Aug 14 '19 at 09:52
  • I didn't see operator == documented for QColor, that's all. – xception Aug 14 '19 at 09:54
  • I was actually expecting Qt to propose such a function in the API, but as it apparently does not, your answer provides the working implementation. Thanx. – jpo38 Aug 14 '19 at 10:04