/* libppm4.c - ppm utility library part 4
**
** 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 */

#include <string.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 void
parseNewHexX11(const char colorname[], long const lmaxval,
               long * const rP, long * const gP, long * const bP) {

    int hexit[256];

    const char* cp;
    long r,g,b;
    int i;

    computeHexTable(hexit);

    cp = colorname + 4;
    r = g = b = 0;
    for ( i = 0; *cp != '/'; ++i, ++cp )
        r = r * 16 + hexit[(int)*cp];
    r = pm_rgbnorm( r, lmaxval, i, colorname );
    for ( i = 0, ++cp; *cp != '/'; ++i, ++cp )
        g = g * 16 + hexit[(int)*cp];
    g = pm_rgbnorm( g, lmaxval, i, colorname );
    for ( i = 0, ++cp; *cp != '\0'; ++i, ++cp )
        b = b * 16 + hexit[(int)*cp];
    b = pm_rgbnorm( b, lmaxval, i, colorname );
    if ( r < 0 || r > lmaxval || g < 0 || g > lmaxval 
         || b < 0 || b > lmaxval )
        pm_error("invalid color specifier - \"%s\"", colorname );

    *rP = r; *gP = g; *bP = b;
}



static void
parseNewDecX11(const char colorname[], long const lmaxval,
            long * const rP, long * const gP, long * const bP) {

    float fr, fg, fb;

    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 );
    *rP = fr * lmaxval;
    *gP = fg * lmaxval;
    *bP = fb * lmaxval;
}




static void
parseOldX11(const char colorname[], long const lmaxval,
            long * const rP, long * const gP, long * const bP) {

    int hexit[256];
    long r,g,b;

    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]];
        r = pm_rgbnorm( r, lmaxval, 1, colorname );
        g = pm_rgbnorm( g, lmaxval, 1, colorname );
        b = pm_rgbnorm( b, lmaxval, 1, 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]];
        r = pm_rgbnorm( r, lmaxval, 2, colorname );
        g = pm_rgbnorm( g, lmaxval, 2, colorname );
        b = pm_rgbnorm( b, lmaxval, 2, colorname );
        break;

    case 9:
        r = ( hexit[(int)colorname[1]] << 8 ) +
            ( hexit[(int)colorname[2]] << 4 ) +
            hexit[(int)colorname[3]];
        g = ( hexit[(int)colorname[4]] << 8 ) + 
            ( hexit[(int)colorname[5]] << 4 ) +
            hexit[(int)colorname[6]];
        b = ( hexit[(int)colorname[7]] << 8 ) + 
            ( hexit[(int)colorname[8]] << 4 ) +
            hexit[(int)colorname[9]];
        r = pm_rgbnorm( r, lmaxval, 3, colorname );
        g = pm_rgbnorm( g, lmaxval, 3, colorname );
        b = pm_rgbnorm( b, lmaxval, 3, 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]];
        r = pm_rgbnorm( r, lmaxval, 4, colorname );
        g = pm_rgbnorm( g, lmaxval, 4, colorname );
        b = pm_rgbnorm( b, lmaxval, 4, colorname );
        break;

    default:
        pm_error("invalid color specifier - \"%s\"", colorname );
    }
    if ( r < 0 || r > lmaxval || g < 0 || g > lmaxval 
         || b < 0 || b > lmaxval )
        pm_error("invalid color specifier - \"%s\"", colorname );
    
    *rP = r; *gP = g; *bP = b;
}




static void
parseOldX11Dec(const char colorname[], long const lmaxval,
               long * const rP, long * const gP, long * const bP) {

    float fr, fg, fb;

    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 );
    *rP = fr * lmaxval;
    *gP = fg * lmaxval;
    *bP = fb * lmaxval;
}




pixel
ppm_parsecolor(const char * const colorname, pixval const maxval) {

    pixel p;
    long lmaxval, r, g, b;
    
    lmaxval = maxval;
    if ( strncmp( colorname, "rgb:", 4 ) == 0 )
        /* It's a new-X11-style hexadecimal rgb specifier. */
        parseNewHexX11(colorname, lmaxval, &r, &g, &b);
    else if ( strncmp( colorname, "rgbi:", 5 ) == 0 )
        /* It's a new-X11-style decimal/float rgb specifier. */
        parseNewDecX11(colorname, lmaxval, &r, &g, &b);
    else if ( colorname[0] == '#' )
        /* It's an old-X11-style hexadecimal rgb specifier. */
        parseOldX11(colorname, lmaxval, &r, &g, &b);
    else if ( ( colorname[0] >= '0' && colorname[0] <= '9' ) ||
              colorname[0] == '.' )
        /* It's an old-style decimal/float rgb specifier. */
        parseOldX11Dec(colorname, lmaxval, &r, &g, &b);
    else 
        /* Must be a name from the X-style rgb file. */
        pm_parse_dictionary_name(colorname, lmaxval, &r, &g, &b);

    PPM_ASSIGN( p, r, g, b );
    return p;
}



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(!hexok);
    if (f != NULL) {
        int best_diff, this_diff;
        bool done;

        best_diff = 32767;
        done = FALSE;
        while ( !done ) {
            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;
                    (void) strcpy( colorname, ce.colorname );
                }
            } else
                done = 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;
}


static unsigned int 
positive(unsigned int const arg) {

    if ((int)(arg) < 0)
        return 0;
    else
        return arg;
}



pixel
color_from_ycbcr(unsigned int const y, 
                 int          const cb, 
                 int          const cr) {
/*----------------------------------------------------------------------------
   Return the color that has luminence '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 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,
               positive(y - 0.7145 * cr - 0.3456 * cb),
               y + 1.7710 * cb
        );
    
    return retval;
}


