1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
6 * Copyright (c) 2005, Sven2
7 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
8 *
9 * Distributed under the terms of the ISC license; see accompanying file
10 * "COPYING" for details.
11 *
12 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
13 * See accompanying file "TRADEMARK" for details.
14 *
15 * To redistribute this file separately, substitute the full license texts
16 * for the above references.
17 */
18
19// color calculation routines
20
21#pragma once
22
23#include <algorithm>
24#include <cmath>
25#include <cstdint>
26
27#ifdef _WIN32
28#include "C4Windows.h"
29#endif
30
31// color definitions
32const int FTrans = -1, FWhite = 0, FBlack = 1, FPlayer = 2, FRed = 3;
33const int CBlack = 0, CGray1 = 1, CGray2 = 2, CGray3 = 3, CGray4 = 4, CGray5 = 5, CWhite = 6,
34 CDRed = 7, CDGreen = 8, CDBlue = 9, CRed = 10, CGreen = 11, CLBlue = 12, CYellow = 13, CBlue = 14;
35extern const uint8_t FColors[];
36
37// helper functions
38
39constexpr uint32_t RGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return (a << 24) | (r << 16) | (g << 8) | b; }
40
41#ifndef _WIN32
42
43#define GetRValue(rgb) (static_cast<unsigned char>(rgb))
44#define GetGValue(rgb) (static_cast<unsigned char>((static_cast<unsigned short>(rgb)) >> 8))
45#define GetBValue(rgb) (static_cast<unsigned char>((rgb) >> 16))
46
47constexpr uint32_t RGB(uint8_t r, uint8_t g, uint8_t b) { return r | (g << 8) | (b << 16); }
48
49#endif
50
51// Color triplets
52#define C4RGB(r, g, b) (((static_cast<uint32_t>(r) & 0xff) << 16) | ((static_cast<uint32_t>(g) & 0xff) << 8) | ((b) & 0xff))
53
54namespace
55{
56 struct C4Color
57 {
58 uint8_t r;
59 uint8_t g;
60 uint8_t b;
61 uint8_t a;
62 };
63
64 constexpr C4Color SplitRGB(uint32_t color)
65 {
66 return
67 {
68 .r: static_cast<uint8_t>(color >> 16),
69 .g: static_cast<uint8_t>(color >> 8),
70 .b: static_cast<uint8_t>(color),
71 .a: static_cast<uint8_t>(color >> 24)
72 };
73 }
74
75 template<typename Func>
76 constexpr uint32_t CombineColors(uint32_t dst, uint32_t src, Func func)
77 {
78 const auto [srcR, srcG, srcB, srcA] = SplitRGB(color: src);
79 const auto [dstR, dstG, dstB, dstA] = SplitRGB(color: dst);
80
81 return func(srcR, srcG, srcB, srcA, dstR, dstG, dstB, dstA);
82 }
83
84 template<typename RgbFunc, typename AFunc>
85 constexpr uint32_t CombineColors(uint32_t dst, uint32_t src, RgbFunc rgbFunc, AFunc aFunc)
86 {
87 const auto [srcR, srcG, srcB, srcA] = SplitRGB(color: src);
88 const auto [dstR, dstG, dstB, dstA] = SplitRGB(color: dst);
89
90 return RGBA
91 (
92 rgbFunc(srcR, dstR),
93 rgbFunc(srcG, dstG),
94 rgbFunc(srcB, dstB),
95 aFunc(srcA, dstA)
96 );
97 }
98
99 template<typename RgbFunc, typename AFunc>
100 constexpr uint32_t ModifyColor(uint32_t src, RgbFunc rgbFunc, AFunc aFunc)
101 {
102 const auto [srcR, srcG, srcB, srcA] = SplitRGB(color: src);
103
104 return RGBA
105 (
106 rgbFunc(srcR),
107 rgbFunc(srcG),
108 rgbFunc(srcB),
109 aFunc(srcA)
110 );
111 }
112
113 template<typename RgbFunc>
114 constexpr uint32_t ModifyColor(uint32_t src, RgbFunc rgbFunc)
115 {
116 return ModifyColor(src, rgbFunc, [](uint8_t src) { return src; });
117 }
118}
119
120constexpr void BltAlpha(uint32_t &dst, uint32_t src)
121{
122 if (SplitRGB(color: dst).a == 0xff)
123 {
124 dst = src;
125 return;
126 }
127
128 const auto byAlphaDst = SplitRGB(color: src).a;
129 const auto byAlphaSrc = 0xff - byAlphaDst;
130 dst = CombineColors(dst, src, rgbFunc: [byAlphaDst, byAlphaSrc](uint16_t src, uint16_t dst)
131 {
132 return static_cast<uint8_t>(std::min(a: (src * byAlphaSrc + dst * byAlphaDst) >> 8, b: 0xff));
133 },
134 aFunc: [byAlphaSrc](uint8_t src, uint8_t dst)
135 {
136 return static_cast<uint8_t>(std::max(a: dst - byAlphaSrc, b: 0));
137 });
138}
139
140constexpr void BltAlphaAdd(uint32_t &dst, uint32_t src)
141{
142 if (SplitRGB(color: dst).a == 0xff)
143 {
144 dst = src;
145 return;
146 }
147
148 const auto byAlphaSrc = 0xff - SplitRGB(color: src).a;
149 dst = CombineColors(dst, src, rgbFunc: [byAlphaSrc](uint16_t src, uint16_t dst)
150 {
151 return static_cast<uint8_t>(std::min(a: dst + ((src * byAlphaSrc) >> 8), b: 0xff));
152 },
153 aFunc: [byAlphaSrc](uint8_t src, uint8_t dst)
154 {
155 return static_cast<uint8_t>(std::max(a: dst - byAlphaSrc, b: 0));
156 });
157}
158
159constexpr void ModulateClr(uint32_t &dst, uint32_t src)
160{
161 dst = CombineColors(dst, src, rgbFunc: [](uint16_t src, uint16_t dst)
162 {
163 return static_cast<uint8_t>((src * dst) >> 8);
164 },
165 aFunc: [](uint16_t src, uint16_t dst)
166 {
167 return static_cast<uint8_t>(std::min(a: src + dst - ((src * dst) >> 8), b: 0xff));
168 });
169}
170
171constexpr void ModulateClrA(uint32_t &dst, uint32_t src)
172{
173 dst = CombineColors(dst, src, rgbFunc: [](uint16_t src, uint16_t dst)
174 {
175 return static_cast<uint8_t>((src * dst) >> 8);
176 },
177 aFunc: [](uint16_t src, uint16_t dst)
178 {
179 return static_cast<uint8_t>(std::min(a: src + dst, b: 0xff));
180 });
181}
182
183constexpr void ModulateClrMOD2(uint32_t &dst, uint32_t src)
184{
185 dst = CombineColors(dst, src, rgbFunc: [](uint16_t src, uint16_t dst)
186 {
187 return static_cast<uint8_t>(std::clamp(val: (src + dst - 0x7f) * 2, lo: 0, hi: 0xff));
188 },
189 aFunc: [](uint16_t src, uint16_t dst)
190 {
191 return static_cast<uint8_t>(std::min(a: src + dst, b: 0xff));
192 });
193}
194
195constexpr void ModulateClrMonoA(uint32_t &dst, uint8_t byMod, uint8_t byA)
196{
197 dst = ModifyColor(src: dst, rgbFunc: [byMod](uint16_t dst)
198 {
199 return static_cast<uint8_t>((dst * byMod) >> 8);
200 },
201 aFunc: [byA](uint16_t dst)
202 {
203 return static_cast<uint8_t>(std::min(a: dst + byA, b: 0xff));
204 });
205}
206
207constexpr uint32_t LightenClr(uint32_t &dst)
208{
209 return dst = ModifyColor(src: dst, rgbFunc: [](uint8_t dst)
210 {
211 const auto tmp = (dst & 0x80) | ((dst << 1) & 0xfe);
212 return (dst & 0x80) ? tmp | 0xff : tmp;
213 });
214}
215
216constexpr uint32_t LightenClrBy(uint32_t &dst, uint8_t by)
217{
218 // quite a desaturating method...
219 return dst = ModifyColor(src: dst, rgbFunc: [by](uint8_t dst)
220 {
221 return static_cast<uint8_t>(std::min(a: dst + by, b: 0xff));
222 });
223}
224
225constexpr uint32_t DarkenClrBy(uint32_t &dst, uint8_t by)
226{
227 // quite a desaturating method...
228 return dst = ModifyColor(src: dst, rgbFunc: [by](uint8_t dst)
229 {
230 return static_cast<uint8_t>(std::max(a: dst - by, b: 0));
231 });
232}
233
234[[nodiscard]] constexpr uint32_t PlrClr2TxtClr(uint32_t clr)
235{
236 // convert player color to text color, lightening up when necessary
237 const auto [r, g, b, a] = SplitRGB(color: clr);
238 const auto lgt = std::max(l: {r, g, b});
239
240 return ((lgt < 0x8f) ? LightenClrBy(dst&: clr, by: 0x8f - lgt) : clr) | 0xff000000;
241}
242
243// get modulation that is necessary to transform dwSrcClr to dwDstClr
244// does not support alpha values in dwSrcClr and dwDstClr
245[[nodiscard]] constexpr uint32_t GetClrModulation(uint32_t src, uint32_t dst, uint32_t &back)
246{
247 return CombineColors(dst, src, func: [&back](const int16_t srcR, const int16_t srcG, const int16_t srcB, const uint8_t, const int16_t dstR, const int16_t dstG, const int16_t dstB, const uint8_t)
248 {
249 const auto diffR = dstR - srcR;
250 const auto diffG = dstG - srcG;
251 const auto diffB = dstB - srcB;
252
253 // get max enlightment
254 const auto diff = static_cast<uint8_t>(std::max(l: {0, diffR, diffG, diffB}));
255
256 // is dest > src?
257 if (diff > 0)
258 {
259 // so a back mask must be used
260 auto backVal = [diff](int src, int diffComponent)
261 {
262 return static_cast<uint8_t>(src + (diffComponent * 0xff) / diff);
263 };
264 back = RGBA(r: backVal(srcR, diffR), g: backVal(dstG, diffG), b: backVal(srcB, diffB), a: 0);
265 }
266
267 auto combine = [](int16_t src, uint16_t dst)
268 {
269 return static_cast<uint8_t>(std::min(a: dst * 256 / std::max<int16_t>(a: src, b: 1), b: 0xff));
270 };
271
272 return RGBA
273 (
274 r: combine(srcR, dstR),
275 g: combine(srcG, dstG),
276 b: combine(srcB, dstB),
277 a: diff
278 );
279 });
280}
281
282inline uint32_t NormalizeColors(uint32_t &dwClr1, uint32_t &dwClr2, uint32_t &dwClr3, uint32_t &dwClr4)
283{
284 // normalize the colors to a color in the middle
285 // combine clr1 and clr2 to clr1
286 ModulateClr(dst&: dwClr1, src: dwClr2); LightenClr(dst&: dwClr1);
287 // combine clr3 and clr4 to clr3
288 ModulateClr(dst&: dwClr3, src: dwClr4); LightenClr(dst&: dwClr3);
289 // combine clr1 and clr3 to clr1
290 ModulateClr(dst&: dwClr1, src: dwClr3); LightenClr(dst&: dwClr1);
291 // set other colors, return combined color
292 return dwClr2 = dwClr3 = dwClr4 = dwClr1;
293}
294
295inline uint32_t InvertRGBAAlpha(uint32_t dwFromClr)
296{
297 return (dwFromClr & 0xffffff) | (255 - (dwFromClr >> 24)) << 24;
298}
299
300inline uint16_t ClrDw2W(uint32_t dwClr)
301{
302 return
303 static_cast<uint16_t>((dwClr & 0x000000f0) >> 4) |
304 static_cast<uint16_t>((dwClr & 0x0000f000) >> 8) |
305 static_cast<uint16_t>((dwClr & 0x00f00000) >> 12) |
306 static_cast<uint16_t>((dwClr & 0xf0000000) >> 16);
307}
308
309inline bool rgb2xyY(double r, double g, double b, double *px, double *py, double *pY) // linear rgb to CIE xyY
310{
311 double X = 0.412453 * r + 0.357580 * g + 0.180423 * b;
312 double Y = 0.212671 * r + 0.715160 * g + 0.072169 * b;
313 double Z = 0.019334 * r + 0.119193 * g + 0.950227 * b;
314 double XYZ = X + Y + Z;
315 if (!XYZ)
316 {
317 *px = *py = 0.3; // assume grey cromaticity for black
318 }
319 else
320 {
321 *px = X / XYZ; *py = Y / XYZ;
322 }
323 *pY = Y;
324 return true;
325}
326
327inline bool xy2upvp(double x, double y, double *pu, double *pv) // CIE xy to u'v'
328{
329 double n = -2.0 * x + 12.0 * y + 3.0;
330 if (!n) return false;
331 *pu = 4.0 * x / n;
332 *pv = 9.0 * y / n;
333 return true;
334}
335
336inline bool RGB2rgb(int R, int G, int B, double *pr, double *pg, double *pb, double gamma = 2.2) // monitor RGB (0 to 255) to linear rgb (0.0 to 1.0) assuming default gamma 2.2
337{
338 *pr = pow(x: static_cast<double>(R) / 255.0, y: 1.0 / gamma);
339 *pg = pow(x: static_cast<double>(G) / 255.0, y: 1.0 / gamma);
340 *pb = pow(x: static_cast<double>(B) / 255.0, y: 1.0 / gamma);
341 return true;
342}
343
344// a standard pal
345struct CStdPalette
346{
347 uint8_t Colors[3 * 256];
348 uint8_t Alpha[3 * 256];
349
350 uint32_t GetClr(uint8_t byCol)
351 {
352 return RGB(r: Colors[byCol * 3 + 2], g: Colors[byCol * 3 + 1], b: Colors[byCol * 3]) + (Alpha[byCol] << 24);
353 }
354
355 void EnforceC0Transparency()
356 {
357 Colors[0] = Colors[1] = Colors[2] = 0; Alpha[0] = 255;
358 }
359};
360
361// color mod+add infostructure
362struct CClrModAdd
363{
364 uint32_t dwModClr;
365 uint32_t dwAddClr;
366};
367
368// clrmod-add-map to cover a drawing range in which all draws shall be adjusted by the map
369class CClrModAddMap
370{
371private:
372 CClrModAdd *pMap; size_t iMapSize;
373 int iWdt, iHgt; // number of sections in the map
374 int iOffX, iOffY; // offset to add to drawing positions before applying the map
375 bool fFadeTransparent; // if set, ReduceModulation and AddModulation fade transparent instead of black
376 int iResolutionX, iResolutionY;
377
378public:
379 enum { iDefResolutionX = 64, iDefResolutionY = 64 };
380
381public:
382 CClrModAddMap() : pMap(nullptr), iMapSize(0), iWdt(0), iHgt(0), iOffX(0), iOffY(0), fFadeTransparent(false), iResolutionX(iDefResolutionX), iResolutionY(iDefResolutionY) {}
383 ~CClrModAddMap() { delete[] pMap; }
384
385 void Reset(int iResX, int iResY, int iWdtPx, int iHgtPx, int iOffX, int iOffY, uint32_t dwModClr, uint32_t dwAddClr, int x0, int y0, uint32_t dwBackClr = 0, class C4Surface *backsfc = nullptr); // reset all of map to given values; uses transparent mode and clears rect if a back color is given
386 void ReduceModulation(int cx, int cy, int iRadius1, int iRadius2); // reveal all within iRadius1; fade off until iRadius2
387 void AddModulation(int cx, int cy, int iRadius1, int iRadius2, uint8_t byTransparency); // hide all within iRadius1; fade off until iRadius2
388
389 uint32_t GetModAt(int x, int y) const;
390 int GetResolutionX() const { return iResolutionX; }
391 int GetResolutionY() const { return iResolutionY; }
392};
393
394// used to calc intermediate points of color fades
395class CColorFadeMatrix
396{
397private:
398 int ox, oy, w, h; // offset of x/y
399 struct { int c0, cx, cy, ce; } clrs[4];
400
401public:
402 CColorFadeMatrix(int iX, int iY, int iWdt, int iHgt, uint32_t dwClr1, uint32_t dwClr2, uint32_t dwClr3, uint32_t dwClr4);
403 uint32_t GetColorAt(int iX, int iY);
404};
405