src/colors.cpp

changeset 205
1a4342d80de7
parent 178
a23024fc98e0
child 250
2837b549e616
equal deleted inserted replaced
204:52e10e8d88cc 205:1a4342d80de7
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 - 2020 Teemu Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "colors.h" 1 #include "colors.h"
20 2
21 const ldraw::ColorDefinition ldraw::ColorTable::unknownColor{{}, {}, "Unknown", "???"}; 3 const ColorDefinition unknownColor{{}, {}, "Unknown", "???"};
22 4
23 /** 5 template<typename... Args>
24 * @brief Clears the color table 6 static QString replaced(QString text, Args&&... args)
25 */
26 void ldraw::ColorTable::clear()
27 { 7 {
28 this->definitions = {}; 8 text.replace(args...);
29 } 9 return text;
30
31 /**
32 * @brief Loads colors from LDConfig.ldr
33 * @param device Opened LDConfig.ldr I/O device
34 * @param errors Where to write any errors into
35 * @returns whether or not it succeeded.
36 */
37 Result ldraw::ColorTable::load(QIODevice& device, QTextStream& errors)
38 {
39 this->clear();
40 if (device.isReadable())
41 {
42 QTextStream stream{&device};
43 QString line;
44 while (stream.readLineInto(&line))
45 {
46 this->loadColorFromString(line);
47 }
48 return Success;
49 }
50 else
51 {
52 errors << "could not read colors";
53 return Failure;
54 }
55 }
56
57 /**
58 * @brief Gets color information by color index.
59 * @param color
60 * @returns color table information
61 */
62 const ldraw::ColorDefinition& ldraw::ColorTable::operator[](Color color) const
63 {
64 auto it = this->definitions.find(color.index);
65 if (it != this->definitions.end())
66 {
67 return it->second;
68 }
69 else
70 {
71 return unknownColor;
72 }
73 }
74
75 /**
76 * @brief Gets the amount of elements in the color table
77 * @returns int
78 */
79 int ldraw::ColorTable::size() const
80 {
81 return this->definitions.size();
82 } 10 }
83 11
84 /** 12 /**
85 * @brief Parses an LDConfig.ldr line from a string 13 * @brief Parses an LDConfig.ldr line from a string
86 * @param string LDConfig.ldr line to parse 14 * @param string LDConfig.ldr line to parse
87 */ 15 */
88 void ldraw::ColorTable::loadColorFromString(const QString& string) 16 static auto loadColorFromString(const QString& string)
89 { 17 {
90 const QRegExp pattern{ 18 std::optional<std::pair<ColorIndex, ColorDefinition>> result;
91 R"(^\s*0 \!COLOUR\s+([^\s]+)\s+)"_q + 19 static const QRegExp pattern{QStringLiteral(
92 R"(CODE\s+(\d+)\s+)"_q + 20 R"(^\s*0 \!COLOUR\s+([^\s]+)\s+)"
93 R"(VALUE\s+(\#[0-9a-fA-F]{3,6})\s+)"_q + 21 R"(CODE\s+(\d+)\s+)"
94 R"(EDGE\s+(\#[0-9a-fA-F]{3,6}))"_q + 22 R"(VALUE\s+(\#[0-9a-fA-F]{3,6})\s+)"
95 R"((?:\s+ALPHA\s+(\d+))?)"_q 23 R"(EDGE\s+(\#[0-9a-fA-F]{3,6}))"
96 }; 24 R"((?:\s+ALPHA\s+(\d+))?)"
25 )};
97 if (pattern.indexIn(string) != -1) 26 if (pattern.indexIn(string) != -1)
98 { 27 {
99 const int code = pattern.cap(2).toInt(); 28 const int code = pattern.cap(2).toInt();
100 ColorDefinition& definition = this->definitions[code]; 29 const QString name = pattern.cap(1);
101 definition = {}; // in case there's an existing definition 30 ColorDefinition definition = {
102 definition.name = pattern.cap(1); 31 .faceColor = pattern.cap(3),
103 definition.displayName = definition.name; 32 .edgeColor = pattern.cap(4),
104 definition.displayName.replace("_", " "); 33 .name = name,
105 definition.faceColor = pattern.cap(3); 34 .displayName = replaced(name, "_", " "),
106 definition.edgeColor = pattern.cap(4); 35 };
107 if (not pattern.cap(5).isEmpty()) 36 if (not pattern.cap(5).isEmpty())
108 { 37 {
109 const int alpha = pattern.cap(5).toInt(); 38 const int alpha = pattern.cap(5).toInt();
110 definition.faceColor.setAlpha(alpha); 39 definition.faceColor.setAlpha(alpha);
111 } 40 }
41 result = std::make_pair(ColorIndex{code}, definition);
112 } 42 }
43 return result;
44 }
45
46 /**
47 * @brief Loads colors from LDConfig.ldr
48 */
49 std::optional<ColorTable> loadColorTable(QIODevice &device, QTextStream &errors)
50 {
51 std::optional<ColorTable> result;
52 if (device.isReadable())
53 {
54 result.emplace();
55 QTextStream stream{&device};
56 QString line;
57 while (stream.readLineInto(&line))
58 {
59 const auto pair = loadColorFromString(line);
60 if (pair.has_value()) {
61 (*result)[pair->first] = pair->second;
62 }
63 }
64 }
65 else
66 {
67 errors << "could not read colors";
68 }
69 return result;
113 } 70 }
114 71
115 /** 72 /**
116 * @brief Calculates the luma-value for the given color. 73 * @brief Calculates the luma-value for the given color.
117 * @details c.f. https://en.wikipedia.org/wiki/Luma_(video) 74 * @details c.f. https://en.wikipedia.org/wiki/Luma_(video)
118 * @param color 75 * @param color
119 * @returns luma value [0, 1] 76 * @returns luma value [0, 1]
120 */ 77 */
121 double luma(const QColor& color) 78 qreal luma(const QColor& color)
122 { 79 {
123 return 0.2126 * color.redF() + 0.7152 * color.greenF() + 0.0722 * color.blueF(); 80 return luma(color.redF(), color.greenF(), color.blueF());
124 } 81 }
125 82
126 /** 83 //! @brief Returns a direct color index that codes the specified color value
127 * @brief Returns a direct color index that codes the specified color value 84 ColorIndex directColor(const QColor& color)
128 * @param color
129 * @returns direct color index
130 */
131 ldraw::Color ldraw::directColor(const QColor& color)
132 { 85 {
133 return ldraw::Color{0x2000000 | (color.red() << 16) | (color.green() << 8) | color.blue()}; 86 return directColor(color.red(), color.green(), color.blue());
134 } 87 }
135 88
136 /** 89 //! @brief Returns a face color for @param color, taking direct colors into account
137 * @brief Checks whether or not the specified color index is a direct color 90 std::optional<QColor> colorFace(ColorIndex color, const ColorTable& colorTable)
138 * @param color Color to check
139 * @returns bool
140 */
141 bool ldraw::isDirectColor(ldraw::Color color)
142 { 91 {
143 return color.index >= 0x2000000; 92 std::optional<QColor> result;
93 if (isDirectColor(color)) {
94 const std::array<int, 3> rgb = directColorRgb(color);
95 result = QColor{rgb[0], rgb[1], rgb[2]};
96 }
97 else {
98 const ColorDefinition* def = findInMap(colorTable, color);
99 if (def != nullptr) {
100 result = def->faceColor;
101 }
102 }
103 return result;
144 } 104 }
145 105
146 /** 106 //! @brief Returns an edge color for @param color, taking direct colors into account
147 * @brief Extracts the color value from a direct color index 107 std::optional<QColor> colorEdge(ColorIndex color, const ColorTable& colorTable)
148 * @param color Direct color index
149 * @returns color value. Returns a default-constructed QColor in case a non-direct color is given.
150 */
151 QColor ldraw::directColorFace(ldraw::Color color)
152 { 108 {
153 if (isDirectColor(color)) 109 if (isDirectColor(color)) {
154 { 110 const std::array<int, 3> rgb = directColorRgb(color);
155 return {(color.index >> 16) & 0xff, (color.index >> 8) & 0xff, color.index & 0xff}; 111 return (luma(rgb[0], rgb[1], rgb[2]) < 0.4) ? Qt::white : Qt::black;
156 } 112 }
157 else 113 else {
158 { 114 return colorFace(color, colorTable);
159 return {};
160 } 115 }
161 } 116 }
162 117
163 /** 118 QDataStream& operator<<(QDataStream& stream, ColorIndex color)
164 * @brief Gets the face color for the specified color index
165 * @param color Color index to get face color for
166 * @param colorTable Color table to use for lookup
167 * @returns QColor
168 */
169 QColor ldraw::colorFace(ldraw::Color color, const ldraw::ColorTable& colorTable)
170 {
171 if (isDirectColor(color))
172 {
173 return directColorFace(color);
174 }
175 else
176 {
177 return colorTable[color].faceColor;
178 }
179 }
180
181 QColor ldraw::colorEdge(ldraw::Color color, const ldraw::ColorTable& colorTable)
182 {
183 if (isDirectColor(color))
184 {
185 QColor const faceColor = directColorFace(color);
186 return (luma(faceColor) < 0.4) ? Qt::white : Qt::black;
187 }
188 else
189 {
190 return colorTable[color].faceColor;
191 }
192 }
193
194 /**
195 * @brief Writes a color index into a @c QDataStream
196 * @param stream
197 * @param color
198 * @returns stream
199 */
200 QDataStream& operator<<(QDataStream& stream, ldraw::Color color)
201 { 119 {
202 return stream << color.index; 120 return stream << color.index;
203 } 121 }
204 122
205 /** 123 QDataStream& operator>>(QDataStream& stream, ColorIndex& color)
206 * @brief Reads a color index from a @c QDataStream
207 * @param stream
208 * @param color
209 * @returns stream
210 */
211 QDataStream& operator>>(QDataStream& stream, ldraw::Color& color)
212 { 124 {
213 return stream >> color.index; 125 return stream >> color.index;
214 } 126 }
215 127
216 QString ldraw::colorDisplayName(ldraw::Color color, const ldraw::ColorTable &colorTable) 128 std::optional<QString> colorDisplayName(ColorIndex color, const ColorTable &colorTable)
217 { 129 {
218 if (isDirectColor(color)) 130 std::optional<QString> result;
219 { 131 if (isDirectColor(color)) {
220 return directColorFace(color).name(); 132 result = colorFace(color, colorTable).value_or(QColor{}).name();
221 } 133 }
222 else 134 else {
223 { 135 const ColorDefinition* def = findInMap(colorTable, color);
224 return colorTable[color].displayName; 136 if (def != nullptr) {
137 result = def->displayName;
138 }
225 } 139 }
140 return result;
226 } 141 }

mercurial