/*
------------------------------------------------------------------------------
denef - Decode NEF image files
Copyright (C) 2000 Daniel Stephens (daniel@cheeseplant.org)

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
------------------------------------------------------------------------------
*/

#include <iostream.h>
#include <math.h>
#include <stdlib.h>

#include "interp.h"

static const char *rcsid="$Id: interp_lumin.cc,v 1.3 2000/09/18 07:51:43 daniel Exp $";

// Luminance based interpolator, which uses all colors to interpolate
// luminance channel and then interpolates colors from that.

// Define the region we're willing to interpolate in a single pass (used to
// determine how large of a 'workspace' area to allocate.

static const int zoneWidth  = 256;
static const int zoneHeight = 256;
static const int zoneSize = zoneWidth * zoneHeight;

// Define coeficients for each color when determining luminance.
// These may be a little off but I dont know the ACTUAL colors of the filters
// over the CCD on a D1. This works on the basic assumption that the D1's
// CCD returns a linear strength for each channel.

// From C. Poynton's FAQ on Gamma (http://www.inforamp.net/~poynton/)
// CIE Rec 709
static const double luminCoefRed   = 0.2126;
static const double luminCoefGreen = 0.7152;
static const double luminCoefBlue  = 0.0722;

luminanceInterpolator::luminanceInterpolator()
{
    tmpY  = new double[zoneSize];
    tmpCr = new double[zoneSize];
    tmpCb = new double[zoneSize];
}

luminanceInterpolator::~luminanceInterpolator()
{
    delete [] tmpY;
    delete [] tmpCr;
    delete [] tmpCb;
}

int
luminanceInterpolator::Fringe() const
{
    return 2;
}

int
luminanceInterpolator::Max_Width() const
{
    return zoneWidth - 4;
}

int
luminanceInterpolator::Max_Height() const
{
    return zoneHeight - 4;
}

void
luminanceInterpolator::Interpolate(const nefImageData &nef,
					int xofs, int yofs,
					int width, int height,
					fullImageData &result,
					int rxofs, int ryofs)
{
    /* This function takes a chunk of the NEF data and creates the decoded
       image from it.

       Little bit more complex luminance based algorithm.
    */

    // Interpolate 'intermediate' red and blue values, normalize them all
    // to 0.0 - 1.0
    
    for (int y=-2; y<(height+2); ++y) {
	const short *pn  = nef.Data(xofs, yofs + y - 1);
	const short *p   = nef.Data(xofs, yofs + y    );
	const short *ps  = nef.Data(xofs, yofs + y + 1);

	int ofs = 0;
	if (y & 1) {ofs=1; ++pn; ++p; ++ps;}

	for (int x=ofs-2; x<(width+2); x+=2, pn+=2, p+=2, ps+=2) {
	    double X=(double)(pn[-1]+pn[1]+ps[-1]+ps[1]) * 0.25;

	    X /=  (double)nef.Max_Value();

	    if (y & 1) {
		tmpCr[(y+2)*zoneWidth + (x+2)] = ((double)*p /
						  (double)nef.Max_Value());
		tmpCb[(y+2)*zoneWidth + (x+2)] = X;
	    } else {
		tmpCr[(y+2)*zoneWidth + (x+2)] = X;
		tmpCb[(y+2)*zoneWidth + (x+2)] = ((double)*p  /
						  (double)nef.Max_Value());
	    }
	}
    }

    // Calculate Luminance for each pixel, normalized to 0.0 - 1.0

    for (int y=-2; y<(height+2); ++y) {
	const short *pn  = nef.Data(xofs, yofs + y - 1);
	const short *p   = nef.Data(xofs, yofs + y    );
	const short *ps  = nef.Data(xofs, yofs + y + 1);

	int ofs = (y+2)*zoneWidth;

	bool green_pixel = (y & 1);

	for (int x=-2; x<(width+2);
	     ++x, ++pn, ++p, ++ps, ++ofs, green_pixel = !green_pixel) {
	    double R,B,G;
	    
	    if (green_pixel) {
		R=(tmpCr[ofs-1] + tmpCr[ofs+1] +
		   tmpCr[ofs-zoneWidth] + tmpCr[ofs+zoneWidth]) * 0.25;
		G=(double)*p;
		B=(tmpCb[ofs-1] + tmpCb[ofs+1] +
		   tmpCb[ofs-zoneWidth] + tmpCb[ofs+zoneWidth]) * 0.25;
	    } else {
		R = tmpCr[ofs];
		G=(double)(pn[0]+p[1]+p[-1]+ps[0]) * 0.25;
		B = tmpCb[ofs];
	    }

	    G /= (double)nef.Max_Value();

	    double Y = (luminCoefRed   * R + 
			luminCoefGreen * G +
			luminCoefBlue  * B);
	    
	    tmpY[(y+2)*zoneWidth + (x+2)] = Y;
	}
    }

    // Now calculate chrominances.. Start by transferring known values

    for (int y=-2; y<(height+2); ++y) {
	const short *p   = nef.Data(xofs, yofs + y    );

	int x = -2;
	int ofs = (y+2) * zoneWidth;
	if (y & 1) {++x; ++ofs; ++p;}

	for (; x<(width+2); x+=2, ofs+=2, p+=2) {
	    double Y = tmpY[ofs];
	    if (y & 1) {
		tmpCr[ofs] = (double)*p / (double)nef.Max_Value() - Y;
	    } else {
		tmpCb[ofs] = (double)*p / (double)nef.Max_Value() - Y;
	    }
	}
    }

    // Next interpolate 'diagonals' again

    for (int y=-2; y<(height+2); ++y) {
	int x = -2;
	int ofs = (y+2) * zoneWidth;
	if (y & 1) {++x; ++ofs;}

	for (; x<(width+2); x+=2, ofs+=2) {
	    double *Cx;
	    if (y & 1) {
		Cx = tmpCb;
	    } else {
		Cx = tmpCr;
	    }
	    Cx[ofs] = (Cx[ofs - zoneWidth - 1] + 
		       Cx[ofs + zoneWidth + 1] +
		       Cx[ofs - zoneWidth + 1] +
		       Cx[ofs + zoneWidth - 1]) * 0.25;
	}
    }

    // Finally calculate color values for each pixel

    for (int y=0; y<height; ++y) {
	int ofs = (y+2)*zoneWidth + 2;

	bool green_pixel = (y & 1);

	for (int x=0; x<width; ++x, ++ofs, green_pixel = !green_pixel) {
	    double Y,Cr,Cb;
	    
	    Y=tmpY[ofs];

	    if (green_pixel) {
		Cr=(tmpCr[ofs-1] + tmpCr[ofs+1] +
		    tmpCr[ofs-zoneWidth] + tmpCr[ofs+zoneWidth]) * 0.25;
		Cb=(tmpCb[ofs-1] + tmpCb[ofs+1] +
		    tmpCb[ofs-zoneWidth] + tmpCb[ofs+zoneWidth]) * 0.25;
	    } else {
		Cr = tmpCr[ofs];
		Cb = tmpCb[ofs];
	    }

	    rgbTriple &res = result(rxofs+x, ryofs+y);

	    res.r = Cr + Y;
	    res.b = Cb + Y;
	    
	    res.g = (Y - luminCoefRed * (Cr + Y) - luminCoefBlue * (Cb + Y)) 
		/ luminCoefGreen;
	}
    }
}

