/*
 * ImageScaler.cpp
 *
 * (c) 2002 by Thorsten Scheuermann <tscheuermann@gmx.net>
 *
 * 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.
 */

#include "ImageScaler.h"
#include <string.h>

const int ImageScaler::numComponents = 3;

ImageScaler::ImageScaler()
:	accum(NULL), rowBuf(NULL), dataConsumer(NULL)
{
}

ImageScaler::~ImageScaler()
{
	endImage();			// free any remaining buffers
}

void ImageScaler::setOutputDimension(int width, int height)
{
	destWidth = width;
	destHeight = height;
}

void ImageScaler::setImageDimension(int w, int h)
{
	origWidth = w;
	origHeight = h;
}

void ImageScaler::setNumImageChannels(int c)
{
	// assume c=3 here
}

void ImageScaler::setConsumer(ImageDataSink *c)
{
	dataConsumer = c;
}

void ImageScaler::startImage()
{
	// initialize buffers and scaling factors
	accum = new fixed[destWidth * numComponents];
	rowBuf = new fixed[destWidth * numComponents];
	xScale = (float)origWidth/(float)destWidth;
	yScale = (float)origHeight/(float)destHeight;
	scale = 1/(xScale*yScale);

	accumSize = destWidth * numComponents;
	memset(accum, 0, sizeof(fixed) * destWidth * numComponents);

	yFrac = yScale;

	// send information to consumer
	if(dataConsumer)
	{
		dataConsumer->setImageDimension(destWidth, destHeight);
		dataConsumer->setNumImageChannels(numComponents);
		dataConsumer->startImage();
	}
}

void ImageScaler::endImage()
{
	if(accum)
	{
		delete[] accum;
		accum = NULL;
  }
	if(rowBuf)
	{
		delete[] rowBuf;
		rowBuf = NULL;
	}
	
	if(dataConsumer)
		dataConsumer->endImage();
}

void ImageScaler::imageRow(fixed *rowData)
{
	fprintf(stderr, "ImageScaler::imageRow(fixed *) not implemented!\n");
}

void ImageScaler::imageRow(unsigned char *rowData)
{
	int i;
	fixed oneMinusYFrac;

	// scale in x direction
	memset(rowBuf, 0, sizeof(fixed) * destWidth * numComponents);
	scaleRow(rowData);

	if(yFrac > fixed(1))
	{
		for(i=0; i<accumSize; i++)
			accum[i] += rowBuf[i];
		yFrac -= 1;
	}
	else
	{
		for(i=0; i<accumSize; i++)
		{
			accum[i] += yFrac * rowBuf[i];
			// apply final scaling to pixel
			accum[i] *= scale;
		}

		// pass data on to consumer
		if(dataConsumer)
			dataConsumer->imageRow(accum);

		oneMinusYFrac = 1 - yFrac;
		for(i=0; i<accumSize; i++)
			accum[i] = oneMinusYFrac * rowBuf[i];

		yFrac -= 1;
		// this addition will accumulate the error in yScale!!!
		yFrac += yScale;
	}
}

void ImageScaler::scaleRow(unsigned char *row)
{
	int i;
	int dest;
	fixed frac;
	fixed oneMinusFrac;
	frac = xScale;

	for(i=0, dest=0; i<origWidth; i++)
	{
 		if(frac > fixed(1))
 		{
     	// accumulate whole pixel
			rowBuf[3*dest] += row[3*i];
			rowBuf[3*dest+1] += row[3*i+1];
			rowBuf[3*dest+2] += row[3*i+2];
   		frac -= 1;
   	}
    else
    {
      // accumulate part into current pixel...
			rowBuf[3*dest] += frac * row[3*i];
			rowBuf[3*dest+1] += frac * row[3*i+1];
			rowBuf[3*dest+2] += frac * row[3*i+2];

   		// ...and part into the next pixel
			dest++;
			oneMinusFrac = 1 - frac;
      if(dest == destWidth)
      {
        // Don't write out of bounds. This shouldn't happen but might
        // because of fixed point roundoff errors.
        return;
      }
			rowBuf[3*dest] += oneMinusFrac * row[3*i];
			rowBuf[3*dest+1] += oneMinusFrac * row[3*i+1];
			rowBuf[3*dest+2] += oneMinusFrac * row[3*i+2];

			frac -= 1;
			frac += xScale;
    }
	}
}
