1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2013-2020 Kai Pastor
4 *
5 * This file is part of OpenOrienteering.
6 *
7 * OpenOrienteering is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * OpenOrienteering is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include "map_color.h"
23
24 #include <algorithm>
25 #include <iterator>
26
27 #include <Qt>
28 #include <QCoreApplication>
29 #include <QLatin1Char>
30 #include <QLatin1String>
31
32
33 namespace OpenOrienteering {
34
MapColor()35 MapColor::MapColor()
36 : name(QCoreApplication::translate("OpenOrienteering::Map", "New color")),
37 priority(Undefined),
38 opacity(1.0f),
39 q_color(Qt::black),
40 spot_color_method(MapColor::UndefinedMethod),
41 cmyk_color_method(MapColor::CustomColor),
42 rgb_color_method(MapColor::CmykColor),
43 flags(0),
44 spot_color_name()
45 {
46 Q_ASSERT(isBlack());
47 }
48
MapColor(int priority)49 MapColor::MapColor(int priority)
50 : name(QCoreApplication::translate("OpenOrienteering::Map", "New color")),
51 priority(priority),
52 opacity(1.0f),
53 q_color(Qt::black),
54 spot_color_method(MapColor::UndefinedMethod),
55 cmyk_color_method(MapColor::CustomColor),
56 rgb_color_method(MapColor::CmykColor),
57 flags(0),
58 spot_color_name()
59 {
60 Q_ASSERT(isBlack());
61
62 switch (priority)
63 {
64 case CoveringWhite:
65 setCmyk(QColor(Qt::white));
66 Q_ASSERT(isWhite());
67 opacity = 1000.0f; // HACK: (almost) always opaque, even if multiplied by opacity factors
68 break;
69 case CoveringRed:
70 setRgb(QColor(Qt::red));
71 setCmykFromRgb();
72 opacity = 1000.0f; // HACK: (almost) always opaque, even if multiplied by opacity factors
73 break;
74 case Undefined:
75 setCmyk(QColor(Qt::darkGray));
76 break;
77 case Registration:
78 Q_ASSERT(isBlack());
79 name = QCoreApplication::translate("OpenOrienteering::MapColor", "Registration black (all printed colors)");
80 break;
81 default:
82 ; // no change
83 }
84 }
85
MapColor(const QString & name,int priority)86 MapColor::MapColor(const QString& name, int priority)
87 : name(name),
88 priority(priority),
89 opacity(1.0),
90 q_color(Qt::black),
91 spot_color_method(MapColor::UndefinedMethod),
92 cmyk_color_method(MapColor::CustomColor),
93 rgb_color_method(MapColor::CmykColor),
94 flags(0),
95 spot_color_name()
96 {
97 Q_ASSERT(isBlack());
98 }
99
100
duplicate() const101 MapColor* MapColor::duplicate() const
102 {
103 MapColor* copy = new MapColor(name, priority);
104 copy->cmyk = cmyk;
105 copy->rgb = rgb;
106 copy->opacity = opacity;
107 copy->q_color = q_color;
108 copy->spot_color_method = spot_color_method;
109 copy->cmyk_color_method = cmyk_color_method;
110 copy->rgb_color_method = rgb_color_method;
111 copy->flags = flags;
112 copy->spot_color_name = spot_color_name;
113 copy->screen_angle = screen_angle;
114 copy->screen_frequency = screen_frequency;
115 copy->components = components;
116 return copy;
117 }
118
isBlack() const119 bool MapColor::isBlack() const
120 {
121 return rgb.isBlack() && cmyk.isBlack();
122 }
123
isWhite() const124 bool MapColor::isWhite() const
125 {
126 return rgb.isWhite() && cmyk.isWhite();
127 }
128
129
operator ==(const SpotColorComponents & lhs,const SpotColorComponents & rhs)130 bool operator==(const SpotColorComponents& lhs, const SpotColorComponents& rhs)
131 {
132 return lhs.size() == rhs.size()
133 && std::is_permutation(begin(lhs), end(lhs), begin(rhs), [](const auto& left, const auto& right) {
134 return *left.spot_color == *right.spot_color
135 && qAbs(left.factor - right.factor) < 1e-03;
136 });
137 }
138
componentsEqual(const MapColor & other,bool compare_priority) const139 bool MapColor::componentsEqual(const MapColor& other, bool compare_priority) const
140 {
141 const SpotColorComponents& lhs(components);
142 const SpotColorComponents& rhs(other.components);
143 return lhs.size() == rhs.size()
144 && std::is_permutation(begin(lhs), end(lhs), begin(rhs), [compare_priority](const auto& left, const auto& right) {
145 return left.spot_color->equals(*right.spot_color, compare_priority)
146 && qAbs(left.factor - right.factor) < 1e-03;
147 });
148 }
149
equals(const MapColor & other) const150 bool MapColor::equals(const MapColor& other) const
151 {
152 return equals(other, false);
153 }
154
equals(const MapColor & other,bool compare_priority) const155 bool MapColor::equals(const MapColor& other, bool compare_priority) const
156 {
157 return (!compare_priority || (priority == other.priority)) &&
158 (name.compare(other.name, Qt::CaseInsensitive) == 0) &&
159 (spot_color_method == other.spot_color_method) &&
160 (cmyk_color_method == other.cmyk_color_method) &&
161 (rgb_color_method == other.rgb_color_method) &&
162 (flags == other.flags) &&
163 (cmyk_color_method != CustomColor || cmyk == other.cmyk) &&
164 (rgb_color_method != CustomColor || rgb == other.rgb) &&
165 ( spot_color_method == UndefinedMethod ||
166 (spot_color_method == SpotColor
167 && spot_color_name.compare(other.spot_color_name, Qt::CaseInsensitive) == 0
168 && (screen_frequency <= 0 || other.screen_frequency <= 0
169 || (std::abs(screen_angle - other.screen_angle) < 0.05
170 && std::abs(screen_frequency - other.screen_frequency) < 0.05))) ||
171 (spot_color_method == CustomColor && componentsEqual(other, compare_priority)) ) &&
172 (qAbs(opacity - other.opacity) < 1e-03);
173 }
174
175
setSpotColorName(const QString & spot_color_name)176 void MapColor::setSpotColorName(const QString& spot_color_name)
177 {
178 spot_color_method = MapColor::SpotColor;
179 this->spot_color_name = spot_color_name;
180 components.clear();
181 updateCalculatedColors();
182 }
183
setScreenFrequency(double value)184 void MapColor::setScreenFrequency(double value)
185 {
186 if (spot_color_method == SpotColor)
187 screen_frequency = value;
188 }
189
setScreenAngle(double value)190 void MapColor::setScreenAngle(double value)
191 {
192 if (spot_color_method == SpotColor)
193 screen_angle = value;
194 }
195
setSpotColorComposition(const SpotColorComponents & components)196 void MapColor::setSpotColorComposition(const SpotColorComponents& components)
197 {
198 this->components = components;
199 if (components.empty())
200 spot_color_method = UndefinedMethod;
201 else
202 spot_color_method = CustomColor;
203
204 removeSpotColorComponent(this);
205 updateCompositionName();
206 updateCalculatedColors();
207 }
208
removeSpotColorComponent(const MapColor * color)209 bool MapColor::removeSpotColorComponent(const MapColor* color)
210 {
211 auto size_before = components.size();
212 auto match = [this, color](const SpotColorComponent& scc) { return scc.spot_color == color; };
213 components.erase(std::remove_if(begin(components), end(components), match), end(components));
214 bool changed = components.size() != size_before;
215 if (changed)
216 {
217 if (components.empty())
218 spot_color_method = UndefinedMethod;
219
220 updateCompositionName();
221 updateCalculatedColors();
222 }
223 return changed;
224 }
225
setKnockout(bool flag)226 void MapColor::setKnockout(bool flag)
227 {
228 if (spot_color_method != MapColor::UndefinedMethod)
229 {
230 if (flag)
231 {
232 if (!getKnockout())
233 flags += MapColor::Knockout;
234 }
235 else if (getKnockout())
236 flags -= MapColor::Knockout;
237
238 Q_ASSERT(getKnockout() == flag);
239 }
240 }
241
getKnockout() const242 bool MapColor::getKnockout() const
243 {
244 return (flags & MapColor::Knockout) > 0;
245 }
246
247
setCmyk(const MapColorCmyk & new_cmyk)248 void MapColor::setCmyk(const MapColorCmyk& new_cmyk)
249 {
250 cmyk_color_method = MapColor::CustomColor;
251 cmyk = new_cmyk;
252 updateCalculatedColors();
253 }
254
setCmykFromSpotColors()255 void MapColor::setCmykFromSpotColors()
256 {
257 if (spot_color_method == MapColor::CustomColor)
258 {
259 cmyk_color_method = MapColor::SpotColor;
260 updateCalculatedColors();
261 }
262 }
263
setCmykFromRgb()264 void MapColor::setCmykFromRgb()
265 {
266 if (rgb_color_method == MapColor::CmykColor)
267 rgb_color_method = MapColor::CustomColor;
268
269 cmyk_color_method = MapColor::RgbColor;
270 updateCalculatedColors();
271 }
272
273
setRgb(const MapColorRgb & new_rgb)274 void MapColor::setRgb(const MapColorRgb& new_rgb)
275 {
276 rgb_color_method = MapColor::CustomColor;
277 rgb = new_rgb;
278 updateCalculatedColors();
279 }
280
setRgbFromSpotColors()281 void MapColor::setRgbFromSpotColors()
282 {
283 if (spot_color_method == MapColor::CustomColor)
284 {
285 rgb_color_method = MapColor::SpotColor;
286 updateCalculatedColors();
287 }
288 }
289
setRgbFromCmyk()290 void MapColor::setRgbFromCmyk()
291 {
292 if (cmyk_color_method == MapColor::RgbColor)
293 cmyk_color_method = MapColor::CustomColor;
294
295 rgb_color_method = MapColor::CmykColor;
296 updateCalculatedColors();
297 }
298
updateCompositionName()299 void MapColor::updateCompositionName()
300 {
301 if (spot_color_method != MapColor::SpotColor)
302 {
303 spot_color_name.clear();
304 for (auto& component : components)
305 {
306 if (!spot_color_name.isEmpty())
307 spot_color_name += QLatin1String(", ");
308 spot_color_name += component.spot_color->getSpotColorName() + QLatin1Char(' ')
309 + QString::number(component.factor * 100) /* % */;
310 }
311 }
312 }
313
updateCalculatedColors()314 void MapColor::updateCalculatedColors()
315 {
316 Q_ASSERT(components.size() == 0 || spot_color_method == CustomColor);
317 Q_ASSERT(components.size() > 0 || spot_color_method != CustomColor);
318 Q_ASSERT(!((cmyk_color_method == RgbColor) && (rgb_color_method == CmykColor)));
319
320 if (spot_color_method != CustomColor)
321 {
322 // No composition, thus cannot determine CMYK or RGB from spot colors.
323 if (cmyk_color_method == MapColor::SpotColor)
324 cmyk_color_method = MapColor::CustomColor;
325
326 if (rgb_color_method == MapColor::SpotColor)
327 rgb_color_method = MapColor::CustomColor;
328 }
329 else
330 {
331 if (cmyk_color_method == MapColor::SpotColor)
332 cmyk = cmykFromSpotColors();
333
334 if (rgb_color_method == MapColor::SpotColor)
335 rgb = rgbFromSpotColors();
336 }
337
338 if (cmyk_color_method == MapColor::RgbColor)
339 cmyk = MapColorCmyk(rgb);
340
341 if (rgb_color_method == MapColor::CmykColor)
342 rgb = MapColorRgb(cmyk);
343
344 if (cmyk_color_method != RgbColor)
345 q_color = cmyk;
346 else
347 q_color = rgb;
348
349 Q_ASSERT(components.size() > 0 || cmyk_color_method != MapColor::SpotColor);
350 Q_ASSERT(components.size() > 0 || rgb_color_method != MapColor::SpotColor);
351 }
352
cmykFromSpotColors() const353 MapColorCmyk MapColor::cmykFromSpotColors() const
354 {
355 Q_ASSERT(components.size() > 0);
356
357 MapColorCmyk cmyk(Qt::white);
358 Q_ASSERT(cmyk.isWhite());
359 for (auto&& component : components)
360 {
361 const MapColorCmyk& other = component.spot_color->cmyk;
362 cmyk.c = cmyk.c + component.factor * other.c * (1.0f - cmyk.c);
363 cmyk.m = cmyk.m + component.factor * other.m * (1.0f - cmyk.m);
364 cmyk.y = cmyk.y + component.factor * other.y * (1.0f - cmyk.y);
365 cmyk.k = cmyk.k + component.factor * other.k * (1.0f - cmyk.k);
366 }
367 return cmyk;
368 }
369
rgbFromSpotColors() const370 MapColorRgb MapColor::rgbFromSpotColors() const
371 {
372 Q_ASSERT(components.size() > 0);
373
374 MapColorRgb rgb = QColor(Qt::white);
375 Q_ASSERT(rgb.isWhite());
376 for (auto&& component : components)
377 {
378 const MapColorRgb& other = component.spot_color->rgb;
379 rgb.r = rgb.r - component.factor * (1.0f - other.r) * rgb.r;
380 rgb.g = rgb.g - component.factor * (1.0f - other.g) * rgb.g;
381 rgb.b = rgb.b - component.factor * (1.0f - other.b) * rgb.b;
382 }
383 return rgb;
384 }
385
386
387 } // namespace OpenOrienteering
388