1 /* Copyright (C) 2000-2012 by George Williams */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <fontforge-config.h>
29 
30 #include "charset.h"
31 #include "colorP.h"
32 #include "gdrawP.h"
33 #include "ustring.h"
34 
RevColListFree(struct revcol * rc)35 static void RevColListFree(struct revcol *rc) {
36     struct revcol *next;
37 
38     while ( rc!=NULL ) {
39 	next = rc->next;
40 	free(rc);
41 	rc = next;
42     }
43 }
44 
45 static const GCol white = { 0xff, 0xff, 0xff, 1 }, black = { 0, 0, 0, 0 };
46 
_GImage_GetIndexedPixel(Color col,RevCMap * rev)47 const GCol *_GImage_GetIndexedPixel(Color col,RevCMap *rev) {
48     int val, t, best, r, g, b;
49     struct revitem *this;
50     struct revcol *test, *bestcol;
51 
52     if ( rev==NULL ) {		/* Mono */
53 	if ( COLOR2WHITE(col) )
54 return( &white );
55 	else
56 return( &black );
57     }
58     if ( rev->is_grey ) {
59 	val = COLOR2GREY(col);
60 return( &rev->greys[val]);
61     }
62 
63   reverse_clut:
64     r = COLOR_RED(col); g = COLOR_GREEN(col); b = COLOR_BLUE(col);
65     if ( rev->div_mul==1 ) {
66 	val = r>>rev->div_shift;
67 	val = (val<<rev->side_shift)+(g>>rev->div_shift);
68 	val = (val<<rev->side_shift)+(b>>rev->div_shift);
69     } else {
70 	val = ((r+rev->div_add)*rev->div_mul)>>rev->div_shift;
71 	t = ((g+rev->div_add)*rev->div_mul)>>rev->div_shift;
72 	val = (val*rev->side_cnt)+t;
73 	t = ((b+rev->div_add)*rev->div_mul)>>rev->div_shift;
74 	val = (val*rev->side_cnt)+t;
75     }
76     this = &rev->cube[val];
77     if ( this->sub!=NULL ) {
78 	col &= rev->mask;
79 	rev = this->sub;
80   goto reverse_clut;
81     }
82 
83     test = this->cols[0];
84     if ( test->next==NULL )
85 return( (GCol *) test );
86 
87     if (( best = (r-test->red))<0 ) best = -best;
88     if (( t = (g-test->green))<0 ) t = -t; best += t;
89     if (( t = (b-test->blue))<0 ) t = -t; best += t;
90     bestcol = test;
91     for ( test=test->next; test!=NULL; test = test->next ) {
92 	if (( val = (r-test->red))<0 ) val = -val;
93 	if (( t = (g-test->green))<0 ) t = -t; val += t;
94 	if (( t = (b-test->blue))<0 ) t = -t; val += t;
95 	if ( val<best ) {
96 	    val = best;
97 	    bestcol = test;
98 	}
99     }
100 return( (GCol *) bestcol );
101 }
102 
rccnt(struct revcol * cols)103 static int rccnt( struct revcol *cols) {
104     int cnt = 0;
105     while ( cols!=NULL ) {
106 	++cnt;
107 	cols = cols->next;
108     }
109 return( cnt );
110 }
111 
add_adjacent(struct revcol * test,struct revcol * old,Color col,int dmax)112 static struct revcol *add_adjacent(struct revcol *test, struct revcol *old, Color col, int dmax) {
113     int r=COLOR_RED(col), g=COLOR_GREEN(col), b=COLOR_BLUE(col);
114     int off, t, bestoff;
115     struct revcol *best=NULL;
116 
117     if ( test==NULL || test->dist>dmax )
118 return( old );
119     bestoff = 3*255;
120     for ( test=test; test!=NULL; test = test->next ) {
121 	if ( (off = (r-test->red))<0 ) off = -off;
122 	if ( (t = (g-test->green))<0 ) t = -t; off +=t;
123 	if ( (b = (g-test->blue))<0 ) t = -t; off +=t;
124 	if ( off<bestoff ) {
125 	    bestoff = off;
126 	    best = test;
127 	}
128     }
129     if ( old!=NULL ) {
130 	if ( (off = (r-old->red))<0 ) off = -off;
131 	if ( (t = (g-old->green))<0 ) t = -t; off +=t;
132 	if ( (b = (g-old->blue))<0 ) t = -t; off +=t;
133 	if ( off<bestoff ) {
134 	    bestoff = off;
135 	    best = old;
136 	}
137     }
138     if ( best!=old ) {
139 	if ( old==NULL ) old = calloc(1,sizeof(struct revcol));
140 	*old = *best;
141 	old->next = NULL;
142 	++old->dist;
143     }
144 return( old );
145 }
146 
addrevcol(struct revcol * copy,struct revcol * old,int dist)147 static struct revcol *addrevcol(struct revcol *copy,struct revcol *old, int dist) {
148     struct revcol *rc = malloc(sizeof(struct revcol));
149 
150     *rc = *copy;
151     rc->next = old;
152     rc->dist = dist;
153 return( rc );
154 }
155 
_GClutReverse(int side_cnt,int range,struct revcol * basecol,struct revcol * cols,struct revcol * outercols)156 static RevCMap *_GClutReverse(int side_cnt,int range,struct revcol *basecol,
157 	struct revcol *cols,struct revcol *outercols) {
158     RevCMap *rev;
159     int i, s2, s3, side_size;
160     struct revcol *test;
161     int changed, dmax, anynulls;
162 
163     rev = calloc(1,sizeof(RevCMap));
164 
165     rev->side_cnt = side_cnt;
166     rev->range = range;
167     if ( side_cnt<0 ) {
168 	/* special cases ... */
169 	side_cnt = -side_cnt;
170 	rev->side_cnt = side_cnt;
171 	if ( side_cnt==6 ) {		/* Used for the inefficient standard 6 by cube */
172 	    rev->div_mul = div_tables[0x33][0];
173 	    rev->div_shift = div_tables[0x33][1];
174 	    rev->div_add = 0x19;
175 	}
176 	side_size = 0x33;
177     } else if ( div_tables[side_cnt][0]==1 ) {
178 	rev->side_shift = div_tables[side_cnt][1];
179 	rev->div_shift = div_tables[range][1]-rev->side_shift;
180 	rev->div_mul = 1;
181 	rev->mask = ( 0x010101 * ((-1<rev->div_shift)&0xff) );
182 	side_size = 1<<(rev->div_shift);
183     } else {
184 	side_size = (range+side_cnt-1)/side_cnt;
185 	rev->div_mul = div_tables[side_cnt][0];
186 	rev->div_shift = div_tables[side_cnt][1];
187     }
188 
189     rev->cube = calloc(side_cnt*side_cnt*side_cnt,sizeof(struct revitem));
190     for ( test = cols; test!=NULL; test = test->next ) {
191 	int pos, dist;
192 	int r,g,b;
193 	int rbase = (test->red-basecol->red)/side_size, gbase = (test->green-basecol->green)/side_size, bbase = (test->blue-basecol->blue)/side_size;
194 	for ( r = (test->red-basecol->red-side_size/2)/side_size;
195 		r<=(test->red-basecol->red+side_size/2)/side_size; ++r ) {
196 	    if ( r<0 || r==side_cnt )
197 	continue;
198 	    for ( g = (test->green-basecol->green-side_size/2)/side_size;
199 		    g<=(test->green-basecol->green+side_size/2)/side_size; ++g ) {
200 		if ( g<0 || g==side_cnt )
201 	    continue;
202 		for ( b = (test->blue-basecol->blue-side_size/2)/side_size;
203 			b<=(test->blue-basecol->blue+side_size/2)/side_size; ++b ) {
204 		    if ( b<0 || b==side_cnt )
205 		continue;
206 		    pos = (r*side_cnt + g)*side_cnt + b;
207 		    dist = r!=rbase || g!=gbase || b!=bbase;
208 		    rev->cube[pos].cols[dist] = addrevcol(test,rev->cube[pos].cols[dist],
209 			    dist );
210 		}
211 	    }
212 	}
213     }
214 
215     /* Ok. We just put every color into the sub-cube that it belongs in */
216     /*  but that's not good enough. Say we are looking for a color that */
217     /*  is on the edge of a sub-cube, then the best match for it may be */
218     /*  in one of the adjacent sub-cubes, rather than within it's own */
219     /* So we also ran through the list of colors, casting our net a */
220     /*  little wider. Essentially for every sub-cube we found all the colors */
221     /*  within a subcube centered on the color */
222 
223     /* Some sub-cubes will probably have no information in them. Fill them */
224     /*  in by looking at near-by cubes */
225     s2 = side_cnt*side_cnt; s3 = s2*side_cnt;
226     for ( i=0; i<s3; ++i ) {
227 	if ( rev->cube[i].cols[0]==NULL && rev->cube[i].cols[1]!=NULL ) {
228 	    rev->cube[i].cols[0] = rev->cube[i].cols[1];
229 	    rev->cube[i].cols[1] = NULL;
230 	}
231     }
232     changed = true; anynulls = false; dmax = 0;
233     while ( changed || (anynulls && dmax<256) ) {
234 	changed = false; anynulls = false;
235 	for ( i=0; i<s3; ++i ) {
236 	    if ( rev->cube[i].cols[0]==NULL ) {
237 		int r = i/s2, g = (i/side_cnt)%side_cnt, b = i%side_cnt;
238 		int col = ((r*side_cnt+(side_cnt>>1))<<16) |
239 			  ((g*side_cnt+(side_cnt>>1))<<8) |
240 			  ((b*side_cnt+(side_cnt>>1))) ;
241 		if ( r>0 ) rev->cube[i].cols[0] = add_adjacent(rev->cube[i-s2].cols[0],NULL,col,dmax);
242 		if ( r+1<side_cnt ) rev->cube[i].cols[0] = add_adjacent(rev->cube[i+s2].cols[0],rev->cube[i].cols[0],col,dmax);
243 		if ( g>0 ) rev->cube[i].cols[0] = add_adjacent(rev->cube[i-side_cnt].cols[0],rev->cube[i].cols[0],col,dmax);
244 		if ( g+1<side_cnt ) rev->cube[i].cols[0] = add_adjacent(rev->cube[i+side_cnt].cols[0],rev->cube[i].cols[0],col,dmax);
245 		if ( b>0 ) rev->cube[i].cols[0] = add_adjacent(rev->cube[i-1].cols[0],rev->cube[i].cols[0],col,dmax);
246 		if ( b+1<side_cnt ) rev->cube[i].cols[0] = add_adjacent(rev->cube[i+1].cols[0],rev->cube[i].cols[0],col,dmax);
247 		if ( rev->cube[i].cols[0]!=NULL ) changed = true;
248 		else anynulls = true;
249 	    }
250 	}
251 	++dmax;
252     }
253 
254     if ( anynulls ) {
255 	fprintf(stderr, "I'm sorry I cannot use this visual, please reconfigure your display\n" );
256 	exit(1);
257     }
258 
259     if ( rev->side_shift!=0 ) {
260 	range >>= rev->side_shift;
261 	if ( range>8 ) for ( i=0; i<s3; ++i ) if ( rev->cube[i].cols[0]->dist==0 ) {
262 	    int ql = rccnt(rev->cube[i].cols[0]);
263 	    int nr= -1;
264 	    struct revcol nbase;
265 	    if ( ql>128 )
266 		nr = 16;
267 	    else if ( ql>32 )
268 		nr = 8;
269 	    else if ( ql>=8 )
270 		nr = 4;
271 	    if ( nr!=-1 ) {
272 		nbase.red = basecol->red+(i/s2)*range;
273 		nbase.green = basecol->green+(i/side_cnt)%side_cnt*range;
274 		nbase.blue = basecol->blue+(i%side_cnt)*range;
275 		while ( nr>range ) nr >>= 1;
276 		if ( nr!=1 )
277 		    rev->cube[i].sub = _GClutReverse(nr,range,&nbase,
278 			    rev->cube[i].cols[0],rev->cube[i].cols[1]);
279 	    }
280 	}
281     }
282 return( rev );
283 }
284 
GClutReverse(GClut * clut,int side_cnt)285 RevCMap *GClutReverse(GClut *clut,int side_cnt) {
286     struct revcol *base = NULL;
287     int i;
288     RevCMap *ret;
289     struct revcol basecol;
290 
291     if ( GImageGreyClut(clut) ) {
292 	GCol *greys; int changed;
293 	ret = calloc(1,sizeof(RevCMap));
294 	ret->is_grey = 1;
295 	greys = ret->greys = malloc(256*sizeof(GCol));
296 	for ( i=0; i<256; ++i ) greys[i].pixel = 0x1000;
297 	for ( i=0; i<clut->clut_len; ++i ) {
298 	    int g = clut->clut[i]&0xff;
299 	    greys[g].red = greys[g].green = greys[g].blue = g;
300 	    greys[g].pixel = i;
301 	}
302 	changed = true;
303 	while ( changed ) {
304 	    changed = false;
305 	    for ( i=0; i<256; ++i ) {
306 		if ( greys[i].pixel!=0x1000 ) {
307 		    if ( i!=0 && greys[i-1].pixel == 0x1000 ) {
308 			greys[i-1] = greys[i]; changed = true; }
309 		    if ( i!=255 && greys[i+1].pixel == 0x1000 ) {
310 			greys[i+1] = greys[i]; changed = true; }
311 		}
312 	    }
313 	}
314 return( ret );
315     }
316 
317     for ( i=0; i<clut->clut_len; ++i ) {
318 	struct revcol *rc = malloc(sizeof(struct revcol));
319 	rc->red = COLOR_RED(clut->clut[i]);
320 	rc->green = COLOR_GREEN(clut->clut[i]);
321 	rc->blue = COLOR_BLUE(clut->clut[i]);
322 	rc->index = i;
323 	rc->dist = 0;
324 	rc->next = base;
325 	base = rc;
326     }
327     memset(&basecol,'\0',sizeof(basecol));
328     ret = _GClutReverse(side_cnt,256,&basecol,base,base);
329     while ( base!=NULL ) {
330 	struct revcol *rc = base->next;
331 	free(base);
332 	base = rc;
333     }
334 return( ret );
335 }
336 
GClut_RevCMapFree(RevCMap * rev)337 void GClut_RevCMapFree(RevCMap *rev) {
338     int i;
339 
340     for ( i=0; i<rev->side_cnt*rev->side_cnt*rev->side_cnt; ++i ) {
341 	if ( rev->cube[i].sub!=NULL )
342 	    GClut_RevCMapFree(rev->cube[i].sub);
343 	RevColListFree(rev->cube[i].cols[0]);
344 	RevColListFree(rev->cube[i].cols[1]);
345     }
346     free(rev->cube);
347     free(rev);
348 }
349 
GImageGreyClut(GClut * clut)350 int GImageGreyClut(GClut *clut) {
351     int i, r,b,g;
352 
353     if ( clut==NULL )
354 return( true );
355     for ( i=0; i<clut->clut_len; ++i ) {
356 	r = COLOR_RED(clut->clut[i]);
357 	g = COLOR_GREEN(clut->clut[i]);
358 	b = COLOR_BLUE(clut->clut[i]);
359 	if ( r!=g || g!=b ) {
360 	    clut->is_grey = false;
361 return( false );
362 	}
363     }
364     clut->is_grey = true;
365 return( true );
366 }
367 
368 static struct { char *name; long value; } predefn[] = {
369     { "red", 0xff0000 },
370     { "green", 0x008000 },
371     { "blue", 0x0000ff },
372     { "cyan", 0x00ffff },
373     { "magenta", 0xff00ff },
374     { "yellow", 0xffff00 },
375     { "black", 0x000000 },
376     { "gray", 0x808080 },
377     { "grey", 0x808080 },
378     { "white", 0xffffff },
379     { "maroon", 0x800000 },
380     { "olive", 0x808000 },
381     { "navy", 0x000080 },
382     { "purple", 0x800080 },
383     { "lime", 0x00ff00 },
384     { "aqua", 0x00ffff },
385     { "teal", 0x008080 },
386     { "fuchsia", 0xff0080 },
387     { "silver", 0xcccccc },
388     { NULL, 0 }
389 };
390 
_GImage_ColourFName(char * name)391 Color _GImage_ColourFName(char *name) {
392     int i;
393     int r,g,b,a;
394     double dr,dg,db,da;
395     struct hslrgb hs;
396     Color col;
397 
398     for ( i=0; predefn[i].name!=NULL; ++i )
399 	if ( strmatch(name,predefn[i].name)==0 )
400 return( predefn[i].value );
401 
402     if ( sscanf(name,"%d %d %d", &r, &g, &b )==3 ||
403 	    sscanf(name,"%x %x %x", (unsigned *) &r, (unsigned *) &g, (unsigned *) &b )==3 ||
404 	    (strlen(name)==7 && sscanf(name,"#%2x%2x%2x", (unsigned *) &r, (unsigned *) &g, (unsigned *) &b )==3) ) {
405 	if ( r>255 ) r=255; else if ( r<0 ) r=0;
406 	if ( g>255 ) g=255; else if ( g<0 ) g=0;
407 	if ( b>255 ) b=255; else if ( b<0 ) b=0;
408 return( ((long) r<<16) | (g<<8) | b );
409     } else if ( (strlen(name)==9 && sscanf(name,"#%2x%2x%2x%2x",
410 	    (unsigned *) &a, (unsigned *) &r, (unsigned *) &g, (unsigned *) &b )==4) ) {
411 	if ( a>255 ) a=255; else if ( a<0 ) a=0;
412 	if ( r>255 ) r=255; else if ( r<0 ) r=0;
413 	if ( g>255 ) g=255; else if ( g<0 ) g=0;
414 	if ( b>255 ) b=255; else if ( b<0 ) b=0;
415 	col = ((long) a<<24) | ((long) r<<16) | (g<<8) | b;
416 	if ( (col&0xfffffff0)==0xfffffff0 )
417 	    col &= 0xffffff;	/* I use these colors internally for things like "Undefined" */
418 			    /* but since I also treat colors with 0 alpha channel as fully */
419 			    /* opaque, I can just represent this that way */
420 return( col );
421     } else if ( sscanf(name,"rgb(%lg,%lg,%lg)", &dr, &dg, &db)==3 ) {
422 	if ( dr>1.0 ) dr=1.0; else if ( dr<0 ) dr= 0;
423 	if ( dg>1.0 ) dg=1.0; else if ( dg<0 ) dg= 0;
424 	if ( db>1.0 ) db=1.0; else if ( db<0 ) db= 0;
425 	r = dr*255 +.5;
426 	g = dg*255 +.5;
427 	b = db*255 +.5;
428 return( ((long) r<<16) | (g<<8) | b );
429     } else if ( sscanf(name,"argb(%lg,%lg,%lg,%lg)", &da, &dr, &dg, &db)==4 ) {
430 	if ( da>1.0 ) da=1.0; else if ( da<0 ) da= 0;
431 	if ( dr>1.0 ) dr=1.0; else if ( dr<0 ) dr= 0;
432 	if ( dg>1.0 ) dg=1.0; else if ( dg<0 ) dg= 0;
433 	if ( db>1.0 ) db=1.0; else if ( db<0 ) db= 0;
434 	a = da*255 +.5;
435 	r = dr*255 +.5;
436 	g = dg*255 +.5;
437 	b = db*255 +.5;
438 	col = ((long) a<<24) | ((long) r<<16) | (g<<8) | b;
439 	if ( (col&0xfffffff0)==0xfffffff0 )
440 	    col &= 0xffffff;	/* I use these colors internally for things like "Undefined" */
441 			    /* but since I also treat colors with 0 alpha channel as fully */
442 			    /* opaque, I can just represent this that way */
443 return( col );
444     } else if ( sscanf(name,"hsv(%lg,%lg,%lg)", &hs.h, &hs.s, &hs.v)==3 ) {
445 	/* Hue is an angle in degrees. HS?2RGB does appropriate clipping */
446 	if ( hs.s>1.0 ) hs.s=1.0; else if ( hs.s<0 ) hs.s= 0;
447 	if ( hs.v>1.0 ) hs.v=1.0; else if ( hs.v<0 ) hs.v= 0;
448 	gHSV2RGB(&hs);
449 	r = hs.r*255 +.5;
450 	g = hs.g*255 +.5;
451 	b = hs.b*255 +.5;
452 return( ((long) r<<16) | (g<<8) | b );
453     } else if ( sscanf(name,"hsl(%lg,%lg,%lg)", &hs.h, &hs.s, &hs.l)==3 ) {
454 	/* Hue is an angle in degrees. HS?2RGB does appropriate clipping */
455 	if ( hs.s>1.0 ) hs.s=1.0; else if ( hs.s<0 ) hs.s= 0;
456 	if ( hs.l>1.0 ) hs.l=1.0; else if ( hs.l<0 ) hs.l= 0;
457 	gHSL2RGB(&hs);
458 	r = hs.r*255 +.5;
459 	g = hs.g*255 +.5;
460 	b = hs.b*255 +.5;
461 return( ((long) r<<16) | (g<<8) | b );
462     } else if ( (strlen(name)==4 && sscanf(name,"#%1x%1x%1x", (unsigned *) &r, (unsigned *) &g, (unsigned *) &b )==3) ) {
463 	if ( r>15 ) r=15; else if ( r<0 ) r=0;
464 	if ( g>15 ) g=15; else if ( g<0 ) g=0;
465 	if ( b>15 ) b=15; else if ( b<0 ) b=0;
466 return( ((long) (r*0x110000)) | (g*0x1100) | (b*0x11) );
467     } else if ( (strlen(name)==17 && sscanf(name,"#%4x%4x%4x", (unsigned *) &r, (unsigned *) &g, (unsigned *) &b )==3) ) {
468 	r>>=8; g>>=8; b>>=8;
469 	if ( r>255 ) r=255; else if ( r<0 ) r=0;
470 	if ( g>255 ) g=255; else if ( g<0 ) g=0;
471 	if ( b>255 ) b=255; else if ( b<0 ) b=0;
472 return( ((long) r<<16) | (g<<8) | b );
473     } else if ( sscanf(name,"rgb(%lg%%,%lg%%,%lg%%)", &dr, &dg, &db)==3 ) {
474 	if ( dr>100 ) dr=100; else if ( dr<0 ) dr= 0;
475 	if ( dg>100 ) dg=100; else if ( dg<0 ) dg= 0;
476 	if ( db>100 ) db=100; else if ( db<0 ) db= 0;
477 	r = (dr*255+50)/100 +.5;
478 	g = (dg*255+50)/100 +.5;
479 	b = (db*255+50)/100 +.5;
480 return( ((long) r<<16) | (g<<8) | b );
481     }
482 return( -1 );
483 }
484 
GDrawColorBrighten(Color col,int by)485 Color GDrawColorBrighten(Color col, int by) {
486     int r, g, b;
487     if (( r = COLOR_RED(col)+by )>255 ) r=255;
488     if (( g=COLOR_GREEN(col)+by )>255 ) g=255;
489     if (( b=COLOR_BLUE(col)+by )>255 ) b=255;
490 return( COLOR_CREATE(r,g,b));
491 }
492 
GDrawColorDarken(Color col,int by)493 Color GDrawColorDarken(Color col, int by) {
494     int r, g, b;
495     if (( r = COLOR_RED(col)-by )<0 ) r=0;
496     if (( g=COLOR_GREEN(col)-by )<0 ) g=0;
497     if (( b=COLOR_BLUE(col)-by )<0 ) b=0;
498 return( COLOR_CREATE(r,g,b));
499 }
500