/***********************************************************************

減色処理
　制作：（株）ゼータ
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "memory_debug.h"
#include "mediancut.h"
#include "gif.h"

#define	MC__RED		1
#define	MC__GREEN	2
#define	MC__BLUE	3

#define max(a, b)  (((a) > (b)) ? (a) : (b)) 
#define min(a, b)  (((a) < (b)) ? (a) : (b)) 

/***********************************************************************
static 変数
************************************************************************/
static MedianCutInfo	gif_histgram[MEDIANCUT_THREAD_NUM][MEDIANHIST_SIZE][MEDIANHIST_SIZE][MEDIANHIST_SIZE];

/***********************************************************************
メディアンカット法による減色処理
************************************************************************/
int MedianCut(int *pData, int iWidth, int iHeight, ColorMap * cmap, unsigned char *pCode, int iWorkID)
{
	/* メモリ開放のためのツリーへのポインタリスト */
	ColorTree 	*treeptrs[1024];
	ColorTree	*tree = NULL;
	ColorTree	*newcolnode; 
	RGBColorInfo	info[256+4];
	int			treeptrcount = 0;
	int			count = 0;
	int			i, j, k;
	int			x, y;
	int			rmin, rmax, gmin, gmax, bmin, bmax;
	int			box[2];
	RGBColorInfo	newinfo;
	RGBColorInfo	col2;
	ColorTree	*parent;
	RGB4		*ptr;
	RGB4		*srcptr;
	if(pData == NULL)
		return 0;
	if((iWorkID < 0) || (iWorkID >= MEDIANCUT_THREAD_NUM))
		return 0;
	
	/* ヒストグラムテーブルを初期化 */
	for(i = 0; i < MEDIANHIST_SIZE; i++){
		for(j = 0; j < MEDIANHIST_SIZE; j++){
			for(k = 0; k < MEDIANHIST_SIZE; k++){
				memset(&(gif_histgram[iWorkID][i][j][k]), '\0', sizeof(MedianCutInfo));
			}
		}
	}

	/* 1stボックス算出とヒストグラム算出 */

	ptr = (RGB4 *)pData;
	rmin = rmax = ptr->r;
	gmin = gmax = ptr->g;
	bmin = bmax = ptr->b;
	for(y = 0; y < iHeight; y++) {
		for(x = 0; x < iWidth; x++, ptr++) {
			MedianCutInfo *his = &(gif_histgram[iWorkID][(ptr->r)>>MEDIANHIST_SHIFT][(ptr->g)>>MEDIANHIST_SHIFT][(ptr->b)>>MEDIANHIST_SHIFT]);
		
			if(his->count >= his->size) {
				his->size = (his->size) * 2 + 128;
				his->col = (RGB4 *)d_re_alloc(his->col, (his->size) * sizeof(RGB4));
			}

			his->col[his->count].b = ptr->b;
			his->col[his->count].g = ptr->g;
			his->col[his->count].r = ptr->r;
			his->count++;
			
			if(rmin > ptr->r) rmin = ptr->r;
			if(rmax < ptr->r) rmax = ptr->r;
			if(gmin > ptr->g) gmin = ptr->g;
			if(gmax < ptr->g) gmax = ptr->g;
			if(bmin > ptr->b) bmin = ptr->b;
			if(bmax < ptr->b) bmax = ptr->b;
		}
	}

	newinfo.r_pos = rmin;
	newinfo.g_pos = gmin;
	newinfo.b_pos = bmin;
	newinfo.r_size = rmax-rmin+1;
	newinfo.g_size = gmax-gmin+1;
	newinfo.b_size = bmax-bmin+1;
	newinfo.pixel_count = iWidth * iHeight;
	
	newcolnode = (ColorTree *)(d_alloc(sizeof(ColorTree)));
	treeptrs[treeptrcount++] = newcolnode;
	newcolnode->type = 0;
	newcolnode->left = NULL;
	newcolnode->right = NULL;
	
	info[count] = newinfo;
	info[count].tree = newcolnode;
	tree = newcolnode;
	count++;
	
	/* メディアンカット処理 */
	while(count < 256){
		int hist[256];
		int weit = 0;
		int index = -1;
		int	len;
		int total, counter, pos;
		int type;
		
		for(i = 0; i < count; i++) {
			int infoweit = median_info_maxlen(&(info[i])) * median_info_get_pixel_weit(&(info[i]));
			if(weit < infoweit){
				weit = infoweit;
				index = i;
			}
		}

		len = median_info_maxlen(&(info[index]));
		if(len <= 1)
			break;
		
		type = median_info_maxtype(&(info[index]));
	
		rmin = info[index].r_pos;
		rmax = info[index].r_pos + info[index].r_size -1;
		gmin = info[index].g_pos;
		gmax = info[index].g_pos + info[index].g_size -1;
		bmin = info[index].b_pos;
		bmax = info[index].b_pos + info[index].b_size -1;

		switch(type){
			case MC__RED: /* 赤 */
			for(i = rmin; i <= rmax; i++)
				hist[i] = 0;
			/* ヒストグラム算出 */
			{
				int r1 = rmin >> MEDIANHIST_SHIFT;
				int r2 = rmax >> MEDIANHIST_SHIFT;
				int g1 = gmin >> MEDIANHIST_SHIFT;
				int g2 = gmax >> MEDIANHIST_SHIFT;
				int b1 = bmin >> MEDIANHIST_SHIFT;
				int b2 = bmax >> MEDIANHIST_SHIFT;
				int r, g, b;
				total = 0;
				
				for(r = r1; r<= r2; r++) {
					for(g = g1; g <= g2; g++) {
						for(b = b1; b <= b2; b++) {
							MedianCutInfo *data = &(gif_histgram[iWorkID][r][g][b]);
							RGB4 *ptr = data->col;
							for(i = data->count; i > 0; i--, ptr++) {
								if(rmin <= ptr->r && ptr->r <= rmax
								&& gmin <= ptr->g && ptr->g <= gmax
								&& bmin <= ptr->b && ptr->b <= bmax)
								{
									hist[ptr->r]++;
									total++;
								}
							}
						}
					}
				}
			}
			/* 中点を算出 */
			for(pos = rmin + 1, counter = hist[rmin]; (pos < rmax) && (2*counter < total); counter += hist[pos++]);
			break;

			case MC__GREEN: /* 緑 */
			for(i = gmin; i <= gmax; i++)
				hist[i] = 0;
			/* ヒストグラム算出 */
			{
				int r1 = rmin >> MEDIANHIST_SHIFT;
				int r2 = rmax >> MEDIANHIST_SHIFT;
				int g1 = gmin >> MEDIANHIST_SHIFT;
				int g2 = gmax >> MEDIANHIST_SHIFT;
				int b1 = bmin >> MEDIANHIST_SHIFT;
				int b2 = bmax >> MEDIANHIST_SHIFT;
				int r, g, b;

				total = 0;
				for(r = r1; r <= r2; r++) {
					for(g = g1; g <= g2; g++) {
						for(b = b1; b <= b2; b++) {
							MedianCutInfo *data = &(gif_histgram[iWorkID][r][g][b]);
							RGB4 *ptr = data->col;
							for(i = data->count; i > 0; i--, ptr++) {
								if(rmin <= ptr->r && ptr->r <= rmax
								&& gmin <= ptr->g && ptr->g <= gmax
								&& bmin <= ptr->b && ptr->b <= bmax)
								{
									hist[ptr->g]++;
									total++;
								}
							}
						}
					}
				}
			}
			/* 中点を算出 */
			for(pos = gmin + 1, counter = hist[gmin]; (pos < gmax) && (2*counter < total); counter += hist[pos++]);
			break;

			case MC__BLUE: /* 青 */
			for(i = bmin; i <= bmax; i++)
				hist[i] = 0;
			/* ヒストグラム算出 */
			{
				int r1 = rmin >> MEDIANHIST_SHIFT;
				int r2 = rmax >> MEDIANHIST_SHIFT;
				int g1 = gmin >> MEDIANHIST_SHIFT;
				int g2 = gmax >> MEDIANHIST_SHIFT;
				int b1 = bmin >> MEDIANHIST_SHIFT;
				int b2 = bmax >> MEDIANHIST_SHIFT;
				int r, g, b;

				total = 0;
				for(r = r1; r <= r2; r++) {
					for(g = g1; g <= g2; g++) {
						for(b = b1; b <= b2; b++) {
							MedianCutInfo *data = &(gif_histgram[iWorkID][r][g][b]);
							RGB4 *ptr = data->col;
							for(i = data->count; i > 0; i--, ptr++) {
								if(rmin <= ptr->r && ptr->r <= rmax
								&& gmin <= ptr->g && ptr->g <= gmax
								&& bmin <= ptr->b && ptr->b <= bmax)
								{
									hist[ptr->b]++;
									total++;
								}
							}
						}
					}
				}
			}
			/* 中点を算出 */
			for(pos = bmin+1, counter = hist[bmin]; (pos < bmax) && (2*counter < total); counter += hist[pos++]);
			break;
		}

		if(total == 0) {
			if(count > 0){
				info[index] = info[--count];
			}
			continue;
		}

		col2 = info[index];
		parent = col2.tree;

		if(count > 0){
			info[index] = info[--count];
		}
		
		switch(type) {
			case MC__RED: /* 赤 */
			{
				ColorTree *coltree;

				info[count].r_pos = col2.r_pos;
				info[count].g_pos = col2.g_pos;
				info[count].b_pos = col2.b_pos;
				info[count].r_size = pos - col2.r_pos;
				info[count].g_size = col2.g_size;
				info[count].b_size = col2.b_size;
				info[count].pixel_count = 0;
				
				coltree = (ColorTree *)d_alloc(sizeof(ColorTree));
				treeptrs[treeptrcount++] = coltree;
				coltree->left = NULL;
				coltree->right = NULL;
				coltree->type = 0;
				info[count].tree = coltree;
				box[0] = count++;

				info[count].r_pos = pos;
				info[count].g_pos = col2.g_pos;
				info[count].b_pos = col2.b_pos;
				info[count].r_size = col2.r_size + col2.r_pos - pos;
				info[count].g_size = col2.g_size;
				info[count].b_size = col2.b_size;
				info[count].pixel_count = 0;
				
				coltree = (ColorTree *)d_alloc(sizeof(ColorTree));
				treeptrs[treeptrcount++] = coltree;

				coltree->left = NULL;
				coltree->right = NULL;
				coltree->type = 0;
				info[count].tree = coltree;
				box[1] = count++;
			}
			break;
			
			case MC__GREEN: /* 緑 */
			{
				ColorTree *coltree;
				
				info[count].r_pos = col2.r_pos;
				info[count].g_pos = col2.g_pos;
				info[count].b_pos = col2.b_pos;
				info[count].r_size = col2.r_size;
				info[count].g_size = pos - col2.g_pos;
				info[count].b_size = col2.b_size;
				info[count].pixel_count = 0;
				
				coltree = (ColorTree *)d_alloc(sizeof(ColorTree));
				treeptrs[treeptrcount++] = coltree;
				coltree->left = NULL;
				coltree->right = NULL;
				coltree->type = 0;
				info[count].tree = coltree;
				box[0] = count++;

				info[count].r_pos = col2.r_pos;
				info[count].g_pos = pos;
				info[count].b_pos = col2.b_pos;
				info[count].r_size = col2.r_size;
				info[count].g_size = col2.g_size + col2.g_pos - pos;
				info[count].b_size = col2.b_size;
				info[count].pixel_count = 0;
				
				coltree = (ColorTree *)d_alloc(sizeof(ColorTree));
				treeptrs[treeptrcount++] = coltree;
				coltree->left = NULL;
				coltree->right = NULL;
				coltree->type = 0;
				info[count].tree = coltree;
				box[1] = count++;
			}
			break;
			
			case MC__BLUE: /* 青 */
			{
				ColorTree *coltree;
				info[count].r_pos = col2.r_pos;
				info[count].g_pos = col2.g_pos;
				info[count].b_pos = col2.b_pos;
				info[count].r_size = col2.r_size;
				info[count].g_size = col2.g_size;
				info[count].b_size = pos - col2.b_pos;
				info[count].pixel_count = 0;
				
				coltree = (ColorTree *)d_alloc(sizeof(ColorTree));
				treeptrs[treeptrcount++] = coltree;
				coltree->left = NULL;
				coltree->right = NULL;
				coltree->type = 0;
				coltree->pal_index = 0;
				info[count].tree = coltree;
				box[0] = count++;

				info[count].r_pos = col2.r_pos;
				info[count].g_pos = col2.g_pos;
				info[count].b_pos = pos;
				info[count].r_size = col2.r_size;
				info[count].g_size = col2.g_size;
				info[count].b_size = col2.b_size + col2.b_pos - pos;
				info[count].pixel_count = 0;
				
				coltree = (ColorTree *)d_alloc(sizeof(ColorTree));
				treeptrs[treeptrcount++] = coltree;
				coltree->left = NULL;
				coltree->right = NULL;
				coltree->type = 0;
				info[count].tree = coltree;
				box[1] = count++;
			}
			break;
		}
		
		parent->type = type;
		parent->pos  = pos;
		parent->left  = info[box[0]].tree;
		parent->right = info[box[1]].tree;

		info[box[0]].pixel_count = counter;
		info[box[1]].pixel_count = total - counter;

#if 0
		char mes[64];
		static const char *coltext[] = {"", "  red", "green", "blue"};
		sprintf(mes, "%s: %d %d:%d", coltext[type], pos, counter, total);
		OutputDebugString(mes);
#endif

		for(i = 0; i < 2; i++){
			int rbox_min, rbox_max;
			int gbox_min, gbox_max;
			int bbox_min, bbox_max;
			int r1, r2, g1, g2, b1, b2;
			int	r, g, b;
			int	l;

			rmin = rbox_max = info[box[i]].r_pos + info[box[i]].r_size - 1;
			rmax = rbox_min = info[box[i]].r_pos;
			gmin = gbox_max = info[box[i]].g_pos + info[box[i]].g_size - 1;
			gmax = gbox_min = info[box[i]].g_pos;
			bmin = bbox_max = info[box[i]].b_pos + info[box[i]].b_size - 1;
			bmax = bbox_min = info[box[i]].b_pos;
			
			r1 = rbox_min >> MEDIANHIST_SHIFT;
			r2 = rbox_max >> MEDIANHIST_SHIFT;
			g1 = gbox_min >> MEDIANHIST_SHIFT;
			g2 = gbox_max >> MEDIANHIST_SHIFT;
			b1 = bbox_min >> MEDIANHIST_SHIFT;
			b2 = bbox_max >> MEDIANHIST_SHIFT;

			for(r = r1; r <= r2; r++) {
				for(g = g1; g <= g2; g++) {
					for(b = b1; b <= b2; b++) {
						RGB4 *ptr = gif_histgram[iWorkID][r][g][b].col;
						for(l = gif_histgram[iWorkID][r][g][b].count; l > 0; l--, ptr++) {
							if(rbox_min <= ptr->r && ptr->r <= rbox_max
							&& gbox_min <= ptr->g && ptr->g <= gbox_max
							&& bbox_min <= ptr->b && ptr->b <= bbox_max)
							{
								if(rmin > ptr->r) rmin = ptr->r;
								if(rmax < ptr->r) rmax = ptr->r;
								if(gmin > ptr->g) gmin = ptr->g;
								if(gmax < ptr->g) gmax = ptr->g;
								if(bmin > ptr->b) bmin = ptr->b;
								if(bmax < ptr->b) bmax = ptr->b;
							}
						}
					}
				}
			}
			
			info[box[i]].r_pos = rmin;
			info[box[i]].r_size = rmax - info[box[i]].r_pos + 1;
			info[box[i]].g_pos = gmin;
			info[box[i]].g_size = gmax - info[box[i]].g_pos + 1;
			info[box[i]].b_pos = bmin;
			info[box[i]].b_size = bmax - info[box[i]].b_pos + 1;
		}
	}

	/* パレットのインデックスを算出 */
	for(i = 0; i < count; i++){
		info[i].tree->pal_index = i;
	}

	/* ここまで、分割完了。 */
	for(i = 0; i < count; i++){	
		cmap->color[i].b  = (unsigned char)(info[i].b_pos + info[i].b_size/2);
		cmap->color[i].g = (unsigned char)(info[i].g_pos + info[i].g_size/2);
		cmap->color[i].r   = (unsigned char)(info[i].r_pos + info[i].r_size/2);
	}
	for( ; i < 256; i++)
		cmap->color[i].b = cmap->color[i].g = cmap->color[i].r = 0;
	
	/* treeから色を探して、イメージバッファに色をセット */
	if(pCode != NULL){
		srcptr = (RGB4 *)pData;
		for(y = iHeight; y > 0; y--){
			for(x = iWidth; x > 0; x--, srcptr++){
				/* treeから最も近い色パレットを探す */
				ColorTree *tree_current = tree;
				for( ; tree_current->type != 0; ) {
					switch(tree_current->type) {
						case MC__RED:
							if(srcptr->r < tree_current->pos)	tree_current = tree_current->left;
							else								tree_current = tree_current->right;
							break;
						case MC__GREEN:
							if(srcptr->g < tree_current->pos)	tree_current = tree_current->left;
							else								tree_current = tree_current->right;
							break;
						case MC__BLUE:
							if(srcptr->b < tree_current->pos)	tree_current = tree_current->left;
							else								tree_current = tree_current->right;
							break;
					}
				}
				/* パレットカラーをセット */
				/*
				(*srcptr).b = cmap->color[tree_current->pal_index].b;
				(*srcptr).g = cmap->color[tree_current->pal_index].g;
				(*srcptr).r = cmap->color[tree_current->pal_index].r;
				*/
				*pCode = (unsigned char)(tree_current->pal_index);
				pCode++;
			}
		}
	}
	for(i = 0; i < treeptrcount; i++){
		d_f_ree(treeptrs[i]);
	}
	for(i = 0; i < MEDIANHIST_SIZE; i++){
		for(j = 0; j < MEDIANHIST_SIZE; j++){
			for(k = 0; k < MEDIANHIST_SIZE; k++){
				if(gif_histgram[iWorkID][i][j][k].col !=NULL)
					d_f_ree(gif_histgram[iWorkID][i][j][k].col);
			}
		}
	}

	return -1;
}

int median_info_maxlen(RGBColorInfo *info)
{
	return max(info->r_size, max(info->g_size, info->b_size));
}

int median_info_get_pixel_weit(RGBColorInfo *info)
{
	int	i;
	int	weit = 0;
	for(i = info->pixel_count; i > 0; i>>=1, weit++);
	return weit;
}

int median_info_maxtype(RGBColorInfo *info)
{
	int len = median_info_maxlen(info);
	if(info->r_size == len) return MC__RED;
	if(info->g_size == len) return MC__GREEN;
	if(info->b_size == len) return MC__BLUE;
	return 0;
}
