/*$
 Copyright (C) 2013-2019 Azel.

 This file is part of AzPainter.

 AzPainter 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 3 of the License, or
 (at your option) any later version.

 AzPainter 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, see <http://www.gnu.org/licenses/>.
$*/

/*****************************************
 * PNG 保存
 *****************************************/

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

#include "mDef.h"
#include "mSaveImage.h"


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

typedef struct
{
	mSaveImage *param;

	png_struct *png;
	png_info *pnginfo;

	FILE *fp;
	uint8_t *rowbuf,
		*palbuf;

	mBool start_image;
}SAVEPNG;

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



//=======================
// sub
//=======================


/** PNG エラー関数 */

static void _png_error_func(png_struct *png,png_const_charp mes)
{
	mSaveImage_setMessage((mSaveImage *)png_get_error_ptr(png), mes);

	longjmp(png_jmpbuf(png), 1);
}

/** PNG 警告関数 */

static void _png_warn_func(png_struct *png,png_const_charp mes)
{

}


//===========================
// 書き込みセット
//===========================


/** ヘッダ */

static void _set_header(SAVEPNG *p,mSaveImage *param)
{
	int type,bits;

	bits = param->sample_bits;

	//カラータイプ

	switch(param->coltype)
	{
		case MSAVEIMAGE_COLTYPE_PALETTE:
			type = PNG_COLOR_TYPE_PALETTE;
			bits = param->bits;
			break;
		case MSAVEIMAGE_COLTYPE_RGBA:
			type = PNG_COLOR_TYPE_RGB_ALPHA;
			break;
		case MSAVEIMAGE_COLTYPE_GRAY:
			type = PNG_COLOR_TYPE_GRAY;
			break;
		case MSAVEIMAGE_COLTYPE_GRAY_ALPHA:
			type = PNG_COLOR_TYPE_GRAY_ALPHA;
			break;
		default:
			type = PNG_COLOR_TYPE_RGB;
			break;
	}

	png_set_IHDR(p->png, p->pnginfo,
		param->width, param->height, bits, type,
		PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}

/** パレット */

static int _set_palette(SAVEPNG *p)
{
	p->palbuf = mSaveImage_convertPalette_RGB(p->param);
	if(!p->palbuf) return MSAVEIMAGE_ERR_ALLOC;

	png_set_PLTE(p->png, p->pnginfo, (png_colorp)p->palbuf, p->param->palette_num);

	return 0;
}


//===========================
// main
//===========================


/** 初期化 */

static int _init(SAVEPNG *p)
{
	png_struct *png;
	png_info *pnginfo;

	//png_struct

	png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
		p->param, _png_error_func, _png_warn_func);
	if(!png) return MSAVEIMAGE_ERR_ALLOC;

	p->png = png;

	//png_info

	pnginfo = png_create_info_struct(png);
	if(!pnginfo) return MSAVEIMAGE_ERR_ALLOC;

	p->pnginfo = pnginfo;

	//エラー時

	if(setjmp(png_jmpbuf(png)))
		return -1;

	//出力

	p->fp = mSaveImage_openFILE(&p->param->output);
	if(!p->fp) return MSAVEIMAGE_ERR_OPENFILE;

	png_init_io(png, p->fp);

	return 0;
}

/** イメージ前処理 */

static int _before_image(SAVEPNG *p)
{
	int ret,h,v;
	
	//IHDR
	
	_set_header(p, p->param);

	//パレット

	if(p->param->coltype == MSAVEIMAGE_COLTYPE_PALETTE)
	{
		if((ret = _set_palette(p)))
			return ret;
	}

	//解像度 (dpi or dpm)

	if(mSaveImage_getResolution_dpm(p->param, &h, &v))
		png_set_pHYs(p->png, p->pnginfo, h, v, PNG_RESOLUTION_METER);

	//他設定

	if(p->param->set_option)
	{
		if((ret = (p->param->set_option)(p->param)))
			return ret;
	}

	//書き込み

	png_write_info(p->png, p->pnginfo);

	return 0;
}

/** メイン処理 */

static int _main_proc(SAVEPNG *p)
{
	mSaveImage *param = p->param;
	int ret,i,pitch;

	//初期化

	if((ret = _init(p)))
		return ret;

	//イメージ前の処理

	if((ret = _before_image(p)))
		return ret;

	//Y1行バッファ確保

	pitch = png_get_rowbytes(p->png, p->pnginfo);

	p->rowbuf = (uint8_t *)mMalloc(pitch, FALSE);
	if(!p->rowbuf) return MSAVEIMAGE_ERR_ALLOC;

	//イメージ書き込み

	p->start_image = TRUE;

	for(i = param->height; i > 0; i--)
	{
		//取得
		
		ret = (param->send_row)(param, p->rowbuf, pitch);
		if(ret) return ret;

		//書き込み

		png_write_row(p->png, (png_bytep)p->rowbuf);
	}

	return 0;
}

/** 解放 */

static void _png_free(SAVEPNG *p)
{
	if(p->png)
	{
		//イメージ書き込み終了
	
		if(p->start_image)
			png_write_end(p->png, NULL);
	
		//png_struct,png_info 削除

		png_destroy_write_struct(&p->png, (p->pnginfo)? &p->pnginfo: NULL);
	}

	if(p->fp)
	{
		if(p->param->output.type == MSAVEIMAGE_OUTPUT_TYPE_PATH)
			fclose(p->fp);
		else
			fflush(p->fp);
	}

	mFree(p->rowbuf);
	mFree(p->palbuf);
	mFree(p);
}


//===================


/**

@addtogroup saveimage
@{

*/


/** PNG ファイルに保存 */

mBool mSaveImagePNG(mSaveImage *param)
{
	SAVEPNG *p;
	int ret;

	//確保

	p = (SAVEPNG *)mMalloc(sizeof(SAVEPNG), TRUE);
	if(!p) return FALSE;

	p->param = param;
	param->internal = p;

	//処理

	ret = _main_proc(p);

	if(ret)
	{
		if(ret > 0)
			mSaveImage_setMessage_errno(param, ret);
	}

	//解放

	_png_free(p);

	return (ret == 0);
}

/** PNG 圧縮レベル設定 */

void mSaveImagePNG_setCompressionLevel(mSaveImage *p,int level)
{
	png_set_compression_level(((SAVEPNG *)p->internal)->png, level);
}

/** PNG 透過色セット
 *
 * パレットカラー、グレイスケール、RGB [8bit] 用 */

void mSaveImagePNG_setTransparent_8bit(mSaveImage *p,uint32_t col)
{
	SAVEPNG *pp = (SAVEPNG *)p->internal;
	uint8_t r,g,b,*trbuf,*ps;
	png_color_16 pngcol;
	int i,pnum;

	r = M_GET_R(col);
	g = M_GET_G(col);
	b = M_GET_B(col);

	switch(pp->param->coltype)
	{
		//RGB
		case MSAVEIMAGE_COLTYPE_RGB:
			pngcol.red = r;
			pngcol.green = g;
			pngcol.blue = b;
			
			png_set_tRNS(pp->png, pp->pnginfo, NULL, 0, &pngcol);
			break;
		//グレイスケール
		case MSAVEIMAGE_COLTYPE_GRAY:
			pngcol.gray = r;
			png_set_tRNS(pp->png, pp->pnginfo, NULL, 0, &pngcol);
			break;
		//パレット
		case MSAVEIMAGE_COLTYPE_PALETTE:
			pnum = pp->param->palette_num;
			
			trbuf = (uint8_t *)mMalloc(pnum, FALSE);
			if(!trbuf) return;

			memset(trbuf, 255, pnum);
			
			ps = pp->param->palette_buf;

			for(i = 0; i < pnum && !(ps[0] == r && ps[1] == g && ps[2] == b); i++, ps += 4);

			if(i < pnum)
			{
				trbuf[i] = 0;
				png_set_tRNS(pp->png, pp->pnginfo, trbuf, i + 1, 0);
			}

			mFree(trbuf);
			break;
	}
}

/* @} */
