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 } |