// fopen警告回避
#define _CRT_SECURE_NO_WARNINGS
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "image.h"
#include <math.h>

// Bitmapファイルの定義

#define BMP_FILE_HEADER_SIZE	14
#define BMP_INFO_HEADER_SIZE	40
#define BMP_HEADER_SIZE			(BMP_FILE_HEADER_SIZE + BMP_INFO_HEADER_SIZE)

#define BMP_FILESIZE_OFFSET		2
#define BMP_OFFSET_OFFSET		10
#define BMP_INFO_OFFSET			14
#define BMP_WIDTH_OFFSET		18
#define BMP_HEIGHT_OFFSET		22
#define BMP_PLANES_OFFSET		26
#define BMP_BITCOUNT_OFFSET		28
#define BMP_COMPRESS_OFFSET		30
#define BMP_DATASIZE_OFFSET		34
#define BMP_XPPM_OFFSET			38
#define BMP_YPPM_OFFSET			42


// ２４ビットフルカラーBitmapファイルを読み込む関数、無理に理解する必要はない
// 入力：画像ファイル名  出力：画像とサイズ情報
Image loadImage(const char* filepath)
{
	FILE* fp;
	byte header [BMP_HEADER_SIZE];
	unsigned int width, height;
	unsigned int bitcount;
	//byte* pimage;
	Image image = NULL;
	int i, j, k;

	// ファイルを開く
	fp = fopen(filepath, "rb");

	// ファイルが存在しなければ終了する
	if(fp == NULL) return NULL;

	// ヘッダを読み込む
	fread(header, sizeof(byte),BMP_HEADER_SIZE, fp);

	// 最初の２バイトに"BM"が書きこまれているかチェックする
	if(strncmp(header, "BM", 2))
	   return image;

	//byteデータから画像サイズ・色情報の部分をコピーする
	memcpy(&width,		header + BMP_WIDTH_OFFSET,		sizeof(unsigned int));
	memcpy(&height,		header + BMP_HEIGHT_OFFSET,		sizeof(unsigned int));
	memcpy(&bitcount,	header + BMP_BITCOUNT_OFFSET,	sizeof(unsigned int));

	// ２４ビット３チャンネル画像を生成する
	image = createImage(height, width);

	// 1次元データをRGB画像データに置き換える
	// 入出力時は画像データの上下を逆さまにする(Bitmapファイルの仕様)
	for(i = 0; i < image->height; i++)
		for (j = 0; j < image->width; j++)
			for(k=0; k<3; k++)
				fread(&image->pdata[image->height - i - 1][j][k], sizeof(byte), 1, fp);

	//ファイルを閉じる
	fclose(fp);

	//画像データを格納した場所を返す
	return image;
}
// ２４ビット３チャンネル画像データを２４ビットフルカラーBitmapファイルに書き込む関数、無理に理解する必要はない
void saveImage(Image image, const char* filepath)
{
	FILE* fp;
	int i, j, k;

	//ファイルに書き込む数値を記録する変数
	byte header[BMP_HEADER_SIZE];
	unsigned int width, height;
	unsigned int file_size;
	unsigned int offset_to_date;
	unsigned long info_header_size;
	unsigned int planes;
	unsigned int bitcount;
	unsigned long compress;
	unsigned long data_size;
	long xppm;
	long yppm;
	
	// Bitmapファイルに書きこむ情報を用意する
	width				= image->width;
	height				= image->height;
	file_size			= image->width * image->height * IMAGE_CHANNELS + BMP_HEADER_SIZE;
	offset_to_date		= BMP_HEADER_SIZE;
	info_header_size	= BMP_INFO_HEADER_SIZE;
	planes				= 1;
	bitcount			= IMAGE_BITCOUNT;
	compress			= 0;
	data_size			= image->width * image->height * IMAGE_CHANNELS;
	xppm				= 1;
	yppm				= 1;

	// ヘッダに値を代入する
	header[0] ='B'; 
	header[1] ='M';
	memcpy(header + 2, &file_size, sizeof(file_size));
	header[6] = 0;
	header[7] = 0;
	header[8] = 0; 
	header[9] = 0;
	memcpy(header + 10, &offset_to_date, sizeof(file_size));
	header[11] = 0;
	header[12] = 0;
	header[13] = 0;
	memcpy(header + 14, &info_header_size, sizeof(info_header_size));
	header[15] = 0;
	header[16] = 0;
	header[17] = 0;
	memcpy(header + BMP_WIDTH_OFFSET,		&width,		sizeof(width));
	memcpy(header + BMP_HEIGHT_OFFSET,		&height,	sizeof(height));
	memcpy(header + BMP_PLANES_OFFSET,		&planes,	sizeof(planes));
	memcpy(header + BMP_BITCOUNT_OFFSET,	&bitcount,	sizeof(bitcount));
	memcpy(header + BMP_COMPRESS_OFFSET,	&compress,	sizeof(compress));
	memcpy(header + BMP_DATASIZE_OFFSET,	&data_size,	sizeof(data_size));
	memcpy(header + BMP_XPPM_OFFSET,		&xppm,		sizeof(xppm));
	memcpy(header + BMP_YPPM_OFFSET,		&yppm,		sizeof(yppm));
	header[46] = 0;
	header[47] = 0;
	header[48] = 0;
	header[49] = 0;
	header[50] = 0;
	header[51] = 0;
	header[52] = 0;
	header[53] = 0;

	// ファイルを開く
	fp = fopen(filepath, "wb");

	// ヘッダ情報を書きこむ
	fwrite(header, sizeof(byte), BMP_HEADER_SIZE, fp);

	// 画像データを書き込む
	// 入出力時は画像データの上下を逆さまにする(Bitmapファイルの仕様)
	for (i = 0; i < image->height; i++)
		for (j = 0; j < image->width; j++)
			for(k = 0; k<3; k++)
				fwrite(&image->pdata[image->height-i-1][j][k], sizeof(byte), 1, fp);

	//ファイルを閉じる
	fclose(fp);
}
// ２４ビット３チャンネル画像を生成する関数
Image createImage(int height, int width)
{
	int i,j;
	Image image = NULL;

	// 引数で画像サイズを決める(異常な値ならエラーを出す)
	assert(width > 0 && height > 0);
	
	// 画像のメモリ確保する
	image = malloc(sizeof(struct _Image));

	//画像サイズを記録する
	image->width = width;
	image->height = height;

	// 画像データのメモリを確保する(3次元配列なので上位ランクからメモリを確保する)
	image->pdata = (byte***)malloc(sizeof(double**) * image->height);	//垂直方向のメモリ確保

	for(i=0; i<image->height; i++)
	{
		image->pdata[i] = (byte**)malloc(sizeof(double*) * image->width);		//水平方向のメモリ確保

		for(j=0; j<image->width; j++)
			image->pdata[i][j] = (byte*)malloc(sizeof(double) * IMAGE_CHANNELS);	//各色のメモリ確保
	}

	return image;
}

// ２４ビット３チャンネル画像を解放する関数
void deleteImage(Image image)
{
	int i,j;
	
	// 動的な3次元配列を解放する(ポインタを見失わないように下位ランクから解放する)
	for(i=0; i<image->height; i++)
		for(j=0; j<image->width; j++)
			free(image->pdata[i][j]);	//色チャンネルの解放

	for(i=0; i<image->height; i++)
		free(image->pdata[i]);			//Ｘ軸方向の解放

	free(image->pdata);					//Ｙ軸方向の解放


	// 画像メモリを解放する
	free(image);
}



// 画像処理２：RGB値を指定して塗りつぶす
void fillImage(Image image, byte r,byte g,byte b)
{
	int x,y;

	//ラスタスキャン
	for(y = 0; y < image->height; y++)		// 高さの領域指定
	{
		for (x = 0; x < image->width; x++)	// 幅の領域指定
		{
			image->pdata[y][x][2] = r;
			image->pdata[y][x][1] = g;
			image->pdata[y][x][0] = b;
		}
	}
}

// 画像処理３：グレイスケール処理
void grayscale(Image image)
{
	int x,y;
	int r,g,b;
	int Y;

	//ラスタスキャン
	for(y = 0; y < image->height; y++)		// 高さの領域指定
	{	
		for (x = 0; x < image->width; x++)	// 幅の領域指定
		{
			r =	image->pdata[y][x][2];	// Rの値の置き換え
			g =	image->pdata[y][x][1];	// Gの値の置き換え
			b =	image->pdata[y][x][0];	// Bの値の置き換え
			
			// 出力輝度値の計算
			Y = (r + g + b) / 3;

			// 輝度値の代入
			image->pdata[y][x][2] = Y;
			image->pdata[y][x][1] = Y;
			image->pdata[y][x][0] = Y;
		}
	}
}

// 画僧処理４：ネガポジ反転(色反転)処理
void color_reversal(Image image)
{
	int x,y;
	int r,g,b;

	//ラスタスキャン
	for(y = 0; y < image->height; y++)		// 高さの領域指定
	{
		for (x = 0; x < image->width; x++)	// 幅の領域指定
		{
			r = image->pdata[y][x][2]; // Rの値の置き換え
			g = image->pdata[y][x][1]; // Gの値の置き換え
			b = image->pdata[y][x][0]; // Bの値の置き換え

			// 出力輝度値の計算：ここに適切な計算式を入れてください。
			//image->pdata[y][x][2] = ******* ;
			//image->pdata[y][x][1] = ******* ;
			//image->pdata[y][x][0] = ******* ;
		}
	}
}