99#include < utility>
1010
1111#include < SDL.h>
12+ #include < fmt/format.h>
1213
1314#include " utils/static_vector.hpp"
1415#include " utils/str_cat.hpp"
2122
2223namespace devilution {
2324
24- [[nodiscard]] inline uint32_t GetColorDistance (const SDL_Color &a, const std::array<uint8_t , 3 > &b)
25+ [[nodiscard]] inline uint32_t GetColorDistance (const std::array< uint8_t , 3 > &a, const std::array<uint8_t , 3 > &b)
2526{
26- const int diffr = a. r - b[0 ];
27- const int diffg = a. g - b[1 ];
28- const int diffb = a. b - b[2 ];
27+ const int diffr = a[ 0 ] - b[0 ];
28+ const int diffg = a[ 1 ] - b[1 ];
29+ const int diffb = a[ 2 ] - b[2 ];
2930 return (diffr * diffr) + (diffg * diffg) + (diffb * diffb);
3031}
3132
@@ -58,6 +59,8 @@ constexpr size_t PaletteKdTreeDepth = 5;
5859 */
5960template <size_t RemainingDepth>
6061struct PaletteKdTreeNode {
62+ using RGB = std::array<uint8_t , 3 >;
63+
6164 static constexpr unsigned Coord = (PaletteKdTreeDepth - RemainingDepth) % 3 ;
6265
6366 PaletteKdTreeNode<RemainingDepth - 1 > left;
@@ -97,7 +100,7 @@ struct PaletteKdTreeNode {
97100 }
98101 }
99102
100- [[maybe_unused]] void toGraphvizDot (size_t id, std::span<const uint8_t , 256 > values, std::string &dot) const
103+ [[maybe_unused]] void toGraphvizDot (size_t id, std::span<const std::pair<RGB, uint8_t > , 256 > values, std::string &dot) const
101104 {
102105 StrAppend (dot, " node_" , id, " [label=\" " );
103106 if (Coord == 0 ) {
@@ -123,27 +126,36 @@ struct PaletteKdTreeNode {
123126 */
124127template <>
125128struct PaletteKdTreeNode </* RemainingDepth=*/ 0 > {
129+ using RGB = std::array<uint8_t , 3 >;
130+
126131 // We use inclusive indices to allow for representing the full [0, 255] range.
127132 // An empty node is represented as [1, 0].
128133 uint8_t valuesBegin;
129134 uint8_t valuesEndInclusive;
130135
131- [[maybe_unused]] void toGraphvizDot (size_t id, std::span<const uint8_t , 256 > values, std::string &dot) const
136+ [[maybe_unused]] void toGraphvizDot (size_t id, std::span<const std::pair<RGB, uint8_t > , 256 > values, std::string &dot) const
132137 {
133- StrAppend (dot, " node_" , id, " [shape=box label=\" " );
134- const uint8_t *it = values.data () + valuesBegin;
135- const uint8_t *const end = values.data () + valuesEndInclusive;
136- while (it <= end) {
137- StrAppend (dot, static_cast <int >(*it), " , " );
138- ++it;
139- }
140- if (valuesBegin <= valuesEndInclusive) {
141- dot[dot.size () - 2 ] = ' \" ' ;
142- dot[dot.size () - 1 ] = ' ]' ;
143- dot += " \n " ;
144- } else {
145- StrAppend (dot, " \" ]\n " );
138+ StrAppend (dot, " node_" , id, R"( [shape=plain label=<
139+ <table border="0" cellborder="0" cellspacing="0" cellpadding="2" style="ROUNDED">
140+ <tr>)" );
141+ const std::pair<RGB, uint8_t > *const end = values.data () + valuesEndInclusive;
142+ for (const std::pair<RGB, uint8_t > *it = values.data () + valuesBegin; it <= end; ++it) {
143+ const auto &[rgb, paletteIndex] = *it;
144+ char hexColor[6 ];
145+ fmt::format_to (hexColor, " {:02x}{:02x}{:02x}" , rgb[0 ], rgb[1 ], rgb[2 ]);
146+ StrAppend (dot, R"( <td balign="left" bgcolor="#)" , std::string_view (hexColor, 6 ), " \" >" );
147+ const bool useWhiteText = rgb[0 ] + rgb[1 ] + rgb[2 ] < 350 ;
148+ if (useWhiteText) StrAppend (dot, R"( <font color="white">)" );
149+ StrAppend (dot,
150+ static_cast <int >(rgb[0 ]), " " ,
151+ static_cast <int >(rgb[1 ]), " " ,
152+ static_cast <int >(rgb[2 ]), R"( <br/>)" ,
153+ static_cast <int >(paletteIndex));
154+ if (useWhiteText) StrAppend (dot, " </font>" );
155+ StrAppend (dot, " </td>" );
146156 }
157+ if (valuesBegin > valuesEndInclusive) StrAppend (dot, " <td></td>" );
158+ StrAppend (dot, " </tr>\n </table>>]\n " );
147159 }
148160};
149161
@@ -167,9 +179,8 @@ class PaletteKdTree {
167179 * Colors between skipFrom and skipTo (inclusive) are skipped.
168180 */
169181 explicit PaletteKdTree (const SDL_Color palette[256 ], int skipFrom, int skipTo)
170- : palette_(palette)
171182 {
172- populatePivots (skipFrom, skipTo);
183+ populatePivots (palette, skipFrom, skipTo);
173184 StaticVector<uint8_t , 256 > leafValues[NumLeaves];
174185 for (int i = 0 ; i < 256 ; ++i) {
175186 if (i >= skipFrom && i <= skipTo) continue ;
@@ -186,7 +197,11 @@ class PaletteKdTree {
186197 } else {
187198 leaf.valuesBegin = static_cast <uint8_t >(totalLen);
188199 leaf.valuesEndInclusive = static_cast <uint8_t >(totalLen - 1 + values.size ());
189- std::copy (values.begin (), values.end (), values_.data () + totalLen);
200+
201+ for (size_t i = 0 ; i < values.size (); ++i) {
202+ const uint8_t value = values[i];
203+ values_[totalLen + i] = std::make_pair (RGB { palette[value].r , palette[value].g , palette[value].b }, value);
204+ }
190205 totalLen += values.size ();
191206 }
192207 }
@@ -206,7 +221,7 @@ class PaletteKdTree {
206221 uint8_t best;
207222 uint32_t bestDiff = std::numeric_limits<uint32_t >::max ();
208223 findNearestNeighborVisit (tree_, rgb, bestDiff, best);
209- return best;
224+ return values_[ best]. second ;
210225 }
211226
212227 [[maybe_unused]] [[nodiscard]] std::string toGraphvizDot () const
@@ -242,16 +257,18 @@ class PaletteKdTree {
242257 }
243258
244259 template <size_t RemainingDepth, size_t N>
245- void maybeAddToSubdivisionForMedian (
246- const PaletteKdTreeNode<RemainingDepth> &node, unsigned paletteIndex,
260+ static void maybeAddToSubdivisionForMedian (
261+ const PaletteKdTreeNode<RemainingDepth> &node,
262+ const SDL_Color palette[256 ], unsigned paletteIndex,
247263 std::span<StaticVector<uint8_t , 256 >, N> out)
248264 {
249- const uint8_t color = node.getColorCoordinate (palette_ [paletteIndex]);
265+ const uint8_t color = node.getColorCoordinate (palette [paletteIndex]);
250266 if constexpr (N == 1 ) {
251267 out[0 ].emplace_back (color);
252268 } else {
253269 const bool isLeft = color < node.pivot ;
254270 maybeAddToSubdivisionForMedian (node.child (isLeft),
271+ palette,
255272 paletteIndex,
256273 isLeft
257274 ? out.template subspan <0 , N / 2 >()
@@ -260,7 +277,7 @@ class PaletteKdTree {
260277 }
261278
262279 template <size_t RemainingDepth, size_t N>
263- void setPivotsRecursively (
280+ static void setPivotsRecursively (
264281 PaletteKdTreeNode<RemainingDepth> &node,
265282 std::span<StaticVector<uint8_t , 256 >, N> values)
266283 {
@@ -273,27 +290,27 @@ class PaletteKdTree {
273290 }
274291
275292 template <size_t TargetDepth>
276- void populatePivotsForTargetDepth (int skipFrom, int skipTo)
293+ void populatePivotsForTargetDepth (const SDL_Color palette[ 256 ], int skipFrom, int skipTo)
277294 {
278295 constexpr size_t NumSubdivisions = 1U << TargetDepth;
279296 std::array<StaticVector<uint8_t , 256 >, NumSubdivisions> subdivisions;
280297 const std::span<StaticVector<uint8_t , 256 >, NumSubdivisions> subdivisionsSpan { subdivisions };
281298 for (int i = 0 ; i < 256 ; ++i) {
282299 if (i >= skipFrom && i <= skipTo) continue ;
283- maybeAddToSubdivisionForMedian (tree_, i, subdivisionsSpan);
300+ maybeAddToSubdivisionForMedian (tree_, palette, i, subdivisionsSpan);
284301 }
285302 setPivotsRecursively (tree_, subdivisionsSpan);
286303 }
287304
288305 template <size_t ... TargetDepths>
289- void populatePivotsImpl (int skipFrom, int skipTo, std::index_sequence<TargetDepths...> intSeq) // NOLINT(misc-unused-parameters)
306+ void populatePivotsImpl (const SDL_Color palette[ 256 ], int skipFrom, int skipTo, std::index_sequence<TargetDepths...> intSeq) // NOLINT(misc-unused-parameters)
290307 {
291- (populatePivotsForTargetDepth<TargetDepths>(skipFrom, skipTo), ...);
308+ (populatePivotsForTargetDepth<TargetDepths>(palette, skipFrom, skipTo), ...);
292309 }
293310
294- void populatePivots (int skipFrom, int skipTo)
311+ void populatePivots (const SDL_Color palette[ 256 ], int skipFrom, int skipTo)
295312 {
296- populatePivotsImpl (skipFrom, skipTo, std::make_index_sequence<PaletteKdTreeDepth> {});
313+ populatePivotsImpl (palette, skipFrom, skipTo, std::make_index_sequence<PaletteKdTreeDepth> {});
297314 }
298315
299316 template <size_t RemainingDepth>
@@ -307,29 +324,28 @@ class PaletteKdTree {
307324 // to the current best candidate vs the distance to the edge of the half-space represented
308325 // by the node.
309326 if (bestDiff == std::numeric_limits<uint32_t >::max ()
310- || GetColorDistanceToPlane (node.pivot , coord) < GetColorDistance (palette_ [best], rgb)) {
327+ || GetColorDistanceToPlane (node.pivot , coord) < GetColorDistance (values_ [best]. first , rgb)) {
311328 findNearestNeighborVisit (node.child (coord >= node.pivot ), rgb, bestDiff, best);
312329 }
313330 }
314331
315332 void findNearestNeighborVisit (const PaletteKdTreeNode<0 > &node, const RGB &rgb,
316333 uint32_t &bestDiff, uint8_t &best) const
317334 {
318- const uint8_t *it = values_.data () + node.valuesBegin ;
319- const uint8_t *const end = values_.data () + node.valuesEndInclusive ;
335+ const std::pair<RGB, uint8_t > *it = values_.data () + node.valuesBegin ;
336+ const std::pair<RGB, uint8_t > *const end = values_.data () + node.valuesEndInclusive ;
320337 while (it <= end) {
321- const uint8_t paletteIndex = *it++;
322- const uint32_t diff = GetColorDistance (palette_[paletteIndex] , rgb);
338+ const auto &[paletteColor, paletteIndex] = *it++;
339+ const uint32_t diff = GetColorDistance (paletteColor , rgb);
323340 if (diff < bestDiff) {
324- best = paletteIndex ;
341+ best = static_cast < uint8_t >(it - values_. data () - 1 ) ;
325342 bestDiff = diff;
326343 }
327344 }
328345 }
329346
330- const SDL_Color *palette_;
331347 PaletteKdTreeNode<PaletteKdTreeDepth> tree_;
332- std::array<uint8_t , 256 > values_;
348+ std::array<std::pair<RGB, uint8_t > , 256 > values_;
333349};
334350
335351} // namespace devilution
0 commit comments