File Coverage

/usr/local/lib/perl5/site_perl/5.42.0/x86_64-linux/Colouring/In/XS/include/colouring.h
Criterion Covered Total %
statement 132 220 60.0
branch 52 122 42.6
condition n/a
subroutine n/a
pod n/a
total 184 342 53.8


line stmt bran cond sub pod time code
1             /*
2             * colouring.h - Pure C colour math library (header-only, C89 compatible)
3             *
4             * No Perl/XS dependencies. All functions are static.
5             *
6             * Usage from XS:
7             * #define COLOURING_FATAL(msg) croak("%s", (msg))
8             * #include "colouring.h"
9             *
10             * Usage from plain C:
11             * #include "colouring.h"
12             * // COLOURING_FATAL defaults to fprintf+abort
13             */
14              
15             #ifndef COLOURING_H
16             #define COLOURING_H
17              
18             #include
19             #include
20             #include
21             #include
22              
23             /* ── Error handling ────────────────────────────────────────────── */
24              
25             #ifndef COLOURING_FATAL
26             #define COLOURING_FATAL(msg) do { fprintf(stderr, "colouring: %s\n", (msg)); abort(); } while(0)
27             #endif
28              
29             /* ── Structs ───────────────────────────────────────────────────── */
30              
31             typedef struct {
32             double r; /* 0-255 */
33             double g;
34             double b;
35             double a; /* 0.0-1.0 */
36             } colouring_rgba_t;
37              
38             typedef struct {
39             int h; /* 0-360 */
40             double s; /* 0.0-1.0 */
41             double l; /* 0.0-1.0 */
42             double a; /* 0.0-1.0 */
43             } colouring_hsl_t;
44              
45             typedef struct {
46             double h; /* 0-360 */
47             double s; /* 0.0-1.0 */
48             double v; /* 0.0-1.0 */
49             } colouring_hsv_t;
50              
51             /* ── Scalar utilities ──────────────────────────────────────────── */
52              
53 257           static double colouring_min(double a, double b) {
54 257 100         return a < b ? a : b;
55             }
56              
57 257           static double colouring_max(double a, double b) {
58 257 100         return a > b ? a : b;
59             }
60              
61 169           static double colouring_clamp(double val, double upper) {
62 169           return colouring_min(colouring_max(val, 0), upper);
63             }
64              
65 0           static double colouring_round(double val, int dp) {
66 0           double factor = pow(10.0, dp);
67 0           return floor(val * factor + 0.5) / factor;
68             }
69              
70 40           static double colouring_depercent(const char *str) {
71 40           return atof(str) / 100.0;
72             }
73              
74             /* Format 0.0-1.0 as "NN%". buf must be >= 8 bytes. */
75 0           static void colouring_percent_buf(double num, char *buf, size_t bufsz) {
76 0           snprintf(buf, bufsz, "%.0f%%", num * 100.0);
77 0           }
78              
79             /* ── Hex parsing ───────────────────────────────────────────────── */
80              
81 180           static int colouring_hex2int(const char *hex) {
82 180           int val = 0;
83 540 100         while (*hex) {
84 360           int byte = *hex++;
85 360 50         if (byte >= '0' && byte <= '9') byte = byte - '0';
    100          
86 120 50         else if (byte >= 'a' && byte <= 'f') byte = byte - 'a' + 10;
    50          
87 0 0         else if (byte >= 'A' && byte <= 'F') byte = byte - 'A' + 10;
    0          
88 0           else { COLOURING_FATAL("Invalid hex character"); return 0; }
89 360           val = (val << 4) | (byte & 0xF);
90             }
91 180           return val;
92             }
93              
94 60           static colouring_rgba_t colouring_hex2rgb(const char *hex) {
95             colouring_rgba_t c;
96             int l;
97 60           c.a = 1.0;
98              
99             /* skip leading '#' if present */
100 60 50         if (hex[0] == '#') hex++;
101 60           l = (int)strlen(hex);
102              
103 60 100         if (l == 3) {
104             char h[3];
105 24           h[2] = '\0';
106 24           h[0] = hex[0]; h[1] = hex[0]; c.r = colouring_hex2int(h);
107 24           h[0] = hex[1]; h[1] = hex[1]; c.g = colouring_hex2int(h);
108 24           h[0] = hex[2]; h[1] = hex[2]; c.b = colouring_hex2int(h);
109 36 50         } else if (l == 6) {
110             char h[3];
111 36           h[2] = '\0';
112 36           h[0] = hex[0]; h[1] = hex[1]; c.r = colouring_hex2int(h);
113 36           h[0] = hex[2]; h[1] = hex[3]; c.g = colouring_hex2int(h);
114 36           h[0] = hex[4]; h[1] = hex[5]; c.b = colouring_hex2int(h);
115             } else {
116 0           COLOURING_FATAL("hex length must be 3 or 6");
117             c.r = c.g = c.b = 0;
118             }
119 60           return c;
120             }
121              
122             /* ── Number parsing from colour strings ────────────────────────── */
123              
124             /* Parse numeric values from strings like "rgb(255,0,128)" or "hsl(120,50%,50%)".
125             Writes up to max_out doubles, returns count written. */
126 0           static int colouring_parse_numbers(const char *str, double *out, int max_out) {
127 0           int count = 0;
128             char temp[32];
129 0           int ti = 0;
130             int i;
131 0           int len = (int)strlen(str);
132              
133 0 0         for (i = 0; i < len; i++) {
134 0 0         if ((str[i] >= '0' && str[i] <= '9') || str[i] == '.') {
    0          
    0          
135 0 0         if (ti < 31) temp[ti++] = str[i];
136 0 0         } else if (ti > 0) {
137 0           temp[ti] = '\0';
138 0 0         if (count < max_out) {
139 0           out[count++] = atof(temp);
140             }
141 0           ti = 0;
142             }
143             }
144             /* flush trailing number */
145 0 0         if (ti > 0 && count < max_out) {
    0          
146 0           temp[ti] = '\0';
147 0           out[count++] = atof(temp);
148             }
149 0           return count;
150             }
151              
152             /* ── HSL helper ────────────────────────────────────────────────── */
153              
154 132           static double colouring__hue(double h, double m1, double m2) {
155 132 100         h = h < 0 ? h + 1 : h > 1 ? h - 1 : h;
    50          
156 132 100         if (h * 6.0 < 1.0) return m1 + (m2 - m1) * h * 6.0;
157 92 100         if (h * 2.0 < 1.0) return m2;
158 48 100         if (h * 3.0 < 2.0) return m1 + (m2 - m1) * ((2.0 / 3.0) - h) * 6.0;
159 44           return m1;
160             }
161              
162             /* ── Colour space conversions ──────────────────────────────────── */
163              
164 44           static colouring_rgba_t colouring_hsl2rgb(double h, double s, double l, double a) {
165             colouring_rgba_t c;
166             double m2, m1;
167 44           int hi = (int)h;
168              
169 44           h = (hi % 360) / 360.0;
170 44 50         if (s > 1 || l > 1) {
    50          
171 0           s = s / 100.0;
172 0           l = l / 100.0;
173             }
174 44 100         m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
175 44           m1 = l * 2 - m2;
176              
177 44           c.r = (int)(colouring_clamp(colouring__hue(h + (1.0 / 3.0), m1, m2), 1) * 255);
178 44           c.g = (int)(colouring_clamp(colouring__hue(h, m1, m2), 1) * 255);
179 44           c.b = (int)(colouring_clamp(colouring__hue(h - (1.0 / 3.0), m1, m2), 1) * 255);
180 44           c.a = a;
181 44           return c;
182             }
183              
184 44           static colouring_hsl_t colouring_rgb2hsl(double r, double g, double b, double a) {
185             colouring_hsl_t hsl;
186 44           double rn = r / 255.0;
187 44           double gn = g / 255.0;
188 44           double bn = b / 255.0;
189 44           double mx = colouring_max(colouring_max(rn, gn), bn);
190 44           double mn = colouring_min(colouring_min(rn, gn), bn);
191 44           double d = mx - mn;
192 44           double h = 0;
193 44           double s = 0;
194 44           double l = (mx + mn) / 2.0;
195              
196 44 100         if (mx != mn) {
197 15 100         s = l > 0.5 ? (d / (2.0 - mx - mn)) : (d / (mx + mn));
198 15 100         if (mx == rn)
199 11 50         h = (gn - bn) / d + (gn < bn ? 6 : 0);
200 4 50         else if (mx == gn)
201 0           h = (bn - rn) / d + 2;
202             else
203 4           h = (rn - gn) / d + 4;
204 15           h = h / 6.0;
205             }
206              
207 44           hsl.h = (int)(h * 360);
208 44           hsl.s = s;
209 44           hsl.l = l;
210 44           hsl.a = a;
211 44           return hsl;
212             }
213              
214 0           static colouring_hsv_t colouring_rgb2hsv(double r, double g, double b) {
215             colouring_hsv_t hsv;
216 0           double rn = r / 255.0;
217 0           double gn = g / 255.0;
218 0           double bn = b / 255.0;
219 0           double mx = colouring_max(colouring_max(rn, gn), bn);
220 0           double mn = colouring_min(colouring_min(rn, gn), bn);
221 0           double d = mx - mn;
222 0           double h = 0;
223              
224 0 0         hsv.s = (mx == 0) ? 0 : d / mx;
225 0           hsv.v = mx;
226              
227 0 0         if (mx != mn) {
228 0 0         if (mx == rn)
229 0 0         h = (gn - bn) / d + (gn < bn ? 6 : 0);
230 0 0         else if (mx == gn)
231 0           h = (bn - rn) / d + 2;
232             else
233 0           h = (rn - gn) / d + 4;
234 0           h = h / 6.0;
235             }
236              
237 0           hsv.h = h * 360.0;
238 0           return hsv;
239             }
240              
241             /* ── Colour manipulation ───────────────────────────────────────── */
242              
243 21           static colouring_rgba_t colouring_lighten(double r, double g, double b, double a,
244             double amount, int relative) {
245 21           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
246 21 50         if (relative) {
247             /* Original C code: double l = hsl.l || 1.; -- C logical OR
248             always yields 1 when hsl.l is nonzero, 1 when zero.
249             So relative lighten behaves same as absolute. */
250 0           hsl.l = hsl.l + colouring_clamp(amount, 1);
251             } else {
252 21           hsl.l = hsl.l + colouring_clamp(amount, 1);
253             }
254 21           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
255             }
256              
257 12           static colouring_rgba_t colouring_darken(double r, double g, double b, double a,
258             double amount, int relative) {
259 12           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
260 12 50         if (relative) {
261 0           hsl.l = hsl.l - colouring_clamp(hsl.l * amount, 1);
262             } else {
263 12           hsl.l = hsl.l - colouring_clamp(amount, 1);
264             }
265 12           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
266             }
267              
268 3           static colouring_rgba_t colouring_fade(double r, double g, double b, double a,
269             double amount) {
270 3           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
271 3           hsl.a = amount;
272 3           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
273             }
274              
275 0           static colouring_rgba_t colouring_fadeout(double r, double g, double b, double a,
276             double amount, int relative) {
277 0           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
278 0 0         hsl.a -= colouring_clamp(relative ? hsl.a * amount : amount, 1);
279 0           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
280             }
281              
282 0           static colouring_rgba_t colouring_fadein(double r, double g, double b, double a,
283             double amount, int relative) {
284 0           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
285 0 0         hsl.a += colouring_clamp(relative ? hsl.a * amount : amount, 1);
286 0           hsl.a = colouring_clamp(hsl.a, 1);
287 0           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
288             }
289              
290 2           static colouring_rgba_t colouring_saturate(double r, double g, double b, double a,
291             double amount, int relative) {
292 2           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
293 2 50         hsl.s += colouring_clamp(relative ? hsl.s * amount : amount, 1);
294 2           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
295             }
296              
297 2           static colouring_rgba_t colouring_desaturate(double r, double g, double b, double a,
298             double amount, int relative) {
299 2           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
300 2 50         hsl.s -= colouring_clamp(relative ? hsl.s * amount : amount, 1);
301 2           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
302             }
303              
304 4           static colouring_rgba_t colouring_greyscale(double r, double g, double b, double a) {
305 4           colouring_hsl_t hsl = colouring_rgb2hsl(r, g, b, a);
306 4           hsl.s -= 1.0;
307 4           return colouring_hsl2rgb(hsl.h, hsl.s, hsl.l, hsl.a);
308             }
309              
310 11           static colouring_rgba_t colouring_mix(colouring_rgba_t c1, colouring_rgba_t c2,
311             int weight) {
312             colouring_rgba_t out;
313 11           double w = weight / 100.0;
314 11           double a = c1.a - c2.a;
315             double w1, w2;
316              
317 11           w = (w * 2) - 1;
318 11 50         w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
319 11           w2 = 1 - w1;
320              
321 11           out.r = (c1.r * w1) + (c2.r * w2);
322 11           out.g = (c1.g * w1) + (c2.g * w2);
323 11           out.b = (c1.b * w1) + (c2.b * w2);
324 11           out.a = (c1.a * w) + (c2.a * (1 - w));
325 11           return out;
326             }
327              
328 3           static colouring_rgba_t colouring_tint(colouring_rgba_t c, int weight) {
329             colouring_rgba_t white;
330 3           white.r = 255; white.g = 255; white.b = 255; white.a = 1.0;
331 3           return colouring_mix(white, c, weight);
332             }
333              
334 3           static colouring_rgba_t colouring_shade(colouring_rgba_t c, int weight) {
335             colouring_rgba_t black;
336 3           black.r = 0; black.g = 0; black.b = 0; black.a = 1.0;
337 3           return colouring_mix(black, c, weight);
338             }
339              
340             /* ── Formatting ────────────────────────────────────────────────── */
341              
342 53           static void colouring_fmt_hex(colouring_rgba_t c, char *buf, size_t bufsz,
343             int force_long) {
344 53           int r = (int)c.r, g = (int)c.g, b = (int)c.b;
345 53           snprintf(buf, bufsz, "#%02x%02x%02x", r, g, b);
346 53 50         if (!force_long) {
347             /* shorten "#aabbcc" to "#abc" if possible */
348 53 100         if (buf[1] == buf[2] && buf[3] == buf[4] && buf[5] == buf[6]) {
    100          
    50          
349 17           buf[2] = buf[3];
350 17           buf[3] = buf[5];
351 17           buf[4] = '\0';
352             }
353             }
354 53           }
355              
356 0           static void colouring_fmt_rgb(colouring_rgba_t c, char *buf, size_t bufsz) {
357 0           snprintf(buf, bufsz, "rgb(%d,%d,%d)", (int)c.r, (int)c.g, (int)c.b);
358 0           }
359              
360 2           static void colouring_fmt_rgba(colouring_rgba_t c, char *buf, size_t bufsz) {
361 2           snprintf(buf, bufsz, "rgba(%d,%d,%d,%.2g)", (int)c.r, (int)c.g, (int)c.b, c.a);
362 2           }
363              
364 0           static void colouring_fmt_hsl(colouring_hsl_t hsl, char *buf, size_t bufsz) {
365             char ps[8], pl[8];
366 0           colouring_percent_buf(hsl.s, ps, sizeof(ps));
367 0           colouring_percent_buf(hsl.l, pl, sizeof(pl));
368 0           snprintf(buf, bufsz, "hsl(%d,%s,%s)", hsl.h, ps, pl);
369 0           }
370              
371 0           static void colouring_fmt_hsv(colouring_hsv_t hsv, char *buf, size_t bufsz) {
372             char ps[8], pv[8];
373 0           colouring_percent_buf(hsv.s, ps, sizeof(ps));
374 0           colouring_percent_buf(hsv.v, pv, sizeof(pv));
375 0           snprintf(buf, bufsz, "hsv(%.0f,%s,%s)", hsv.h, ps, pv);
376 0           }
377              
378 0           static void colouring_fmt_term(colouring_rgba_t c, char *buf, size_t bufsz) {
379 0           snprintf(buf, bufsz, "r%dg%db%d", (int)c.r, (int)c.g, (int)c.b);
380 0           }
381              
382 0           static void colouring_fmt_on_term(colouring_rgba_t c, char *buf, size_t bufsz) {
383 0           snprintf(buf, bufsz, "on_r%dg%db%d", (int)c.r, (int)c.g, (int)c.b);
384 0           }
385              
386             /* ── Colour string conversion (dispatch) ──────────────────────── */
387              
388             /* Parse any supported colour string into RGBA.
389             Returns 1 on success, 0 on failure. */
390 61           static int colouring_parse(const char *str, colouring_rgba_t *out) {
391 61 100         if (str[0] == '#') {
392 60           *out = colouring_hex2rgb(str);
393 60           return 1;
394 1 50         } else if (str[0] == 'r' && str[1] == 'g' && str[2] == 'b') {
    50          
    0          
395             double nums[4];
396 0           int n = colouring_parse_numbers(str, nums, 4);
397 0 0         if (n < 3) return 0;
398 0           out->r = nums[0];
399 0           out->g = nums[1];
400 0           out->b = nums[2];
401 0 0         out->a = n >= 4 ? nums[3] : 1.0;
402 0           return 1;
403 1 50         } else if (str[0] == 'h' && str[1] == 's' && str[2] == 'l') {
    0          
    0          
404             double nums[4];
405 0           int n = colouring_parse_numbers(str, nums, 4);
406 0 0         if (n < 3) return 0;
407 0 0         *out = colouring_hsl2rgb(nums[0], nums[1], nums[2],
408             n >= 4 ? nums[3] : 1.0);
409 0           return 1;
410             }
411 1           return 0;
412             }
413              
414             #endif /* COLOURING_H */