--- a/src/colors.cpp Wed Jun 08 19:33:00 2022 +0300 +++ b/src/colors.cpp Wed Jun 08 20:41:21 2022 +0300 @@ -1,115 +1,72 @@ -/* - * LDForge: LDraw parts authoring CAD - * Copyright (C) 2013 - 2020 Teemu Piippo - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - #include "colors.h" -const ldraw::ColorDefinition ldraw::ColorTable::unknownColor{{}, {}, "Unknown", "???"}; +const ColorDefinition unknownColor{{}, {}, "Unknown", "???"}; -/** - * @brief Clears the color table - */ -void ldraw::ColorTable::clear() -{ - this->definitions = {}; -} - -/** - * @brief Loads colors from LDConfig.ldr - * @param device Opened LDConfig.ldr I/O device - * @param errors Where to write any errors into - * @returns whether or not it succeeded. - */ -Result ldraw::ColorTable::load(QIODevice& device, QTextStream& errors) +template<typename... Args> +static QString replaced(QString text, Args&&... args) { - this->clear(); - if (device.isReadable()) - { - QTextStream stream{&device}; - QString line; - while (stream.readLineInto(&line)) - { - this->loadColorFromString(line); - } - return Success; - } - else - { - errors << "could not read colors"; - return Failure; - } -} - -/** - * @brief Gets color information by color index. - * @param color - * @returns color table information - */ -const ldraw::ColorDefinition& ldraw::ColorTable::operator[](Color color) const -{ - auto it = this->definitions.find(color.index); - if (it != this->definitions.end()) - { - return it->second; - } - else - { - return unknownColor; - } -} - -/** - * @brief Gets the amount of elements in the color table - * @returns int - */ -int ldraw::ColorTable::size() const -{ - return this->definitions.size(); + text.replace(args...); + return text; } /** * @brief Parses an LDConfig.ldr line from a string * @param string LDConfig.ldr line to parse */ -void ldraw::ColorTable::loadColorFromString(const QString& string) +static auto loadColorFromString(const QString& string) { - const QRegExp pattern{ - R"(^\s*0 \!COLOUR\s+([^\s]+)\s+)"_q + - R"(CODE\s+(\d+)\s+)"_q + - R"(VALUE\s+(\#[0-9a-fA-F]{3,6})\s+)"_q + - R"(EDGE\s+(\#[0-9a-fA-F]{3,6}))"_q + - R"((?:\s+ALPHA\s+(\d+))?)"_q - }; + std::optional<std::pair<ColorIndex, ColorDefinition>> result; + static const QRegExp pattern{QStringLiteral( + R"(^\s*0 \!COLOUR\s+([^\s]+)\s+)" + R"(CODE\s+(\d+)\s+)" + R"(VALUE\s+(\#[0-9a-fA-F]{3,6})\s+)" + R"(EDGE\s+(\#[0-9a-fA-F]{3,6}))" + R"((?:\s+ALPHA\s+(\d+))?)" + )}; if (pattern.indexIn(string) != -1) { const int code = pattern.cap(2).toInt(); - ColorDefinition& definition = this->definitions[code]; - definition = {}; // in case there's an existing definition - definition.name = pattern.cap(1); - definition.displayName = definition.name; - definition.displayName.replace("_", " "); - definition.faceColor = pattern.cap(3); - definition.edgeColor = pattern.cap(4); + const QString name = pattern.cap(1); + ColorDefinition definition = { + .faceColor = pattern.cap(3), + .edgeColor = pattern.cap(4), + .name = name, + .displayName = replaced(name, "_", " "), + }; if (not pattern.cap(5).isEmpty()) { const int alpha = pattern.cap(5).toInt(); definition.faceColor.setAlpha(alpha); } + result = std::make_pair(ColorIndex{code}, definition); } + return result; +} + +/** + * @brief Loads colors from LDConfig.ldr + */ +std::optional<ColorTable> loadColorTable(QIODevice &device, QTextStream &errors) +{ + std::optional<ColorTable> result; + if (device.isReadable()) + { + result.emplace(); + QTextStream stream{&device}; + QString line; + while (stream.readLineInto(&line)) + { + const auto pair = loadColorFromString(line); + if (pair.has_value()) { + (*result)[pair->first] = pair->second; + } + } + } + else + { + errors << "could not read colors"; + } + return result; } /** @@ -118,109 +75,67 @@ * @param color * @returns luma value [0, 1] */ -double luma(const QColor& color) +qreal luma(const QColor& color) { - return 0.2126 * color.redF() + 0.7152 * color.greenF() + 0.0722 * color.blueF(); + return luma(color.redF(), color.greenF(), color.blueF()); } -/** - * @brief Returns a direct color index that codes the specified color value - * @param color - * @returns direct color index - */ -ldraw::Color ldraw::directColor(const QColor& color) +//! @brief Returns a direct color index that codes the specified color value +ColorIndex directColor(const QColor& color) { - return ldraw::Color{0x2000000 | (color.red() << 16) | (color.green() << 8) | color.blue()}; + return directColor(color.red(), color.green(), color.blue()); } -/** - * @brief Checks whether or not the specified color index is a direct color - * @param color Color to check - * @returns bool - */ -bool ldraw::isDirectColor(ldraw::Color color) +//! @brief Returns a face color for @param color, taking direct colors into account +std::optional<QColor> colorFace(ColorIndex color, const ColorTable& colorTable) { - return color.index >= 0x2000000; + std::optional<QColor> result; + if (isDirectColor(color)) { + const std::array<int, 3> rgb = directColorRgb(color); + result = QColor{rgb[0], rgb[1], rgb[2]}; + } + else { + const ColorDefinition* def = findInMap(colorTable, color); + if (def != nullptr) { + result = def->faceColor; + } + } + return result; } -/** - * @brief Extracts the color value from a direct color index - * @param color Direct color index - * @returns color value. Returns a default-constructed QColor in case a non-direct color is given. - */ -QColor ldraw::directColorFace(ldraw::Color color) +//! @brief Returns an edge color for @param color, taking direct colors into account +std::optional<QColor> colorEdge(ColorIndex color, const ColorTable& colorTable) { - if (isDirectColor(color)) - { - return {(color.index >> 16) & 0xff, (color.index >> 8) & 0xff, color.index & 0xff}; + if (isDirectColor(color)) { + const std::array<int, 3> rgb = directColorRgb(color); + return (luma(rgb[0], rgb[1], rgb[2]) < 0.4) ? Qt::white : Qt::black; } - else - { - return {}; + else { + return colorFace(color, colorTable); } } -/** - * @brief Gets the face color for the specified color index - * @param color Color index to get face color for - * @param colorTable Color table to use for lookup - * @returns QColor - */ -QColor ldraw::colorFace(ldraw::Color color, const ldraw::ColorTable& colorTable) -{ - if (isDirectColor(color)) - { - return directColorFace(color); - } - else - { - return colorTable[color].faceColor; - } -} - -QColor ldraw::colorEdge(ldraw::Color color, const ldraw::ColorTable& colorTable) -{ - if (isDirectColor(color)) - { - QColor const faceColor = directColorFace(color); - return (luma(faceColor) < 0.4) ? Qt::white : Qt::black; - } - else - { - return colorTable[color].faceColor; - } -} - -/** - * @brief Writes a color index into a @c QDataStream - * @param stream - * @param color - * @returns stream - */ -QDataStream& operator<<(QDataStream& stream, ldraw::Color color) +QDataStream& operator<<(QDataStream& stream, ColorIndex color) { return stream << color.index; } -/** - * @brief Reads a color index from a @c QDataStream - * @param stream - * @param color - * @returns stream - */ -QDataStream& operator>>(QDataStream& stream, ldraw::Color& color) +QDataStream& operator>>(QDataStream& stream, ColorIndex& color) { return stream >> color.index; } -QString ldraw::colorDisplayName(ldraw::Color color, const ldraw::ColorTable &colorTable) +std::optional<QString> colorDisplayName(ColorIndex color, const ColorTable &colorTable) { - if (isDirectColor(color)) - { - return directColorFace(color).name(); + std::optional<QString> result; + if (isDirectColor(color)) { + result = colorFace(color, colorTable).value_or(QColor{}).name(); } - else - { - return colorTable[color].displayName; + else { + const ColorDefinition* def = findInMap(colorTable, color); + if (def != nullptr) { + result = def->displayName; + } } + return result; }