1 /*=========================================================================
2
3 Program: Visualization Toolkit
4 Module: vtkCIEDE2000.cxx
5
6 Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7 All rights reserved.
8 See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9
10 This software is distributed WITHOUT ANY WARRANTY; without even
11 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 PURPOSE. See the above copyright notice for more information.
13
14 =========================================================================*/
15 /*=========================================================================
16 The MIT License (MIT)
17
18 Copyright (c) 2015 Greg Fiumara
19
20 Permission is hereby granted, free of charge, to any person obtaining a copy
21 of this software and associated documentation files (the "Software"), to deal
22 in the Software without restriction, including without limitation the rights
23 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24 copies of the Software, and to permit persons to whom the Software is
25 furnished to do so, subject to the following conditions:
26
27 The above copyright notice and this permission notice shall be included in all
28 copies or substantial portions of the Software.
29
30 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36 SOFTWARE.
37 =========================================================================*/
38
39 #include "vtkCIEDE2000.h"
40
41 #include <algorithm> // std::min, std::max
42 #include <array>
43 #include <deque>
44 #include <limits>
45 #include <set>
46 #include <utility> // std::pair, std::make_pair
47 #include <vtkMath.h>
48
49 namespace CIEDE2000
50 {
51
52 //----------------------------------------------------------------------------
53 static const int COLORSPACE_SIZE_X = 17;
54 static const int COLORSPACE_SIZE_Y = 17;
55 static const int COLORSPACE_SIZE_Z = 17;
56
57 static const int NEIGHBORHOOD_SIZE_X = 1;
58 static const int NEIGHBORHOOD_SIZE_Y = 1;
59 static const int NEIGHBORHOOD_SIZE_Z = 1;
60
61 typedef int PositionComponent;
62 typedef std::array<PositionComponent, 3> Position;
63 typedef double Distance;
64
65 //----------------------------------------------------------------------------
getPosition(const double rgb[3],Position & pos)66 inline static void getPosition(const double rgb[3], Position& pos)
67 {
68 static const double EPSILON = 0.000001;
69
70 pos[0] = static_cast<PositionComponent>(rgb[0] * (COLORSPACE_SIZE_X - EPSILON));
71 pos[1] = static_cast<PositionComponent>(rgb[1] * (COLORSPACE_SIZE_Y - EPSILON));
72 pos[2] = static_cast<PositionComponent>(rgb[2] * (COLORSPACE_SIZE_Z - EPSILON));
73 }
74
75 //----------------------------------------------------------------------------
getRGBColor(const Position & pos,double rgb[3])76 inline static void getRGBColor(const Position& pos, double rgb[3])
77 {
78 rgb[0] = pos[0] / static_cast<double>(COLORSPACE_SIZE_X - 1);
79 rgb[1] = pos[1] / static_cast<double>(COLORSPACE_SIZE_Y - 1);
80 rgb[2] = pos[2] / static_cast<double>(COLORSPACE_SIZE_Z - 1);
81 }
82
83 //----------------------------------------------------------------------------
getLabColor(const Position & pos,double _lab[3])84 inline static void getLabColor(const Position& pos, double _lab[3])
85 {
86 double rgb[3];
87 getRGBColor(pos, rgb);
88
89 vtkMath::RGBToLab(rgb, _lab);
90 }
91
92 //----------------------------------------------------------------------------
getIndex(const Position & pos)93 inline static int getIndex(const Position& pos)
94 {
95 return pos[0] + COLORSPACE_SIZE_X * (pos[1] + COLORSPACE_SIZE_Y * pos[2]);
96 }
97
98 //----------------------------------------------------------------------------
GetCIEDeltaE2000(const double lab1[3],const double lab2[3])99 double GetCIEDeltaE2000(const double lab1[3], const double lab2[3])
100 {
101 // The three constants used in the CIEDE2000 measure
102 static const double k_L = 1.0;
103 static const double k_C = 1.0;
104 static const double k_H = 1.0;
105
106 // Calculate and return Delta E
107
108 double C1 = std::sqrt((lab1[1] * lab1[1]) + (lab1[2] * lab1[2]));
109 double C2 = std::sqrt((lab2[1] * lab2[1]) + (lab2[2] * lab2[2]));
110
111 double barC = 0.5 * (C1 + C2);
112
113 double G =
114 0.5 * (1.0 - std::sqrt(std::pow(barC, 7.0) / (std::pow(barC, 7.0) + std::pow(25.0, 7.0))));
115
116 double a1Prime = (1.0 + G) * lab1[1];
117 double a2Prime = (1.0 + G) * lab2[1];
118
119 double CPrime1 = std::sqrt((a1Prime * a1Prime) + (lab1[2] * lab1[2]));
120 double CPrime2 = std::sqrt((a2Prime * a2Prime) + (lab2[2] * lab2[2]));
121
122 double hPrime1;
123 if ((lab1[2] == 0.0) && (a1Prime == 0.0))
124 {
125 hPrime1 = 0.0;
126 }
127 else
128 {
129 hPrime1 = std::atan2(lab1[2], a1Prime);
130 if (hPrime1 < 0.0)
131 {
132 hPrime1 += 2.0 * vtkMath::Pi();
133 }
134 }
135
136 double hPrime2;
137 if ((lab2[2] == 0.0) && (a2Prime == 0.0))
138 {
139 hPrime2 = 0.0;
140 }
141 else
142 {
143 hPrime2 = std::atan2(lab2[2], a2Prime);
144 if (hPrime2 < 0.0)
145 {
146 hPrime2 += 2.0 * vtkMath::Pi();
147 }
148 }
149
150 double deltaLPrime = lab2[0] - lab1[0];
151
152 double deltaCPrime = CPrime2 - CPrime1;
153
154 double CPrimeProduct = CPrime1 * CPrime2;
155
156 double deltahPrime;
157 if (CPrimeProduct == 0.0)
158 {
159 deltahPrime = 0.0;
160 }
161 else
162 {
163 deltahPrime = hPrime2 - hPrime1;
164
165 if (deltahPrime < -vtkMath::Pi())
166 {
167 deltahPrime += 2.0 * vtkMath::Pi();
168 }
169 else if (deltahPrime > vtkMath::Pi())
170 {
171 deltahPrime -= 2.0 * vtkMath::Pi();
172 }
173 }
174
175 double deltaHPrime = 2.0 * std::sqrt(CPrimeProduct) * std::sin(0.5 * deltahPrime);
176
177 double barLPrime = 0.5 * (lab1[0] + lab2[0]);
178
179 double barCPrime = 0.5 * (CPrime1 + CPrime2);
180
181 double hPrimeSum = hPrime1 + hPrime2;
182
183 double barhPrime;
184 if (CPrime1 * CPrime2 == 0.0)
185 {
186 barhPrime = hPrimeSum;
187 }
188 else
189 {
190 if (std::fabs(hPrime1 - hPrime2) <= vtkMath::Pi())
191 {
192 barhPrime = 0.5 * hPrimeSum;
193 }
194 else
195 {
196 if (hPrimeSum < 2.0 * vtkMath::Pi())
197 {
198 barhPrime = 0.5 * (hPrimeSum + 2.0 * vtkMath::Pi());
199 }
200 else
201 {
202 barhPrime = 0.5 * (hPrimeSum - 2.0 * vtkMath::Pi());
203 }
204 }
205 }
206
207 double T = 1.0 - 0.17 * std::cos(barhPrime - (vtkMath::Pi() * 30.0 / 180.0)) +
208 0.24 * std::cos(2.0 * barhPrime) + 0.32 * std::cos(3.0 * barhPrime + (vtkMath::Pi() * 6.0 / 180.0)) -
209 0.20 * std::cos(4.0 * barhPrime - (vtkMath::Pi() * 63.0 / 180.0));
210
211 double deltaTheta = (vtkMath::Pi() * 30.0 / 180.0) *
212 std::exp(-std::pow((barhPrime - (vtkMath::Pi() * 275.0 / 180.0)) / (vtkMath::Pi() * 25.0 / 180.0), 2.0));
213
214 double R_C =
215 2.0 * std::sqrt(std::pow(barCPrime, 7.0) / (std::pow(barCPrime, 7.0) + std::pow(25.0, 7.0)));
216
217 double S_L =
218 1.0 + (0.015 * pow(barLPrime - 50.0, 2.0) / std::sqrt(20.0 + std::pow(barLPrime - 50.0, 2.0)));
219
220 double S_C = 1.0 + (0.045 * barCPrime);
221
222 double S_H = 1.0 + (0.015 * barCPrime * T);
223
224 double R_T = -std::sin(2.0 * deltaTheta) * R_C;
225
226 double deltaE = std::sqrt(std::pow(deltaLPrime / (k_L * S_L), 2.0) +
227 std::pow(deltaCPrime / (k_C * S_C), 2.0) + std::pow(deltaHPrime / (k_H * S_H), 2.0) +
228 R_T * (deltaCPrime / (k_C * S_C)) * (deltaHPrime / (k_H * S_H)));
229
230 return deltaE;
231 }
232
233 //----------------------------------------------------------------------------
GetColorPath(const double rgb1[3],const double rgb2[3],std::vector<Node> & path)234 double GetColorPath(const double rgb1[3], const double rgb2[3], std::vector<Node>& path)
235 {
236 Position pos1, pos2;
237 getPosition(rgb1, pos1);
238 getPosition(rgb2, pos2);
239
240 // Use Dijkstra's algorithm backwards to calculate the shortest distances from
241 // the second color
242
243 std::deque<Distance> distances(COLORSPACE_SIZE_X * COLORSPACE_SIZE_Y * COLORSPACE_SIZE_Z,
244 std::numeric_limits<Distance>::infinity());
245 std::deque<Position> predecessors(COLORSPACE_SIZE_X * COLORSPACE_SIZE_Y * COLORSPACE_SIZE_Z);
246
247 // Use a set as the priority queue so we can update an entry in the queue by
248 // deleting the old entry and re-inserting the new entry.
249 // The set is sorted first by the distance from the seed node, so that the
250 // first entry always is the node that can be reached shortest.
251 std::set<std::pair<Distance, Position> > front;
252
253 // Start backwards and use the second color as seed
254 distances[getIndex(pos2)] = static_cast<Distance>(0);
255 front.insert(std::make_pair(static_cast<Distance>(0), pos2));
256
257 while (!front.empty())
258 {
259 Distance currentDist = front.begin()->first;
260 Position currentPos = front.begin()->second;
261
262 front.erase(front.begin());
263
264 double currentLabColor[3];
265 getLabColor(currentPos, currentLabColor);
266
267 int minNeighborPosX = std::max(static_cast<int>(currentPos[0]) - NEIGHBORHOOD_SIZE_X, 0);
268 int minNeighborPosY = std::max(static_cast<int>(currentPos[1]) - NEIGHBORHOOD_SIZE_Y, 0);
269 int minNeighborPosZ = std::max(static_cast<int>(currentPos[2]) - NEIGHBORHOOD_SIZE_Z, 0);
270
271 int maxNeighborPosX =
272 std::min(static_cast<int>(currentPos[0]) + NEIGHBORHOOD_SIZE_X, COLORSPACE_SIZE_X - 1);
273 int maxNeighborPosY =
274 std::min(static_cast<int>(currentPos[1]) + NEIGHBORHOOD_SIZE_Y, COLORSPACE_SIZE_Y - 1);
275 int maxNeighborPosZ =
276 std::min(static_cast<int>(currentPos[2]) + NEIGHBORHOOD_SIZE_Z, COLORSPACE_SIZE_Z - 1);
277
278 for (int neighborPosZ = minNeighborPosZ; neighborPosZ <= maxNeighborPosZ; ++neighborPosZ)
279 {
280 for (int neighborPosY = minNeighborPosY; neighborPosY <= maxNeighborPosY; ++neighborPosY)
281 {
282 for (int neighborPosX = minNeighborPosX; neighborPosX <= maxNeighborPosX; ++neighborPosX)
283 {
284 Position neighborPos;
285 neighborPos[0] = neighborPosX;
286 neighborPos[1] = neighborPosY;
287 neighborPos[2] = neighborPosZ;
288
289 if (neighborPos == currentPos)
290 {
291 continue;
292 }
293
294 double neighborLabColor[3];
295 getLabColor(neighborPos, neighborLabColor);
296
297 Distance deltaE = static_cast<Distance>(GetCIEDeltaE2000(currentLabColor, neighborLabColor));
298
299 int neighborIdx = getIndex(neighborPos);
300
301 Distance oldNeighborDist = distances[neighborIdx];
302 Distance newNeighborDist = currentDist + deltaE;
303
304 if (newNeighborDist < oldNeighborDist)
305 {
306 front.erase(std::make_pair(oldNeighborDist, neighborPos));
307
308 distances[neighborIdx] = newNeighborDist;
309 predecessors[neighborIdx] = currentPos;
310 front.insert(std::make_pair(newNeighborDist, neighborPos));
311 }
312 }
313 }
314 }
315 }
316
317 // We started backwards from the second color, so the overall length of the
318 // path is the distance value at the position of the first color
319 Distance pathDistance = distances[getIndex(pos1)];
320
321 // Start the path from the first color and follow each node's predecessor
322 // until the second color is reached.
323 // Since each node was reached shortest from its predecessor, this results in
324 // a shortest path from the first to the second color.
325
326 path.clear();
327
328 Position currentPos = pos1;
329
330 while (true)
331 {
332 int currentIdx = getIndex(currentPos);
333
334 Node node;
335 getRGBColor(currentPos, node.rgb);
336
337 // The shortest distance from the first color to the node is the overall
338 // shortest distance
339 // from the first to the second color minus the shortest distance from the
340 // second color to the node.
341 node.distance = pathDistance - distances[currentIdx];
342
343 path.push_back(node);
344
345 if (currentPos == pos2)
346 {
347 break;
348 }
349
350 currentPos = predecessors[currentIdx];
351 }
352
353 // Force the first and the last node's color to be exact
354 path.front().rgb[0] = rgb1[0];
355 path.front().rgb[1] = rgb1[1];
356 path.front().rgb[2] = rgb1[2];
357 path.back().rgb[0] = rgb2[0];
358 path.back().rgb[1] = rgb2[1];
359 path.back().rgb[2] = rgb2[2];
360
361 // Return the overall length of the path
362 return pathDistance;
363 }
364 //----------------------------------------------------------------------------
365
366 } // namespace CIEDE2000
367