/*
 * $Header: /home/gene/library/website/docsrc/exrtoppm/src/RCS/pixel.cpp,v 395.1 2008/04/20 17:25:50 gene Exp $
 *
 * Copyright (c) 2005 Gene Michael Stover.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include "this.h"

/*
 * IMPLEMENTATION NOTE
 *
 * I lifted most of this code (like 99 percent of it) from
 * the file "ImageView.cpp" in the source code for OpenEXR 1.4.
 * I changed it so that it uses Standard C & C++ functions,
 * not the Math template library from OpenEXR.  I also changed
 * the name of the Gamma function so suit my style.
 *
 * I changed "float" to "double" cuz I'm old school & believe
 * that single-precision float is... Anyway, I prefer double.
 *
 * All comments which begin with double-slash (//) are from the
 * OpenEXR source code.
 */

using namespace Imf;

//
// Conversion from raw pixel data to data for the OpenGL frame buffer:
//
//  1) Compensate for fogging by subtracting defog
//     from the raw pixel values.
//
//  2) Multiply the defogged pixel values by
//     2^(exposure + 2.47393).
//
//  3) Values, which are now 1.0, are called "middle gray".
//     If defog and exposure are both set to 0.0, then
//     middle gray corresponds to a raw pixel value of 0.18.
//     In step 6, middle gray values will be mapped to an
//     intensity 3.5 f-stops below the display's maximum
//     intensity.
//
//  4) Apply a knee function.  The knee function has two
//     parameters, kneeLow and kneeHigh.  Pixel values
//     below 2^kneeLow are not changed by the knee
//     function.  Pixel values above kneeLow are lowered
//     according to a logarithmic curve, such that the
//     value 2^kneeHigh is mapped to 2^3.5 (in step 6,
//     this value will be mapped to the the display's
//     maximum intensity).
//
//  5) Gamma-correct the pixel values, assuming that the
//     screen's gamma is 2.2 (or 1 / 0.4545).
//
//  6) Scale the values such that pixels middle gray
//     pixels are mapped to 84.66 (or 3.5 f-stops below
//     the display's maximum intensity).
//
//  7) Clamp the values to [0, 255].
//


static double
knee (double x, double f)
{
  return log (x * f + 1) / f;
}


static double
findKneeF (double x, double y)
{
  double f0 = 0;
  double f1 = 1;

  while (knee (x, f1) > y)
  {
    f0 = f1;
    f1 = f1 * 2;
  }

  for (int i = 0; i < 30; ++i)
  {
    double f2 = (f0 + f1) / 2;
    double y2 = knee (x, f2);

    if (y2 < y)
      f1 = f2;
    else
      f0 = f2;
  }

  return (f0 + f1) / 2;
}

static double
clamp (double a, double l, double h)
{
  return (a < l)? l : ((a > h)? h : a);
}


namespace {
  double max (double a, double b) { return a > b ? a : b; }

  struct Gamma
  {
    double m, d, kl, f;

    double operator () (half h);
  };

  double
  Gamma::operator () (half h)
  {
    //
    // Defog
    //

    double x = max (0.f, (h - d));

    //
    // Exposure
    //

    x *= m;

    //
    // Knee
    //

    if (x > kl)
      x = kl + knee (x - kl, f);

    //
    // Gamma
    //

    x = pow (x, 0.4545);

    //
    // Scale and clamp
    //

    return clamp (x * 84.66, 0.0, 255.0);
  }
};

static Gamma *
S_MakeGamma (double exposure, double defog, double kneeLow, double kneeHigh)
{
  Gamma *gamma = NULL;

  gamma = new Gamma;
  if (gamma != NULL) {
    gamma->m = pow (2.0, exposure + 2.47393);
    gamma->d = defog;
    gamma->kl = pow (2.0, kneeLow);
    gamma->f = findKneeF (pow (2.0, kneeHigh) - gamma->kl, 
                          pow (2.0, 3.5) - gamma->kl);
  } else {
    fprintf (stderr, "\n%s:%d:", __FILE__, __LINE__);
    fprintf (stderr, " new Gamma failed");
  }
  return gamma;
}

void
PIXEL_ExrToPpm (Imf::Rgba *src, PpmPixel *dst)
{
  static Gamma *gamma = NULL;

  assert (src != NULL);
  assert (dst != NULL);
  if (gamma == NULL) {
    gamma = S_MakeGamma (0.0, 0.0, 0.0, 5.0);
  }
  assert (gamma != NULL);
  dst->r = (*gamma) (src->r);
  dst->g = (*gamma) (src->g);
  dst->b = (*gamma) (src->b);
}

/* --- end of file --- */
