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