diff options
Diffstat (limited to 'lib/libpamcolor.c')
-rw-r--r-- | lib/libpamcolor.c | 643 |
1 files changed, 619 insertions, 24 deletions
diff --git a/lib/libpamcolor.c b/lib/libpamcolor.c index f3ca9a86..e1a24c66 100644 --- a/lib/libpamcolor.c +++ b/lib/libpamcolor.c @@ -12,27 +12,349 @@ offset stuff. */ #define _FILE_OFFSET_BITS 64 -#define _LARGE_FILES - -#define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ -#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ +#define _LARGE_FILES #include <string.h> #include <limits.h> +#include <math.h> #include "netpbm/pm_c_util.h" +#include "netpbm/mallocvar.h" +#include "netpbm/nstring.h" +#include "netpbm/colorname.h" + +#include "netpbm/pam.h" +#include "netpbm/ppm.h" + + + +static unsigned int +hexDigitValue(char const digit) { + + switch (digit) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: + pm_error("Invalid hex digit '%c'", digit); + return 0; /* Defeat compiler warning */ + } +} + + + +static void +parseHexDigits(const char * const string, + char const delim, + samplen * const nP, + unsigned int * const digitCtP) { + + unsigned int digitCt; + unsigned long n; + unsigned long range; + /* 16 for one hex digit, 256 for two hex digits, etc. */ + + for (digitCt = 0, n = 0, range = 1; string[digitCt] != delim; ) { + char const digit = string[digitCt]; + if (digit == '\0') + pm_error("rgb: color spec '%s' ends prematurely", string); + else { + n = n * 16 + hexDigitValue(digit); + range *= 16; + ++digitCt; + } + } + if (range <= 1) + pm_error("No digits where hexadecimal number expected in rgb: " + "color spec '%s'", string); + + *nP = (samplen) n / (range-1); + *digitCtP = digitCt; +} + -#include "pam.h" -#include "ppm.h" + +static void +parseNewHexX11(char const colorname[], + tuplen const color) { +/*---------------------------------------------------------------------------- + Determine what color colorname[] specifies in the new style hex + color specification format (e.g. rgb:55/40/55). + + Return that color as *colorP. + + Assume colorname[] starts with "rgb:", but otherwise it might be + gibberish. +-----------------------------------------------------------------------------*/ + const char * cp; + unsigned int digitCt; + + cp = &colorname[4]; + + parseHexDigits(cp, '/', &color[PAM_RED_PLANE], &digitCt); + + cp += digitCt; + ++cp; /* Skip the slash */ + + parseHexDigits(cp, '/', &color[PAM_GRN_PLANE], &digitCt); + + cp += digitCt; + ++cp; /* Skip the slash */ + + parseHexDigits(cp, '\0', &color[PAM_BLU_PLANE], &digitCt); +} + + + +static bool +isNormal(samplen const arg) { + + return arg >= 0.0 && arg <= 1.0; +} + + + +static void +parseNewDecX11(const char * const colorname, + tuplen const color) { + + int rc; + + rc = sscanf(colorname, "rgbi:%f/%f/%f", + &color[PAM_RED_PLANE], + &color[PAM_GRN_PLANE], + &color[PAM_BLU_PLANE]); + + if (rc != 3) + pm_error("invalid color specifier '%s'", colorname); + + if (!(isNormal(color[PAM_RED_PLANE]) && + isNormal(color[PAM_GRN_PLANE]) && + isNormal(color[PAM_BLU_PLANE]))) { + pm_error("invalid color specifier '%s' - " + "values must be between 0.0 and 1.0", colorname); + } +} + + + +static void +parseInteger(const char * const colorname, + tuplen const color) { + + unsigned int maxval; + unsigned int r, g, b; + int rc; + + rc = sscanf(colorname, "rgb-%u:%u/%u/%u", &maxval, &r, &g, &b); + + if (rc != 4) + pm_error("invalid color specifier '%s'. " + "If it starts with \"rgb-\", then it must have the format " + "rgb-<MAXVAL>:<RED>:<GRN>:<BLU>, " + "where <MAXVAL>, <RED>, <GRN>, and <BLU> are " + "unsigned integers", + colorname); + + if (maxval < 1 || maxval > PNM_OVERALLMAXVAL) + pm_error("Maxval in color specification '%s' is %u, " + "which is invalid because it is not between " + "1 and %u, inclusive", + colorname, maxval, PNM_OVERALLMAXVAL); + + if (r > maxval) + pm_error("Red value in color specification '%s' is %u, " + "whcih is invalid because the specified maxval is %u", + colorname, r, maxval); + if (g > maxval) + pm_error("Green value in color specification '%s' is %u, " + "whcih is invalid because the specified maxval is %u", + colorname, g, maxval); + if (b > maxval) + pm_error("Blue value in color specification '%s' is %u, " + "whcih is invalid because the specified maxval is %u", + colorname, b, maxval); + + color[PAM_RED_PLANE] = (float)r/maxval; + color[PAM_GRN_PLANE] = (float)g/maxval; + color[PAM_BLU_PLANE] = (float)b/maxval; +} + + + +static void +parseOldX11(const char * const colorname, + tuplen const color) { +/*---------------------------------------------------------------------------- + Return as *colorP the color specified by the old X11 style color + specififier colorname[] (e.g. #554055). +-----------------------------------------------------------------------------*/ + if (!pm_strishex(&colorname[1])) + pm_error("Non-hexadecimal characters in #-type color specification"); + + switch (strlen(colorname) - 1 /* (Number of hex digits) */) { + case 3: + color[PAM_RED_PLANE] = (samplen)hexDigitValue(colorname[1])/15; + color[PAM_GRN_PLANE] = (samplen)hexDigitValue(colorname[2])/15; + color[PAM_BLU_PLANE] = (samplen)hexDigitValue(colorname[3])/15; + break; + + case 6: + color[PAM_RED_PLANE] = + ((samplen)(hexDigitValue(colorname[1]) << 4) + + (samplen)(hexDigitValue(colorname[2]) << 0)) + / 255; + color[PAM_GRN_PLANE] = + ((samplen)(hexDigitValue(colorname[3]) << 4) + + (samplen)(hexDigitValue(colorname[4]) << 0)) + / 255; + color[PAM_BLU_PLANE] = + ((samplen)(hexDigitValue(colorname[5]) << 4) + + (samplen)(hexDigitValue(colorname[6]) << 0)) + / 255; + break; + + case 9: + color[PAM_RED_PLANE] = + ((samplen)(hexDigitValue(colorname[1]) << 8) + + (samplen)(hexDigitValue(colorname[2]) << 4) + + (samplen)(hexDigitValue(colorname[3]) << 0)) + / 4095; + color[PAM_GRN_PLANE] = + ((samplen)(hexDigitValue(colorname[4]) << 8) + + (samplen)(hexDigitValue(colorname[5]) << 4) + + (samplen)(hexDigitValue(colorname[6]) << 0)) + / 4095; + color[PAM_BLU_PLANE] = + ((samplen)(hexDigitValue(colorname[7]) << 8) + + (samplen)(hexDigitValue(colorname[8]) << 4) + + (samplen)(hexDigitValue(colorname[9]) << 0)) + / 4095; + break; + + case 12: + color[PAM_RED_PLANE] = + ((samplen)(hexDigitValue(colorname[1]) << 12) + + (samplen)(hexDigitValue(colorname[2]) << 8) + + (samplen)(hexDigitValue(colorname[3]) << 4) + + (samplen)(hexDigitValue(colorname[4]) << 0)) + / 65535; + color[PAM_GRN_PLANE] = + ((samplen)(hexDigitValue(colorname[5]) << 12) + + (samplen)(hexDigitValue(colorname[6]) << 8) + + (samplen)(hexDigitValue(colorname[7]) << 4) + + (samplen)(hexDigitValue(colorname[8]) << 0)) + / 65535; + color[PAM_BLU_PLANE] = + ((samplen)(hexDigitValue(colorname[ 9]) << 12) + + (samplen)(hexDigitValue(colorname[10]) << 8) + + (samplen)(hexDigitValue(colorname[11]) << 4) + + (samplen)(hexDigitValue(colorname[12]) << 0)) + / 65535; + break; + + default: + pm_error("invalid color specifier '%s'", colorname); + } +} + + + +static void +parseOldX11Dec(const char* const colorname, + tuplen const color) { + + int rc; + + rc = sscanf(colorname, "%f,%f,%f", + &color[PAM_RED_PLANE], + &color[PAM_GRN_PLANE], + &color[PAM_BLU_PLANE]); + + if (rc != 3) + pm_error("invalid color specifier '%s'", colorname); + + if (!(isNormal(color[PAM_RED_PLANE]) && + isNormal(color[PAM_GRN_PLANE]) && + isNormal(color[PAM_BLU_PLANE]))) { + pm_error("invalid color specifier '%s' - " + "values must be between 0.0 and 1.0", colorname); + } +} + + + +tuplen +pnm_parsecolorn(const char * const colorname) { + + tuplen retval; + + MALLOCARRAY_NOFAIL(retval, 3); + + if (strneq(colorname, "rgb:", 4)) + /* It's a new-X11-style hexadecimal rgb specifier. */ + parseNewHexX11(colorname, retval); + else if (strneq(colorname, "rgbi:", 5)) + /* It's a new-X11-style decimal/float rgb specifier. */ + parseNewDecX11(colorname, retval); + else if (strneq(colorname, "rgb-", 4)) + /* It's a Netpbm-native decimal integer rgb specifier */ + parseInteger(colorname, retval); + else if (colorname[0] == '#') + /* It's an old-X11-style hexadecimal rgb specifier. */ + parseOldX11(colorname, retval); + else if ((colorname[0] >= '0' && colorname[0] <= '9') || + colorname[0] == '.') + /* It's an old-style decimal/float rgb specifier. */ + parseOldX11Dec(colorname, retval); + else + /* Must be a name from the X-style rgb file. */ + pm_parse_dictionary_namen(colorname, retval); + + return retval; +} + + + +static void +warnIfNotExact(const char * const colorname, + tuple const rounded, + tuplen const exact, + sample const maxval, + unsigned int const plane) { + + float const epsilon = 1.0/65536.0; + + if (fabs((float)(rounded[plane] / maxval) - exact[plane]) > epsilon) { + pm_message("WARNING: Component %u of color '%s' is %f, " + "which cannot be represented precisely with maxval %lu. " + "Approximating as %lu.", + plane, colorname, exact[plane], maxval, rounded[plane]); + } +} tuple -pnm_parsecolor(const char * const colorname, - sample const maxval) { +pnm_parsecolor2(const char * const colorname, + sample const maxval, + int const closeOk) { tuple retval; - pixel color; + tuplen color; struct pam pam; pam.len = PAM_STRUCT_SIZE(bytes_per_sample); @@ -42,17 +364,32 @@ pnm_parsecolor(const char * const colorname, retval = pnm_allocpamtuple(&pam); - color = ppm_parsecolor(colorname, maxval); + color = pnm_parsecolorn(colorname); - retval[PAM_RED_PLANE] = PPM_GETR(color); - retval[PAM_GRN_PLANE] = PPM_GETG(color); - retval[PAM_BLU_PLANE] = PPM_GETB(color); + pnm_unnormalizetuple(&pam, color, retval); + + if (!closeOk) { + warnIfNotExact(colorname, retval, color, maxval, PAM_RED_PLANE); + warnIfNotExact(colorname, retval, color, maxval, PAM_GRN_PLANE); + warnIfNotExact(colorname, retval, color, maxval, PAM_BLU_PLANE); + } + + free(color); return retval; } +tuple +pnm_parsecolor(const char * const colorname, + sample const maxval) { + + return pnm_parsecolor2(colorname, maxval, true); +} + + + const char * pnm_colorname(struct pam * const pamP, tuple const color, @@ -64,7 +401,7 @@ pnm_colorname(struct pam * const pamP, if (pamP->depth < 3) PPM_ASSIGN(colorp, color[0], color[0], color[0]); - else + else PPM_ASSIGN(colorp, color[PAM_RED_PLANE], color[PAM_GRN_PLANE], @@ -72,8 +409,8 @@ pnm_colorname(struct pam * const pamP, colorname = ppm_colorname(&colorp, pamP->maxval, hexok); - retval = strdup(colorname); - if (retval == NULL) + retval = pm_strdup(colorname); + if (retval == pm_strsol) pm_error("Couldn't get memory for color name string"); return retval; @@ -81,21 +418,279 @@ pnm_colorname(struct pam * const pamP, +static tuple +scaledRgb(struct pam * const pamP, + tuple const color, + sample const maxval) { + + tuple scaledColor; + + struct pam pam; + + pam.size = sizeof(pam); + pam.len = PAM_STRUCT_SIZE(allocation_depth); + pam.maxval = pamP->maxval; + pam.depth = pamP->depth; + pam.allocation_depth = 3; + + scaledColor = pnm_allocpamtuple(&pam); + + pnm_scaletuple(&pam, scaledColor, color, maxval); + + pnm_maketuplergb(&pam, scaledColor); + + return scaledColor; +} + + + +const char * +pnm_colorspec_rgb_integer(struct pam * const pamP, + tuple const color, + sample const maxval) { + + const char * retval; + + tuple scaledColor = scaledRgb(pamP, color, maxval); + + pm_asprintf(&retval, "rgb-%lu:%lu/%lu/%lu", + maxval, + scaledColor[PAM_RED_PLANE], + scaledColor[PAM_GRN_PLANE], + scaledColor[PAM_BLU_PLANE] + ); + + pnm_freepamtuple(scaledColor); + + return retval; +} + + + +const char * +pnm_colorspec_rgb_norm(struct pam * const pamP, + tuple const color, + unsigned int const digitCt) { + + const char * retval; + + tuple rgbColor; + + tuplen normColor; + + struct pam rgbPam; + + rgbPam.size = sizeof(rgbPam); + rgbPam.len = PAM_STRUCT_SIZE(allocation_depth); + rgbPam.maxval = pamP->maxval; + rgbPam.depth = pamP->depth; + rgbPam.allocation_depth = 3; + + rgbColor = pnm_allocpamtuple(&rgbPam); + + pnm_assigntuple(&rgbPam, rgbColor, color); /* initial value */ + + pnm_maketuplergb(&rgbPam, rgbColor); + + normColor = pnm_allocpamtuplen(&rgbPam); + + rgbPam.depth = 3; + + pnm_normalizetuple(&rgbPam, rgbColor, normColor); + + { + const char * format; + pm_asprintf(&format, "rgbi:%%.%uf/%%.%uf/%%.%uf", + digitCt, digitCt, digitCt); + + pm_asprintf(&retval, format, + normColor[PAM_RED_PLANE], + normColor[PAM_GRN_PLANE], + normColor[PAM_BLU_PLANE] + ); + pm_strfree(format); + } + + pnm_freepamtuplen(normColor); + pnm_freepamtuple(rgbColor); + + return retval; +} + + + +const char * +pnm_colorspec_rgb_x11(struct pam * const pamP, + tuple const color, + unsigned int const hexDigitCt) { + + const char * retval; + + sample maxval; + const char * format; + + switch(hexDigitCt) { + case 1: + maxval = 15; + format = "rgb:%01x:%01x:%01x"; + break; + case 2: + maxval = 255; + format = "rgb:%02x:%02x:%02x"; + break; + case 3: + maxval = 4095; + format = "rgb:%03x:%03x:%03x"; + break; + case 4: + maxval = 65535; + format = "rgb:%04x:%04x:%04x"; + break; + default: + pm_error("Invalid number of hex digits " + "for X11 color specification: %u. " + "Must be 1, 2, 3, or 4", hexDigitCt); + } + + { + tuple const scaledColor = scaledRgb(pamP, color, maxval); + + pm_asprintf(&retval, format, + scaledColor[PAM_RED_PLANE], + scaledColor[PAM_GRN_PLANE], + scaledColor[PAM_BLU_PLANE] + ); + + pnm_freepamtuple(scaledColor); + } + return retval; +} + + + +const char * +pnm_colorspec_dict(struct pam * const pamP, + tuple const color) { +/*---------------------------------------------------------------------------- + Return the name from the color dictionary of color 'color'. + + Return it in newly allocated pm_strdrup storage. + + If the color is not in the dictionary, or the dictionary doesn't even + exist (file not found in any of the possible places), return NULL. + + The color dictionary uses maxval 255, so we match to that precision. + E.g. if a component of 'color' is 1000 out of maxval 65535 (which would be + 3.9 out of maxval 255), we consider it a match to a component value of 4 + in the color dictionary. +-----------------------------------------------------------------------------*/ + tuple scaledColor = scaledRgb(pamP, color, PAM_COLORFILE_MAXVAL); + + FILE * dictFileP; + const char * colorname; + + dictFileP = pm_openColornameFile(NULL, false); + + if (dictFileP) { + bool eof; + for (colorname = NULL, eof = false; !colorname && !eof; ) { + struct colorfile_entry const ce = pm_colorget(dictFileP); + + if (ce.colorname) { + if (scaledColor[PAM_RED_PLANE] == (sample)ce.r && + scaledColor[PAM_GRN_PLANE] == (sample)ce.g && + scaledColor[PAM_BLU_PLANE] == (sample)ce.b) { + colorname = pm_strdup(ce.colorname); + } + } else + eof = TRUE; + } + + fclose(dictFileP); + } else + colorname = NULL; + + pnm_freepamtuple(scaledColor); + + return colorname; +} + + + +const char * +pnm_colorspec_dict_close(struct pam * const pamP, + tuple const color) { +/*---------------------------------------------------------------------------- + Return the name from the color dictionary of the color closst to 'color'. + + Return it in newly allocated pm_strdrup storage. + + If the color dictionary is empty, or the dictionary doesn't even exist + (file not found in any of the possible places), return a null string. + This is the only case in which we would return a null string, as the + color dictionary cannot define a null string color name. +-----------------------------------------------------------------------------*/ + tuple scaledColor = scaledRgb(pamP, color, PAM_COLORFILE_MAXVAL); + + FILE * dictFileP; + static char colorname[200]; + + dictFileP = pm_openColornameFile(NULL, false); + + if (dictFileP) { + unsigned int bestDiff; + bool eof; + + for (bestDiff = 32767, eof = FALSE; !eof && bestDiff > 0; ) { + struct colorfile_entry const ce = pm_colorget(dictFileP); + + if (ce.colorname) { + unsigned int const thisDiff = + abs((int)scaledColor[PAM_RED_PLANE] - (int)ce.r) + + abs((int)scaledColor[PAM_GRN_PLANE] - (int)ce.g) + + abs((int)scaledColor[PAM_BLU_PLANE] - (int)ce.b); + + if (thisDiff < bestDiff) { + bestDiff = thisDiff; + STRSCPY(colorname, ce.colorname); + } + } else + eof = TRUE; + } + + fclose(dictFileP); + + if (bestDiff == 32767) { + /* Color file contain no entries, so we can't even pick a close + one + */ + STRSCPY(colorname, ""); + } + } else + STRSCPY(colorname, ""); + + pnm_freepamtuple(scaledColor); + + return pm_strdup(colorname); +} + + + double pnm_lumin_factor[3] = {PPM_LUMINR, PPM_LUMING, PPM_LUMINB}; void -pnm_YCbCrtuple(tuple const tuple, - double * const YP, - double * const CbP, +pnm_YCbCrtuple(tuple const tuple, + double * const YP, + double * const CbP, double * const CrP) { /*---------------------------------------------------------------------------- - Assuming that the tuple 'tuple' is of tupletype RGB, return the + Assuming that the tuple 'tuple' is of tupletype RGB, return the Y/Cb/Cr representation of the color represented by the tuple. -----------------------------------------------------------------------------*/ int const red = (int)tuple[PAM_RED_PLANE]; int const grn = (int)tuple[PAM_GRN_PLANE]; int const blu = (int)tuple[PAM_BLU_PLANE]; - + *YP = (+ PPM_LUMINR * red + PPM_LUMING * grn + PPM_LUMINB * blu); *CbP = (- 0.16874 * red - 0.33126 * grn + 0.50000 * blu); *CrP = (+ 0.50000 * red - 0.41869 * grn - 0.08131 * blu); @@ -103,11 +698,11 @@ pnm_YCbCrtuple(tuple const tuple, -void +void pnm_YCbCr_to_rgbtuple(const struct pam * const pamP, tuple const tuple, double const Y, - double const Cb, + double const Cb, double const Cr, int * const overflowP) { @@ -128,7 +723,7 @@ pnm_YCbCr_to_rgbtuple(const struct pam * const pamP, } else if (rgb[plane] < 0.0) { overflow = TRUE; tuple[plane] = 0; - } else + } else tuple[plane] = (sample)rgb[plane]; } if (overflowP) |