Blame editor/pnmgamma.c

Packit 78deda
/* pnmgamma.c - perform gamma correction on a PNM image
Packit 78deda
**
Packit 78deda
** Copyright (C) 1991 by Bill Davidson and Jef Poskanzer.
Packit 78deda
**
Packit 78deda
** Permission to use, copy, modify, and distribute this software and its
Packit 78deda
** documentation for any purpose and without fee is hereby granted, provided
Packit 78deda
** that the above copyright notice appear in all copies and that both that
Packit 78deda
** copyright notice and this permission notice appear in supporting
Packit 78deda
** documentation.  This software is provided "as is" without express or
Packit 78deda
** implied warranty.
Packit 78deda
*/
Packit 78deda
Packit 78deda
#include <assert.h>
Packit 78deda
#include <math.h>
Packit 78deda
#include <ctype.h>
Packit 78deda
Packit 78deda
#include "pm_c_util.h"
Packit 78deda
#include "mallocvar.h"
Packit 78deda
#include "shhopt.h"
Packit 78deda
#include "pnm.h"
Packit 78deda
Packit 78deda
enum transferFunction {
Packit 78deda
    XF_EXP,
Packit 78deda
    XF_EXP_INVERSE,
Packit 78deda
    XF_BT709RAMP,
Packit 78deda
    XF_BT709RAMP_INVERSE,
Packit 78deda
    XF_SRGBRAMP,
Packit 78deda
    XF_SRGBRAMP_INVERSE,
Packit 78deda
    XF_BT709_TO_SRGB,
Packit 78deda
    XF_SRGB_TO_BT709
Packit 78deda
};
Packit 78deda
Packit 78deda
struct cmdlineInfo {
Packit 78deda
    /* All the information the user supplied in the command line,
Packit 78deda
       in a form easy for the program to use.
Packit 78deda
    */
Packit 78deda
    const char * filespec;  /* '-' if stdin */
Packit 78deda
    enum transferFunction transferFunction;
Packit 78deda
    float rgamma, ggamma, bgamma;
Packit 78deda
    unsigned int maxval;
Packit 78deda
    unsigned int makeNewMaxval;
Packit 78deda
};
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
interpretOldArguments(int                  const argc,
Packit 78deda
                      char **              const argv,
Packit 78deda
                      float                const defaultGamma,
Packit 78deda
                      struct cmdlineInfo * const cmdlineP) {
Packit 78deda
Packit 78deda
    /* Use the old syntax wherein the gamma values come from arguments.
Packit 78deda
       If there is one argument, it's a gamma value for all three
Packit 78deda
       components.  If 3 arguments, it's separate gamma values.  If
Packit 78deda
       2, it's a single gamma value plus a file name.  If 4, it's
Packit 78deda
       separate gamma values plus a file name.
Packit 78deda
    */
Packit 78deda
    if (argc-1 == 0) {
Packit 78deda
        cmdlineP->rgamma = defaultGamma;
Packit 78deda
        cmdlineP->ggamma = defaultGamma;
Packit 78deda
        cmdlineP->bgamma = defaultGamma;
Packit 78deda
        cmdlineP->filespec = "-";
Packit 78deda
    } else if (argc-1 == 1) {
Packit 78deda
        cmdlineP->rgamma = atof(argv[1]);
Packit 78deda
        cmdlineP->ggamma = atof(argv[1]);
Packit 78deda
        cmdlineP->bgamma = atof(argv[1]);
Packit 78deda
        cmdlineP->filespec = "-";
Packit 78deda
    } else if (argc-1 == 2) {
Packit 78deda
        cmdlineP->rgamma = atof(argv[1]);
Packit 78deda
        cmdlineP->ggamma = atof(argv[1]);
Packit 78deda
        cmdlineP->bgamma = atof(argv[1]);
Packit 78deda
        cmdlineP->filespec = argv[2];
Packit 78deda
    } else if (argc-1 == 3) {
Packit 78deda
        cmdlineP->rgamma = atof(argv[1]);
Packit 78deda
        cmdlineP->ggamma = atof(argv[2]);
Packit 78deda
        cmdlineP->bgamma = atof(argv[3]);
Packit 78deda
        cmdlineP->filespec = "-";
Packit 78deda
    } else if (argc-1 == 4) {
Packit 78deda
        cmdlineP->rgamma = atof(argv[1]);
Packit 78deda
        cmdlineP->ggamma = atof(argv[2]);
Packit 78deda
        cmdlineP->bgamma = atof(argv[3]);
Packit 78deda
        cmdlineP->filespec = argv[4];
Packit 78deda
    } else 
Packit 78deda
        pm_error("Wrong number of arguments.  "
Packit 78deda
                 "You may have 0, 1, or 3 gamma values "
Packit 78deda
                 "plus zero or one filename");
Packit 78deda
        
Packit 78deda
    if (cmdlineP->rgamma <= 0.0 || 
Packit 78deda
        cmdlineP->ggamma <= 0.0 || 
Packit 78deda
        cmdlineP->bgamma <= 0.0 )
Packit 78deda
        pm_error("Invalid gamma value.  Must be positive floating point "
Packit 78deda
                 "number.");
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
getGammaFromOpts(struct cmdlineInfo * const cmdlineP,
Packit 78deda
                 bool                 const gammaSpec,
Packit 78deda
                 float                const gammaOpt,
Packit 78deda
                 bool                 const rgammaSpec,
Packit 78deda
                 bool                 const ggammaSpec,
Packit 78deda
                 bool                 const bgammaSpec,
Packit 78deda
                 float                const defaultGamma) {
Packit 78deda
Packit 78deda
    if (gammaSpec)
Packit 78deda
        if (gammaOpt < 0.0)
Packit 78deda
            pm_error("Invalid gamma value %f.  Must be positive.", gammaOpt);
Packit 78deda
    
Packit 78deda
    if (rgammaSpec) {
Packit 78deda
        if (cmdlineP->rgamma < 0.0)
Packit 78deda
            pm_error("Invalid red gamma value %f.  Must be positive.",
Packit 78deda
                     cmdlineP->rgamma);
Packit 78deda
    } else {
Packit 78deda
        if (gammaSpec)
Packit 78deda
            cmdlineP->rgamma = gammaOpt;
Packit 78deda
        else 
Packit 78deda
            cmdlineP->rgamma = defaultGamma;
Packit 78deda
    }
Packit 78deda
    if (ggammaSpec) {
Packit 78deda
        if (cmdlineP->ggamma < 0.0) 
Packit 78deda
            pm_error("Invalid green gamma value %f.  Must be positive.",
Packit 78deda
                     cmdlineP->ggamma);
Packit 78deda
    } else {
Packit 78deda
        if (gammaSpec)
Packit 78deda
            cmdlineP->ggamma = gammaOpt;
Packit 78deda
        else 
Packit 78deda
            cmdlineP->ggamma = defaultGamma;
Packit 78deda
    }
Packit 78deda
    if (bgammaSpec) {
Packit 78deda
        if (cmdlineP->bgamma < 0.0)
Packit 78deda
            pm_error("Invalid blue gamma value %f.  Must be positive.",
Packit 78deda
                     cmdlineP->bgamma);
Packit 78deda
    } else {
Packit 78deda
        if (gammaSpec)
Packit 78deda
            cmdlineP->bgamma = gammaOpt;
Packit 78deda
        else
Packit 78deda
            cmdlineP->bgamma = defaultGamma;
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
parseCommandLine(int argc, char ** argv, 
Packit 78deda
                 struct cmdlineInfo * const cmdlineP) {
Packit 78deda
Packit 78deda
    optEntry *option_def;
Packit 78deda
        /* Instructions to pm_optParseOptions3 on how to parse our options.
Packit 78deda
         */
Packit 78deda
    optStruct3 opt;
Packit 78deda
Packit 78deda
    unsigned int bt709ramp, srgbramp, ungamma, bt709tosrgb, srgbtobt709;
Packit 78deda
    unsigned int bt709tolinear, lineartobt709;
Packit 78deda
    unsigned int gammaSpec, rgammaSpec, ggammaSpec, bgammaSpec;
Packit 78deda
    float gammaOpt;
Packit 78deda
    float defaultGamma;
Packit 78deda
    unsigned int option_def_index;
Packit 78deda
Packit 78deda
    MALLOCARRAY_NOFAIL(option_def, 100);
Packit 78deda
Packit 78deda
    option_def_index = 0;   /* incremented by OPTENT3 */
Packit 78deda
    OPTENT3(0, "ungamma",       OPT_FLAG,   NULL,
Packit 78deda
            &ungamma,                 0);
Packit 78deda
    OPTENT3(0, "bt709tolinear", OPT_FLAG,   NULL,
Packit 78deda
            &bt709tolinear,           0);
Packit 78deda
    OPTENT3(0, "lineartobt709", OPT_FLAG,   NULL,
Packit 78deda
            &lineartobt709,           0);
Packit 78deda
    OPTENT3(0, "bt709ramp",     OPT_FLAG,   NULL,
Packit 78deda
            &bt709ramp,               0);
Packit 78deda
    OPTENT3(0, "cieramp",       OPT_FLAG,   NULL,
Packit 78deda
            &bt709ramp,               0);
Packit 78deda
    OPTENT3(0, "srgbramp",      OPT_FLAG,   NULL,
Packit 78deda
            &srgbramp,                0);
Packit 78deda
    OPTENT3(0, "bt709tosrgb",   OPT_FLAG,   NULL,
Packit 78deda
            &bt709tosrgb,             0);
Packit 78deda
    OPTENT3(0, "srgbtobt709",   OPT_FLAG,   NULL,
Packit 78deda
            &srgbtobt709,             0);
Packit 78deda
    OPTENT3(0, "maxval",        OPT_UINT,   &cmdlineP->maxval,
Packit 78deda
            &cmdlineP->makeNewMaxval, 0);
Packit 78deda
    OPTENT3(0, "gamma",         OPT_FLOAT,  &gammaOpt,
Packit 78deda
            &gammaSpec,               0);
Packit 78deda
    OPTENT3(0, "rgamma",        OPT_FLOAT,  &cmdlineP->rgamma,
Packit 78deda
            &rgammaSpec,              0);
Packit 78deda
    OPTENT3(0, "ggamma",        OPT_FLOAT,  &cmdlineP->ggamma,
Packit 78deda
            &ggammaSpec,              0);
Packit 78deda
    OPTENT3(0, "bgamma",        OPT_FLOAT,  &cmdlineP->bgamma,
Packit 78deda
            &bgammaSpec,              0);
Packit 78deda
Packit 78deda
    opt.opt_table = option_def;
Packit 78deda
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
Packit 78deda
    opt.allowNegNum = TRUE; 
Packit 78deda
Packit 78deda
    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
Packit 78deda
        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
Packit 78deda
Packit 78deda
    if (bt709tolinear + lineartobt709 + bt709ramp + srgbramp +
Packit 78deda
        bt709tosrgb + srgbtobt709 > 1)
Packit 78deda
        pm_error("You may specify only one function option");
Packit 78deda
    else {
Packit 78deda
        if (bt709tolinear) {
Packit 78deda
            if (ungamma)
Packit 78deda
                pm_error("You cannot specify -ungamma with -bt709tolinear");
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_BT709RAMP_INVERSE;
Packit 78deda
        } else if (lineartobt709) {
Packit 78deda
            if (ungamma)
Packit 78deda
                pm_error("You cannot specify -ungamma with -lineartobt709");
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_BT709RAMP;
Packit 78deda
        } else if (bt709tosrgb) {
Packit 78deda
            if (ungamma)
Packit 78deda
                pm_error("You cannot specify -ungamma with -bt709tosrgb");
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_BT709_TO_SRGB;
Packit 78deda
        } else if (srgbtobt709) {
Packit 78deda
            if (ungamma)
Packit 78deda
                pm_error("You cannot specify -ungamma with -srgbtobt709");
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_SRGB_TO_BT709;
Packit 78deda
        } else if (bt709ramp) {
Packit 78deda
            if (ungamma)
Packit 78deda
                cmdlineP->transferFunction = XF_BT709RAMP_INVERSE;
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_BT709RAMP;
Packit 78deda
        } else if (srgbramp) {
Packit 78deda
            if (ungamma)
Packit 78deda
                cmdlineP->transferFunction = XF_SRGBRAMP_INVERSE;
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_SRGBRAMP;
Packit 78deda
        } else {
Packit 78deda
            if (ungamma)
Packit 78deda
                cmdlineP->transferFunction = XF_EXP_INVERSE;
Packit 78deda
            else
Packit 78deda
                cmdlineP->transferFunction = XF_EXP;
Packit 78deda
        }
Packit 78deda
    }
Packit 78deda
Packit 78deda
    if (cmdlineP->makeNewMaxval) {
Packit 78deda
        if (cmdlineP->maxval > PNM_OVERALLMAXVAL)
Packit 78deda
            pm_error("Largest possible maxval is %u.  You specified %u",
Packit 78deda
                     PNM_OVERALLMAXVAL, cmdlineP->maxval);
Packit 78deda
    }
Packit 78deda
Packit 78deda
    switch (cmdlineP->transferFunction) {
Packit 78deda
    case XF_BT709RAMP:
Packit 78deda
    case XF_BT709RAMP_INVERSE:
Packit 78deda
    case XF_SRGB_TO_BT709:
Packit 78deda
        defaultGamma = 1.0/0.45;
Packit 78deda
        break;
Packit 78deda
    case XF_SRGBRAMP:
Packit 78deda
    case XF_SRGBRAMP_INVERSE:
Packit 78deda
    case XF_BT709_TO_SRGB:
Packit 78deda
        /* The whole function is often approximated with
Packit 78deda
           exponent 2.2 and no linear piece.  We do the linear
Packit 78deda
           piece, so we use the real exponent of 2.4.
Packit 78deda
        */
Packit 78deda
        defaultGamma = 2.4;
Packit 78deda
        break;
Packit 78deda
    case XF_EXP:
Packit 78deda
    case XF_EXP_INVERSE:
Packit 78deda
        defaultGamma = 2.2;
Packit 78deda
        break;
Packit 78deda
    }
Packit 78deda
Packit 78deda
    if (bt709tolinear || lineartobt709 || bt709tosrgb || srgbtobt709) {
Packit 78deda
        /* Use the new syntax wherein the gamma values come from options,
Packit 78deda
           not arguments.  So if there's an argument, it's a file name.
Packit 78deda
        */
Packit 78deda
        getGammaFromOpts(cmdlineP, gammaSpec, gammaOpt,
Packit 78deda
                         rgammaSpec, ggammaSpec, bgammaSpec, defaultGamma);
Packit 78deda
Packit 78deda
        if (argc-1 < 1)
Packit 78deda
            cmdlineP->filespec = "-";
Packit 78deda
        else {
Packit 78deda
            cmdlineP->filespec = argv[1];
Packit 78deda
            if (argc-1 > 1)
Packit 78deda
                pm_error("Too many arguments (%u).  With this function, there "
Packit 78deda
                         "is at most one argument:  the file name", argc-1);
Packit 78deda
        }
Packit 78deda
    } else {
Packit 78deda
        if (gammaSpec || rgammaSpec || ggammaSpec || bgammaSpec)
Packit 78deda
            pm_error("With this function, you specify the gamma values in "
Packit 78deda
                     "arguments, not with the -gamma, etc.");
Packit 78deda
        interpretOldArguments(argc, argv, defaultGamma, cmdlineP);
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildPowGamma(xelval       table[],
Packit 78deda
              xelval const maxval,
Packit 78deda
              xelval const newMaxval,
Packit 78deda
              double const gamma) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the given gamma value.
Packit 78deda
  
Packit 78deda
   This function depends on pow(3m).  If you don't have it, you can
Packit 78deda
   simulate it with '#define pow(x,y) exp((y)*log(x))' provided that
Packit 78deda
   you have the exponential function exp(3m) and the natural logarithm
Packit 78deda
   function log(3m).  I can't believe I actually remembered my log
Packit 78deda
   identities.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    xelval i;
Packit 78deda
    double const oneOverGamma = 1.0 / gamma;
Packit 78deda
Packit 78deda
    for (i = 0 ; i <= maxval; ++i) {
Packit 78deda
        double const normalized = ((double) i) / maxval;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double const v = pow(normalized, oneOverGamma);
Packit 78deda
Packit 78deda
        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
Packit 78deda
            /* denormalize, round and clip */
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildBt709Gamma(xelval       table[],
Packit 78deda
                xelval const maxval,
Packit 78deda
                xelval const newMaxval,
Packit 78deda
                double const gamma) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the ITU Recommendation
Packit 78deda
   BT.709 gamma transfer function.
Packit 78deda
Packit 78deda
   'gamma' must be 1/0.45 for true Rec. 709.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    double const oneOverGamma = 1.0 / gamma;
Packit 78deda
    xelval i;
Packit 78deda
Packit 78deda
    /* This transfer function is linear for sample values 0
Packit 78deda
       .. maxval*.018 and an exponential for larger sample values.
Packit 78deda
       The exponential is slightly stretched and translated, though,
Packit 78deda
       unlike the popular pure exponential gamma transfer function.
Packit 78deda
    */
Packit 78deda
    xelval const linearCutoff = (xelval) (maxval * 0.018 + 0.5);
Packit 78deda
    double const linearExpansion = 
Packit 78deda
        (1.099 * pow(0.018, oneOverGamma) - 0.099) / 0.018;
Packit 78deda
    double const maxvalScaler = (double)newMaxval/maxval;
Packit 78deda
Packit 78deda
    for (i = 0; i <= linearCutoff; ++i) 
Packit 78deda
        table[i] = i * linearExpansion * maxvalScaler + 0.5;
Packit 78deda
    for (; i <= maxval; ++i) {
Packit 78deda
        double const normalized = ((double) i) / maxval;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double const v = 1.099 * pow(normalized, oneOverGamma) - 0.099;
Packit 78deda
        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
Packit 78deda
            /* denormalize, round, and clip */
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildBt709GammaInverse(xelval       table[],
Packit 78deda
                       xelval const maxval,
Packit 78deda
                       xelval const newMaxval,
Packit 78deda
                       double const gamma) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the Inverse of the ITU
Packit 78deda
   Rec. BT.709 gamma transfer function.
Packit 78deda
Packit 78deda
   'gamma' must be 1/0.45 for true Rec. 709.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    double const oneOverGamma = 1.0 / gamma;
Packit 78deda
    xelval i;
Packit 78deda
Packit 78deda
    /* This transfer function is linear for sample values 0
Packit 78deda
       .. maxval*.018 and an exponential for larger sample values.
Packit 78deda
       The exponential is slightly stretched and translated, though,
Packit 78deda
       unlike the popular pure exponential gamma transfer function.
Packit 78deda
    */
Packit 78deda
Packit 78deda
    xelval const linearCutoff = (xelval) (maxval * 0.018 + 0.5);
Packit 78deda
    double const linearCompression = 
Packit 78deda
        0.018 / (1.099 * pow(0.018, oneOverGamma) - 0.099);
Packit 78deda
    double const maxvalScaler = (double)newMaxval/maxval;
Packit 78deda
Packit 78deda
    for (i = 0; i <= linearCutoff / linearCompression; ++i) 
Packit 78deda
        table[i] = i * linearCompression * maxvalScaler + 0.5;
Packit 78deda
Packit 78deda
    for (; i <= maxval; ++i) {
Packit 78deda
        double const normalized = ((double) i) / maxval;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double const v = pow((normalized + 0.099) / 1.099, gamma);
Packit 78deda
        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
Packit 78deda
            /* denormalize, round, and clip */
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildSrgbGamma(xelval       table[],
Packit 78deda
               xelval const maxval,
Packit 78deda
               xelval const newMaxval,
Packit 78deda
               double const gamma) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the IEC SRGB gamma
Packit 78deda
   transfer function (Standard IEC 61966-2-1).
Packit 78deda
Packit 78deda
   'gamma' must be 2.4 for true SRGB
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    double const oneOverGamma = 1.0 / gamma;
Packit 78deda
    xelval i;
Packit 78deda
Packit 78deda
    /* This transfer function is linear for sample values 0
Packit 78deda
       .. maxval*.040405 and an exponential for larger sample values.
Packit 78deda
       The exponential is slightly stretched and translated, though,
Packit 78deda
       unlike the popular pure exponential gamma transfer function.
Packit 78deda
    */
Packit 78deda
    xelval const linearCutoff = (xelval) maxval * 0.0031308 + 0.5;
Packit 78deda
    double const linearExpansion = 
Packit 78deda
        (1.055 * pow(0.0031308, oneOverGamma) - 0.055) / 0.0031308;
Packit 78deda
    double const maxvalScaler = (double)newMaxval/maxval;
Packit 78deda
Packit 78deda
    for (i = 0; i <= linearCutoff; ++i) 
Packit 78deda
        table[i] = i * linearExpansion * maxvalScaler + 0.5;
Packit 78deda
    for (; i <= maxval; ++i) {
Packit 78deda
        double const normalized = ((double) i) / maxval;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double const v = 1.055 * pow(normalized, oneOverGamma) - 0.055;
Packit 78deda
        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
Packit 78deda
            /* denormalize, round, and clip */
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildSrgbGammaInverse(xelval       table[],
Packit 78deda
                      xelval const maxval,
Packit 78deda
                      xelval const newMaxval,
Packit 78deda
                      double const gamma) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the Inverse of the IEC SRGB gamma
Packit 78deda
   transfer function (Standard IEC 61966-2-1).
Packit 78deda
Packit 78deda
   'gamma' must be 2.4 for true SRGB
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    double const oneOverGamma = 1.0 / gamma;
Packit 78deda
    xelval i;
Packit 78deda
Packit 78deda
    /* This transfer function is linear for sample values 0
Packit 78deda
       .. maxval*.040405 and an exponential for larger sample values.
Packit 78deda
       The exponential is slightly stretched and translated, though,
Packit 78deda
       unlike the popular pure exponential gamma transfer function.
Packit 78deda
    */
Packit 78deda
    xelval const linearCutoff = (xelval) maxval * 0.0031308 + 0.5;
Packit 78deda
    double const linearCompression = 
Packit 78deda
        0.0031308 / (1.055 * pow(0.0031308, oneOverGamma) - 0.055);
Packit 78deda
    double const maxvalScaler = (double)newMaxval/maxval;
Packit 78deda
Packit 78deda
    for (i = 0; i <= linearCutoff / linearCompression; ++i) 
Packit 78deda
        table[i] = i * linearCompression * maxvalScaler + 0.5;
Packit 78deda
    for (; i <= maxval; ++i) {
Packit 78deda
        double const normalized = ((double) i) / maxval;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double const v = pow((normalized + 0.055) / 1.055, gamma);
Packit 78deda
        table[i] = MIN((xelval)(v * newMaxval + 0.5), newMaxval);  
Packit 78deda
            /* denormalize, round, and clip */
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildBt709ToSrgbGamma(xelval       table[],
Packit 78deda
                      xelval const maxval,
Packit 78deda
                      xelval const newMaxval,
Packit 78deda
                      double const gammaSrgb) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the combination of the
Packit 78deda
   inverse of ITU Rec BT.709 and the forward SRGB gamma transfer
Packit 78deda
   functions.  I.e. this converts from Rec 709 to SRGB.
Packit 78deda
Packit 78deda
   'gammaSrgb' must be 2.4 for true SRGB.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    double const oneOverGamma709  = 0.45;
Packit 78deda
    double const gamma709         = 1.0 / oneOverGamma709;
Packit 78deda
    double const oneOverGammaSrgb = 1.0 / gammaSrgb;
Packit 78deda
    double const normalizer       = 1.0 / maxval;
Packit 78deda
Packit 78deda
    /* This transfer function is linear for sample values 0
Packit 78deda
       .. maxval*.018 and an exponential for larger sample values.
Packit 78deda
       The exponential is slightly stretched and translated, though,
Packit 78deda
       unlike the popular pure exponential gamma transfer function.
Packit 78deda
    */
Packit 78deda
Packit 78deda
    xelval const linearCutoff709 = (xelval) (maxval * 0.018 + 0.5);
Packit 78deda
    double const linearCompression709 = 
Packit 78deda
        0.018 / (1.099 * pow(0.018, oneOverGamma709) - 0.099);
Packit 78deda
Packit 78deda
    double const linearCutoffSrgb = 0.0031308;
Packit 78deda
    double const linearExpansionSrgb = 
Packit 78deda
        (1.055 * pow(0.0031308, oneOverGammaSrgb) - 0.055) / 0.0031308;
Packit 78deda
Packit 78deda
    xelval i;
Packit 78deda
Packit 78deda
    for (i = 0; i <= maxval; ++i) {
Packit 78deda
        double const normalized = i * normalizer;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double radiance;
Packit 78deda
        double srgb;
Packit 78deda
Packit 78deda
        if (i < linearCutoff709 / linearCompression709)
Packit 78deda
            radiance = normalized * linearCompression709;
Packit 78deda
        else
Packit 78deda
            radiance = pow((normalized + 0.099) / 1.099, gamma709);
Packit 78deda
Packit 78deda
        assert(radiance <= 1.0);
Packit 78deda
Packit 78deda
        if (radiance < linearCutoffSrgb * normalizer)
Packit 78deda
            srgb = radiance * linearExpansionSrgb;
Packit 78deda
        else
Packit 78deda
            srgb = 1.055 * pow(normalized, oneOverGammaSrgb) - 0.055;
Packit 78deda
Packit 78deda
        assert(srgb <= 1.0);
Packit 78deda
Packit 78deda
        table[i] = srgb * newMaxval + 0.5;
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
buildSrgbToBt709Gamma(xelval       table[],
Packit 78deda
                      xelval const maxval,
Packit 78deda
                      xelval const newMaxval,
Packit 78deda
                      double const gamma709) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Build a gamma table of size maxval+1 for the combination of the
Packit 78deda
   inverse of SRGB and the forward ITU Rec BT.709 gamma transfer
Packit 78deda
   functions.  I.e. this converts from SRGB to Rec 709.
Packit 78deda
Packit 78deda
   'gamma709' must be 1/0.45 for true Rec. 709.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    double const oneOverGamma709  = 1.0 / gamma709;
Packit 78deda
    double const gammaSrgb        = 2.4;
Packit 78deda
    double const oneOverGammaSrgb = 1.0 / gammaSrgb;
Packit 78deda
    double const normalizer       = 1.0 / maxval;
Packit 78deda
Packit 78deda
    /* This transfer function is linear for sample values 0
Packit 78deda
       .. maxval*.040405 and an exponential for larger sample values.
Packit 78deda
       The exponential is slightly stretched and translated, though,
Packit 78deda
       unlike the popular pure exponential gamma transfer function.
Packit 78deda
    */
Packit 78deda
    xelval const linearCutoffSrgb = (xelval) maxval * 0.0031308 + 0.5;
Packit 78deda
    double const linearCompressionSrgb = 
Packit 78deda
        0.0031308 / (1.055 * pow(0.0031308, oneOverGammaSrgb) - 0.055);
Packit 78deda
Packit 78deda
    xelval const linearCutoff709 = (xelval) (maxval * 0.018 + 0.5);
Packit 78deda
    double const linearExpansion709 = 
Packit 78deda
        (1.099 * pow(0.018, oneOverGamma709) - 0.099) / 0.018;
Packit 78deda
Packit 78deda
    xelval i;
Packit 78deda
Packit 78deda
    for (i = 0; i <= maxval; ++i) {
Packit 78deda
        double const normalized = i * normalizer;
Packit 78deda
            /* Xel sample value normalized to 0..1 */
Packit 78deda
        double radiance;
Packit 78deda
        double bt709;
Packit 78deda
Packit 78deda
        if (i < linearCutoffSrgb / linearCompressionSrgb)
Packit 78deda
            radiance = normalized * linearCompressionSrgb;
Packit 78deda
        else
Packit 78deda
            radiance = pow((normalized + 0.099) / 1.099, gammaSrgb);
Packit 78deda
Packit 78deda
        assert(radiance <= 1.0);
Packit 78deda
Packit 78deda
        if (radiance < linearCutoff709 * normalizer)
Packit 78deda
            bt709 = radiance * linearExpansion709;
Packit 78deda
        else
Packit 78deda
            bt709 = 1.055 * pow(normalized, oneOverGamma709) - 0.055;
Packit 78deda
Packit 78deda
        assert(bt709 <= 1.0);
Packit 78deda
Packit 78deda
        table[i] = bt709 * newMaxval + 0.5;
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
createGammaTables(enum transferFunction const transferFunction,
Packit 78deda
                  xelval                const maxval,
Packit 78deda
                  xelval                const newMaxval,
Packit 78deda
                  double                const rgamma, 
Packit 78deda
                  double                const ggamma, 
Packit 78deda
                  double                const bgamma,
Packit 78deda
                  xelval **             const rtableP,
Packit 78deda
                  xelval **             const gtableP,
Packit 78deda
                  xelval **             const btableP) {
Packit 78deda
Packit 78deda
    /* Allocate space for the tables. */
Packit 38c941
    overflow_add(maxval, 1);
Packit 78deda
    MALLOCARRAY(*rtableP, maxval+1);
Packit 78deda
    MALLOCARRAY(*gtableP, maxval+1);
Packit 78deda
    MALLOCARRAY(*btableP, maxval+1);
Packit 78deda
    if (*rtableP == NULL || *gtableP == NULL || *btableP == NULL)
Packit 78deda
        pm_error("Can't get memory to make gamma transfer tables");
Packit 78deda
Packit 78deda
    /* Build the gamma corection tables. */
Packit 78deda
    switch (transferFunction) {
Packit 78deda
    case XF_BT709RAMP: {
Packit 78deda
        buildBt709Gamma(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildBt709Gamma(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildBt709Gamma(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_BT709RAMP_INVERSE: {
Packit 78deda
        buildBt709GammaInverse(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildBt709GammaInverse(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildBt709GammaInverse(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_SRGBRAMP: {
Packit 78deda
        buildSrgbGamma(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildSrgbGamma(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildSrgbGamma(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_SRGBRAMP_INVERSE: {
Packit 78deda
        buildSrgbGammaInverse(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildSrgbGammaInverse(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildSrgbGammaInverse(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_EXP: {
Packit 78deda
        buildPowGamma(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildPowGamma(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildPowGamma(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_EXP_INVERSE: {
Packit 78deda
        buildPowGamma(*rtableP, maxval, newMaxval, 1.0/rgamma);
Packit 78deda
        buildPowGamma(*gtableP, maxval, newMaxval, 1.0/ggamma);
Packit 78deda
        buildPowGamma(*btableP, maxval, newMaxval, 1.0/bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_BT709_TO_SRGB: {
Packit 78deda
        buildBt709ToSrgbGamma(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildBt709ToSrgbGamma(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildBt709ToSrgbGamma(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
Packit 78deda
    case XF_SRGB_TO_BT709: {
Packit 78deda
        buildSrgbToBt709Gamma(*rtableP, maxval, newMaxval, rgamma);
Packit 78deda
        buildSrgbToBt709Gamma(*gtableP, maxval, newMaxval, ggamma);
Packit 78deda
        buildSrgbToBt709Gamma(*btableP, maxval, newMaxval, bgamma);
Packit 78deda
    } break;
Packit 78deda
    }
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
convertRaster(FILE *   const ifP,
Packit 78deda
              FILE *   const ofP,
Packit 78deda
              int      const cols,
Packit 78deda
              int      const rows,
Packit 78deda
              xelval   const maxval,
Packit 78deda
              int      const format,
Packit 78deda
              xelval   const outputMaxval,
Packit 78deda
              int      const outputFormat,
Packit 78deda
              xelval * const rtable,
Packit 78deda
              xelval * const gtable,
Packit 78deda
              xelval * const btable) {
Packit 78deda
Packit 78deda
    xel * xelrow;
Packit 78deda
    unsigned int row;
Packit 78deda
Packit 78deda
    xelrow = pnm_allocrow(cols);
Packit 78deda
Packit 78deda
    for (row = 0; row < rows; ++row) {
Packit 78deda
        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
Packit 78deda
Packit 78deda
        pnm_promoteformatrow(xelrow, cols, maxval, format, 
Packit 78deda
                             maxval, outputFormat);
Packit 78deda
Packit 78deda
        switch (PNM_FORMAT_TYPE(outputFormat)) {
Packit 78deda
        case PPM_TYPE: {
Packit 78deda
            unsigned int col;
Packit 78deda
            for (col = 0; col < cols; ++col) {
Packit 78deda
                xelval const r = PPM_GETR(xelrow[col]);
Packit 78deda
                xelval const g = PPM_GETG(xelrow[col]);
Packit 78deda
                xelval const b = PPM_GETB(xelrow[col]);
Packit 78deda
                PPM_ASSIGN(xelrow[col], rtable[r], gtable[g], btable[b]);
Packit 78deda
            }
Packit 78deda
        } break;
Packit 78deda
Packit 78deda
        case PGM_TYPE: {
Packit 78deda
            unsigned int col;
Packit 78deda
            for (col = 0; col < cols; ++col) {
Packit 78deda
                xelval const xel = PNM_GET1(xelrow[col]);
Packit 78deda
                PNM_ASSIGN1(xelrow[col], gtable[xel]);
Packit 78deda
            }
Packit 78deda
        } break;
Packit 78deda
        default:
Packit 78deda
            pm_error("Internal error.  Impossible format type");
Packit 78deda
        }
Packit 78deda
        pnm_writepnmrow(ofP, xelrow, cols, outputMaxval, outputFormat, 0);
Packit 78deda
    }
Packit 78deda
    pnm_freerow(xelrow);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
main(int argc, char *argv[]) {
Packit 78deda
    struct cmdlineInfo cmdline;
Packit 78deda
    FILE * ifP;
Packit 78deda
    xelval maxval;
Packit 78deda
    int rows, cols, format;
Packit 78deda
    xelval outputMaxval;
Packit 78deda
    int outputFormat;
Packit 78deda
    xelval * rtable;
Packit 78deda
    xelval * gtable;
Packit 78deda
    xelval * btable;
Packit 78deda
Packit 78deda
    pnm_init(&argc, argv);
Packit 78deda
Packit 78deda
    parseCommandLine(argc, argv, &cmdline);
Packit 78deda
Packit 78deda
    ifP = pm_openr(cmdline.filespec);
Packit 78deda
Packit 78deda
    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
Packit 78deda
Packit 78deda
    if (PNM_FORMAT_TYPE(format) == PPM_TYPE)
Packit 78deda
        outputFormat = PPM_TYPE;
Packit 78deda
    else if (cmdline.rgamma != cmdline.ggamma 
Packit 78deda
             || cmdline.ggamma != cmdline.bgamma) 
Packit 78deda
        outputFormat = PPM_TYPE;
Packit 78deda
    else 
Packit 78deda
        outputFormat = PGM_TYPE;
Packit 78deda
Packit 78deda
    if (PNM_FORMAT_TYPE(format) != outputFormat) {
Packit 78deda
        if (outputFormat == PPM_TYPE)
Packit 78deda
            pm_message("Promoting to PPM");
Packit 78deda
        if (outputFormat == PGM_TYPE)
Packit 78deda
            pm_message("Promoting to PGM");
Packit 78deda
    }
Packit 78deda
Packit 78deda
    outputMaxval = cmdline.makeNewMaxval ? cmdline.maxval : maxval;
Packit 78deda
Packit 78deda
    createGammaTables(cmdline.transferFunction, maxval,
Packit 78deda
                      outputMaxval,
Packit 78deda
                      cmdline.rgamma, cmdline.ggamma, cmdline.bgamma,
Packit 78deda
                      &rtable, &gtable, &btable);
Packit 78deda
Packit 78deda
    pnm_writepnminit(stdout, cols, rows, outputMaxval, outputFormat, 0);
Packit 78deda
Packit 78deda
    convertRaster(ifP, stdout, cols, rows, maxval, format,
Packit 78deda
                  outputMaxval, outputFormat,
Packit 78deda
                  rtable, gtable, btable);
Packit 78deda
Packit 78deda
    pm_close(ifP);
Packit 78deda
    pm_close(stdout);
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}