/*$
apdtool
Copyright (c) 2020 Azel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
$*/

/*****************************
 * 画像処理
 *****************************/

#include <stdio.h>
#include <string.h>

#include "mlk.h"
#include "mlk_rectbox.h"
#include "mlk_str.h"
#include "mlk_string.h"
#include "mlk_charset.h"
#include "mlk_imagebuf.h"
#include "mlk_saveimage.h"
#include "mlk_imageconv.h"

#include "def.h"


//-------------------

static const char *g_format_ext[] = {".png", ".bmp", ".psd"};

//-------------------


/** 透明クリア */

void image_fill_zero(mImageBuf2 *p)
{
	uint8_t **ppbuf;
	int iy,size;

	ppbuf = p->ppbuf;
	size = p->line_bytes;

	for(iy = p->height; iy; iy--, ppbuf++)
		memset(*ppbuf, 0, size);
}

/** 白クリア */

void image_fill_white(mImageBuf2 *p)
{
	uint8_t **ppbuf;
	int iy,size;

	ppbuf = p->ppbuf;
	size = p->line_bytes;

	for(iy = p->height; iy; iy--, ppbuf++)
		memset(*ppbuf, 255, size);
}

/** 16bit 白クリア */

void image_fill_white16(mImageBuf2 *p)
{
	uint16_t **ppbuf,*pd;
	int i,size;

	ppbuf = (uint16_t **)p->ppbuf;
	size = p->line_bytes;

	//先頭行をセット

	pd = *(ppbuf++);

	for(i = p->width * 3; i; i--)
		*(pd++) = 1<<15;

	//残り行をコピー

	pd = *((uint16_t **)p->ppbuf);

	for(i = p->height - 1; i > 0; i--, ppbuf++)
		memcpy(*ppbuf, pd, size);
}

/** 16bit RGB -> 8bit RGB に変換 */

void image_convert_16to8(mImageBuf2 *p)
{
	uint16_t **pps,*ps;
	uint8_t *pd;
	int iy,ix,cnt;

	pps = (uint16_t **)p->ppbuf;
	cnt = p->width * 3;

	for(iy = p->height; iy; iy--)
	{
		ps = *(pps++);
		pd = (uint8_t *)ps;
		
		for(ix = cnt; ix; ix--, ps++)
			*(pd++) = ((*ps) * 255 + 0x4000) >> 15;
	}
}


//=====================================
// タイルイメージ変換 (-> RGBA 8bit)
//=====================================


#define _COL16_TO_8(p)  ((((*(p) << 8) | *(p + 1)) * 255 + (1<<14)) >> 15)


/** RGBA-16bit (BE) タイル */

void set_tileimage_rgba16(mImageBuf2 *p,uint8_t *tilebuf,int x,int y,uint32_t col,TexItem *tex)
{
	mRect rc;
	int ix,iy,w,xtop,pitchs,xx,yy;
	uint8_t *ps,*pd,**ppd;

	//イメージ範囲内にクリッピング

	rc.x1 = x, rc.y1 = y;
	rc.x2 = x + 63, rc.y2 = y + 63;

	if(!mRectClipBox_d(&rc, 0, 0, p->width, p->height))
		return;

	//

	ps = tilebuf + (rc.y1 - y) * 128 + (rc.x1 - x) * 2;
	ppd = p->ppbuf + rc.y1;
	w = rc.x2 - rc.x1 + 1;
	xtop = rc.x1 * 4;
	pitchs = 128 - w * 2;

	for(iy = rc.y2 - rc.y1 + 1, yy = rc.y1; iy; iy--, yy++, ppd++)
	{
		pd = *ppd + xtop;
		xx = rc.x1;
		
		for(ix = w; ix; ix--, xx++, pd += 4, ps += 2)
		{
			pd[0] = _COL16_TO_8(ps);
			pd[1] = _COL16_TO_8(ps + 8192);
			pd[2] = _COL16_TO_8(ps + 8192*2);
			pd[3] = _COL16_TO_8(ps + 8192*3);

			if(tex && pd[3])
				pd[3] = pd[3] * TextureItem_getOpacity(tex, xx, yy) / 255;
		}

		ps += pitchs;
	}
}

/** GRAY+A-16bit (BE) タイル */

void set_tileimage_graya16(mImageBuf2 *p,uint8_t *tilebuf,int x,int y,uint32_t col,TexItem *tex)
{
	mRect rc;
	int ix,iy,w,xtop,pitchs,xx,yy;
	uint8_t *ps,*pd,**ppd;

	//イメージ範囲内にクリッピング

	rc.x1 = x, rc.y1 = y;
	rc.x2 = x + 63, rc.y2 = y + 63;

	if(!mRectClipBox_d(&rc, 0, 0, p->width, p->height))
		return;

	//

	ps = tilebuf + (rc.y1 - y) * 128 + (rc.x1 - x) * 2;
	ppd = p->ppbuf + rc.y1;
	w = rc.x2 - rc.x1 + 1;
	xtop = rc.x1 * 4;
	pitchs = 128 - w * 2;

	for(iy = rc.y2 - rc.y1 + 1, yy = rc.y1; iy; iy--, yy++, ppd++)
	{
		pd = *ppd + xtop;
		xx = rc.x1;
		
		for(ix = w; ix; ix--, xx++, pd += 4, ps += 2)
		{
			pd[0] = pd[1] = pd[2] = _COL16_TO_8(ps);
			pd[3] = _COL16_TO_8(ps + 8192);

			if(tex && pd[3])
				pd[3] = pd[3] * TextureItem_getOpacity(tex, xx, yy) / 255;
		}

		ps += pitchs;
	}
}

/** A-16bit (BE) タイル */

void set_tileimage_a16(mImageBuf2 *p,uint8_t *tilebuf,int x,int y,uint32_t col,TexItem *tex)
{
	mRect rc;
	int ix,iy,w,xtop,pitchs,xx,yy;
	uint8_t *ps,*pd,**ppd,r,g,b;

	//イメージ範囲内にクリッピング

	rc.x1 = x, rc.y1 = y;
	rc.x2 = x + 63, rc.y2 = y + 63;

	if(!mRectClipBox_d(&rc, 0, 0, p->width, p->height))
		return;

	//

	ps = tilebuf + (rc.y1 - y) * 128 + (rc.x1 - x) * 2;
	ppd = p->ppbuf + rc.y1;
	w = rc.x2 - rc.x1 + 1;
	xtop = rc.x1 * 4;
	pitchs = 128 - w * 2;

	r = MLK_RGB_R(col);
	g = MLK_RGB_G(col);
	b = MLK_RGB_B(col);

	for(iy = rc.y2 - rc.y1 + 1, yy = rc.y1; iy; iy--, yy++, ppd++)
	{
		pd = *ppd + xtop;
		xx = rc.x1;
		
		for(ix = w; ix; ix--, xx++, pd += 4, ps += 2)
		{
			pd[0] = r;
			pd[1] = g;
			pd[2] = b;
			pd[3] = _COL16_TO_8(ps);

			if(tex && pd[3])
				pd[3] = pd[3] * TextureItem_getOpacity(tex, xx, yy) / 255;
		}

		ps += pitchs;
	}
}

/** A-1bit タイル */

void set_tileimage_a1(mImageBuf2 *p,uint8_t *tilebuf,int x,int y,uint32_t col,TexItem *tex)
{
	mRect rc;
	int ix,iy,w,xtop,xx,yy;
	uint8_t *ps,*psY,*pd,**ppd,r,g,b,f,fleft;

	//イメージ範囲内にクリッピング

	rc.x1 = x, rc.y1 = y;
	rc.x2 = x + 63, rc.y2 = y + 63;

	if(!mRectClipBox_d(&rc, 0, 0, p->width, p->height))
		return;

	//

	psY = tilebuf + (rc.y1 - y) * 8 + ((rc.x1 - x) >> 3);
	ppd = p->ppbuf + rc.y1;
	w = rc.x2 - rc.x1 + 1;
	xtop = rc.x1 * 4;
	fleft = 1 << (7 - ((rc.x1 - x) & 7));

	r = MLK_RGB_R(col);
	g = MLK_RGB_G(col);
	b = MLK_RGB_B(col);

	for(iy = rc.y2 - rc.y1 + 1, yy = rc.y1; iy; iy--, yy++, ppd++)
	{
		ps = psY;
		pd = *ppd + xtop;
		f = fleft;
		xx = rc.x1;
		
		for(ix = w; ix; ix--, xx++, pd += 4)
		{
			if(*ps & f)
			{
				pd[0] = r;
				pd[1] = g;
				pd[2] = b;

				if(tex)
					pd[3] = TextureItem_getOpacity(tex, xx, yy);
				else
					pd[3] = 255;
			}

			f >>= 1;
			if(!f)
			{
				ps++;
				f = 0x80;
			}
		}

		psY += 8;
	}
}

/** RGBA-8bit タイル */

void set_tileimage_rgba8(mImageBuf2 *p,uint8_t *tilebuf,int x,int y,uint32_t col,TexItem *tex)
{
	mRect rc;
	int ix,iy,w,pitchs,xx,yy;
	uint8_t *ps,*pd,**ppd;

	//イメージ範囲内にクリッピング

	rc.x1 = x, rc.y1 = y;
	rc.x2 = x + 63, rc.y2 = y + 63;

	if(!mRectClipBox_d(&rc, 0, 0, p->width, p->height))
		return;

	//

	ps = tilebuf + (rc.y1 - y) * 256 + (rc.x1 - x) * 4;
	ppd = p->ppbuf + rc.y1;
	w = rc.x2 - rc.x1 + 1;
	pitchs = 256 - w * 4;

	for(iy = rc.y2 - rc.y1 + 1, yy = rc.y1; iy; iy--, yy++, ppd++)
	{
		pd = *ppd + rc.x1 * 4;
		xx = rc.x1;
		
		for(ix = w; ix; ix--, xx++, pd += 4, ps += 4)
		{
			*((uint32_t *)pd) = *((uint32_t *)ps);

			if(tex && pd[3])
				pd[3] = pd[3] * TextureItem_getOpacity(tex, xx, yy) / 255;
		}

		ps += pitchs;
	}
}


//==========================
// 画像ファイル書き込み
//==========================


/* RGB -> GRAY 8bit に変換 */

static void _convert_gray8(mImageBuf2 *p)
{
	uint8_t **ppbuf,*pd,*ps;
	int ix,iy;

	ppbuf = p->ppbuf;

	for(iy = p->height; iy; iy--, ppbuf++)
	{
		pd = ps = *ppbuf;

		for(ix = p->width; ix; ix--, ps += 3)
			*(pd++) = (ps[0] * 77 + ps[1] * 150 + ps[2] * 29) >> 8;
	}
}

/* RGB -> GRAY 1bit に変換 */

static void _convert_gray1(mImageBuf2 *p)
{
	uint8_t **ppbuf,*pd,*ps,f,val;
	int ix,iy,c;

	ppbuf = p->ppbuf;

	for(iy = p->height; iy; iy--, ppbuf++)
	{
		pd = ps = *ppbuf;
		f = 0x80;
		val = 0;

		for(ix = p->width; ix; ix--, ps += 3)
		{
			c = (ps[0] * 77 + ps[1] * 150 + ps[2] * 29) >> 8;
			
			if(c & 128)
				val |= f;

			f >>= 1;
			if(!f)
			{
				*(pd++) = val;
				f = 0x80, val = 0;
			}
		}

		if(f != 0x80)
			*pd = val;
	}
}

/* Y1行イメージセット */

static mlkerr _save_setrow(mSaveImage *p,int y,uint8_t *buf,int line_bytes)
{
	memcpy(buf, *((uint8_t **)p->param1 + y), line_bytes);

	return MLKERR_OK;
}

/* Y1行イメージセット (PSD) */

static mlkerr _save_setrow_ch(mSaveImage *p,int y,int ch,uint8_t *buf,int line_bytes)
{
	uint8_t *ps;
	int i;

	ps = *((uint8_t **)p->param1 + y) + ch;

	if(p->coltype == MSAVEIMAGE_COLTYPE_GRAY)
		memcpy(buf, ps, line_bytes);
	else if(p->samples_per_pixel == 3)
	{
		//RGB
	
		for(i = p->width; i; i--, ps += 3)
			*(buf++) = *ps;
	}
	else
	{
		//RGBA
	
		for(i = p->width; i; i--, ps += 4)
			*(buf++) = *ps;
	}

	return MLKERR_OK;
}

/** 画像ファイル書き込み
 *
 * return: 0 以外でエラー */

static int _save_image(mImageBuf2 *img,const char *filename)
{
	mSaveImage si;
	mFuncSaveImage funcs[] = {
		mSaveImagePNG, mSaveImageBMP, mSaveImagePSD
	};
	int format;

	format = g_work.format;

	mSaveImage_init(&si);

	si.open.type = MSAVEIMAGE_OPEN_FILENAME;
	si.open.filename = filename;
	si.width = img->width;
	si.height = img->height;
	si.bits_per_sample = 8;
	si.setrow = _save_setrow;
	si.setrow_ch = _save_setrow_ch;
	si.param1 = img->ppbuf;

	//解像度

	if(g_work.src_dpi)
	{
		si.reso_unit = MSAVEIMAGE_RESOUNIT_DPI;
		si.reso_horz = g_work.src_dpi;
		si.reso_vert = g_work.src_dpi;
	}

	//カラータイプ

	if(IS_PROC_LAYER)
	{
		//レイヤ (RGBA)

		si.coltype = MSAVEIMAGE_COLTYPE_RGB;
		si.samples_per_pixel = 4;
	}
	else if(format != FORMAT_BMP && (g_work.flags & FLAGS_IMAGE_TO_MONO))
	{
		//GRAY 1bit

		si.coltype = MSAVEIMAGE_COLTYPE_GRAY;
		si.bits_per_sample = 1;
		si.samples_per_pixel = 1;

		_convert_gray1(img);
	}
	else if(format != FORMAT_BMP && (g_work.flags & FLAGS_IMAGE_TO_GRAY))
	{
		//GRAY 8bit

		si.coltype = MSAVEIMAGE_COLTYPE_GRAY;
		si.samples_per_pixel = 1;

		_convert_gray8(img);
	}
	else
	{
		//RGB

		si.coltype = MSAVEIMAGE_COLTYPE_RGB;
		si.samples_per_pixel = 3;
	}

	//保存

	return (funcs[format])(&si, NULL);
}


//=========================
// イメージ出力
//=========================


/** 合成イメージを出力 */

void write_image_blend(mImageBuf2 *img)
{
	mStr str = MSTR_INIT,str2 = MSTR_INIT;

	if(!(g_work.flags & FLAGS_OUTPUT_DIR)
		&& mStrIsnotEmpty(&g_work.strOutput))
	{
		//出力ファイル名指定あり

		mStrCopy(&str, &g_work.strOutput);
	}
	else
	{
		//出力ディレクトリ + 入力ファイル名

		if(g_work.flags & FLAGS_OUTPUT_DIR)
			mStrCopy(&str, &g_work.strOutput);

		mStrPathGetBasename_noext(&str2, g_work.strInputFile.buf);
		mStrAppendText(&str2, g_format_ext[g_work.format]);

		mStrPathJoin(&str, str2.buf);
		mStrFree(&str2);
	}

	//保存

	putchar(' ');
	mPutUTF8_stdout(str.buf);

	if(_save_image(img, str.buf))
		puts(" : error");
	else
		puts(" : ok");

	fflush(stdout);

	//

	mStrFree(&str);
}

/** レイヤイメージを出力
 *
 * dig: 連番桁数 */

void write_image_layer(mImageBuf2 *img,int no,int dig)
{
	mStr str = MSTR_INIT,str2 = MSTR_INIT;
	char m[8];

	//ディレクトリ
	//(strOutput が既存ディレクトリなら、指定ディレクトリ。
	// それ以外はカレントディレクトリ)

	if(g_work.flags & FLAGS_OUTPUT_DIR)
		mStrCopy(&str, &g_work.strOutput);

	//先頭ファイル名

	if(g_work.flags & FLAGS_HAVE_LAYER_PREFIX)
	{
		//接頭語指定あり
		
		if(mStrIsEmpty(&g_work.strLayerPrefix))
			//空文字列の場合は、パス区切り追加
			mStrPathAppendDirSep(&str);
		else
			mStrPathJoin(&str, g_work.strLayerPrefix.buf);
	}
	else
	{
		//指定なし = "入力ファイル名_"

		mStrPathGetBasename_noext(&str2, g_work.strInputFile.buf);
		mStrAppendChar(&str2, '_');

		mStrPathJoin(&str, str2.buf);
		mStrFree(&str2);
	}

	//連番

	mIntToStr_dig(m, no, dig);
	mStrAppendText(&str, m);

	//拡張子

	mStrAppendText(&str, g_format_ext[g_work.format]);

	//保存

	putchar(' ');
	mPutUTF8_stdout(str.buf);

	if(_save_image(img, str.buf))
		puts(" : error");
	else
		puts(" : ok");

	fflush(stdout);

	//

	mStrFree(&str);
}

