Fossil SCM

fossil-scm / src / color.c
Blame History Raw 543 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to select colors based on branch and
19
** user names.
20
**
21
*/
22
#include "config.h"
23
#include <string.h>
24
#include "color.h"
25
26
/*
27
** 140 standard CSS color names and their corresponding RGB values,
28
** in alphabetical order by name so that we can do a binary search
29
** for lookup.
30
*/
31
static const struct CssColors {
32
const char *zName; /* CSS Color name, lower case */
33
unsigned int iRGB; /* Corresponding RGB value */
34
} aCssColors[] = {
35
{ "aliceblue", 0xf0f8ff },
36
{ "antiquewhite", 0xfaebd7 },
37
{ "aqua", 0x00ffff },
38
{ "aquamarine", 0x7fffd4 },
39
{ "azure", 0xf0ffff },
40
{ "beige", 0xf5f5dc },
41
{ "bisque", 0xffe4c4 },
42
{ "black", 0x000000 },
43
{ "blanchedalmond", 0xffebcd },
44
{ "blue", 0x0000ff },
45
{ "blueviolet", 0x8a2be2 },
46
{ "brown", 0xa52a2a },
47
{ "burlywood", 0xdeb887 },
48
{ "cadetblue", 0x5f9ea0 },
49
{ "chartreuse", 0x7fff00 },
50
{ "chocolate", 0xd2691e },
51
{ "coral", 0xff7f50 },
52
{ "cornflowerblue", 0x6495ed },
53
{ "cornsilk", 0xfff8dc },
54
{ "crimson", 0xdc143c },
55
{ "cyan", 0x00ffff },
56
{ "darkblue", 0x00008b },
57
{ "darkcyan", 0x008b8b },
58
{ "darkgoldenrod", 0xb8860b },
59
{ "darkgray", 0xa9a9a9 },
60
{ "darkgreen", 0x006400 },
61
{ "darkkhaki", 0xbdb76b },
62
{ "darkmagenta", 0x8b008b },
63
{ "darkolivegreen", 0x556b2f },
64
{ "darkorange", 0xff8c00 },
65
{ "darkorchid", 0x9932cc },
66
{ "darkred", 0x8b0000 },
67
{ "darksalmon", 0xe9967a },
68
{ "darkseagreen", 0x8fbc8f },
69
{ "darkslateblue", 0x483d8b },
70
{ "darkslategray", 0x2f4f4f },
71
{ "darkturquoise", 0x00ced1 },
72
{ "darkviolet", 0x9400d3 },
73
{ "deeppink", 0xff1493 },
74
{ "deepskyblue", 0x00bfff },
75
{ "dimgray", 0x696969 },
76
{ "dodgerblue", 0x1e90ff },
77
{ "firebrick", 0xb22222 },
78
{ "floralwhite", 0xfffaf0 },
79
{ "forestgreen", 0x228b22 },
80
{ "fuchsia", 0xff00ff },
81
{ "gainsboro", 0xdcdcdc },
82
{ "ghostwhite", 0xf8f8ff },
83
{ "gold", 0xffd700 },
84
{ "goldenrod", 0xdaa520 },
85
{ "gray", 0x808080 },
86
{ "green", 0x008000 },
87
{ "greenyellow", 0xadff2f },
88
{ "honeydew", 0xf0fff0 },
89
{ "hotpink", 0xff69b4 },
90
{ "indianred", 0xcd5c5c },
91
{ "indigo", 0x4b0082 },
92
{ "ivory", 0xfffff0 },
93
{ "khaki", 0xf0e68c },
94
{ "lavender", 0xe6e6fa },
95
{ "lavenderblush", 0xfff0f5 },
96
{ "lawngreen", 0x7cfc00 },
97
{ "lemonchiffon", 0xfffacd },
98
{ "lightblue", 0xadd8e6 },
99
{ "lightcoral", 0xf08080 },
100
{ "lightcyan", 0xe0ffff },
101
{ "lightgoldenrodyellow", 0xfafad2 },
102
{ "lightgrey", 0xd3d3d3 },
103
{ "lightgreen", 0x90ee90 },
104
{ "lightpink", 0xffb6c1 },
105
{ "lightsalmon", 0xffa07a },
106
{ "lightseagreen", 0x20b2aa },
107
{ "lightskyblue", 0x87cefa },
108
{ "lightslategray", 0x778899 },
109
{ "lightsteelblue", 0xb0c4de },
110
{ "lightyellow", 0xffffe0 },
111
{ "lime", 0x00ff00 },
112
{ "limegreen", 0x32cd32 },
113
{ "linen", 0xfaf0e6 },
114
{ "magenta", 0xff00ff },
115
{ "maroon", 0x800000 },
116
{ "mediumaquamarine", 0x66cdaa },
117
{ "mediumblue", 0x0000cd },
118
{ "mediumorchid", 0xba55d3 },
119
{ "mediumpurple", 0x9370d8 },
120
{ "mediumseagreen", 0x3cb371 },
121
{ "mediumslateblue", 0x7b68ee },
122
{ "mediumspringgreen", 0x00fa9a },
123
{ "mediumturquoise", 0x48d1cc },
124
{ "mediumvioletred", 0xc71585 },
125
{ "midnightblue", 0x191970 },
126
{ "mintcream", 0xf5fffa },
127
{ "mistyrose", 0xffe4e1 },
128
{ "moccasin", 0xffe4b5 },
129
{ "navajowhite", 0xffdead },
130
{ "navy", 0x000080 },
131
{ "oldlace", 0xfdf5e6 },
132
{ "olive", 0x808000 },
133
{ "olivedrab", 0x6b8e23 },
134
{ "orange", 0xffa500 },
135
{ "orangered", 0xff4500 },
136
{ "orchid", 0xda70d6 },
137
{ "palegoldenrod", 0xeee8aa },
138
{ "palegreen", 0x98fb98 },
139
{ "paleturquoise", 0xafeeee },
140
{ "palevioletred", 0xd87093 },
141
{ "papayawhip", 0xffefd5 },
142
{ "peachpuff", 0xffdab9 },
143
{ "peru", 0xcd853f },
144
{ "pink", 0xffc0cb },
145
{ "plum", 0xdda0dd },
146
{ "powderblue", 0xb0e0e6 },
147
{ "purple", 0x800080 },
148
{ "red", 0xff0000 },
149
{ "rosybrown", 0xbc8f8f },
150
{ "royalblue", 0x4169e1 },
151
{ "saddlebrown", 0x8b4513 },
152
{ "salmon", 0xfa8072 },
153
{ "sandybrown", 0xf4a460 },
154
{ "seagreen", 0x2e8b57 },
155
{ "seashell", 0xfff5ee },
156
{ "sienna", 0xa0522d },
157
{ "silver", 0xc0c0c0 },
158
{ "skyblue", 0x87ceeb },
159
{ "slateblue", 0x6a5acd },
160
{ "slategray", 0x708090 },
161
{ "snow", 0xfffafa },
162
{ "springgreen", 0x00ff7f },
163
{ "steelblue", 0x4682b4 },
164
{ "tan", 0xd2b48c },
165
{ "teal", 0x008080 },
166
{ "thistle", 0xd8bfd8 },
167
{ "tomato", 0xff6347 },
168
{ "turquoise", 0x40e0d0 },
169
{ "violet", 0xee82ee },
170
{ "wheat", 0xf5deb3 },
171
{ "white", 0xffffff },
172
{ "whitesmoke", 0xf5f5f5 },
173
{ "yellow", 0xffff00 },
174
{ "yellowgreen", 0x9acd32 },
175
};
176
177
/*
178
** Attempt to translate a CSS color name into an integer that
179
** represents the equivalent RGB value. Ignore alpha if provided.
180
** If the name cannot be translated, return -1.
181
*/
182
int color_name_to_rgb(const char *zName){
183
if( zName==0 || zName[0]==0 ) return -1;
184
if( zName[0]=='#' ){
185
int i, v = 0;
186
for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){
187
v = v*16 + fossil_hexvalue(zName[i]);
188
}
189
if( i==4 ){
190
v = fossil_hexvalue(zName[1])*0x110000 +
191
fossil_hexvalue(zName[2])*0x1100 +
192
fossil_hexvalue(zName[3])*0x11;
193
return v;
194
}
195
if( i==7 ){
196
return v;
197
}
198
return -1;
199
}else{
200
int iMin = 0;
201
int iMax = count(aCssColors)-1;
202
while( iMin<=iMax ){
203
int iMid = (iMin+iMax)/2;
204
int c = sqlite3_stricmp(aCssColors[iMid].zName, zName);
205
if( c==0 ) return aCssColors[iMid].iRGB;
206
if( c<0 ){
207
iMin = iMid+1;
208
}else{
209
iMax = iMid-1;
210
}
211
}
212
return -1;
213
}
214
}
215
216
/*
217
** SETTING: raw-bgcolor boolean default=off
218
**
219
** Fossil usually tries to adjust user-specified background colors
220
** for check-ins so that the text is readable and so that the color
221
** is not too garish. This setting disables that filter. When
222
** this setting is on, the user-selected background colors are shown
223
** exactly as requested.
224
*/
225
226
/*
227
** Shift a color provided by the user so that it is suitable
228
** for use as a background color in the current skin.
229
**
230
** The return value is a #HHHHHH color name contained in
231
** static space that is overwritten on the next call.
232
**
233
** If we cannot make sense of the background color recommendation
234
** that is the input, then return NULL.
235
**
236
** The iFgClr parameter is normally 0. But for testing purposes, set
237
** it to 1 for a black foregrounds and 2 for a white foreground.
238
*/
239
const char *reasonable_bg_color(const char *zRequested, int iFgClr){
240
int iRGB = color_name_to_rgb(zRequested);
241
int r, g, b; /* RGB components of requested color */
242
static int systemFg = 0; /* 1==black-foreground 2==white-foreground */
243
int fg; /* Foreground color to actually use */
244
static char zColor[10]; /* Return value */
245
246
if( iFgClr ){
247
fg = iFgClr;
248
}else if( systemFg==0 ){
249
if( db_get_boolean("raw-bgcolor",0) ){
250
fg = systemFg = 3;
251
}else{
252
fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1;
253
}
254
}else{
255
fg = systemFg;
256
}
257
if( fg>=3 ) return zRequested;
258
259
if( iRGB<0 ) return 0;
260
r = (iRGB>>16) & 0xff;
261
g = (iRGB>>8) & 0xff;
262
b = iRGB & 0xff;
263
if( fg==1 ){
264
/* Dark text on a light background. Adjust so that
265
** no color component is less than 255-K, resulting in
266
** a pastel background color. Color adjustment is quadratic
267
** so that colors that are further out of range have a greater
268
** adjustment. */
269
const int K = 79;
270
int k, x, m;
271
m = r<g ? r : g;
272
if( m>b ) m = b;
273
k = (m*m)/255 + K;
274
x = 255 - k;
275
r = (k*r)/255 + x;
276
g = (k*g)/255 + x;
277
b = (k*b)/255 + x;
278
}else{
279
/* Light text on a dark background. Adjust so that
280
** no color component is greater than K, resulting in
281
** a low-intensity, low-saturation background color.
282
** The color adjustment is quadratic so that colors that
283
** are further out of range have a greater adjustment. */
284
const int K = 112;
285
int k, m;
286
m = r>g ? r : g;
287
if( m<b ) m = b;
288
k = 255 - (255-K)*(m*m)/65025;
289
r = (k*r)/255;
290
g = (k*g)/255;
291
b = (k*b)/255;
292
}
293
sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
294
return zColor;
295
}
296
297
/*
298
** Compute a hash on a branch or user name
299
*/
300
static unsigned int hash_of_name(const char *z){
301
unsigned int h = 0;
302
int i;
303
for(i=0; z[i]; i++ ){
304
h = (h<<11) ^ (h<<1) ^ (h>>3) ^ z[i];
305
}
306
return h;
307
}
308
309
/*
310
** Hash a string and use the hash to determine a background color.
311
**
312
** This value returned is in static space and is overwritten with
313
** each subsequent call.
314
*/
315
char *hash_color(const char *z){
316
unsigned int h = 0; /* Hash on the branch name */
317
int r, g, b; /* Values for red, green, and blue */
318
int h1, h2, h3, h4; /* Elements of the hash value */
319
int mx, mn; /* Components of HSV */
320
static char zColor[10]; /* The resulting color */
321
static int ix[3] = {0,0}; /* Color chooser parameters */
322
323
if( ix[0]==0 ){
324
if( skin_detail_boolean("white-foreground") ){
325
ix[0] = 0x50;
326
ix[1] = 0x20;
327
}else{
328
ix[0] = 0xf8;
329
ix[1] = 0x20;
330
}
331
}
332
h = hash_of_name(z);
333
h1 = h % 6; h /= 6;
334
h3 = h % 10; h /= 10;
335
h4 = h % 10; h /= 10;
336
mx = ix[0] - h3;
337
mn = mx - h4 - ix[1];
338
h2 = (h%(mx - mn)) + mn;
339
switch( h1 ){
340
case 0: r = mx; g = h2, b = mn; break;
341
case 1: r = h2; g = mx, b = mn; break;
342
case 2: r = mn; g = mx, b = h2; break;
343
case 3: r = mn; g = h2, b = mx; break;
344
case 4: r = h2; g = mn, b = mx; break;
345
default: r = mx; g = mn, b = h2; break;
346
}
347
sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
348
return zColor;
349
}
350
351
/*
352
** Determine a color for users based on their login string.
353
**
354
** SETTING: user-color-map width=40 block-text
355
**
356
** The user-color-map setting can be used to override user color choices.
357
** The setting is a list of space-separated words pairs. The first word
358
** of each pair is a login name. The second word is an alternative name
359
** used by the color chooser algorithm.
360
**
361
** This list is intended to be relatively short. The idea is to only use
362
** this map to resolve color collisions between common users.
363
**
364
** Visit /hash-color-test?rand for a list of suggested names for the
365
** second word of each pair in the list.
366
*/
367
char *user_color(const char *zLogin){
368
static int once = 0;
369
static int nMap = 0;
370
static char **azMap = 0;
371
static int *anMap = 0;
372
int i;
373
if( !once ){
374
char *zMap = (char*)db_get("user-color-map",0);
375
once = 1;
376
if( zMap && zMap[0] ){
377
if( !g.interp ) Th_FossilInit(0);
378
Th_SplitList(g.interp, zMap, (int)strlen(zMap),
379
&azMap, &anMap, &nMap);
380
for(i=0; i<nMap; i++) azMap[i][anMap[i]] = 0;
381
}
382
}
383
for(i=0; i<nMap-1; i+=2){
384
if( strcmp(zLogin, azMap[i])==0 ) return hash_color(azMap[i+1]);
385
}
386
return hash_color(zLogin);
387
}
388
389
/*
390
** COMMAND: test-hash-color
391
**
392
** Usage: %fossil test-hash-color TAG ...
393
**
394
** Print out the color names associated with each tag. Used for
395
** testing the hash_color() function.
396
*/
397
void test_hash_color(void){
398
int i;
399
for(i=2; i<g.argc; i++){
400
fossil_print("%20s: %s\n", g.argv[i], hash_color(g.argv[i]));
401
}
402
}
403
404
/*
405
** WEBPAGE: hash-color-test
406
**
407
** Print out the color names associated with each tag. Used for
408
** testing the hash_color() function.
409
*/
410
void test_hash_color_page(void){
411
const char *zBr;
412
char zNm[10];
413
int i, cnt;
414
login_check_credentials();
415
if( P("rand")!=0 ){
416
int j;
417
for(i=0; i<10; i++){
418
sqlite3_uint64 u;
419
char zClr[10];
420
sqlite3_randomness(sizeof(u), &u);
421
cnt = 3+(u%2);
422
u /= 2;
423
for(j=0; j<cnt; j++){
424
zClr[j] = 'a' + (u%26);
425
u /= 26;
426
}
427
zClr[j] = 0;
428
sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
429
cgi_replace_parameter(fossil_strdup(zNm), fossil_strdup(zClr));
430
}
431
}
432
style_set_current_feature("test");
433
style_header("Hash Color Test");
434
for(i=cnt=0; i<10; i++){
435
sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
436
zBr = P(zNm);
437
if( zBr && zBr[0] ){
438
@ <p style='border:1px solid;background-color:%s(hash_color(zBr));'>
439
@ %h(zBr) - hash 0x%x(hash_of_name(zBr)) - color %s(hash_color(zBr)) -
440
@ Omnes nos quasi oves erravimus unusquisque in viam
441
@ suam declinavit.</p>
442
cnt++;
443
}
444
}
445
if( cnt ){
446
@ <hr>
447
}
448
@ <form method="POST">
449
@ <p>Enter candidate branch names below and see them displayed in their
450
@ default background colors above.</p>
451
for(i=0; i<10; i++){
452
sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i);
453
zBr = P(zNm);
454
@ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br>
455
}
456
@ <input type="submit" value="Submit">
457
@ <input type="submit" name="rand" value="Random">
458
@ </form>
459
style_finish_page();
460
}
461
462
/*
463
** WEBPAGE: test-bgcolor
464
**
465
** Show how user-specified background colors will be rendered
466
** using the reasonable_bg_color() algorithm.
467
*/
468
void test_bgcolor_page(void){
469
const char *zReq; /* Requested color name */
470
const char *zBG; /* Actual color provided */
471
const char *zBg1;
472
char zNm[10];
473
static const char *azDflt[] = {
474
"red", "orange", "yellow", "green", "blue", "indigo", "violet",
475
"tan", "brown", "gray",
476
};
477
const int N = count(azDflt);
478
int i, cnt, iClr, r, g, b;
479
char *zFg;
480
login_check_credentials();
481
style_set_current_feature("test");
482
style_header("Background Color Test");
483
for(i=cnt=0; i<N; i++){
484
sqlite3_snprintf(sizeof(zNm),zNm,"b%c",'a'+i);
485
zReq = PD(zNm,azDflt[i]);
486
if( zReq==0 || zReq[0]==0 ) continue;
487
if( cnt==0 ){
488
@ <table border="1" cellspacing="0" cellpadding="10">
489
@ <tr>
490
@ <th>Requested Background
491
@ <th>Light mode
492
@ <th>Dark mode
493
@ </tr>
494
}
495
cnt++;
496
zBG = reasonable_bg_color(zReq, 0);
497
if( zBG==0 ){
498
@ <tr><td colspan="3" align="center">\
499
@ "%h(zReq)" is not a recognized color name</td></tr>
500
continue;
501
}
502
iClr = color_name_to_rgb(zReq);
503
r = (iClr>>16) & 0xff;
504
g = (iClr>>8) & 0xff;
505
b = iClr & 0xff;
506
if( 3*r + 7*g + b > 6*255 ){
507
zFg = "black";
508
}else{
509
zFg = "white";
510
}
511
if( zReq[0]!='#' ){
512
char zReqRGB[12];
513
sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq));
514
@ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\
515
@ Requested color "%h(zReq)" (%h(zReqRGB))</td>
516
}else{
517
@ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\
518
@ Requested color "%h(zReq)"</td>
519
}
520
zBg1 = reasonable_bg_color(zReq,1);
521
@ <td style='color:black;background-color:%h(zBg1);'>\
522
@ Background color for dark text: %h(zBg1)</td>
523
zBg1 = reasonable_bg_color(zReq,2);
524
@ <td style='color:white;background-color:%h(zBg1);'>\
525
@ Background color for light text: %h(zBg1)</td></tr>
526
}
527
if( cnt ){
528
@ </table>
529
@ <hr>
530
}
531
@ <form method="POST">
532
@ <p>Enter CSS color names below and see them shifted into corresponding
533
@ background colors above.</p>
534
for(i=0; i<N; i++){
535
sqlite3_snprintf(sizeof(zNm),zNm,"b%c",'a'+i);
536
@ <input type="text" size="30" name='%s(zNm)' \
537
@ value='%h(PD(zNm,azDflt[i]))'><br>
538
}
539
@ <input type="submit" value="Submit">
540
@ </form>
541
style_finish_page();
542
}
543

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button