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