Worldstone
 All Classes Files Functions Variables Enumerations Enumerator Macros Pages
Palette.cpp
1 //
2 // Created by Lectem on 06/11/2016.
3 //
4 
5 #include "Palette.h"
6 #include <Platform.h>
7 #include <stdio.h>
8 #include <FileStream.h>
9 #include <algorithm>
10 #include <cassert>
11 #include <cmath>
12 #include <limits>
13 #include <string.h>
14 
16 WS_PRAGMA_DIAGNOSTIC_IGNORED_GNU("-Wmissing-field-initializers")
17 
18 namespace WorldStone
19 {
20 
21 bool Palette::decode(const char* filename)
22 {
23  FileStream str{filename};
24  return decode(&str);
25 }
26 
27 bool Palette::decode(IStream* file)
28 {
29  if (file && file->good()) {
30  for (size_t i = 0; i < colorCount; i++)
31  {
32  // order is BGR, not RGB
33  colors[i].b = static_cast<uint8_t>(file->getc());
34  colors[i].g = static_cast<uint8_t>(file->getc());
35  colors[i].r = static_cast<uint8_t>(file->getc());
36  colors[i]._padding = 0;
37  }
38  return file->good();
39  }
40  return false;
41 }
42 
43 uint8_t Palette::GetClosestColorIndex(Palette::Color color)
44 {
45  uint32_t closestColorDistance = std::numeric_limits<uint32_t>::max();
46  size_t closestColorIndex = 0;
47  size_t currentIndex = 0;
48  for (Color palColor : colors)
49  {
50  const int diffRed = palColor.r - color.r;
51  const int diffGreen = palColor.g - color.g;
52  const int diffBlue = palColor.b - color.b;
53  const uint32_t distance =
54  uint32_t(diffRed * diffRed + diffGreen * diffGreen + diffBlue * diffBlue);
55  if (distance < closestColorDistance) {
56  closestColorDistance = distance;
57  closestColorIndex = currentIndex;
58  }
59  currentIndex++;
60  }
61  assert(closestColorIndex < 256);
62  return uint8_t(closestColorIndex);
63 }
64 
65 namespace
66 {
67 
68 struct ColorHSL
69 {
70  double hue;
71  double sat;
72  double lum;
73 };
74 
75 ColorHSL ConvertRGBtoHSL(const Palette::Color& rgb)
76 {
77  ColorHSL hsl;
78  const double red = rgb.r / 255.0;
79  const double green = rgb.g / 255.0;
80  const double blue = rgb.b / 255.0;
81  const double minRGB = std::min<double>({red, green, blue});
82  const double maxRGB = std::max({red, green, blue});
83  const double minMaxSum = minRGB + maxRGB;
84 
85  hsl.lum = minMaxSum / 2.0;
86  assert(hsl.lum >= 0.0 && hsl.lum <= 1.0);
87 
88  if (minRGB == maxRGB) {
89  hsl.hue = 0.0;
90  hsl.sat = 0.0;
91 
92  return hsl;
93  }
94  else
95  {
96 
97  const double deltaMinMax = maxRGB - minRGB;
98  if (hsl.lum > 0.5) {
99  hsl.sat = deltaMinMax / (2.0 - minMaxSum);
100  }
101  else
102  {
103  hsl.sat = deltaMinMax / minMaxSum;
104  }
105  assert(hsl.sat >= 0.0 && hsl.sat <= 1.0);
106 
107  if (maxRGB == red) {
108  hsl.hue = (green - blue) / deltaMinMax;
109  }
110  else if (maxRGB == green)
111  {
112  hsl.hue = (blue - red) / deltaMinMax + 2.0;
113  }
114  else // if (maxRGB == blue)
115  {
116  hsl.hue = (red - green) / deltaMinMax + 4.0;
117  }
118  hsl.hue *= 60.0; // Put the hue in degrees
119  if (hsl.hue < 0.0) hsl.hue += 360.0;
120  assert(hsl.hue >= 0 && hsl.hue < 360.0);
121  return hsl;
122  }
123 }
124 
125 double hue2rgb(double p, double q, double t)
126 {
127  if (t < 0.0) t += 360.0;
128  if (t > 360.0) t -= 360.0;
129  if (t < 60.0) return (q - p) * t / 60.0 + p;
130  if (t < 180.0) return q;
131  if (t < 240.0) return (q - p) * (240.0 - t) / 60.0 + p;
132  return p;
133 }
134 
135 Palette::Color ConvertHSLtoRGB(const ColorHSL& hsl)
136 {
137  Palette::Color rgbColor;
138  if (hsl.sat == 0.0) {
139  rgbColor.r = rgbColor.g = rgbColor.b = uint8_t(hsl.lum * 255.0);
140  return rgbColor;
141  }
142  else
143  {
144  double q;
145  if (hsl.lum > 0.5)
146  q = hsl.lum + hsl.sat - hsl.lum * hsl.sat;
147  else
148  q = (hsl.sat + 1.0) * hsl.lum;
149  const double p = 2.0 * hsl.lum - q;
150 
151  rgbColor.r = uint8_t(hue2rgb(p, q, hsl.hue + 120.0) * 255.0);
152  rgbColor.g = uint8_t(hue2rgb(p, q, hsl.hue) * 255.0);
153  rgbColor.b = uint8_t(hue2rgb(p, q, hsl.hue - 120.0) * 255.0);
154  return rgbColor;
155  }
156 }
157 int GetAlphaBlendRatioFromLevel(int alphaBlendLevel)
158 {
159  switch (alphaBlendLevel)
160  {
161  case 0: return 191;
162  case 1: return 127;
163  case 2: return 63;
164  default: assert(false); return 0;
165  }
166 }
167 
168 void PL2CreateLightLevelVariations(PL2& pl2)
169 {
170 
171  for (size_t variationIndex = 0; variationIndex < 32; variationIndex++)
172  {
173  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
174  {
175  const Palette::Color baseColor = pl2.basePalette.colors[colorIndex];
176  const Palette::Color lightColor{
177  uint8_t(((variationIndex + 1) * baseColor.r) >> 5),
178  uint8_t(((variationIndex + 1) * baseColor.g) >> 5),
179  uint8_t(((variationIndex + 1) * baseColor.b) >> 5),
180  };
181  pl2.lightLevelVariations[variationIndex].indices[colorIndex] =
182  pl2.basePalette.GetClosestColorIndex(lightColor);
183  }
184  }
185 }
186 
187 void PL2CreateInvColorVariations(PL2& pl2)
188 {
189 
190  for (size_t variationIndex = 0; variationIndex < Palette::colorCount; variationIndex++)
191  {
192  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
193  {
194  const Palette::Color baseColor = pl2.basePalette.colors[colorIndex];
195  const Palette::Color lightColor{
196  uint8_t((((variationIndex + 1) * (255u - baseColor.r)) >> 4) + baseColor.r),
197  uint8_t((((variationIndex + 1) * (255u - baseColor.g)) >> 4) + baseColor.g),
198  uint8_t((((variationIndex + 1) * (255u - baseColor.b)) >> 4) + baseColor.b),
199  };
200  pl2.invColorVariations[variationIndex].indices[colorIndex] =
201  pl2.basePalette.GetClosestColorIndex(lightColor);
202  }
203  }
204 }
205 
206 void PL2CreateSelectedUnitShift(PL2& pl2, ColorHSL hslColors[Palette::colorCount])
207 {
208  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
209  {
210 
211  ColorHSL tmpColorHSL = hslColors[colorIndex];
212  if (tmpColorHSL.lum != 0.0) {
213  tmpColorHSL.lum = std::min(tmpColorHSL.lum + 0.2, 1.0);
214  }
215 
216  pl2.selectedUnitShift.indices[colorIndex] =
217  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
218  }
219 }
220 
221 void PL2CreateAlphaBlend(PL2& pl2)
222 {
223  for (int alphaBlendLevel = 0; alphaBlendLevel < 3; alphaBlendLevel++)
224  {
225  const int alphaBlendRatio = GetAlphaBlendRatioFromLevel(alphaBlendLevel);
226  for (size_t dstColorIndex = 0; dstColorIndex < Palette::colorCount; ++dstColorIndex)
227  {
228  const Palette::Color dstColor = pl2.basePalette.colors[dstColorIndex];
229  for (size_t srcColorIndex = 0; srcColorIndex < Palette::colorCount; ++srcColorIndex)
230  {
231  const Palette::Color srcColor = pl2.basePalette.colors[srcColorIndex];
232  const int invBlendRatio = 255 - alphaBlendRatio;
233  const Palette::Color blendOuput{
234  uint8_t((invBlendRatio * srcColor.r + alphaBlendRatio * dstColor.r) / 0xFF),
235  uint8_t((invBlendRatio * srcColor.g + alphaBlendRatio * dstColor.g) / 0xFF),
236  uint8_t((invBlendRatio * srcColor.b + alphaBlendRatio * dstColor.b) / 0xFF),
237  };
238  pl2.alphaBlend[alphaBlendLevel][srcColorIndex].indices[dstColorIndex] =
239  pl2.basePalette.GetClosestColorIndex(blendOuput);
240  }
241  }
242  }
243 }
244 
245 void PL2CreateAdditiveBlend(PL2& pl2)
246 {
247  for (size_t dstColorIndex = 0; dstColorIndex < Palette::colorCount; ++dstColorIndex)
248  {
249  const Palette::Color dstColor = pl2.basePalette.colors[dstColorIndex];
250  for (size_t srcColorIndex = 0; srcColorIndex < Palette::colorCount; ++srcColorIndex)
251  {
252  const Palette::Color srcColor = pl2.basePalette.colors[srcColorIndex];
253  const Palette::Color blendOuput{
254  uint8_t(std::min(srcColor.r + dstColor.r, 0xFF)),
255  uint8_t(std::min(srcColor.g + dstColor.g, 0xFF)),
256  uint8_t(std::min(srcColor.b + dstColor.b, 0xFF)),
257  };
258  pl2.additiveBlend[srcColorIndex].indices[dstColorIndex] =
259  pl2.basePalette.GetClosestColorIndex(blendOuput);
260  }
261  }
262 }
263 
264 void PL2CreateMultiplicativeBlend(PL2& pl2)
265 {
266  for (size_t dstColorIndex = 0; dstColorIndex < Palette::colorCount; ++dstColorIndex)
267  {
268  const Palette::Color dstColor = pl2.basePalette.colors[dstColorIndex];
269  for (size_t srcColorIndex = 0; srcColorIndex < Palette::colorCount; ++srcColorIndex)
270  {
271  const Palette::Color srcColor = pl2.basePalette.colors[srcColorIndex];
272  const Palette::Color blendOuput{
273  uint8_t((srcColor.r * dstColor.r) / 0xFF),
274  uint8_t((srcColor.g * dstColor.g) / 0xFF),
275  uint8_t((srcColor.b * dstColor.b) / 0xFF),
276  };
277  pl2.multiplicativeBlend[srcColorIndex].indices[dstColorIndex] =
278  pl2.basePalette.GetClosestColorIndex(blendOuput);
279  }
280  }
281 }
282 
283 void PL2CreateColorshifts(PL2& pl2, ColorHSL hslColors[Palette::colorCount])
284 {
285  for (int hueShiftIndex = 0; hueShiftIndex < 24; ++hueShiftIndex)
286  {
287  // Create a palette with hue variations
288  // 24 hue variations of 15degrees
289  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
290  {
291  ColorHSL tmpColorHSL = hslColors[colorIndex];
292  tmpColorHSL.hue += (double)hueShiftIndex * 15.0;
293  if (tmpColorHSL.hue > 360.0) // Fix the hue range if needed
294  tmpColorHSL.hue -= 360.0;
295 
296  pl2.hueVariations[hueShiftIndex].indices[colorIndex] =
297  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
298  }
299  }
300  for (int hueShiftIndex = 0; hueShiftIndex < 24; ++hueShiftIndex)
301  {
302  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
303  {
304 
305  ColorHSL tmpColorHSL = hslColors[colorIndex];
306  tmpColorHSL.hue += (double)hueShiftIndex * 15.0;
307  if (tmpColorHSL.hue > 360.0) tmpColorHSL.hue -= 360.0;
308  tmpColorHSL.sat = 0.5;
309 #ifdef USE_FIXED_VERSION
310  tmpColorHSL.lum -= 0.1;
311 #else
312  tmpColorHSL.lum -= double(0.1f); // The game actually uses the float constant which can
313  // give slightly off results
314 #endif
315  if (tmpColorHSL.lum < 0.0) tmpColorHSL.lum = 0.0;
316 
317  pl2.hueVariations[24 + hueShiftIndex].indices[colorIndex] =
318  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
319  }
320  }
321  for (int hueShiftIndex = 0; hueShiftIndex < 24; ++hueShiftIndex)
322  {
323  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
324  {
325 
326  ColorHSL tmpColorHSL = hslColors[colorIndex];
327 
328  tmpColorHSL.hue += (double)hueShiftIndex * 15.0;
329  if (tmpColorHSL.hue > 360.0) tmpColorHSL.hue -= 360.0;
330 
331  tmpColorHSL.sat = 0.5;
332 
333 #ifdef USE_FIXED_VERSION
334  tmpColorHSL.lum += 0.2;
335 #else
336  tmpColorHSL.lum += double(0.2f); // The game actually uses the float constant which can
337  // give slightly off results
338 #endif
339  if (tmpColorHSL.lum > 1.0) {
340  tmpColorHSL.lum = 1.0;
341  }
342 
343  pl2.hueVariations[48 + hueShiftIndex].indices[colorIndex] =
344  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
345  }
346  }
347 
348  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
349  {
350  ColorHSL tmpColorHSL = hslColors[colorIndex];
351  tmpColorHSL.sat = 0;
352  tmpColorHSL.lum = tmpColorHSL.lum / 2.0;
353  pl2.hueVariations[72].indices[colorIndex] =
354  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
355  }
356 
357  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
358  {
359  ColorHSL tmpColorHSL = hslColors[colorIndex];
360  tmpColorHSL.sat = 0;
361 
362 #ifdef USE_FIXED_VERSION
363  tmpColorHSL.lum += 0.2;
364  tmpColorHSL.lum /= 1.2;
365 #else
366  // The game actually uses the float constant which can give slightly off results
367  tmpColorHSL.lum += double(0.2f);
368  tmpColorHSL.lum /= double(1.2f);
369 #endif
370  pl2.hueVariations[73].indices[colorIndex] =
371  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
372  }
373  // See previous comment about reusing the previous colors
374  for (int hueShiftIndex = 0; hueShiftIndex < 24; ++hueShiftIndex)
375  {
376  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
377  {
378  ColorHSL tmpColorHSL = hslColors[colorIndex];
379  // Only shift non red/orange (blood/fire?) colors
380  if (tmpColorHSL.hue > 45.0 && tmpColorHSL.hue < 315.0) {
381 
382  tmpColorHSL.hue += (double)hueShiftIndex * 15.0;
383  if (tmpColorHSL.hue > 360.0) tmpColorHSL.hue -= 360.0;
384 
385  pl2.hueVariations[74 + hueShiftIndex].indices[colorIndex] =
386  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
387  }
388  else
389  {
390 #ifdef USE_FIXED_VERSION
391  pl2.hueVariations[74 + hueShiftIndex].indices[colorIndex] = colorIndex;
392 #else
393  pl2.hueVariations[74 + hueShiftIndex].indices[colorIndex] =
394  pl2.hueVariations[73 + hueShiftIndex].indices[colorIndex];
395 #endif
396  }
397  }
398  }
399 
400  for (int hueShiftIndex = 0; hueShiftIndex < 12; ++hueShiftIndex)
401  {
402  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
403  {
404 
405  ColorHSL tmpColorHSL = hslColors[colorIndex];
406  tmpColorHSL.hue = (double)hueShiftIndex * 30.0;
407  tmpColorHSL.sat = 1.0;
408  pl2.hueVariations[99 + hueShiftIndex].indices[colorIndex] =
409  pl2.basePalette.GetClosestColorIndex(ConvertHSLtoRGB(tmpColorHSL));
410  }
411  }
412 
413  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
414  {
415  Palette::Color color = pl2.basePalette.colors[colorIndex];
416  const double colorMagnitudeDouble =
417  std::sqrt((double)(color.r * color.r + color.g * color.g + color.b * color.b));
418 // Note : This does not make sense to not clamp the value, as the integer will wrap.
419 // However, this is what is done for the original game, so keep it here for reference
420 #ifdef USE_FIXED_VERSION
421  // The one that clamps the value
422  const uint8_t colorMagnitude = uint8_t(std::min(int(colorMagnitudeDouble), 255));
423 #else
424  // The one from the game (d2cmp.dll), incorrect
425  const uint8_t colorMagnitude = uint8_t(colorMagnitudeDouble);
426 #endif
427 
428  pl2.redTones.indices[colorIndex] =
429  pl2.basePalette.GetClosestColorIndex({colorMagnitude, 0, 0});
430  pl2.greenTones.indices[colorIndex] =
431  pl2.basePalette.GetClosestColorIndex({0, colorMagnitude, 0});
432  pl2.blueTones.indices[colorIndex] =
433  pl2.basePalette.GetClosestColorIndex({0, 0, colorMagnitude});
434  }
435 }
436 
437 void PL2CreateMaxComponentBlend(PL2& pl2)
438 {
439  for (size_t dstColorIndex = 0; dstColorIndex < Palette::colorCount; ++dstColorIndex)
440  {
441  const Palette::Color dstColor = pl2.basePalette.colors[dstColorIndex];
442  const uint8_t maxComponentDst = std::max({dstColor.r, dstColor.g, dstColor.b});
443  for (size_t srcColorIndex = 0; srcColorIndex < Palette::colorCount; ++srcColorIndex)
444  {
445  const uint8_t invMaxComponentDst = 0xFF - maxComponentDst;
446  const Palette::Color srcColor = pl2.basePalette.colors[srcColorIndex];
447  const Palette::Color blendOuput{
448  uint8_t((invMaxComponentDst * srcColor.r + maxComponentDst * dstColor.r) / 0xFF),
449  uint8_t((invMaxComponentDst * srcColor.g + maxComponentDst * dstColor.g) / 0xFF),
450  uint8_t((invMaxComponentDst * srcColor.b + maxComponentDst * dstColor.b) / 0xFF),
451  };
452  pl2.maxComponentBlend[srcColorIndex].indices[dstColorIndex] =
453  pl2.basePalette.GetClosestColorIndex(blendOuput);
454  }
455  }
456 }
457 
458 void PL2CreateDarkenedUnitShift(PL2& pl2)
459 {
460  for (size_t colorIndex = 0; colorIndex < Palette::colorCount; ++colorIndex)
461  {
462  Palette::Color tmpColor = pl2.basePalette.colors[colorIndex];
463  tmpColor.r -= tmpColor.r / 3;
464  tmpColor.g -= tmpColor.g / 3;
465  tmpColor.b -= tmpColor.b / 3;
466 
467  pl2.darkenedColorShift.indices[colorIndex] = pl2.basePalette.GetClosestColorIndex(tmpColor);
468  }
469 }
470 
471 // clang-format off
472 static const Palette::Color24Bits defaultTextColors[13] = {
473  {0xFF, 0xFF, 0xFF},
474  {0xFF, 0x4D, 0x4D},
475  {0x00, 0xFF, 0x00},
476  {0x69, 0x69, 0xFF},
477  {0xC7, 0xB3, 0x77},
478  {0x69, 0x69, 0x69},
479  {0x00, 0x00, 0x00},
480  {0xD0, 0xC2, 0x7D},
481  {0xFF, 0xA8, 0x00},
482  {0xFF, 0xFF, 0x64},
483  {0x00, 0x80, 0x00},
484  {0xAE, 0x00, 0xFF},
485  {0x00, 0xC8, 0x00},
486 };
487 // clang-format on
488 
489 void PL2CreateTextColorshifts(PL2& pl2)
490 {
491  static_assert(sizeof(defaultTextColors) == 13 * 3, "There must be 13 default text colors.");
492  memcpy(pl2.textColors, defaultTextColors, sizeof(defaultTextColors));
493  // It seems the game sets the first color as black. Again, weird, but this is what is done in
494  // d2cmp.dll and seen in the game.
495  pl2.textColorShifts[0].indices.fill(0u);
496  for (size_t textColorIndex = 1; textColorIndex < 13; textColorIndex++)
497  {
498  const Palette::Color24Bits textColor = pl2.textColors[textColorIndex];
499  for (size_t colorIndex = 0; colorIndex < 256; ++colorIndex)
500  {
501  const Palette::Color baseColor = pl2.basePalette.colors[colorIndex];
502  // Note : We multiply the text color by the red component, I find this is a bit weird
503  // but will keep it this way for reference, this is how the game (d2cmp.dll) does it.
504  const uint8_t textColorIntensity = baseColor.r;
505  const Palette::Color newColor{
506  uint8_t((textColor.r * textColorIntensity) / 0xFF),
507  uint8_t((textColor.g * textColorIntensity) / 0xFF),
508  uint8_t((textColor.b * textColorIntensity) / 0xFF),
509  };
510  pl2.textColorShifts[textColorIndex].indices[colorIndex] =
511  pl2.basePalette.GetClosestColorIndex(newColor);
512  }
513  }
514 }
515 } // anonymous namespace
516 
517 std::unique_ptr<PL2> PL2::CreateFromPalette(const Palette& palette)
518 {
519  // Note : make_unique means the palshifts will be initialized to 0
520  auto pl2Ptr = std::make_unique<PL2>();
521 
522  PL2& pl2 = *pl2Ptr;
523  pl2.basePalette = palette;
524  ColorHSL hslColors[Palette::colorCount];
525 
526  for (size_t i = 0; i < Palette::colorCount; i++)
527  {
528  hslColors[i] = ConvertRGBtoHSL(palette.colors[i]);
529  }
530 
531  PL2CreateLightLevelVariations(pl2);
532  PL2CreateInvColorVariations(pl2);
533  PL2CreateSelectedUnitShift(pl2, hslColors);
534  PL2CreateAlphaBlend(pl2);
535  PL2CreateAdditiveBlend(pl2);
536  PL2CreateMultiplicativeBlend(pl2);
537  PL2CreateColorshifts(pl2, hslColors);
538  PL2CreateMaxComponentBlend(pl2);
539  PL2CreateDarkenedUnitShift(pl2);
540  PL2CreateTextColorshifts(pl2);
541  return pl2Ptr;
542 }
543 
544 std::unique_ptr<PL2> PL2::ReadFromStream(IStream* stream)
545 {
546  static_assert(sizeof(PL2) == 443175, "PL2 struct does not match the size of the files");
547  static_assert(std::is_trivially_copyable<PL2>::value, "PL2 is not trivially copyable");
548  if (stream && stream->good()) {
549  auto pl2 = std::make_unique<PL2>();
550  if (stream->read(pl2.get(), sizeof(PL2)) == sizeof(PL2)) return pl2;
551  }
552  return nullptr;
553 }
554 
555 } // namespace WorldStone
#define WS_PRAGMA_DIAGNOSTIC_IGNORED_GNU(_x)
Silence GCC/clang-specific warnings.
Definition: Platform.h:95
#define WS_PRAGMA_DIAGNOSTIC_IGNORED_CLANG(_x)
Silence clang-specific warnings.
Definition: Platform.h:97
Platform specific tools and macros.