#ifndef MODULE_BITMAP_BITMAP_FUNCTIONS_H
#define MODULE_BITMAP_BITMAP_FUNCTIONS_H

// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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

/** \file
          \author Anders Dahnielson (anders@dahnielson.com)
*/

#include <k3dsdk/bitmap.h>

namespace libk3dbitmap
{

/////////////////////////////////////////////////
// Bitmap Manipulation

/// Rotate bitmap 90 degrees clockwise
template <typename bitmap_t>
void bitmap_rotate90CW(bitmap_t& Source, bitmap_t& Destination)
{
        // Sanity checks ...
	if (Source.width() != Destination.height() ||
	    Source.height() != Destination.width())
		return;

	// For each row in the destination ...
	for(k3d::pixel_size_t y = 0; y < Destination.height(); ++y)
	{
		typename bitmap_t::iterator source = Source.begin() + (Destination.width()-1) * Source.width(); // lower-left pixel in source
		typename bitmap_t::iterator destination = Destination.begin() + y * Destination.width();        // left-most pixel in destination scanline

		// For each pixel in the destination row ...
		for(k3d::pixel_size_t x = 0; x < Destination.width(); ++x)
		{
			*(destination + x) = *(source + y);
			source -= Source.width();
		}
	}
}

/// Rotate bitmap 90 degrees counter-clockwise
template <typename bitmap_t>
void bitmap_rotate90CCW(bitmap_t& Source, bitmap_t& Destination)
{
        // Sanity checks ...
	if (Source.width() != Destination.height() ||
	    Source.height() != Destination.width())
		return;

	// For each row in the destination ...
	for(k3d::pixel_size_t y = 0; y < Destination.height(); ++y)
	{
		typename bitmap_t::iterator source = Source.begin() + (Destination.height()-1);          // upper-right pixel in source
		typename bitmap_t::iterator destination = Destination.begin() + y * Destination.width(); // left-most pixel in destination scanline

		// For each pixel in the destination row ...
		for(k3d::pixel_size_t x = 0; x < Destination.width(); ++x)
		{
			*(destination + x) = *(source - y);
			source += Source.width();
		}
	}
}

/// Rotate bitmap 180 degrees
template <typename bitmap_t>
void bitmap_rotate180(bitmap_t& Source, bitmap_t& Destination)
{
        // Sanity checks ...
	if (Source.width() != Destination.width() ||
	    Source.height() != Destination.height())
		return;

	typename bitmap_t::iterator source = Source.begin() + (Destination.width()-1) + (Destination.height()-1) * Source.width(); // lower-right pixel in source

	// For each row in the destination ...
	for(k3d::pixel_size_t y = 0; y < Destination.height(); ++y)
	{
		typename bitmap_t::iterator destination = Destination.begin() + y * Destination.width(); // left-most pixel in destination scanline

		// For each pixel in the destination row ...
		for(k3d::pixel_size_t x = 0; x < Destination.width(); ++x)
		{
			*(destination + x) = *(source - x);
		}
		source -= Source.width();
	}
}

/// Flip bitmap
template <typename bitmap_t>
void bitmap_flip(bitmap_t& Source, bitmap_t& Destination)
{
        // Sanity checks ...
	if (Source.width() != Destination.width() ||
	    Source.height() != Destination.height())
		return;

	typename bitmap_t::iterator source = Source.begin() + (Destination.width()-1); // upper-right pixel in source

	// For each row in the destination ...
	for(k3d::pixel_size_t y = 0; y < Destination.height(); ++y)
	{
		typename bitmap_t::iterator destination = Destination.begin() + y * Destination.width(); // left-most pixel in destination scanline

		// For each pixel in the destination row ...
		for(k3d::pixel_size_t x = 0; x < Destination.width(); ++x)
		{
			*(destination + x) = *(source - x);
		}
		source += Source.width();
	}
}

/// Flop bitmap
template <typename bitmap_t>
void bitmap_flop(bitmap_t& Source, bitmap_t& Destination)
{
        // Sanity checks ...
	if (Source.width() != Destination.width() ||
	    Source.height() != Destination.height())
		return;

	typename bitmap_t::iterator source = Source.begin() + (Destination.height()-1) * Source.width(); // lower-left pixel in source

	// For each row in the destination ...
	for(k3d::pixel_size_t y = 0; y < Destination.height(); ++y)
	{
		typename bitmap_t::iterator destination = Destination.begin() + y * Destination.width(); // left-most pixel in destination scanline

		// For each pixel in the destination row ...
		for(k3d::pixel_size_t x = 0; x < Destination.width(); ++x)
		{
			*(destination + x) = *(source + x);
		}
		source -= Source.width();
	}
}

/// Pad bitmap
template <typename bitmap_t>
void bitmap_padding(bitmap_t& Source, bitmap_t& Destination, 
		    const k3d::pixel_size_t left, const k3d::pixel_size_t right, 
		    const k3d::pixel_size_t top, const k3d::pixel_size_t bottom)
{
        // Sanity checks ...
	if (Destination.width() != Source.width()+left+right ||
	    Destination.height() != Source.height()+top+bottom)
		return;

	for(k3d::pixel_size_t y = 0; y < Source.height(); ++y)
	{
	        typename bitmap_t::iterator source = Source.begin() + y * Source.width();
		typename bitmap_t::iterator destination = Destination.begin() + (y + top) * Destination.width();
		
		for(k3d::pixel_size_t x = 0; x < Source.width(); ++x)
			*(destination + left + x) = *(source + x);
	}
}

/// Crop bitmap
template <typename bitmap_t>
void bitmap_croping(bitmap_t& Source, bitmap_t& Destination, 
		    const k3d::pixel_size_t left, const k3d::pixel_size_t right, 
		    const k3d::pixel_size_t top, const k3d::pixel_size_t bottom)
{
        // Sanity checks ...
	if (Destination.width() != Source.width()-left-right ||
	    Destination.height() != Source.height()-top-bottom)
		return;

	for(k3d::pixel_size_t y = 0; y < Destination.height(); ++y)
	{
	        typename bitmap_t::iterator source = Source.begin() + (y + top) * Source.width();
		typename bitmap_t::iterator destination = Destination.begin() + y * Destination.width();
		
		for(k3d::pixel_size_t x = 0; x < Destination.width(); ++x)
			*(destination + x) = *(source + left + x);
	}
}

/////////////////////////////////////////////////
// Wolbergs's Resampling Algorithm

// Based on "Digital Image Warping" by George Wolberg [Wolberg90] as described 
// in "One-Dimensional Resampling with Inverse and Forward Mapping Functions" 
// by George Wolberg et al. [WolbergEtAl00]

// f        an array of real-valued coordinates denoting the forward mapping of each input pixel
// in       array of input scanline pixels
// out      array of output scanline pixels
// inlen    the lenght of the input scanline
// outlen   the lenght of the output scanline

/// Wolberg's Forward Mapping Algorithm
template <typename array_t, typename scanline_t>
void walg_forward(array_t f, scanline_t& in, scanline_t& out, int inlen, int outlen)
{
	int     u,         // index into input image
		x,         // index into output image
		ul,        // index of the leftmost input pixel
		ur,        // index of the rightmost input pixel
		ix0,       // index of the left side of interval
		ix1;       // index of the right side of interval
	double  x0,        // index of the left side of interval
		x1;        // index of the right side of interval
	k3d::pixel dI;     // intensity increment over interval

	// clear output/accumulator array
	for (x=0; x < outlen; ++x)
		out[x] = k3d::pixel(0,0,0,k3d::pixel::sample_traits::transparent());

	// check if no input scanline pixel projects to [0,outlen] range
	if (f[inlen] < 0 || f[0] > outlen) return;  // 100% clipping

	// init ul, the left-extent of the input scanline
	for (u=0; f[u] < 0; ++u);  // advance u
	ul = u;                    // init ul

	// process first interval: clip left straddle
	if (u > 0) 
	{
		u = ul - 1;
		x0 = f[u];
		x1 = f[u+1];
		ix0 = static_cast<int>(x0 - 1);
		ix1 = static_cast<int>(x1);

		// central interval; will be clipped for x < 0
		dI.red   = (in[u+1].red   - in[u].red)   / (x1 - x0);
		dI.green = (in[u+1].green - in[u].green) / (x1 - x0);
		dI.blue  = (in[u+1].blue  - in[u].blue)  / (x1 - x0);
		dI.alpha = (in[u+1].alpha - in[u].alpha) / (x1 - x0);
		for (x=0; x < ix1 && x < outlen; ++x)
		{
			out[x].red   = in[u].red   + dI.red   * (x-x0);
			out[x].green = in[u].green + dI.green * (x-x0);
			out[x].blue  = in[u].blue  + dI.blue  * (x-x0);
			out[x].alpha = in[u].alpha + dI.alpha * (x-x0);
		}

		// right straddle
		if (ix1 != x1 && ix1 < outlen)
		{
			out[ix1].red   = (in[u].red   + dI.red   * (ix1-x0)) * (x1-ix1);
			out[ix1].green = (in[u].green + dI.green * (ix1-x0)) * (x1-ix1);
			out[ix1].blue  = (in[u].blue  + dI.blue  * (ix1-x0)) * (x1-ix1);
			out[ix1].alpha = (in[u].alpha + dI.alpha * (ix1-x0)) * (x1-ix1);
		}
	}

	// init, ur the right-extent of the input scanline
	for (u=inlen; f[u] > outlen; --u);   // advance u
	ur = (u == inlen) ? inlen-1 : u;     // init ur

	// check if only one input pixel covers scanline
	if (u == ul) return;

	// process last interval: clip right straddle
	if (u < inlen) {
		x0 = f[u];                      // real-valued left index
		ix0 = static_cast<int>(f[u]);   // int-valued left index

		x1 = f[u+1];                    // real-valued right index
		ix1 = static_cast<int>(f[u+1]); // int-valued right index
		
		// left straddle
		out[ix0].red   += in[u].red   * (ix0-x0+1);
		out[ix0].green += in[u].green * (ix0-x0+1);
		out[ix0].blue  += in[u].blue  * (ix0-x0+1);
		out[ix0].alpha += in[u].alpha * (ix0-x0+1);

		// central interval: will be clipped for x >= outlen
		dI.red   = (in[u+1].red   - in[u].red)   / (x1 - x0);
		dI.green = (in[u+1].green - in[u].green) / (x1 - x0);
		dI.blue  = (in[u+1].blue  - in[u].blue)  / (x1 - x0);
		dI.alpha = (in[u+1].alpha - in[u].alpha) / (x1 - x0);
		for (x=ix0+1; x < ix1 && x < outlen; ++x)
		{
			out[x].red   = in[u].red   + dI.red   * (x-x0);
			out[x].green = in[u].green + dI.green * (x-x0);
			out[x].blue  = in[u].blue  + dI.blue  * (x-x0);
			out[x].alpha = in[u].alpha + dI.alpha * (x-x0);
		}
	}

	// main loop
	for (u=ul; u <= ur; ++u)
	{
		x0 = f[u];                      // real-valued left index
		ix0 = static_cast<int>(f[u]);   // int-valued left index

		x1 = f[u+1];                    // real-valued right index
		ix1 = static_cast<int>(f[u+1]); // int-valued right index

		// check if interval is embedded in one output pixel
		if (ix0 == ix1)
		{
			out[ix1].red   += in[u].red   * (x1-x0);   // accumulate pixel
			out[ix1].green += in[u].green * (x1-x0);   // accumulate pixel
			out[ix1].blue  += in[u].blue  * (x1-x0);   // accumulate pixel
			out[ix1].alpha += in[u].alpha * (x1-x0);   // accumulate pixel
			continue;                      // next input pixel
		}

		// left straddle
		out[ix0].red   += in[u].red   * (ix0-x0+1);       // add input fragment
		out[ix0].green += in[u].green * (ix0-x0+1);       // add input fragment
		out[ix0].blue  += in[u].blue  * (ix0-x0+1);       // add input fragment
		out[ix0].alpha += in[u].alpha * (ix0-x0+1);       // add input fragment

		// central interval
		dI.red   = (in[u+1].red   - in[u].red)   / (x1 - x0);   // for linear interpolation
		dI.green = (in[u+1].green - in[u].green) / (x1 - x0);   // for linear interpolation
		dI.blue  = (in[u+1].blue  - in[u].blue)  / (x1 - x0);   // for linear interpolation
		dI.alpha = (in[u+1].alpha - in[u].alpha) / (x1 - x0);   // for linear interpolation
		for (x=ix0+1; x < ix1; ++x)           // visit all pixels
		{
			out[x].red   = in[u].red   + dI.red   * (x-x0);   // init output pixel
			out[x].green = in[u].green + dI.green * (x-x0);   // init output pixel
			out[x].blue  = in[u].blue  + dI.blue  * (x-x0);   // init output pixel
			out[x].alpha = in[u].alpha + dI.alpha * (x-x0);   // init output pixel
		}

		// right straddle
		if (x1 != ix1)                        // output pixel fragment
		{
			out[ix1].red   = (in[u].red   + dI.red   * (ix1-x0)) * (x1-ix1);
			out[ix1].green = (in[u].green + dI.green * (ix1-x0)) * (x1-ix1);
			out[ix1].blue  = (in[u].blue  + dI.blue  * (ix1-x0)) * (x1-ix1);
			out[ix1].alpha = (in[u].alpha + dI.alpha * (ix1-x0)) * (x1-ix1);
		}
	}
}

/////////////////////////////////////////////////
// Scanline

/// Class to iterate through a bitmap in scanline order
template<typename T>
class scanline
{
public:
	scanline(T* p, std::size_t len) :
		m_p(p),
		m_len(len)
	{
	}

	/// Access individual pixels in scanline
	T& operator [] (std::size_t i)
	{
		return m_p[i];
	}

        /// Increment to next scanline
        T* operator ++ ()
        {
                m_p = &m_p[m_len];
                return m_p;
        }

	/// Increment to next scanline
        T* operator ++ (int)
        {
                m_p = &m_p[m_len];
                return m_p;
        }

        /// Get scanline length
        std::size_t size()
        {
                return m_len;
        }

private:
        T* m_p;
        std::size_t m_len;
};

} // namespace libk3dbitmap

#endif // !MODULE_BITMAP_BITMAP_FUNCTIONS_H
