about summary refs log tree commit diff
path: root/lib/libppmcolor.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /lib/libppmcolor.c
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'lib/libppmcolor.c')
-rw-r--r--lib/libppmcolor.c710
1 files changed, 710 insertions, 0 deletions
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
new file mode 100644
index 00000000..ff1a1e67
--- /dev/null
+++ b/lib/libppmcolor.c
@@ -0,0 +1,710 @@
+/*
+** Copyright (C) 1989 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "colorname.h"
+
+
+static void
+computeHexTable(int hexit[]) {
+    int i;
+    for ( i = 0; i < 256; ++i )
+        hexit[i] = 1234567890;
+    hexit['0'] = 0;
+    hexit['1'] = 1;
+    hexit['2'] = 2;
+    hexit['3'] = 3;
+    hexit['4'] = 4;
+    hexit['5'] = 5;
+    hexit['6'] = 6;
+    hexit['7'] = 7;
+    hexit['8'] = 8;
+    hexit['9'] = 9;
+    hexit['a'] = hexit['A'] = 10;
+    hexit['b'] = hexit['B'] = 11;
+    hexit['c'] = hexit['C'] = 12;
+    hexit['d'] = hexit['D'] = 13;
+    hexit['e'] = hexit['E'] = 14;
+    hexit['f'] = hexit['F'] = 15;
+}
+
+
+
+static long
+invRgbnorm(pixval       const rgb,
+           pixval       const maxval,
+           unsigned int const hexDigits) {
+/*----------------------------------------------------------------------------
+  This is the inverse of 'rgbnorm', below.
+-----------------------------------------------------------------------------*/
+    long retval;
+
+    switch (hexDigits) {
+    case 1:
+        retval = (long)((double) rgb * 15 / maxval + 0.5);
+        break;
+    case 2:
+        retval = (long) ((double) rgb * 255 / maxval + 0.5);
+        break;
+    case 3:
+        retval = (long) ((double) rgb * 4095 / maxval + 0.5);
+        break;
+    case 4:
+        retval = (long) ((double) rgb * 65535UL / maxval + 0.5);
+        break;
+    default:
+        pm_message("Internal error in invRgbnorm()");
+        abort();
+    }
+    return retval;
+}
+
+
+
+static pixval
+rgbnorm(long         const rgb, 
+        pixval       const maxval, 
+        unsigned int const hexDigitCount, 
+        bool         const closeOk,
+        const char * const colorname) {
+/*----------------------------------------------------------------------------
+   Normalize the color (r, g, or b) value 'rgb', which was specified
+   with 'hexDigitCount' digits, to a maxval of 'maxval'.  If the
+   number of digits isn't valid, issue an error message and identify
+   the complete color color specification in error as 'colorname'.
+
+   For example, if the user says 0ff/000/000 and the maxval is 100,
+   then rgb is 0xff, n is 3, and our result is 
+   0xff / (16**3-1) * 100 = 6.
+
+-----------------------------------------------------------------------------*/
+    pixval retval;
+
+    assert(hexDigitCount > 0);
+
+    switch (hexDigitCount) {
+    case 1:
+        retval = (pixval)((double) rgb * maxval / 15 + 0.5);
+        break;
+    case 2:
+        retval = (pixval) ((double) rgb * maxval / 255 + 0.5);
+        break;
+    case 3:
+        retval = (pixval) ((double) rgb * maxval / 4095 + 0.5);
+        break;
+    case 4:
+        retval = (pixval) ((double) rgb * maxval / 65535L + 0.5);
+        break;
+    default:
+        pm_error("color specifier '%s' has too many digits", colorname);
+    }
+
+    if (!closeOk) {
+        long const newrgb = invRgbnorm(retval, maxval, hexDigitCount);
+        if (newrgb != rgb)
+            pm_message("WARNING: Component 0x%lx of color '%s' "
+                       "cannot be represented precisely with maxval %u.  "
+                       "Approximating as %u.",
+                       rgb, colorname, maxval, retval);
+    }
+    return retval;
+}
+
+
+
+static void
+parseNewHexX11(const char       colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    int hexit[256];
+
+    const char* cp;
+    pixval r,g,b;
+    pixval rNorm, gNorm, bNorm;
+
+    int i;
+
+    computeHexTable(hexit);
+
+    cp = colorname + 4;
+
+    for (i = 0, r = 0; *cp != '/'; ++i, ++cp)
+        r = r * 16 + hexit[(int)*cp];
+    rNorm = rgbnorm(r, maxval, i, closeOk, colorname);
+
+    for (i = 0, g = 0,++cp; *cp != '/'; ++i, ++cp )
+        g = g * 16 + hexit[(int)*cp];
+    gNorm = rgbnorm(g, maxval, i, closeOk, colorname);
+
+    for (i = 0, b = 0, ++cp; *cp != '\0'; ++i, ++cp )
+        b = b * 16 + hexit[(int)*cp];
+    bNorm = rgbnorm(b, maxval, i, closeOk, colorname);
+
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+static void
+parseNewDecX11(char       const colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    float const epsilon = 1.0/65536.0;
+    float fr, fg, fb;
+    pixval rNorm, gNorm, bNorm;
+
+    if (sscanf( colorname, "rgbi:%f/%f/%f", &fr, &fg, &fb) != 3)
+        pm_error("invalid color specifier '%s'", colorname);
+    if (fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
+        || fb < 0.0 || fb > 1.0)
+        pm_error("invalid color specifier '%s' - "
+                 "values must be between 0.0 and 1.0", colorname );
+
+    rNorm = fr * maxval + 0.5;
+    gNorm = fg * maxval + 0.5;
+    bNorm = fb * maxval + 0.5;
+
+    if (!closeOk) {
+        if (fabs((double)rNorm/maxval - fr) > epsilon ||
+            fabs((double)gNorm/maxval - fg) > epsilon ||
+            fabs((double)bNorm/maxval - fb) > epsilon)
+            pm_message("WARNING: Color '%s' cannot be represented "
+                       "precisely with maxval %u.  "
+                       "Approximating as (%u,%u,%u).",
+                       colorname, maxval, rNorm, gNorm, bNorm);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+static void
+parseOldX11(const char       colorname[], 
+            pixval     const maxval,
+            bool       const closeOk,
+            pixel *    const colorP) {
+
+    int hexit[256];
+    long r,g,b;
+    pixval rNorm, gNorm, bNorm;
+    
+    computeHexTable(hexit);
+
+    switch (strlen(colorname) - 1 /* (Number of hex digits) */) {
+    case 3:
+        r = hexit[(int)colorname[1]];
+        g = hexit[(int)colorname[2]];
+        b = hexit[(int)colorname[3]];
+        rNorm = rgbnorm(r, maxval, 1, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 1, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 1, closeOk, colorname);
+        break;
+
+    case 6:
+        r = (hexit[(int)colorname[1]] << 4 ) + hexit[(int)colorname[2]];
+        g = (hexit[(int)colorname[3]] << 4 ) + hexit[(int)colorname[4]];
+        b = (hexit[(int)colorname[5]] << 4 ) + hexit[(int)colorname[6]];
+        rNorm = rgbnorm(r, maxval, 2, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 2, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 2, closeOk, colorname);
+        break;
+
+    case 9:
+        r = (hexit[(int)colorname[1]] << 8) +
+            (hexit[(int)colorname[2]] << 4) +
+            (hexit[(int)colorname[3]] << 0);
+        g = (hexit[(int)colorname[4]] << 8) + 
+            (hexit[(int)colorname[5]] << 4) +
+            (hexit[(int)colorname[6]] << 0);
+        b = (hexit[(int)colorname[7]] << 8) + 
+            (hexit[(int)colorname[8]] << 4) +
+            (hexit[(int)colorname[9]] << 0);
+        rNorm = rgbnorm(r, maxval, 3, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 3, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 3, closeOk, colorname);
+        break;
+
+    case 12:
+        r = (hexit[(int)colorname[1]] << 12) + 
+            (hexit[(int)colorname[2]] <<  8) +
+            (hexit[(int)colorname[3]] <<  4) + hexit[(int)colorname[4]];
+        g = (hexit[(int)colorname[5]] << 12) + 
+            (hexit[(int)colorname[6]] <<  8) +
+            (hexit[(int)colorname[7]] <<  4) + hexit[(int)colorname[8]];
+        b = (hexit[(int)colorname[9]] << 12) + 
+            (hexit[(int)colorname[10]] << 8) +
+            (hexit[(int)colorname[11]] << 4) + hexit[(int)colorname[12]];
+        rNorm = rgbnorm(r, maxval, 4, closeOk, colorname);
+        gNorm = rgbnorm(g, maxval, 4, closeOk, colorname);
+        bNorm = rgbnorm(b, maxval, 4, closeOk, colorname);
+        break;
+
+    default:
+        pm_error("invalid color specifier '%s'", colorname);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+
+static void
+parseOldX11Dec(const char       colorname[], 
+               pixval     const maxval,
+               bool       const closeOk,
+               pixel *    const colorP) {
+
+    float const epsilon = 1.0/65536.0;
+
+    float fr, fg, fb;
+    pixval rNorm, gNorm, bNorm;
+
+    if (sscanf(colorname, "%f,%f,%f", &fr, &fg, &fb) != 3)
+        pm_error("invalid color specifier '%s'", colorname);
+    if (fr < 0.0 || fr > 1.0 || fg < 0.0 || fg > 1.0 
+        || fb < 0.0 || fb > 1.0)
+        pm_error("invalid color specifier '%s' - "
+                 "values must be between 0.0 and 1.0", colorname );
+
+    rNorm = fr * maxval + 0.5;
+    gNorm = fg * maxval + 0.5;
+    bNorm = fb * maxval + 0.5;
+
+    if (!closeOk) {
+        if (fabs((float)rNorm/maxval - fr) > epsilon ||
+            fabs((float)gNorm/maxval - fg) > epsilon ||
+            fabs((float)bNorm/maxval - fb) > epsilon)
+            pm_message("WARNING: Color '%s' cannot be represented "
+                       "precisely with maxval %u.  "
+                       "Approximating as (%u,%u,%u).",
+                       colorname, maxval, rNorm, gNorm, bNorm);
+    }
+    PPM_ASSIGN(*colorP, rNorm, gNorm, bNorm);
+}
+
+
+
+pixel
+ppm_parsecolor2(const char * const colorname,
+                pixval       const maxval,
+                int          const closeOk) {
+
+    pixel color;
+    
+    if (strncmp(colorname, "rgb:", 4) == 0)
+        /* It's a new-X11-style hexadecimal rgb specifier. */
+        parseNewHexX11(colorname, maxval, closeOk, &color);
+    else if (strncmp(colorname, "rgbi:", 5) == 0)
+        /* It's a new-X11-style decimal/float rgb specifier. */
+        parseNewDecX11(colorname, maxval, closeOk, &color);
+    else if (colorname[0] == '#')
+        /* It's an old-X11-style hexadecimal rgb specifier. */
+        parseOldX11(colorname, maxval, closeOk, &color);
+    else if ((colorname[0] >= '0' && colorname[0] <= '9') ||
+             colorname[0] == '.')
+        /* It's an old-style decimal/float rgb specifier. */
+        parseOldX11Dec(colorname, maxval, closeOk, &color);
+    else 
+        /* Must be a name from the X-style rgb file. */
+        pm_parse_dictionary_name(colorname, maxval, closeOk, &color);
+    
+    return color;
+}
+
+
+
+pixel
+ppm_parsecolor(const char * const colorname, 
+               pixval       const maxval) {
+
+    return ppm_parsecolor2(colorname, maxval, TRUE);
+}
+
+
+
+char*
+ppm_colorname(const pixel* const colorP, 
+              pixval       const maxval, 
+              int          const hexok)   {
+
+    int r, g, b;
+    FILE* f;
+    static char colorname[200];
+
+    if (maxval == 255) {
+        r = PPM_GETR(*colorP);
+        g = PPM_GETG(*colorP);
+        b = PPM_GETB(*colorP);
+    } else {
+        r = (int) PPM_GETR(*colorP) * 255 / (int) maxval;
+        g = (int) PPM_GETG(*colorP) * 255 / (int) maxval;
+        b = (int) PPM_GETB(*colorP) * 255 / (int) maxval;
+    }
+
+    f = pm_openColornameFile(NULL, !hexok);
+    if (f != NULL) {
+        int best_diff, this_diff;
+        bool eof;
+
+        best_diff = 32767;
+        eof = FALSE;
+        while (!eof && best_diff > 0 ) {
+            struct colorfile_entry const ce = pm_colorget(f);
+            if (ce.colorname)  {
+                this_diff = abs(r - ce.r) + abs(g - ce.g) + abs(b - ce.b);
+                if (this_diff < best_diff) {
+                    best_diff = this_diff;
+                    strcpy(colorname, ce.colorname);
+                }
+            } else
+                eof = TRUE;
+        }
+        fclose(f);
+        if (best_diff != 32767 && (best_diff == 0 || ! hexok))
+            return colorname;
+    }
+
+    /* Color lookup failed, but caller is willing to take an X11-style
+       hex specifier, so return that.
+    */
+    sprintf(colorname, "#%02x%02x%02x", r, g, b);
+    return colorname;
+}
+
+
+
+#define MAXCOLORNAMES 1000u
+
+static void
+processColorfileEntry(struct colorfile_entry const ce,
+                      colorhash_table        const cht,
+                      const char **          const colornames,
+                      pixel *                const colors,
+                      unsigned int *         const colornameIndexP) {
+
+    if (*colornameIndexP >= MAXCOLORNAMES)
+        pm_error("Too many colors in colorname dictionary.  "
+                 "Max allowed is %u", MAXCOLORNAMES);
+    else {
+        pixel color;
+
+        PPM_ASSIGN(color, ce.r, ce.g, ce.b);
+
+        if (ppm_lookupcolor(cht, &color) >= 0) {
+            /* The color is already in the hash, which means we saw it
+               earlier in the file.  We prefer the first name that the
+               file gives for each color, so we just ignore the
+               current entry.  
+            */
+        } else {
+            ppm_addtocolorhash(cht, &color, *colornameIndexP);
+            colornames[*colornameIndexP] = strdup(ce.colorname);
+            colors[*colornameIndexP] = color;
+            if (colornames[*colornameIndexP] == NULL)
+                pm_error("Unable to allocate space for color name");
+            ++(*colornameIndexP);
+        }
+    }
+}
+
+
+
+static void
+readcolordict(const char *    const fileName,
+              bool            const mustOpen,
+              unsigned int *  const nColorsP,
+              const char **   const colornames,
+              pixel * const   colors,
+              colorhash_table const cht) {
+
+    FILE * colorFile;
+
+    colorFile = pm_openColornameFile(fileName, mustOpen);
+
+    if (colorFile != NULL) {
+        unsigned int colornameIndex;
+        bool done;
+
+        colornameIndex = 0;  /* initial value */
+        done = FALSE;
+        while (!done) {
+            struct colorfile_entry const ce = pm_colorget(colorFile);
+
+            if (!ce.colorname)  
+                done = TRUE;
+            else 
+                processColorfileEntry(ce, cht, colornames, colors,
+                                      &colornameIndex);
+        }
+
+        *nColorsP = colornameIndex;
+
+        while (colornameIndex < MAXCOLORNAMES)
+            colornames[colornameIndex++] = NULL;
+
+        fclose(colorFile);
+    }
+}
+
+
+
+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) {
+
+    colorhash_table cht;
+    const char ** colornames;
+    pixel * colors;
+    unsigned int nColors;
+
+    cht = ppm_alloccolorhash();
+
+    MALLOCARRAY(colornames, MAXCOLORNAMES);
+
+    colors = ppm_allocrow(MAXCOLORNAMES);
+
+    if (colornames == NULL)
+        pm_error("Unable to allocate space for colorname table.");
+
+    readcolordict(fileName, mustOpen, &nColors, colornames, colors, cht);
+
+    if (chtP)
+        *chtP = cht;
+    else
+        ppm_freecolorhash(cht);
+    if (colornamesP)
+        *colornamesP = colornames;
+    else
+        ppm_freecolornames(colornames);
+    if (colorsP)
+        *colorsP = colors;
+    else
+        ppm_freerow(colors);
+    if (nColorsP)
+        *nColorsP = nColors;
+}
+
+
+
+void
+ppm_readcolornamefile(const char *      const fileName, 
+                      int               const mustOpen,
+                      colorhash_table * const chtP, 
+                      const char ***    const colornamesP) {
+
+    ppm_readcolordict(fileName, mustOpen, NULL, colornamesP, NULL, chtP);
+}
+
+
+
+void
+ppm_freecolornames(const char ** const colornames) {
+
+    unsigned int i;
+
+    for (i = 0; i < MAXCOLORNAMES; ++i)
+        if (colornames[i])
+            free((char *)colornames[i]);
+
+    free(colornames);
+}
+
+
+
+static unsigned int 
+nonnegative(unsigned int const arg) {
+
+    if ((int)(arg) < 0)
+        return 0;
+    else
+        return arg;
+}
+
+
+
+pixel
+ppm_color_from_ycbcr(unsigned int const y, 
+                     int          const cb, 
+                     int          const cr) {
+/*----------------------------------------------------------------------------
+   Return the color that has luminance 'y', blue chrominance 'cb', and
+   red chrominance 'cr'.
+
+   The 3 input values can be on any scale (as long as it's the same
+   scale for all 3) and the maxval of the returned pixel value is the
+   same as that for the input values.
+
+   Rounding may cause an output value to be greater than the maxval.
+-----------------------------------------------------------------------------*/
+    pixel retval;
+
+    PPM_ASSIGN(retval, 
+               y + 1.4022 * cr,
+               nonnegative(y - 0.7145 * cr - 0.3456 * cb),
+               y + 1.7710 * cb
+        );
+    
+    return retval;
+}
+
+
+
+pixel
+ppm_color_from_hsv(struct hsv const hsv,
+                   pixval     const maxval) {
+
+    pixel retval;
+    double R, G, B;
+
+    if (hsv.s == 0) {
+        R = hsv.v;
+        G = hsv.v;
+        B = hsv.v;
+    } else {
+        unsigned int const sectorSize = 60;
+            /* Color wheel is divided into six 60 degree sectors. */
+        unsigned int const sector = (hsv.h/sectorSize);
+            /* The sector in which our color resides.  Value is in 0..5 */
+        double const f = (hsv.h - sector*sectorSize)/60;
+            /* The fraction of the way the color is from one side of
+               our sector to the other side, going clockwise.  Value is
+               in [0, 1).
+            */
+        double const m = (hsv.v * (1 - hsv.s));
+        double const n = (hsv.v * (1 - (hsv.s * f)));
+        double const k = (hsv.v * (1 - (hsv.s * (1 - f))));
+
+        switch (sector) {
+        case 0:
+            R = hsv.v;
+            G = k;
+            B = m;
+            break;
+        case 1:
+            R = n;
+            G = hsv.v;
+            B = m;
+            break;
+        case 2:
+            R = m;
+            G = hsv.v;
+            B = k;
+            break;
+        case 3:
+            R = m;
+            G = n;
+            B = hsv.v;
+            break;
+        case 4:
+            R = k;
+            G = m;
+            B = hsv.v;
+            break;
+        case 5:
+            R = hsv.v;
+            G = m;
+            B = n;
+            break;
+        default:
+            pm_error("Invalid H value passed to color_from_HSV: %f", hsv.h);
+        }
+    }
+    PPM_ASSIGN(retval, 
+               ROUNDU(R * maxval),
+               ROUNDU(G * maxval),
+               ROUNDU(B * maxval));
+
+    return retval;
+}
+
+
+
+struct hsv
+ppm_hsv_from_color(pixel  const color,
+                   pixval const maxval) {
+
+    double const epsilon = 1e-5;
+
+    double const R = (double)PPM_GETR(color) / maxval;
+    double const G = (double)PPM_GETG(color) / maxval;
+    double const B = (double)PPM_GETB(color) / maxval;
+
+    enum hueSector {SECTOR_RED, SECTOR_GRN, SECTOR_BLU};
+    enum hueSector hueSector;
+
+    struct hsv retval;
+    double range;
+
+    if (R >= G) {
+        if (R >= B) {
+            hueSector = SECTOR_RED;
+            retval.v = R;
+        } else {
+            hueSector = SECTOR_BLU;
+            retval.v = B;
+        }
+    } else {
+        if (G >= B) {
+            hueSector = SECTOR_GRN;
+            retval.v = G;
+        } else {
+            hueSector = SECTOR_BLU;
+            retval.v = B;
+        }
+    }
+
+    range = retval.v - MIN(R, MIN(G, B));
+
+    if (retval.v < epsilon)
+        retval.s = 0.0;
+    else
+        retval.s = range/retval.v;
+
+    if (range < epsilon)
+        /* It's gray, so hue really has no meaning.  We arbitrarily pick 0 */
+        retval.h = 0.0;
+    else {
+        double const cr = (retval.v - R) / range;
+        double const cg = (retval.v - G) / range;
+        double const cb = (retval.v - B) / range;
+
+        double angle;  /* hue angle, in range -30 - +330 */
+
+        switch(hueSector) {
+        case SECTOR_RED: angle =   0.0 + 60.0 * (cb - cg); break;
+        case SECTOR_GRN: angle = 120.0 + 60.0 * (cr - cb); break;
+        case SECTOR_BLU: angle = 240.0 + 60.0 * (cg - cr); break;
+        }
+        retval.h = angle >= 0.0 ? angle : 360 + angle;
+    }
+
+    return retval;
+}
+
+