1 /*
2 ** Copyright (C) 1989 by Jef Poskanzer.
3 **
4 ** Permission to use, copy, modify, and distribute this software and its
5 ** documentation for any purpose and without fee is hereby granted, provided
6 ** that the above copyright notice appear in all copies and that both that
7 ** copyright notice and this permission notice appear in supporting
8 ** documentation. This software is provided "as is" without express or
9 ** implied warranty.
10 */
11
12 #include <assert.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <math.h>
16
17 #include "netpbm/pm_c_util.h"
18 #include "netpbm/mallocvar.h"
19 #include "netpbm/nstring.h"
20 #include "ppm.h"
21 #include "colorname.h"
22 #include "pam.h"
23
24
25 pixel
ppm_parsecolor2(const char * const colorname,pixval const maxval,int const closeOk)26 ppm_parsecolor2(const char * const colorname,
27 pixval const maxval,
28 int const closeOk) {
29
30 tuple const color = pnm_parsecolor2(colorname, maxval, closeOk);
31
32 pixel retval;
33
34 PPM_PUTR(retval, color[PAM_RED_PLANE]);
35 PPM_PUTG(retval, color[PAM_GRN_PLANE]);
36 PPM_PUTB(retval, color[PAM_BLU_PLANE]);
37
38 free(color);
39
40 return retval;
41 }
42
43
44
45 pixel
ppm_parsecolor(const char * const colorname,pixval const maxval)46 ppm_parsecolor(const char * const colorname,
47 pixval const maxval) {
48
49 return ppm_parsecolor2(colorname, maxval, TRUE);
50 }
51
52
53
54 char *
ppm_colorname(const pixel * const colorP,pixval const maxval,int const hexok)55 ppm_colorname(const pixel * const colorP,
56 pixval const maxval,
57 int const hexok) {
58
59 int r, g, b;
60 FILE * f;
61 static char colorname[200];
62 /* Null string means no suitable name so far */
63
64 if (maxval == 255) {
65 r = PPM_GETR(*colorP);
66 g = PPM_GETG(*colorP);
67 b = PPM_GETB(*colorP);
68 } else {
69 r = (int) PPM_GETR(*colorP) * 255 / (int) maxval;
70 g = (int) PPM_GETG(*colorP) * 255 / (int) maxval;
71 b = (int) PPM_GETB(*colorP) * 255 / (int) maxval;
72 }
73
74 f = pm_openColornameFile(NULL, !hexok);
75
76 if (!f)
77 STRSCPY(colorname, "");
78 else {
79 int bestDiff;
80 bool eof;
81
82 for (bestDiff = 32767, eof = FALSE;
83 !eof && bestDiff > 0; ) {
84 struct colorfile_entry const ce = pm_colorget(f);
85 if (ce.colorname) {
86 int const thisDiff =
87 abs(r - (int)ce.r) +
88 abs(g - (int)ce.g) +
89 abs(b - (int)ce.b);
90
91 if (thisDiff < bestDiff) {
92 bestDiff = thisDiff;
93 STRSCPY(colorname, ce.colorname);
94 }
95 } else
96 eof = TRUE;
97 }
98 fclose(f);
99
100 if (bestDiff == 32767) {
101 /* Color file contain no entries, so we can't even pick a close
102 one
103 */
104 STRSCPY(colorname, "");
105 } else if (bestDiff > 0 && hexok) {
106 /* We didn't find an exact match and user is willing to accept
107 hex, so we don't have to use an approximate match.
108 */
109 STRSCPY(colorname, "");
110 }
111 }
112
113 if (streq(colorname, "")) {
114 if (hexok) {
115 /* Color lookup failed, but caller is willing to take an X11-style
116 hex specifier, so return that.
117 */
118 sprintf(colorname, "#%02x%02x%02x", r, g, b);
119 } else {
120 pm_error("Couldn't find any name colors at all");
121 }
122 }
123
124 return colorname;
125 }
126
127
128
129 #define MAXCOLORNAMES 1000u
130
131 static const char **
allocColorNames()132 allocColorNames() {
133
134 const char ** colornames;
135
136 MALLOCARRAY(colornames, MAXCOLORNAMES);
137
138 if (colornames) {
139 unsigned int i;
140 for (i = 0; i < MAXCOLORNAMES; ++i)
141 colornames[i] = NULL;
142 }
143 return colornames;
144 }
145
146
147
148 static colorhash_table
allocColorHash(void)149 allocColorHash(void) {
150
151 colorhash_table cht;
152 jmp_buf jmpbuf;
153 jmp_buf * origJmpbufP;
154
155 if (setjmp(jmpbuf) != 0)
156 cht = NULL;
157 else {
158 pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
159 cht = ppm_alloccolorhash();
160 }
161 pm_setjmpbuf(origJmpbufP);
162
163 return cht;
164 }
165
166
167
168 static void
processColorfileEntry(struct colorfile_entry const ce,colorhash_table const cht,const char ** const colornames,pixel * const colors,unsigned int * const colornameIndexP,const char ** const errorP)169 processColorfileEntry(struct colorfile_entry const ce,
170 colorhash_table const cht,
171 const char ** const colornames,
172 pixel * const colors,
173 unsigned int * const colornameIndexP,
174 const char ** const errorP) {
175
176 if (*colornameIndexP >= MAXCOLORNAMES)
177 pm_asprintf(errorP, "Too many colors in colorname dictionary. "
178 "Max allowed is %u", MAXCOLORNAMES);
179 else {
180 pixel color;
181
182 PPM_ASSIGN(color, ce.r, ce.g, ce.b);
183
184 if (ppm_lookupcolor(cht, &color) >= 0) {
185 /* The color is already in the hash, which means we saw it
186 earlier in the file. We prefer the first name that the
187 file gives for each color, so we just ignore the
188 current entry.
189 */
190 *errorP = NULL;
191 } else {
192 ppm_addtocolorhash(cht, &color, *colornameIndexP);
193 colornames[*colornameIndexP] = pm_strdup(ce.colorname);
194 colors[*colornameIndexP] = color;
195 if (colornames[*colornameIndexP] == pm_strsol)
196 pm_asprintf(errorP, "Unable to allocate space for color name");
197 else {
198 *errorP = NULL;
199 ++(*colornameIndexP);
200 }
201 }
202 }
203 }
204
205
206
207 static void
openColornameFile(const char * const fileName,bool const mustOpen,FILE ** const filePP,const char ** const errorP)208 openColornameFile(const char * const fileName,
209 bool const mustOpen,
210 FILE ** const filePP,
211 const char ** const errorP) {
212
213 jmp_buf jmpbuf;
214 jmp_buf * origJmpbufP;
215
216 if (setjmp(jmpbuf) != 0) {
217 pm_asprintf(errorP, "Failed to open color name file");
218 } else {
219 pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
220
221 *filePP = pm_openColornameFile(fileName, mustOpen);
222
223 *errorP = NULL; /* Would have longjmped if there were a problem */
224 }
225 pm_setjmpbuf(origJmpbufP);
226 }
227
228
229
230 static void
readOpenColorFile(FILE * const colorFileP,unsigned int * const nColorsP,const char ** const colornames,pixel * const colors,colorhash_table const cht,const char ** const errorP)231 readOpenColorFile(FILE * const colorFileP,
232 unsigned int * const nColorsP,
233 const char ** const colornames,
234 pixel * const colors,
235 colorhash_table const cht,
236 const char ** const errorP) {
237 /*----------------------------------------------------------------------------
238 Read the color dictionary file *colorFileP and add the colors in it
239 to colornames[], colors[], and 'cht'.
240
241 colornames[] and colors[] must be allocated with MAXCOLORNAMES entries
242 at entry.
243
244 We may add colors to 'cht' even if we fail.
245 -----------------------------------------------------------------------------*/
246 unsigned int nColorsDone;
247 bool done;
248
249 nColorsDone = 0;
250 done = FALSE;
251 *errorP = NULL;
252
253 while (!done && !*errorP) {
254 struct colorfile_entry const ce = pm_colorget(colorFileP);
255
256 if (!ce.colorname)
257 done = TRUE;
258 else
259 processColorfileEntry(ce, cht, colornames, colors,
260 &nColorsDone, errorP);
261 }
262 *nColorsP = nColorsDone;
263
264 if (*errorP) {
265 unsigned int colorIndex;
266
267 for (colorIndex = 0; colorIndex < nColorsDone; ++colorIndex)
268 pm_strfree(colornames[colorIndex]);
269 }
270 }
271
272
273
274 static void
readColorFile(const char * const fileName,bool const mustOpen,unsigned int * const nColorsP,const char ** const colornames,pixel * const colors,colorhash_table const cht,const char ** const errorP)275 readColorFile(const char * const fileName,
276 bool const mustOpen,
277 unsigned int * const nColorsP,
278 const char ** const colornames,
279 pixel * const colors,
280 colorhash_table const cht,
281 const char ** const errorP) {
282 /*----------------------------------------------------------------------------
283 Read the color dictionary file named 'fileName' and add the colors in it
284 to colornames[], colors[], and 'cht'. Return as *nColorsP the number
285 of colors in it.
286
287 If the file is not openable (e.g. not file by that name exists), abort the
288 program if 'mustOpen' is true; otherwise, return values indicating a
289 dictionary with no colors.
290
291 colornames[] and colors[] must be allocated with MAXCOLORNAMES entries
292 at entry.
293
294 We may add colors to 'cht' even if we fail.
295 -----------------------------------------------------------------------------*/
296 FILE * colorFileP;
297
298 openColornameFile(fileName, mustOpen, &colorFileP, errorP);
299 if (!*errorP) {
300 if (colorFileP == NULL) {
301 /* Couldn't open it, but Caller says treat same as
302 empty file
303 */
304 *nColorsP = 0;
305 *errorP = NULL;
306 } else {
307 readOpenColorFile(colorFileP, nColorsP, colornames, colors, cht,
308 errorP);
309
310 fclose(colorFileP);
311 }
312 }
313 }
314
315
316
317 static void
readcolordict(const char * const fileName,bool const mustOpen,unsigned int * const nColorsP,const char *** const colornamesP,pixel ** const colorsP,colorhash_table * const chtP,const char ** const errorP)318 readcolordict(const char * const fileName,
319 bool const mustOpen,
320 unsigned int * const nColorsP,
321 const char *** const colornamesP,
322 pixel ** const colorsP,
323 colorhash_table * const chtP,
324 const char ** const errorP) {
325
326 const char ** colornames;
327
328 colornames = allocColorNames();
329
330 if (colornames == NULL)
331 pm_asprintf(errorP, "Unable to allocate space for colorname table.");
332 else {
333 pixel * colors;
334
335 MALLOCARRAY(colors, MAXCOLORNAMES);
336
337 if (colors == NULL)
338 pm_asprintf(errorP, "Unable to allocate space for color table.");
339 else {
340 colorhash_table cht;
341
342 cht = allocColorHash();
343
344 if (cht == NULL)
345 pm_asprintf(errorP, "Unable to allocate space for color hash");
346 else {
347 readColorFile(fileName, mustOpen,
348 nColorsP, colornames, colors, cht,
349 errorP);
350
351 if (*errorP)
352 ppm_freecolorhash(cht);
353 else
354 *chtP = cht;
355 }
356 if (*errorP)
357 free(colors);
358 else
359 *colorsP = colors;
360 }
361 if (*errorP)
362 free(colornames);
363 else
364 *colornamesP = colornames;
365 }
366 }
367
368
369
370 void
ppm_readcolordict(const char * const fileName,int const mustOpen,unsigned int * const nColorsP,const char *** const colornamesP,pixel ** const colorsP,colorhash_table * const chtP)371 ppm_readcolordict(const char * const fileName,
372 int const mustOpen,
373 unsigned int * const nColorsP,
374 const char *** const colornamesP,
375 pixel ** const colorsP,
376 colorhash_table * const chtP) {
377 /*----------------------------------------------------------------------------
378 Read the color dictionary from the file named 'fileName'. If we can't open
379 the file (e.g. because it does not exist), and 'mustOpen' is false, return
380 an empty dictionary (it contains no colors). But if 'mustOpen' is true,
381 abort the program instead of returning an empty dictionary.
382
383 Return as *nColorsP the number of colors in the dictionary.
384
385 Return as *colornamesP the names of those colors. *colornamesP is a
386 malloced array that Caller must free with ppm_freecolornames().
387 The first *nColorsP entries are valid; *chtP contains indices into this
388 array.
389
390 Return as *colorsP the colors. *colorsP is a malloced array of size
391 MAXCOLORS with the first elements filled in and the rest undefined.
392
393 Return as *chtP a color hash table mapping each color in the dictionary
394 to the index into *colornamesP for the name of the color.
395
396 Each of 'nColorsP, 'colornamesP', and 'colorsP' may be null, in which case
397 we do not return the corresponding information (or allocate memory for it).
398 -----------------------------------------------------------------------------*/
399 colorhash_table cht;
400 const char ** colornames;
401 pixel * colors;
402 unsigned int nColors;
403 const char * error;
404
405 readcolordict(fileName, mustOpen, &nColors, &colornames, &colors, &cht,
406 &error);
407
408 if (error) {
409 pm_errormsg("%s", error);
410 pm_strfree(error);
411 pm_longjmp();
412 } else {
413 if (chtP)
414 *chtP = cht;
415 else
416 ppm_freecolorhash(cht);
417 if (colornamesP)
418 *colornamesP = colornames;
419 else
420 ppm_freecolornames(colornames);
421 if (colorsP)
422 *colorsP = colors;
423 else
424 ppm_freerow(colors);
425 if (nColorsP)
426 *nColorsP = nColors;
427 }
428 }
429
430
431
432 void
ppm_readcolornamefile(const char * const fileName,int const mustOpen,colorhash_table * const chtP,const char *** const colornamesP)433 ppm_readcolornamefile(const char * const fileName,
434 int const mustOpen,
435 colorhash_table * const chtP,
436 const char *** const colornamesP) {
437
438 ppm_readcolordict(fileName, mustOpen, NULL, colornamesP, NULL, chtP);
439 }
440
441
442
443 void
ppm_freecolornames(const char ** const colornames)444 ppm_freecolornames(const char ** const colornames) {
445
446 unsigned int i;
447
448 for (i = 0; i < MAXCOLORNAMES; ++i)
449 if (colornames[i])
450 free((char *)colornames[i]);
451
452 free(colornames);
453 }
454
455
456
457 static unsigned int
nonnegative(unsigned int const arg)458 nonnegative(unsigned int const arg) {
459
460 if ((int)(arg) < 0)
461 return 0;
462 else
463 return arg;
464 }
465
466
467
468 pixel
ppm_color_from_ycbcr(unsigned int const y,int const cb,int const cr)469 ppm_color_from_ycbcr(unsigned int const y,
470 int const cb,
471 int const cr) {
472 /*----------------------------------------------------------------------------
473 Return the color that has luminance 'y', blue chrominance 'cb', and
474 red chrominance 'cr'.
475
476 The 3 input values can be on any scale (as long as it's the same
477 scale for all 3) and the maxval of the returned pixel value is the
478 same as that for the input values.
479
480 Rounding may cause an output value to be greater than the maxval.
481 -----------------------------------------------------------------------------*/
482 pixel retval;
483
484 PPM_ASSIGN(retval,
485 y + 1.4022 * cr,
486 nonnegative(y - 0.7145 * cr - 0.3456 * cb),
487 y + 1.7710 * cb
488 );
489
490 return retval;
491 }
492
493
494
495 pixel
ppm_color_from_hsv(struct hsv const hsv,pixval const maxval)496 ppm_color_from_hsv(struct hsv const hsv,
497 pixval const maxval) {
498
499 pixel retval;
500 double R, G, B;
501
502 if (hsv.s == 0) {
503 R = hsv.v;
504 G = hsv.v;
505 B = hsv.v;
506 } else {
507 unsigned int const sectorSize = 60;
508 /* Color wheel is divided into six 60 degree sectors. */
509 unsigned int const sector = (hsv.h/sectorSize);
510 /* The sector in which our color resides. Value is in 0..5 */
511 double const f = (hsv.h - sector*sectorSize)/60;
512 /* The fraction of the way the color is from one side of
513 our sector to the other side, going clockwise. Value is
514 in [0, 1).
515 */
516 double const m = (hsv.v * (1 - hsv.s));
517 double const n = (hsv.v * (1 - (hsv.s * f)));
518 double const k = (hsv.v * (1 - (hsv.s * (1 - f))));
519
520 switch (sector) {
521 case 0:
522 R = hsv.v;
523 G = k;
524 B = m;
525 break;
526 case 1:
527 R = n;
528 G = hsv.v;
529 B = m;
530 break;
531 case 2:
532 R = m;
533 G = hsv.v;
534 B = k;
535 break;
536 case 3:
537 R = m;
538 G = n;
539 B = hsv.v;
540 break;
541 case 4:
542 R = k;
543 G = m;
544 B = hsv.v;
545 break;
546 case 5:
547 R = hsv.v;
548 G = m;
549 B = n;
550 break;
551 default:
552 pm_error("Invalid H value passed to color_from_HSV: %f", hsv.h);
553 }
554 }
555 PPM_ASSIGN(retval,
556 ppm_unnormalize(R, maxval),
557 ppm_unnormalize(G, maxval),
558 ppm_unnormalize(B, maxval));
559
560 return retval;
561 }
562
563
564
565 struct hsv
ppm_hsv_from_color(pixel const color,pixval const maxval)566 ppm_hsv_from_color(pixel const color,
567 pixval const maxval) {
568
569 double const epsilon = 1e-5;
570
571 double const R = (double)PPM_GETR(color) / maxval;
572 double const G = (double)PPM_GETG(color) / maxval;
573 double const B = (double)PPM_GETB(color) / maxval;
574
575 enum hueSector {SECTOR_RED, SECTOR_GRN, SECTOR_BLU};
576 enum hueSector hueSector;
577
578 struct hsv retval;
579 double range;
580
581 if (R >= G) {
582 if (R >= B) {
583 hueSector = SECTOR_RED;
584 retval.v = R;
585 } else {
586 hueSector = SECTOR_BLU;
587 retval.v = B;
588 }
589 } else {
590 if (G >= B) {
591 hueSector = SECTOR_GRN;
592 retval.v = G;
593 } else {
594 hueSector = SECTOR_BLU;
595 retval.v = B;
596 }
597 }
598
599 range = retval.v - MIN(R, MIN(G, B));
600
601 if (retval.v < epsilon)
602 retval.s = 0.0;
603 else
604 retval.s = range/retval.v;
605
606 if (range < epsilon)
607 /* It's gray, so hue really has no meaning. We arbitrarily pick 0 */
608 retval.h = 0.0;
609 else {
610 double const cr = (retval.v - R) / range;
611 double const cg = (retval.v - G) / range;
612 double const cb = (retval.v - B) / range;
613
614 double angle; /* hue angle, in range -30 - +330 */
615
616 switch(hueSector) {
617 case SECTOR_RED: angle = 0.0 + 60.0 * (cb - cg); break;
618 case SECTOR_GRN: angle = 120.0 + 60.0 * (cr - cb); break;
619 case SECTOR_BLU: angle = 240.0 + 60.0 * (cg - cr); break;
620 }
621 retval.h = angle >= 0.0 ? angle : 360 + angle;
622 }
623
624 return retval;
625 }
626
627
628