/*
 * The Progressive Graphics File; http://www.libpgf.org
 * 
 * $Date: 2006-06-04 22:05:59 +0200 (So, 04 Jun 2006) $
 * $Revision: 229 $
 * 
 * This file Copyright (C) 2006 xeraina GmbH, Switzerland
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
 * as published by the Free Software Foundation; either version 2.1
 * 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 Subband.cpp
/// @brief PGF wavelet subband class implementation
/// @author C. Stamm

#include "Subband.h"
#include "Encoder.h"
#include "Decoder.h"

/////////////////////////////////////////////////////////////////////
// Default constructor
CSubband::CSubband() 
: m_width(0)
, m_height(0)
, m_size(0)
, m_level(0)
, m_orientation(LL)
, m_data(0)
, m_dataPos(0)
#ifdef __PGFROISUPPORT__
, m_nTiles(0)
#endif
{
}

/////////////////////////////////////////////////////////////////////
// Destructor
CSubband::~CSubband() {
	FreeMemory();
}

/////////////////////////////////////////////////////////////////////
// Initialize subband parameters
void CSubband::Initialize(UINT32 width, UINT32 height, int level, Orientation orient) {
	m_width = width;
	m_height = height;
	m_size = m_width*m_height;
	m_level = level;
	m_orientation = orient;
	m_data = 0;
	m_dataPos = 0;
#ifdef __PGFROISUPPORT__
	m_ROI.left = 0;
	m_ROI.top = 0;
	m_ROI.right = m_width;
	m_ROI.bottom = m_height;
	m_nTiles = 0;
#endif
}

/////////////////////////////////////////////////////////////////////
// Allocate a memory buffer to store all wavelet coefficients of this subband.
// @return True if the allocation works without any problems
bool CSubband::AllocMemory() {
	UINT32 oldSize = m_size;

#ifdef __PGFROISUPPORT__
	m_size = BufferWidth()*m_ROI.Height();
#endif
	ASSERT(m_size > 0);

	if (m_data) {
		if (oldSize >= m_size) {
			return true;
		} else {
			delete[] m_data;
			m_data = new(std::nothrow) DataT[m_size];
			return (m_data != 0);
		}
	} else {
		m_data = new(std::nothrow) DataT[m_size];
		return (m_data != 0);
	}
}

/////////////////////////////////////////////////////////////////////
// Delete the memory buffer of this subband.
void CSubband::FreeMemory() {
	if (m_data) {
		delete[] m_data; m_data = 0;
	}
}

/////////////////////////////////////////////////////////////////////
// Perform subband quantization with given quantization parameter.
// A scalar quantization (with dead-zone) is used. A large quantization value
// results in strong quantization and therefore in big quality loss.
// @param quantParam A quantization parameter (larger or equal to 0)
void CSubband::Quantize(int quantParam) {
	if (m_orientation == LL) {
		quantParam -= (m_level + 1);
		// uniform rounding quantization
		if (quantParam > 0) {
			quantParam--;
			for (UINT32 i=0; i < m_size; i++) {
				if (m_data[i] < 0) {
					m_data[i] = -(((-m_data[i] >> quantParam) + 1) >> 1);
				} else {
					m_data[i] = ((m_data[i] >> quantParam) + 1) >> 1;
				}
			}
		}
	} else {
		if (m_orientation == HH) {
			quantParam -= (m_level - 1);
		} else {
			quantParam -= m_level;
		}
		// uniform deadzone quantization
		if (quantParam > 0) {
			int threshold = ((1 << quantParam) * 7)/5;	// good value
			quantParam--;
			for (UINT32 i=0; i < m_size; i++) {
				if (m_data[i] < -threshold) {
					m_data[i] = -(((-m_data[i] >> quantParam) + 1) >> 1);
				} else if (m_data[i] > threshold) {
					m_data[i] = ((m_data[i] >> quantParam) + 1) >> 1;
				} else {
					m_data[i] = 0;
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////
/// Perform subband dequantization with given quantization parameter.
/// A scalar quantization (with dead-zone) is used. A large quantization value
/// results in strong quantization and therefore in big quality loss.
/// @param quantParam A quantization parameter (larger or equal to 0)
void CSubband::Dequantize(int quantParam) {
	if (m_orientation == LL) {
		quantParam -= m_level + 1;
	} else if (m_orientation == HH) {
		quantParam -= m_level - 1;
	} else {
		quantParam -= m_level;
	}
	if (quantParam > 0) {
		for (UINT32 i=0; i < m_size; i++) {
			m_data[i] <<= quantParam;
		}
	}
}

/////////////////////////////////////////////////////////////////////
/// Extracts a rectangular subregion of this subband.
/// Write wavelet coefficients into buffer.
/// It might throw an IOException.
/// @param encoder An encoder instance
/// @param tile True if just a rectangular region is extracted, false if the entire subband is extracted.
/// @param tileX Tile index in x-direction
/// @param tileY Tile index in y-direction
void CSubband::ExtractTile(CEncoder& encoder, bool tile /*= false*/, UINT32 tileX /*= 0*/, UINT32 tileY /*= 0*/) {
#ifdef __PGFROISUPPORT__
	if (tile) {
		// compute tile position and size
		UINT32 xPos, yPos, w, h;
		TilePosition(tileX, tileY, xPos, yPos, w, h);

		// write values into buffer using partitiong scheme
		encoder.Partition(this, w, h, xPos + yPos*m_width, m_width);
	} else 
#endif
	{
		tileX; tileY; tile; // prevents from unreferenced formal parameter warning
		// write values into buffer using partitiong scheme
		encoder.Partition(this, m_width, m_height, 0, m_width);
	}
}

/////////////////////////////////////////////////////////////////////
/// Decoding and dequantization of this subband.
/// It might throw an IOException.
/// @param decoder A decoder instance
/// @param quantParam Dequantization value
/// @param tile True if just a rectangular region is placed, false if the entire subband is placed.
/// @param tileX Tile index in x-direction
/// @param tileY Tile index in y-direction
void CSubband::PlaceTile(CDecoder& decoder, int quantParam, bool tile /*= false*/, UINT32 tileX /*= 0*/, UINT32 tileY /*= 0*/) {
	// allocate memory
	if (!AllocMemory()) ReturnWithError(InsufficientMemory);

	// correct quantParam with normalization factor
	if (m_orientation == LL) {
		quantParam -= m_level + 1;
	} else if (m_orientation == HH) {
		quantParam -= m_level - 1;
	} else {
		quantParam -= m_level;
	}
	if (quantParam < 0) quantParam = 0;

#ifdef __PGFROISUPPORT__
	if (tile) {
		UINT32 xPos, yPos, w, h;

		// compute tile position and size
		TilePosition(tileX, tileY, xPos, yPos, w, h);
		
		ASSERT(xPos >= m_ROI.left && yPos >= m_ROI.top);
		decoder.Partition(this, quantParam, w, h, (xPos - m_ROI.left) + (yPos - m_ROI.top)*BufferWidth(), BufferWidth());
	} else 
#endif
	{
		tileX; tileY; tile; // prevents from unreferenced formal parameter warning
		// read values into buffer using partitiong scheme
		decoder.Partition(this, quantParam, m_width, m_height, 0, m_width);
	}
}



#ifdef __PGFROISUPPORT__
//////////////////////////////////////////////////////////////////////
/// Set ROI
void CSubband::SetAlignedROI(const PGFRect& roi) {
	ASSERT(roi.left <= m_width); 
	ASSERT(roi.top <= m_height); 
	
	m_ROI = roi; 
	if (m_ROI.right > m_width) m_ROI.right = m_width; 
	if (m_ROI.bottom > m_height) m_ROI.bottom = m_height;
}

//////////////////////////////////////////////////////////////////////
/// Compute tile position and size.
/// @param tileX Tile index in x-direction
/// @param tileY Tile index in y-direction
/// @param xPos [out] Offset to left
/// @param yPos [out] Offset to top
/// @param w [out] Tile width
/// @param h [out] Tile height
void CSubband::TilePosition(UINT32 tileX, UINT32 tileY, UINT32& xPos, UINT32& yPos, UINT32& w, UINT32& h) const {
	ASSERT(tileX < m_nTiles); ASSERT(tileY < m_nTiles);
	// example
	// band = HH, w = 30, ldTiles = 2 -> 4 tiles in a row/column
	// --> tile widths
	// 8 7 8 7
	// 
	// tile partitioning scheme
	// 0 1 2 3
	// 4 5 6 7
	// 8 9 A B
	// C D E F

	UINT32 nTiles = m_nTiles;
	UINT32 m;
	UINT32 left = 0, right = nTiles;
	UINT32 top = 0, bottom = nTiles;

	xPos = 0;
	yPos = 0;
	w = m_width;
	h = m_height;

	while (nTiles > 1) {
		// compute xPos and w with binary search
		m = left + ((right - left) >> 1);
		if (tileX >= m) {
			xPos += (w + 1) >> 1;
			w >>= 1;
			left = m;
		} else {
			w = (w + 1) >> 1;
			right = m;
		}
		// compute yPos and h with binary search
		m = top + ((bottom - top) >> 1);
		if (tileY >= m) {
			yPos += (h + 1) >> 1;
			h >>= 1;
			top = m;
		} else {
			h = (h + 1) >> 1;
			bottom = m;
		}
		nTiles >>= 1;
	}
	ASSERT(xPos < m_width && (xPos + w <= m_width));
	ASSERT(yPos < m_height && (yPos + h <= m_height));
}

//////////////////////////////////////////////////////////////////////
/// Compute tile index and extrem position (x,y) of given position (xPos, yPos).
void CSubband::TileIndex(bool topLeft, UINT32 xPos, UINT32 yPos, UINT32& tileX, UINT32& tileY, UINT32& x, UINT32& y) const {
	UINT32 m;
	UINT32 left = 0, right = m_width;
	UINT32 top = 0, bottom = m_height;
	UINT32 nTiles = m_nTiles;

	if (xPos > m_width) xPos = m_width;
	if (yPos > m_height) yPos = m_height;

	if (topLeft) {
		// compute tileX with binary search
		tileX = 0;
		while (nTiles > 1) {
			nTiles >>= 1;
			m = left + ((right - left + 1) >> 1);
			if (xPos < m) {
				// exclusive m
				right = m;
			} else {
				tileX += nTiles;
				left = m;
			}
		}
		x = left;
		ASSERT(tileX >= 0 && tileX < m_nTiles);

		// compute tileY with binary search
		nTiles = m_nTiles;
		tileY = 0;
		while (nTiles > 1) {
			nTiles >>= 1;
			m = top + ((bottom - top + 1) >> 1);
			if (yPos < m) {
				// exclusive m
				bottom = m;
			} else {
				tileY += nTiles;
				top = m;
			}
		}
		y = top;
		ASSERT(tileY >= 0 && tileY < m_nTiles);

	} else {
		// compute tileX with binary search
		tileX = 1;
		while (nTiles > 1) {
			nTiles >>= 1;
			m = left + ((right - left + 1) >> 1);
			if (xPos <= m) {
				// inclusive m
				right = m;
			} else {
				tileX += nTiles;
				left = m;
			}
		}
		x = right;
		ASSERT(tileX > 0 && tileX <= m_nTiles);

		// compute tileY with binary search
		nTiles = m_nTiles;
		tileY = 1;
		while (nTiles > 1) {
			nTiles >>= 1;
			m = top + ((bottom - top + 1) >> 1);
			if (yPos <= m) {
				// inclusive m
				bottom = m;
			} else {
				tileY += nTiles;
				top = m;
			}
		}
		y = bottom;
		ASSERT(tileY > 0 && tileY <= m_nTiles);
	}
}

#endif
