/*
 *	app.c
 *
 *	dpavigen - AVI 生成ツール
 *	Copyright (C) 2013 Naoyuki Sawa
 *
 *	* Mon Sep 02 00:00:29 JST 2013 Naoyuki Sawa
 *	- 1st リリース。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Win32 */
#define STRICT
#include <windows.h>
#include <vfw.h>
#pragma comment(lib, "vfw32.lib")
/* Lua */
#include "../../liblua/lua.h"
#include "../../liblua/lauxlib.h"
#include "../../liblua/lualib.h"

#pragma warning(disable:4244)	//'...' : '...' から '...' に変換しました。ﾃﾞｰﾀが失われているかもしれません。

/* ■重要■
 * Lua C APIの実行時チェックを有効にするために、以下の設定を行いました。
 * ［プロジェクト］
 *   ⇒［設定］
 *   ⇒「設定の対象」 = Win32 Debug, liblua
 *   ⇒「C/C++」
 *   ⇒「カテゴリ」 = 一般
 *   ⇒「プリプロセッサの定義」 = LUA_USE_APICHECK を追加
 * ［プロジェクト］
 *   ⇒［設定］
 *   ⇒「設定の対象」 = Win32 Release, liblua
 *   ⇒「C/C++」
 *   ⇒「カテゴリ」 = 一般
 *   ⇒「プリプロセッサの定義」 = LUA_USE_APICHECK を追加, NDEBUG を削除
 * (設定の意味は、"../../liblua/Makefile"のコメントを参照してください。)
 */

#define VERSION "20130902"

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

#define CFG_FNAME "dpavigen.cfg" /* 圧縮設定ファイル名 */

/* オプション */
int opt_Silent;		/* 1ならば、進捗表示を行わない */
int g_DispX = 320;	/* 幅[ピクセル] */
int g_DispY = 240;	/* 高さ[ピクセル] */
int g_Rate  =  30;	/* フレームレート[フレーム/秒] */

/* Lua */
lua_State* g_pL;	/* Luaステート */
lua_State* g_pThread;	/* */
int        g_lThread;	/* */

/* VFW */
PAVIFILE            g_pAviFile;			/* AVIファイル */
AVISTREAMINFO       g_AviStreamInfo;		/* 非圧縮ストリームのフォーマット */
PAVISTREAM          g_pAviStream;		/* 非圧縮ストリーム */
PAVISTREAM          g_pAviCompressedStream;	/* 圧縮ストリーム */
AVICOMPRESSOPTIONS* g_pAviCompressOptions;	/* 圧縮設定 */
BITMAPINFOHEADER    g_BitmapInfoHeader;		/* 圧縮ストリームのフォーマット */

HDC        g_hDc;		/* メモリDC */
HBITMAP    g_hBitmap;		/* DIBセクション */
DIBSECTION g_DibSection;	/* DIBセクションの情報 */
HFONT      g_hFont;		/* フォント */
LOGFONT    g_LogFont;		/* 論理フォントの特性 */

const char* g_pAviFileName;	/* AVIファイル名 */
int         g_iFrameNumber;	/* フレーム番号(0〜) */

/*--------------------------------------------------------------------------*/

__declspec(noreturn) void usage();
__declspec(noreturn) void die(const char* fmt, ...);
#define DIE() die(__FILE__, __LINE__)
__declspec(noreturn) int atpanic(lua_State* L);

/* C関数。Luaのグローバル環境に登録する */
int cPset(lua_State* L);
int cLine(lua_State* L);
int cFill(lua_State* L);
int cText(lua_State* L);
const luaL_Reg TBL_Reg[] = {
	{ "Pset", cPset },
	{ "Line", cLine },
	{ "Fill", cFill },
	{ "Text", cText },
	{ NULL, NULL },
};

#define __CLIP_H__
/* getopt */
#include "../../include/getopt.h"
#include "../../getopt.c"
#undef  __CLIP_H__

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

int main(int argc, char* argv[]) {
	int RetVal, Size, opt, top;
	HGDIOBJ hGdiObj;
	FILE* fp;
/*{{Lua*/
	/* Luaを初期化します。 */
	g_pL = luaL_newstate();
	if(g_pL == NULL) { die("luaL_newstate が失敗しました。"); }
	lua_atpanic(g_pL, atpanic);
	luaL_openlibs(g_pL);
/*}}Lua*/
	/* コマンドラインを解析します。 */
	if(argc <= 1) { usage(); }
	while((opt = getopt(argc, argv, "f:e:x:y:r:chs")) != -1) {
		switch(opt) {
		case 'f':
			/* Luaファイルを読み込みます。
			 * - 「-f Luaファイル名」オプションは、複数回指定可能です。
			 *   「-f Luaファイル名」オプションが複数回指定された場合、それら全てのLuaファイルを読み込みます。
			 */
		//{{
		//	top = lua_gettop(g_pL);
		//	  if(luaL_dofile(g_pL, optarg) != 0) { die("%s: luaL_dofile が失敗しました。", optarg); }
		//	lua_settop(g_pL, top); /* luaL_dofile()の戻り値の数は'LUA_MULTRET'。明示的に捨てる処理が必要である。 */
		//↑これではロードエラー時のメッセージが判り辛いので、↓このように変更しました。
			top = lua_gettop(g_pL);
			  if(luaL_loadfile(g_pL, optarg) != 0) { die("luaL_loadfile: %s", luaL_checkstring(g_pL, -1)); }
			  lua_call(g_pL, 0, 0);
			lua_settop(g_pL, top);
		//}}
			break;
		case 'e':
			/* Luaスクリプトを実行します。
			 * - 「-e Luaスクリプト」オプションは、複数回指定可能です。
			 *   「-f Luaスクリプト」オプションが複数回指定された場合、それら全てのLuaスクリプトを実行します。
			 */
			top = lua_gettop(g_pL);
			  if(luaL_loadstring(g_pL, optarg) != 0) { die("luaL_loadstring: %s", luaL_checkstring(g_pL, -1)); }
			  lua_call(g_pL, 0, 0);
			lua_settop(g_pL, top);
			break;
		case 'x':
			g_DispX = atoi(optarg);
			if(g_DispX <= 0) { die("%s: -%cオプションの引数が不正です。", optarg, optopt); }
			break;
		case 'y':
			g_DispY = atoi(optarg);
			if(g_DispY <= 0) { die("%s: -%cオプションの引数が不正です。", optarg, optopt); }
			break;
		case 'r':
			g_Rate = atoi(optarg);
			if(g_Rate <= 0) { die("%s: -%cオプションの引数が不正です。", optarg, optopt); }
			break;
		case 'c':
			//圧縮設定ファイルを削除します。
			remove(CFG_FNAME);
			break;
		case 'h':
			usage();
			break;
		case 's':
			opt_Silent = 1;
			break;
		default:
			die("%c: オプション文字の使い方が不正です。", optopt);
			break;
		}
	}
	/* オプション以降、残りのコマンドライン引数は、AVIファイル名です。(複数指定不可) */
	g_pAviFileName = argv[optind++];
	if(optind != argc) { die("AVIファイル名が指定されていないか,又は,複数指定されています。"); }
	/* VFWを初期化します。 */
	AVIFileInit();
	/* AVIファイルを作成します。 */
	remove(g_pAviFileName);	//AVIFileOpen()にOF_CREATEを指定しても、既存のファイルが小さくならないようだ。AVIFileOpen()の不具合のような気がするが、仕方が無いので明示的に削除して回避する。
	RetVal = AVIFileOpen(&g_pAviFile, g_pAviFileName, OF_CREATE|OF_WRITE|OF_SHARE_EXCLUSIVE, NULL);
	if(RetVal != 0) { die("%s: AVIStreamInfo が失敗しました。", g_pAviFileName); }
	/* 非圧縮ストリームを作成します。 */
	memset(&g_AviStreamInfo, 0, sizeof g_AviStreamInfo);
	g_AviStreamInfo.fccType		= streamtypeVIDEO;
	g_AviStreamInfo.dwScale		= 1;
	g_AviStreamInfo.dwRate		= g_Rate;
	g_AviStreamInfo.rcFrame.right	= g_DispX;
	g_AviStreamInfo.rcFrame.bottom	= g_DispY;
	RetVal = AVIFileCreateStream(g_pAviFile, &g_pAviStream, &g_AviStreamInfo);
	if(RetVal != 0) { die("%s: AVIFileCreateStream が失敗しました。", g_pAviFileName); }
	/* 圧縮設定ファイルを開きます。 */
	fp = fopen(CFG_FNAME, "rb");
	/* 圧縮設定ファイルが有れば… */
	if(fp != NULL) {
		/* 圧縮設定ファイルのサイズを取得します。 */
		RetVal = fseek(fp, 0, SEEK_END);
		if(RetVal != 0) { die("%s: fseek が失敗しました。", CFG_FNAME); }
		Size = ftell(fp);
		if(Size < sizeof(AVICOMPRESSOPTIONS)) { die("%sが壊れています。%sを削除するか,又は,-cオプションを指定して再実行してください。", CFG_FNAME, CFG_FNAME); }
		/* 圧縮設定ファイルを読み込みます。 */
		rewind(fp);
		g_pAviCompressOptions = malloc(Size);
		if(g_pAviCompressOptions == NULL) { die("malloc が失敗しました。"); }
		RetVal = fread(g_pAviCompressOptions, 1, Size, fp);
		if(RetVal != Size) { die("%s: fread が失敗しました。", CFG_FNAME); }
		if(Size != (int)((sizeof(AVICOMPRESSOPTIONS) + g_pAviCompressOptions->cbFormat + g_pAviCompressOptions->cbParms))) {
			if(RetVal == 0) { die("%sが壊れています。%sを削除するか,又は,-cオプションを指定して再実行してください。", CFG_FNAME, CFG_FNAME); }
		}
		/* lpFormatとlpParmsのポインタを再設定します。 */
		g_pAviCompressOptions->lpFormat = (g_pAviCompressOptions->cbFormat == 0) ? NULL : (char*)(g_pAviCompressOptions + 1);
		g_pAviCompressOptions->lpParms  = (g_pAviCompressOptions->cbParms  == 0) ? NULL : (char*)(g_pAviCompressOptions + 1) + g_pAviCompressOptions->cbFormat;
		/* 圧縮設定ファイルを閉じます。 */
		RetVal = fclose(fp);
		if(RetVal != 0) { die("%s: fclose が失敗しました。", CFG_FNAME); }
	/* 圧縮設定ファイルが無ければ… */
	} else {
		/* 圧縮設定ダイアログを表示します。 */
		g_pAviCompressOptions = calloc(sizeof(AVICOMPRESSOPTIONS), 1);	//クリア必須。AVISaveOptions()へ渡すAVICOMPRESSOPTIONSは、ダイアログの初期値として参照されるため、有効なAVICOMPRESSOPTIONSであるか,又は,クリアされていなければならない。ただし実際には、有効なAVICOMPRESSOPTIONSであっても問題が有る。ダイアログで圧縮形式を変更して、AVISaveOptions()がlpFormat,lpParmsを再割当てした時に、アプリケーションのメモリアロケータと整合せず異常動作になる。結局のところ、ダイアログの初期値は指定できず、クリア値を渡すしかない。
		if(g_pAviCompressOptions == NULL) { die("calloc が失敗しました。"); }
		RetVal = AVISaveOptions(NULL, ICMF_CHOOSE_KEYFRAME|ICMF_CHOOSE_DATARATE, 1, &g_pAviStream, &g_pAviCompressOptions);
		if(RetVal == 0) { die("圧縮設定がキャンセルされました。"); }
	}
	/* 圧縮ストリームを作成します。 */
	RetVal = AVIMakeCompressedStream(&g_pAviCompressedStream, g_pAviStream, g_pAviCompressOptions, NULL);
	if(RetVal != 0) { die("%s: AVIMakeCompressedStream が失敗しました。圧縮設定が不正である可能性があります。-cオプションを指定して再実行してください。", g_pAviFileName); }
	/* 圧縮ストリームのフォーマットを設定します。 */
	memset(&g_BitmapInfoHeader, 0, sizeof g_BitmapInfoHeader);
	g_BitmapInfoHeader.biSize	= sizeof(BITMAPINFOHEADER);
	g_BitmapInfoHeader.biWidth	= g_DispX;
	g_BitmapInfoHeader.biHeight	= g_DispY;
	g_BitmapInfoHeader.biPlanes	= 1;
	g_BitmapInfoHeader.biBitCount	= 24;
	RetVal = AVIStreamSetFormat(g_pAviCompressedStream, 0, &g_BitmapInfoHeader, sizeof g_BitmapInfoHeader);
	if(RetVal != 0) { die("%s: AVIStreamSetFormat が失敗しました。圧縮設定が不正である可能性があります。-cオプションを指定して再実行してください。", g_pAviFileName); }
	/* 圧縮ストリームの作成,及び,フォーマット設定に成功したら、この圧縮設定は正常であると判断し、次回のために圧縮設定ファイルに保存します。 */
	fp = fopen(CFG_FNAME, "wb");
	if(fp == NULL) { die("%s: fopen が失敗しました。", CFG_FNAME); }
	RetVal = fwrite(g_pAviCompressOptions, 1, sizeof(AVICOMPRESSOPTIONS), fp);
	if(RetVal != sizeof(AVICOMPRESSOPTIONS)) { die("%s: fwrite が失敗しました。", CFG_FNAME); }
	if(g_pAviCompressOptions->cbFormat != 0) {
		RetVal = fwrite(g_pAviCompressOptions->lpFormat, 1, g_pAviCompressOptions->cbFormat, fp);
		if(RetVal != (int)g_pAviCompressOptions->cbFormat) { die("%s: fwrite が失敗しました。", CFG_FNAME); }
	}
	if(g_pAviCompressOptions->cbParms != 0) {
		RetVal = fwrite(g_pAviCompressOptions->lpParms, 1, g_pAviCompressOptions->cbParms, fp);
		if(RetVal != (int)g_pAviCompressOptions->cbParms) { die("%s: fwrite が失敗しました。", CFG_FNAME); }
	}
	RetVal = fclose(fp);
	if(RetVal != 0) { die("%s: fclose が失敗しました。", CFG_FNAME); }
	/* メモリDCを作成します。 */
	g_hDc = CreateCompatibleDC(NULL);
	if(g_hDc == NULL) { die("CreateCompatibleDC が失敗しました。"); }
	/* DIBセクションを作成します。 */
	g_hBitmap = CreateDIBSection(NULL, (BITMAPINFO*)&g_BitmapInfoHeader, DIB_RGB_COLORS, NULL/*ppvBits*/, NULL, 0);	//pvBitsはg_DibSection.dsBm.bmBitsと同じなので取得不要である。尚、UnDoc'dだがppvBits引数はNULLで大丈夫なようだ。
	if(g_hBitmap == NULL) { die("CreateDIBSection が失敗しました。"); }
	/* DIBセクションの情報を取得します。 */
	RetVal = GetObject(g_hBitmap, sizeof g_DibSection, &g_DibSection);
	if(RetVal == 0) { die("GetObject が失敗しました。"); }
	/* フォントを作成します。 */
	memset(&g_LogFont, 0, sizeof g_LogFont);
	g_LogFont.lfHeight  = 12;	//これ未満のサイズにすると、勝手にMSゴシックになる。
	g_LogFont.lfCharSet = SHIFTJIS_CHARSET;
	strcpy(g_LogFont.lfFaceName, "ＭＳ 明朝");
	g_hFont = CreateFontIndirect(&g_LogFont);
	if(g_hFont == NULL) { die("CreateFontIndirect が失敗しました。"); }
	/* メモリDCの状態を保存します。 */
	RetVal = SaveDC(g_hDc);
	if(RetVal == 0) { die("SaveDC が失敗しました。"); }
	/* メモリDCにDIBセクションを選択します。 */
	hGdiObj = SelectObject(g_hDc, g_hBitmap);
	if(hGdiObj == 0) { die("SelectObject が失敗しました。"); }
	/* メモリDCにフォントを選択します。 */
	hGdiObj = SelectObject(g_hDc, g_hFont);
	if(hGdiObj == 0) { die("SelectObject が失敗しました。"); }
	/* メモリDCの背景モードを設定します。 */
	RetVal = SetBkMode(g_hDc, TRANSPARENT);
	if(RetVal == 0) { die("SetBkMode が失敗しました。"); }
/*{{Lua*/
	/* C関数を登録します。 */
	top = lua_gettop(g_pL);
	  lua_pushglobaltable(g_pL);
	  luaL_setfuncs(g_pL, TBL_Reg, 0);
	lua_settop(g_pL, top);
	/* グローバル変数を登録します。 */
	lua_pushnumber(g_pL, g_DispX); lua_setglobal(g_pL, "DispX");	/* 幅[ピクセル] */
	lua_pushnumber(g_pL, g_DispY); lua_setglobal(g_pL, "DispY");	/* 高さ[ピクセル] */
	lua_pushnumber(g_pL, g_Rate ); lua_setglobal(g_pL, "Rate" );	/* フレームレート[フレーム/秒] */
	/* コルーチンを作成します。 */
	g_pThread = lua_newthread(g_pL);
	if(g_pThread == NULL) { die("lua_newthread が失敗しました。"); }
	lua_getglobal(g_pThread, "Main");
	if(!lua_isfunction(g_pThread, -1)) { die("%s が定義されていません。", "Main"); }
	for(;;) {
		/* コルーチンを実行します。 */
		RetVal = lua_resume(g_pThread, NULL, 0);
		if(RetVal == 0) { break; }
		if(RetVal != LUA_YIELD) { atpanic(g_pThread); }
		lua_settop(g_pThread, 0);	//lua_resume()の戻り値の数は'LUA_MULTRET'。明示的に捨てる処理が必要である。尚、g_pThreadはコルーチン専用なので、スタックトップを0と決め打って良い。
/*}}Lua*/
		/* 圧縮ストリームに1フレーム書き込みます。 */
		RetVal = AVIStreamWrite(g_pAviCompressedStream, g_iFrameNumber++, 1,
			g_DibSection.dsBm.bmBits,
			g_DibSection.dsBm.bmWidthBytes * g_DibSection.dsBm.bmHeight,
			0/*AVIIF_KEYFRAME*/, NULL, NULL);	//キーフレーム頻度は圧縮設定にて指定されているので、追加のキーフレームを指定することは不要である。
		if(RetVal != 0) { die("%s: AVIStreamWrite が失敗しました。", g_pAviFileName); }
/*{{進捗表示用*/
		if(!opt_Silent) { fprintf(stderr, "%s: %df\r", g_pAviFileName, g_iFrameNumber); }
/*}}進捗表示用*/
	}
/*{{進捗表示用*/
	if(!opt_Silent) { fprintf(stderr, "\n"); }
/*}}進捗表示用*/
	/* メモリDCの状態を復元します。 */
	RetVal = RestoreDC(g_hDc, -1);
	if(RetVal == 0) { die("RestoreDC が失敗しました。"); }
	/* フォントを削除します。 */
	RetVal = DeleteObject(g_hFont);
	if(RetVal == 0) { die("DeleteObject が失敗しました。"); }
	/* DIBセクションを削除します。 */
	RetVal = DeleteObject(g_hBitmap);
	if(RetVal == 0) { die("DeleteObject が失敗しました。"); }
	/* メモリDCを削除します。 */
	RetVal = DeleteDC(g_hDc);
	if(RetVal == 0) { die("DeleteDC が失敗しました。"); }
	/* 圧縮ストリームを閉じます。 */
	RetVal = AVIStreamRelease(g_pAviCompressedStream);
	if(RetVal != 0) { die("%s: AVIStreamRelease が失敗しました。", g_pAviFileName); }
	/* 非圧縮ストリームを閉じます。 */
	RetVal = AVIStreamRelease(g_pAviStream);
	if(RetVal != 0) { die("%s: AVIStreamRelease が失敗しました。", g_pAviFileName); }
	/* AVIファイルを閉じます。 */
	RetVal = AVIFileRelease(g_pAviFile);
	if(RetVal != 0) { die("%s: AVIFileRelease が失敗しました。", g_pAviFileName); }
	/* VFWを終了します。 */
	AVIFileExit();
/*{{Lua*/
	/* Luaを終了します。 */
	lua_close(g_pL);
/*}}Lua*/
	return EXIT_SUCCESS;
}

/*--------------------------------------------------------------------------*/

/* 使い方を表示し、エラー終了する。 */
void usage() {
	fprintf(stderr, "dpavigen - AVI 生成ツール (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2013 Naoyuki Sawa All Rights Reserved.\n");
	fprintf(stderr, "Usage:\n");
	fprintf(stderr, "    dpavigen [options] -f script.lua [-f script.lua ...] movie.avi\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "    -h            使い方を表示します。\n");
	fprintf(stderr, "    -s            進捗を表示しません。\n");
	fprintf(stderr, "    -c            圧縮設定を行います。(初期値=%sから読み込む)\n", CFG_FNAME);
	fprintf(stderr, "    -x DispX      幅  [ピクセル]を指定します。(初期値=%d)\n", g_DispX);
	fprintf(stderr, "    -y DispY      高さ[ピクセル]を指定します。(初期値=%d)\n", g_DispY);
	fprintf(stderr, "    -r Rate       フレームレートを指定します。(初期値=%d)\n", g_Rate );
	fprintf(stderr, "    -f script.lua Luaスクリプトファイルを読み込みます。(複数指定可)\n");
	fprintf(stderr, "    -e lua-script Luaスクリプト文字列を直接実行します。(複数指定可)\n");
	fprintf(stderr, "Summary:\n");
	fprintf(stderr, "  - AVIファイルの各フレームを、Luaスクリプトで描画します。\n");
	fprintf(stderr, "    Luaスクリプトを複数指定した場合、全て連結したものとして扱います。\n");
	fprintf(stderr, "    AVIファイルを複数指定した場合も、全て連結したものとして扱います。\n");
	fprintf(stderr, "  - Luaスクリプトには、以下の関数が定義されていなければなりません。\n");
	fprintf(stderr, "    Main                      コルーチンのメイン関数として呼び出されます。\n");
	fprintf(stderr, "  - コルーチンのメイン関数は、下記のC関数を利用できます。\n");
	fprintf(stderr, "    Pset(x,y,v)               点を描きます。(白黒)\n");
	fprintf(stderr, "    Pset(x,y,{r,g,b})         点を描きます。(カラー)\n");
	fprintf(stderr, "    Line(x1,y1,x2,y2,v)       線を描きます。(白黒)\n");
	fprintf(stderr, "    Line(x1,y1,x2,y2,{r,g,b}) 線を描きます。(カラー)\n");
	fprintf(stderr, "    Fill(x,y,w,h,v)           矩形を描きます。(白黒)\n");
	fprintf(stderr, "    Fill(x,y,w,h,{r,g,b})     矩形を描きます。(カラー)\n");
	fprintf(stderr, "    Text(x,y,\"s\",v)           文字列を描きます。(白黒)\n");
	fprintf(stderr, "    Text(x,y,\"s\",{r,g,b})     文字列を描きます。(カラー)\n");
	fprintf(stderr, "  - コルーチンのメイン関数は、下記のグローバル変数を利用できます。\n");
	fprintf(stderr, "    DispX                     幅  [ピクセル]\n");
	fprintf(stderr, "    DispY                     高さ[ピクセル]\n");
	fprintf(stderr, "    Rate                      フレームレート\n");
	fprintf(stderr, "Example:\n");
	fprintf(stderr, "    dpavigen.exe -x 128 -y 88 -r 20 -f Sample.lua Output.avi\n");
	exit(EXIT_FAILURE);
}

/*--------------------------------------------------------------------------*/

/* メッセージを表示し、エラー終了する。 */
void die(const char* fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	 fprintf(stderr, "\n### ");
	vfprintf(stderr, fmt, ap);
	 fprintf(stderr, "\n");
	va_end(ap);
#ifdef  _DEBUG
	__asm int 3; /* デバッグし易いように、デバッグ時はデバッガで止める */
#endif/*_DEBUG*/
	exit(EXIT_FAILURE);
}

/*--------------------------------------------------------------------------*/

/* Luaのパニック関数 */
int atpanic(lua_State* L) {
	die("atpanic: %s", luaL_checkstring(L, -1));
}

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

static COLORREF GetColorRef(lua_State* L, int narg) {
	double r, g, b;
	if(lua_istable(L, narg)) {
		/* カラーの色指定の場合… */
		lua_rawgeti(L, narg, 1); r = luaL_checknumber(L, -1); lua_pop(L, 1);
		lua_rawgeti(L, narg, 2); g = luaL_checknumber(L, -1); lua_pop(L, 1);
		lua_rawgeti(L, narg, 3); b = luaL_checknumber(L, -1); lua_pop(L, 1);
	} else {
		/* 白黒の輝度指定の場合… */
		r = g = b = luaL_checknumber(L, narg);
	}
	r *= (255 + 1); if(r < 0) { r = 0; } if(r > 255) { r = 255; }
	g *= (255 + 1); if(g < 0) { g = 0; } if(g > 255) { g = 255; }
	b *= (255 + 1); if(b < 0) { b = 0; } if(b > 255) { b = 255; }
	return RGB(r, g, b);
}

/*--------------------------------------------------------------------------*/

/*  Pset(x, y, v)
 *  Pset(x, y, {r,g,b})
 */
int cPset(lua_State* L) {
	int x, y;
	COLORREF c;
	x = luaL_checkinteger(L, 1);	/* 引数1: x座標 */
	y = luaL_checkinteger(L, 2);	/* 引数2: y座標 */
	c = GetColorRef(L, 3);		/* 引数3: 色(白黒,又は,カラー) */
	/* 点を描きます。 */
	SetPixel(g_hDc, x, y, c);
	return 0;
}

/*--------------------------------------------------------------------------*/

/*  Line(x1, y1, x2, y2, v)
 *  Line(x1, y1, x2, y2, {r,g,b})
 */
int cLine(lua_State* L) {
	int x1, y1, x2, y2;
	COLORREF c;
	HPEN hPen;
	x1 = luaL_checkinteger(L, 1);	/* 引数1: 始点x座標 */
	y1 = luaL_checkinteger(L, 2);	/* 引数2: 始点y座標 */
	x2 = luaL_checkinteger(L, 3);	/* 引数3: 終点x座標 */
	y2 = luaL_checkinteger(L, 4);	/* 引数4: 終点y座標 */
	c = GetColorRef(L, 5);		/* 引数5: 色(白黒,又は,カラー) */
	/* ペンを作成します。 */
	hPen = CreatePen(PS_SOLID, 0, c);
	/* メモリDCの状態を保存します。 */
	SaveDC(g_hDc);
	/* ペンを選択します。 */
	SelectObject(g_hDc, hPen);
	/* 線を描きます。 */
	MoveToEx(g_hDc, x1, y1, NULL);
	LineTo(g_hDc, x2, y2);
	/* メモリDCの状態を復元します。 */
	RestoreDC(g_hDc, -1);
	/* ペンを削除します。 */
	DeleteObject(hPen);
	return 0;
}

/*--------------------------------------------------------------------------*/

/*  Fill(x, y, w, h, v)
 *  Fill(x, y, w, h, {r,g,b})
 */
int cFill(lua_State* L) {
	RECT rc;
	COLORREF c;
	HBRUSH hBrush;
	rc.left   = luaL_checkinteger(L, 1);		/* 引数1: x座標 */
	rc.top    = luaL_checkinteger(L, 2);		/* 引数2: y座標 */
	rc.right  = luaL_checkinteger(L, 3) + rc.left;	/* 引数3: 幅 */
	rc.bottom = luaL_checkinteger(L, 4) + rc.top;	/* 引数4: 高さ */
	c = GetColorRef(L, 5);				/* 引数5: 色(白黒,又は,カラー) */
	/* ブラシを作成します。 */
	hBrush = CreateSolidBrush(c);
	/* 矩形を描きます。 */
	FillRect(g_hDc, &rc, hBrush);
	/* ブラシを削除します。 */
	DeleteObject(hBrush);
	return 0;
}

/*--------------------------------------------------------------------------*/

/*  Text(x, y, "s", v)
 *  Text(x, y, "s", {r,g,b})
 */
int cText(lua_State* L) {
	int x, y;
	const char* s;
	COLORREF c;
	x = luaL_checkinteger(L, 1);	/* 引数1: x座標 */
	y = luaL_checkinteger(L, 2);	/* 引数2: y座標 */
	s = luaL_checkstring(L, 3);	/* 引数3: 文字列 */
	c = GetColorRef(L, 4);		/* 引数4: 色(白黒,又は,カラー) */
	/* 文字色を設定します。 */
	SetTextColor(g_hDc, c);
	/* 文字列を描きます。 */
	TextOut(g_hDc, x, y, s, strlen(s));
	return 0;
}

