//----------------------------------------------------------------------------------------------
//
//  gConsole+bitmapEx.m : グラフィック描画用コンソール＋ビットマップ拡張
//  X-BASIC'
//
//  Created by AIG-Soft(iMac) on 2013/01/23.
//  Copyright (c) 2013年 AIG-Soft. All rights reserved.
//

#import "gConsole+bitmap.h"
#import "clipping.h"
#import "rgbhsv.h"

@implementation gConsole (bitmapEx)

//---------------------------------------------------------------------------------------
// 拡張bitmap関数群(V1.7)
//---------------------------------------------------------------------------------------
// ライン(両端からの同時描画)
// http://fussy.web.fc2.com/algo/algo1-3.htmより

-(void)bitmapLine:(int)sx sy:(int)sy ex:(int)ex ey:(int)ey r:(int)r g:(int)g b:(int)b alpha:(int)alpha
{
	int i;
	
	setClippingArea(0, 0, self.bitmapWidth-1,self.bitmapHeight-1);
	if ( clipping( &sx,&sy, &ex,&ey ) < 0 ) return; // 座標クリッピング
	
	int dx = ( ex > sx )? ex - sx : sx - ex;
	int dy = ( ey > sy )? ey - sy : sy - ey;
	
	int dirX = ( ex > sx )? 1 : -1;
	int dirY = ( ey > sy )? 1 : -1;
	
	/* 傾きが1より小さい場合 */
	if( dx > dy ) {
		int E = -dx;
		for ( i = 0 ; i < ( dx + 1 ) >> 1 ; i++ ) {
			[self bitmapPset:sx y:sy r:r g:g b:b alpha:alpha];
			[self bitmapPset:ex y:ey r:r g:g b:b alpha:alpha];
			sx += dirX;
			ex -= dirX;
			E += 2 * dy;
			if ( E >= 0 ) {
				sy += dirY;
				ey -= dirY;
				E -= 2 * dx;
			}
		}
		if ( ( dx % 2 ) == 0 ) [self bitmapPset:sx y:sy r:r g:g b:b alpha:alpha]; /* dx + 1 が奇数の場合、残った中央の点を最後に描画 */
		/* 傾きが1以上の場合 */
	} else {
		int E = -dy;
		for ( i = 0 ; i < ( dy + 1 ) >> 1 ; i++ ) {
			[self bitmapPset:sx y:sy r:r g:g b:b alpha:alpha];
			[self bitmapPset:ex y:ey r:r g:g b:b alpha:alpha];
			sy += dirY;
			ey -= dirY;
			E += 2 * dx;
			if ( E >= 0 ) {
				sx += dirX;
				ex -= dirX;
				E -= 2 * dy;
			}
		}
		if ( ( dy % 2 ) == 0 ) [self bitmapPset:sx y:sy r:r g:g b:b alpha:alpha]; /* dy + 1 が奇数の場合、残った中央の点を最後に描画 */
	}
}

-(void)bitmapLine:(int)sx sy:(int)sy ex:(int)ex ey:(int)ey rgba:(SLong)rgba
{
	int r,g,b,alpha;
	splitRGB(rgba, &r ,&g, &b, &alpha);
	[self bitmapLine:sx sy:sy ex:ex ey:ey r:r g:g b:b alpha:alpha];
}

//---------

-(void)bitmapBox:(int)sx sy:(int)sy ex:(int)ex ey:(int)ey r:(int)r g:(int)g b:(int)b alpha:(int)alpha
{
	checkBitmapBoxArea(&sx,&sy,&ex,&ey); // 座標の大小入れ替え
	[self bitmapLine:sx sy:sy ex:ex ey:sy r:r g:g b:b alpha:alpha]; // 上横
	[self bitmapLine:sx sy:ey ex:ex ey:ey r:r g:g b:b alpha:alpha]; // 下横
	//
	[self bitmapLine:sx sy:sy ex:sx ey:ey r:r g:g b:b alpha:alpha]; // 左縦
	[self bitmapLine:ex sy:sy ex:ex ey:ey r:r g:g b:b alpha:alpha]; // 右縦
}

-(void)bitmapBox:(int)sx sy:(int)sy ex:(int)ex ey:(int)ey rgba:(ULong)rgba
{
	int r,g,b,alpha;
	splitRGB(rgba, &r ,&g, &b, &alpha);
	[self bitmapBox:sx sy:sy ex:ex ey:ey r:r g:g b:b alpha:alpha];
}

-(void)bitmapFill:(int)sx sy:(int)sy ex:(int)ex ey:(int)ey r:(int)r g:(int)g b:(int)b alpha:(int)alpha
{
	checkBitmapBoxArea(&sx,&sy,&ex,&ey); // 座標の大小入れ替え
	for (int y=sy;y<=ey;y++) {
		[self bitmapLine:sx sy:y ex:ex ey:y r:r g:g b:b alpha:alpha]; // 横
	}
}

-(void)bitmapFill:(int)sx sy:(int)sy ex:(int)ex ey:(int)ey rgba:(ULong)rgba
{
	int r,g,b,alpha;
	splitRGB(rgba, &r ,&g, &b, &alpha);
	[self bitmapFill:sx sy:sy ex:ex ey:ey r:r g:g b:b alpha:alpha];
}

//---------------------------------------------------------------------------------------
// 円描画

-(void)bitmapCircle:(int)x0 y:(int)y0 rr:(int)rr r:(int)r g:(int)g b:(int)b alpha:(int)alpha
// 中心(x0,y0)とする半径rの真円を描く
{
	if (rr==0) return; // 半径0の時は描画しない
	
#if 1
	// http://dencha.ojaru.jp/programs_07/pg_graphic_09a2.htmlより
	int diameter=rr*2; // 直径
	SLong cx = 0, cy=diameter/2+1;
    double d;
    d = -diameter*diameter + 4*cy*cy -4*cy +2;
    SLong dx, dy;
    dx = 4;
    dy = -8*cy+8;
	int mirror_center_x=x0;
	int mirror_center_y=y0;
    if ((diameter&1) ==0) {
        mirror_center_x++;
        mirror_center_y++;
    }
	
    for (cx=0; cx <= cy ; cx++) {
        if (d>0) {
            d += dy;
            dy+= 8;
            cy--;
        }
        [self bitmapPset:(int)(cy + x0)					y:(int)(cx + y0)				r:r g:g b:b alpha:alpha]; //   0- 45度の間
		[self bitmapPset:(int)(cx + x0)	 				y:(int)(cy + y0) 				r:r g:g b:b alpha:alpha]; //  45- 90度の間
        [self bitmapPset:(int)(-cx + mirror_center_x)	y:(int)(cy + y0)				r:r g:g b:b alpha:alpha]; //  90-135度の間
        [self bitmapPset:(int)(-cy + mirror_center_x)	y:(int)(cx + y0) 				r:r g:g b:b alpha:alpha]; // 135-180度の間
		[self bitmapPset:(int)(-cy + mirror_center_x)	y:(int)(-cx + mirror_center_y)	r:r g:g b:b alpha:alpha]; // 180-225度の間
        [self bitmapPset:(int)(-cx + mirror_center_x)	y:(int)(-cy + mirror_center_y) 	r:r g:g b:b alpha:alpha]; // 225-270度の間
        [self bitmapPset:(int)(cx + x0)	 				y:(int)(-cy + mirror_center_y) 	r:r g:g b:b alpha:alpha]; // 270-315度の間
		[self bitmapPset:(int)(cy + x0)	 				y:(int)(-cx + mirror_center_y) 	r:r g:g b:b alpha:alpha]; // 315-360度の間
		
        d += dx;
        dx+=8;
    }
#else
	// http://fussy.web.fc2.com/algo/algo2-1.htm
	// 半径が小さい時の誤差が大きすぎてダメ(丸にならない)
	int x = rr;
	int y = 0;
	int F = -2 * r + 3;
	
	while ( x >= y ) {
		[self bitmapPset:x0+x y:y0+y r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0-x y:y0+y r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0+x y:y0-y r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0-x y:y0-y r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0+y y:y0+x r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0-y y:y0+x r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0+y y:y0-x r:r g:g b:b alpha:alpha];
		[self bitmapPset:x0-y y:y0-x r:r g:g b:b alpha:alpha];
		if ( F >= 0 ) {
			x--;
			F -= 4 * x;
		}
		y++;
		F += 4 * y + 2;
	}
#endif
}

-(void)bitmapCircle:(int)x0 y:(int)y0 rr:(int)rr rgba:(ULong)rgba
{
	int r,g,b,alpha;
	splitRGB(rgba, &r ,&g, &b, &alpha);
	[self bitmapCircle:x0 y:y0 rr:rr r:r g:g b:b alpha:alpha];
}

//---------------------------------------------------------------------------------------
// 楕円描画
// http://jariten.exblog.jp/60051

-(void)bitmapEllipse:(int)x0 y:(int)y0 rr:(int)rr hv:(int)hv r:(int)r g:(int)g b:(int)b alpha:(int)alpha
// 楕円の長半径を aa、短半径を bb とすると、扁平率hv=(aa-bb)/aa=(1-bb/aa)
// 半径
{
//	扁平率	<=256のとき	aa=rr,bb=(hv/256)*rr
//			> 256のとき	aa=(256/hv)*rr,bb=rr
	// 整数演算するため、式の順番を変えて小数点以下が途中で発生しないようにする
	int aa,bb;
	if (hv<=256) {
		aa=rr;
		bb=(rr*hv)/256;
	} else {
		aa=(rr*256)/hv;
		bb=rr;
	}
	nslog(@"bitmapEllipse");
	nslog(@"rr=%d,hv=%d",rr,hv);
	[self bitmapEllipse:x0 y:y0 aa:aa bb:bb r:r g:g b:b alpha:alpha];
}

-(void)bitmapEllipse:(int)x0 y:(int)y0 aa:(int)aa bb:(int)bb r:(int)r g:(int)g b:(int)b alpha:(int)alpha
// 楕円を描く
// 中心:x0,y0 x軸半径:aa, y軸半径:bb の楕円
{
	printSelf();
	nslog(@"aa=%d,bb=%d",aa,bb);
	int x,x1,y,y1,rr;
	if (aa > bb) {
		x=rr=aa;
		y=0;
		while (x>=y) {
			x1=x*bb/aa;
			y1=y*bb/aa;
			[self bitmapPset:x0+x y:y0+y1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0+x y:y0-y1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-x y:y0+y1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-x y:y0-y1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0+y y:y0+x1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0+y y:y0-x1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-y y:y0+x1 r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-y y:y0-x1 r:r g:g b:b alpha:alpha];
			if ((rr -= y++*2+1)<=0) rr+= --x*2;
		}
	} else {
		x=rr=bb;
		y=0;
		while (x>=y) {
			x1=x*aa/bb;
			y1=y*aa/bb;
			[self bitmapPset:x0+x1 y:y0+y r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0+x1 y:y0-y r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-x1 y:y0+y r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-x1 y:y0-y r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0+y1 y:y0+x r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0+y1 y:y0-x r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-y1 y:y0+x r:r g:g b:b alpha:alpha];
			[self bitmapPset:x0-y1 y:y0-x r:r g:g b:b alpha:alpha];
			if ((rr -= y++*2+1)<=0) rr+= --x*2;
		}
	}
}

-(void)bitmapEllipse:(int)x0 y:(int)y0 aa:(int)aa bb:(int)bb rgba:(ULong)rgba
{
	int r,g,b,alpha;
	splitRGB(rgba, &r ,&g, &b, &alpha);
	[self bitmapEllipse:x0 y:y0 aa:aa bb:bb r:r g:g b:b alpha:alpha];
}

-(void)bitmapEllipse:(int)x0 y:(int)y0 rr:(int)rr hv:(int)hv rgba:(ULong)rgba
{
	int r,g,b,alpha;
	splitRGB(rgba, &r ,&g, &b, &alpha);
	[self bitmapEllipse:x0 y:y0 rr:rr hv:hv r:r g:g b:b alpha:alpha];
}

//---------------------------------------------------------------------------------------
// ペイント
// http://fussy.web.fc2.com/algo/algo3-2.htmより

#define MAXSIZE	1024 /* バッファサイズ */

// グラフィック最小座標：固定
#define MINX 0
#define MINY 0

struct BufStr {
	int lx; /* 領域右端のX座標 */
	int rx; /* 領域右端のX座標 */
	int y;  /* 領域のY座標 */
	int oy; /* 親ラインのY座標 */
};
struct BufStr buff[MAXSIZE]; /* シード登録用バッファ */
struct BufStr *sIdx, *eIdx;  /* buffの先頭・末尾ポインタ */

/*
 scanLine : 線分からシードを探索してバッファに登録する
 lx,rx : 線分のX座標の範囲
 y     : 線分のY座標
 oy    : 親ラインのY座標
 rgba  : 領域色
 */
-(void)bitmapScanLine:(int)lx rx:(int)rx y:(int)y oy:(int)oy rgba:(ULong)col
{
	while ( lx <= rx ) {
		
		/* 非領域色を飛ばす */
		for ( ; lx < rx ; lx++ ) {
			if ( [self bitmapPoint:lx y:y] == col ) break;
		}
		if ( [self bitmapPoint:lx y:y] != col ) break;
		
		eIdx->lx = lx;
		
		/* 領域色を飛ばす */
		for ( ; lx <= rx ; lx++ ) {
			if ( [self bitmapPoint:lx y:y] != col ) break;
		}
		
		eIdx->rx = lx - 1;
		eIdx->y  = y;
		eIdx->oy = oy;
		
		if ( ++eIdx == &buff[MAXSIZE] ) {
			eIdx = buff;
		}
	}
}

/*
 paint : 塗り潰し処理(高速版)
 x, y  : 開始座標
 paintCol : 塗り潰す時の色(描画色)
 */
-(void)bitmapPaint:(int)x y:(int)y rgba:(ULong)paintCol
{
	int lx, rx; /* 塗り潰す線分の両端のX座標 */
	int ly;     /* 塗り潰す線分のY座標 */
	int oy;     /* 親ラインのY座標 */
	int i;
	
	ULong col = [self bitmapPoint:x y:y]; /* 閉領域の色(領域色) */
	if ( col == paintCol ) return;	    /* 領域色と描画色が等しければ処理不要 */
	
	sIdx = buff;
	eIdx = buff + 1;
	sIdx->lx = sIdx->rx = x;
	sIdx->y  = sIdx->oy = y;
	
	do {
		lx = sIdx->lx;
		rx = sIdx->rx;
		ly = sIdx->y;
		oy = sIdx->oy;
		
		int lxsav = lx - 1;
		int rxsav = rx + 1;
		
		if ( ++sIdx == &buff[MAXSIZE] ) sIdx = buff;
		
		/* 処理済のシードなら無視 */
		if ( [self bitmapPoint:lx y:ly] != col ) continue;
		
		/* 右方向の境界を探す */
		while ( rx < (self.bitmapWidth-1) ) {
			if ( [self bitmapPoint:rx + 1 y:ly ] != col ) break;
			rx++;
		}
		/* 左方向の境界を探す */
		while ( lx > MINX ) {
			if ( [self bitmapPoint:lx - 1 y:ly ] != col ) break;
			lx--;
		}
		/* lx-rxの線分を描画 */
		for ( i = lx; i <= rx; i++ ) [self bitmapPset:i y:ly rgba:paintCol];
		
		/* 真上のスキャンラインを走査する */
		if ( ly - 1 >= MINY ) {
			if ( ly - 1 == oy ) {
				[self bitmapScanLine:lx    rx:lxsav y:ly-1 oy:ly rgba:col];
				[self bitmapScanLine:rxsav rx:rx    y:ly-1 oy:ly rgba:col];
			} else {
				[self bitmapScanLine:lx    rx:rx    y:ly-1 oy:ly rgba:col];
			}
		}
		
		/* 真下のスキャンラインを走査する */
		if ( ly + 1 <= (self.bitmapHeight-1) ) {
			if ( ly + 1 == oy ) {
				[self bitmapScanLine:lx    rx:lxsav y:ly+1 oy:ly rgba:col];
				[self bitmapScanLine:rxsav rx:rx    y:ly+1 oy:ly rgba:col];
			} else {
				[self bitmapScanLine:lx    rx:rx    y:ly+1 oy:ly rgba:col];
			}
		}
		
	} while ( sIdx != eIdx );
}

-(void)bitmapPaint:(int)x y:(int)y r:(int)r g:(int)g b:(int)b alpha:(int)alpha
{
	[self bitmapPaint:x y:y rgba:rgb(r, g, b ,alpha)];
}

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

@end
