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