Open9

Siv3Dでドット絵ツールを作るときのメモ

せがわせがわ
String EncodeImageToBase64(const Image& image)
{
	const auto webp = image.encodeWebP(Lossless::Yes);
	const auto compressedData = Zlib::Compress(webp, Zlib::MaxCompressionLevel);
	return compressedData.base64Str();
}

Image DecodeImageFromBase64(StringView base64TextData)
{
	const auto decodedData = Base64::Decode(base64TextData);
	const auto decompressedData = Zlib::Decompress(decodedData);
	return Image(MemoryReader(decompressedData));
}
せがわせがわ

//ドット絵としてぼやけないように const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

せがわせがわ

グリッドを描画

void DrawGrid(const Size& canvasSize, int32 scaling, const ColorF& color = ColorF{ 0.0, 0.3 })
{
	for (auto i : step(canvasSize.x))
	{
		Rect{ i * scaling, 0, 1, canvasSize.x * scaling }.draw(color);
	}

	for (auto i : step(canvasSize.y))
	{
		Rect{ 0, i * scaling, canvasSize.y * scaling, 1 }.draw(color);
	}
}
せがわせがわ
タイトル

//画面構成やレイアウトはJSONで変更できるように
// セーブデータはJSON+
//ドット絵のサイズは最初に選ばせるように 数値入力
//ドット絵の出力方法はいくつか選べるように(PNG、JSON、BMPなど)
//オリジナルのパレットも作れるように いくつかのプリセット スポイト
//レイヤーの機能は持たせるように 必須 半透明を
//ドット絵としてぼやけないように const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
//拡大率を変えられるように
//半透明の表現が必要 灰色と白のチェック画像の自動生成
//アンドゥ・リドゥ history

//グリッドの表示 8倍以降
//for (auto i : step(61))
//{
//	Rect{ 0, i * 10, 600, 1 }.draw(ColorF{ 0.4 });
//	Rect{ i * 10, 0, 1, 600 }.draw(ColorF{ 0.4 });
//}


# include <Siv3D.hpp>

String EncodeImageToBase64(const Image& image)
{
	const auto webp = image.encodeWebP(Lossless::Yes);
	const auto compressedData = Zlib::Compress(webp, Zlib::MaxCompressionLevel);
	return compressedData.base64Str();
}

Image DecodeImageFromBase64(StringView base64TextData)
{
	const auto decodedData = Base64::Decode(base64TextData);
	const auto decompressedData = Zlib::Decompress(decodedData);
	return Image(MemoryReader(decompressedData));
}

void DrawGrid(const Size& canvasSize, int32 scaling, const ColorF& color = ColorF{ 0.0, 0.1 })
{
	for (auto i : step(canvasSize.x))
	{
		Rect{ i * scaling, 0, 1, canvasSize.x * scaling }.draw(color);
	}

	for (auto i : step(canvasSize.y))
	{
		Rect{ 0, i * scaling, canvasSize.y * scaling, 1 }.draw(color);
	}
}

struct Layer
{
	String index;
	DynamicTexture texture;
};

Image GenerateBackgroundImage(const Size& canvasSize)
{
	return Image(canvasSize, Arg::generator = [](const Point& p) {return (p.x + p.y) % 2 == 0 ? Color(255) : Color(230); });
}

class PixelArtData
{
public:

	PixelArtData()
	{
		m_backgroundTexture = Texture(GenerateBackgroundImage(m_canvasSize));
	}

	Size m_canvasSize{ 32, 32 };

	Texture m_backgroundTexture;

	HashTable<String, Image> m_imageDataTable;

	Array<Layer> m_layers;

	size_t m_index = 0;

	String generateImageIndex()
	{
		return ToString(++m_index);
	}

	void draw(int32 scaling) const 
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
		// テクスチャを表示

		m_backgroundTexture.scaled(scaling).draw();

		for (const auto& layer : m_layers)
		{
			layer.texture.scaled(scaling).draw();
		}
	}

	void addLayerImage()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 0));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void clearAllImage()
	{
		for (auto& image : m_imageDataTable)
		{
			image.second.fill(Color(255, 255, 255, 0));
		}
	}

	void updateTexture()
	{
		for (auto& layer : m_layers)
		{
			layer.texture.fill(m_imageDataTable[layer.index]);
		}
	}
};

PixelArtData ImportArtDataFromJSON(const JSON& json)
{
	//バージョン管理

	//基本情報	

	//Image情報

	//Layer情報

}

JSON ExportArtDataToJSON(const PixelArtData& art)
{
	JSON json;

	json[U"test"] = U"wow";


}



void Main()
{
	int32 scaling = 16;

	// ペンの太さ
	constexpr int32 thickness = 1;

	// ペンの色
	Color penColor = Palette::Orange;

	// 書き込み用の画像データを用意
	//Image image = DecodeImageFromBase64(U"eNoBrgBR/1JJRkamAAAAV0VCUFZQOEyaAAAALz/ADwAPMP/zP//zH3iAW2uboowOIPQ6CKcwZMogpBDPCLcAZM5GG+GyPg/yDfZLHNH/CeCjqSvFSsqCmjZ3OiP1TC/UsMeEoUZf8MJAo9YEYalr8SHc7DMM+Xbj5RSjfOtjmR0tPGChDMHDDHgQHaoQCVOL/LncvquRSf5ByjaCahlh4jBP9KXBRJq/ogJJ7yS2vOI5+o5/LvgzT50=");

	// 表示用のテクスチャ(内容を更新するので DynamicTexture)
	//DynamicTexture texture{ image };

	PixelArtData data;

	data.addLayerImage();
	data.addLayerImage();
	data.addLayerImage();

	String currentIndex = ToString(1);

	while (System::Update())
	{
		if (Key1.down())
		{
			currentIndex = U"1";
		}
		if (Key2.down())
		{
			currentIndex = U"2";
		}
		if (Key3.down())
		{
			currentIndex = U"3";
		}

		if (MouseL.pressed())
		{
			auto& image = data.m_imageDataTable[currentIndex];

			// 書き込む線の始点は直前のフレームのマウスカーソル座標
			// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
			const Point from = (MouseL.down() ? Cursor::Pos() / scaling : Cursor::PreviousPos() / scaling);

			// 書き込む線の終点は現在のマウスカーソル座標
			const Point to = Cursor::Pos() / scaling;

			// image に線を書き込む
			Line{ from, to }.overwrite(image, thickness, penColor, Antialiased::No);

			data.updateTexture();
		}

		// 描いたものを消去するボタンが押されたら
		if (SimpleGUI::Button(U"Clear", Vec2{ 640, 500}, 120))
		{
			data.clearAllImage();

			data.updateTexture();
		}


		//if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		//{
		//	Stopwatch stopwatch;
		//	stopwatch.start();
		//	Clipboard::SetText(EncodeImageToBase64(image));
		//	Print << stopwatch.usF();
		//}

		if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		{
			ExportArtDataToJSON(data);
		}

		HSV color;
		if (SimpleGUI::ColorPicker(color, Vec2{ 640, 240 }, true))
		{
			penColor = color.toColor();
		}


		// テクスチャを表示

		data.draw(scaling);

		//layerごとのプレビュー
		{
			const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

			//もっとリストとしてきれいに配置したい。インタラクティブに
			for (size_t i = 0; i < data.m_layers.size(); ++i)
			{
				data.m_backgroundTexture.draw(Arg::topRight = Point(Scene::Width(), i * data.m_canvasSize.y));
				data.m_layers[i].texture.draw(Arg::topRight = Point(Scene::Width(), i * data.m_canvasSize.y));
			}
		}

		Rect(data.m_canvasSize).setPos(Arg::topRight = Point(Scene::Width(),( Parse<int32>(currentIndex)-1) * data.m_canvasSize.y)).drawFrame(2.0, Palette::Black);

		//DrawGrid(data.m_canvasSize, scaling);
	}
}

せがわせがわ
タイトル

//画面構成やレイアウトはJSONで変更できるように
// セーブデータはJSON+
//ドット絵のサイズは最初に選ばせるように 数値入力
//ドット絵の出力方法はいくつか選べるように(PNG、JSON、BMPなど)
//オリジナルのパレットも作れるように いくつかのプリセット スポイト
//レイヤーの機能は持たせるように 必須 半透明を
//ドット絵としてぼやけないように const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
//拡大率を変えられるように

//アンドゥ・リドゥ history

//グリッドの表示 8倍以降
//for (auto i : step(61))
//{
//	Rect{ 0, i * 10, 600, 1 }.draw(ColorF{ 0.4 });
//	Rect{ i * 10, 0, 1, 600 }.draw(ColorF{ 0.4 });
//}

//RGBパレットが必要
//右クリックでスポイト


# include <Siv3D.hpp>

String EncodeImageToBase64(const Image& image)
{
	const auto webp = image.encodeWebP(Lossless::Yes);
	const auto compressedData = Zlib::Compress(webp, Zlib::MaxCompressionLevel);
	return compressedData.base64Str();
}

Image DecodeImageFromBase64(StringView base64TextData)
{
	const auto decodedData = Base64::Decode(base64TextData);
	const auto decompressedData = Zlib::Decompress(decodedData);
	return Image(MemoryReader(decompressedData));
}

void DrawGrid(const Size& canvasSize, int32 scaling, const ColorF& color = ColorF{ 0.0, 0.1 })
{
	for (auto i : step(canvasSize.x))
	{
		Rect{ i * scaling, 0, 1, canvasSize.x * scaling }.draw(color);
	}

	for (auto i : step(canvasSize.y))
	{
		Rect{ 0, i * scaling, canvasSize.y * scaling, 1 }.draw(color);
	}
}

struct Layer
{
	String index;
	DynamicTexture texture;
};

//半透明の表現が必要 灰色と白のチェック画像の自動生成
Image GenerateBackgroundImage(const Size& canvasSize)
{
	return Image(canvasSize, Arg::generator = [](const Point& p) {return (p.x + p.y) % 2 == 0 ? Color(255) : Color(230); });
}

class PixelArtData
{
public:

	PixelArtData()
	{
		m_backgroundTexture = Texture(GenerateBackgroundImage(m_canvasSize));
		m_palette = Image(32, 32, Palette::White);
	}

	Size m_canvasSize{ 32, 32 };

	Texture m_backgroundTexture;

	HashTable<String, Image> m_imageDataTable;

	Array<Layer> m_layers;

	size_t m_index = 0;

	Image m_palette;

	String generateImageIndex()
	{
		return ToString(++m_index);
	}

	void draw(int32 scaling) const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
		// テクスチャを表示

		m_backgroundTexture.scaled(scaling).draw();

		for (const auto& layer : m_layers)
		{
			layer.texture.scaled(scaling).draw();
		}
	}

	void addLayerImage()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 0));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void clearAllImage()
	{
		for (auto& image : m_imageDataTable)
		{
			image.second.fill(Color(255, 255, 255, 0));
		}
	}

	void updateTexture()
	{
		for (auto& layer : m_layers)
		{
			layer.texture.fill(m_imageDataTable[layer.index]);
		}
	}

	void drawLayers(size_t currentIndex) const 
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

		//もっとリストとしてきれいに配置したい。インタラクティブに
		for (size_t i = 0; i < m_layers.size(); ++i)
		{
			const size_t k = m_layers.size() - 1 - i;

			m_backgroundTexture.draw(Point(0, k * m_canvasSize.y));
			m_layers[i].texture.draw(Point(0, k * m_canvasSize.y));
		}


		const size_t j = m_layers.size() - 1 - currentIndex;

		Rect(m_canvasSize).setPos(0, j* m_canvasSize.y).drawFrame(2.0, Palette::Black);
	}

	Image generateIntegratedImage()
	{
		if (m_layers.size() == 0)
		{
			return Image();
		}

		Image result = m_imageDataTable[m_layers[0].index];

		for (size_t i = 1; i < m_layers.size(); ++i)
		{
			m_imageDataTable[m_layers[i].index].stamp(result,0, 0);
		}

		return result;
	}
};

PixelArtData ImportArtDataFromJSON(const JSON& json)
{
	//バージョン管理

	//基本情報	

	//Image情報

	//Layer情報

}

JSON ExportArtDataToJSON(const PixelArtData& art)
{
	JSON json;

	json[U"test"] = U"wow";

	return json;
}

Transformer2D TranslateDrawingPos(const Vec2& pos)
{
	return Transformer2D(Mat3x2::Translate(pos));
}



void Main()
{
	int32 scaling = 16;

	// ペンの太さ
	constexpr int32 thickness = 1;

	// ペンの色
	Color penColor = Palette::Orange;

	// 書き込み用の画像データを用意
	//Image image = DecodeImageFromBase64(U"eNoBrgBR/1JJRkamAAAAV0VCUFZQOEyaAAAALz/ADwAPMP/zP//zH3iAW2uboowOIPQ6CKcwZMogpBDPCLcAZM5GG+GyPg/yDfZLHNH/CeCjqSvFSsqCmjZ3OiP1TC/UsMeEoUZf8MJAo9YEYalr8SHc7DMM+Xbj5RSjfOtjmR0tPGChDMHDDHgQHaoQCVOL/LncvquRSf5ByjaCahlh4jBP9KXBRJq/ogJJ7yS2vOI5+o5/LvgzT50=");

	// 表示用のテクスチャ(内容を更新するので DynamicTexture)
	//DynamicTexture texture{ image };

	PixelArtData data;

	data.addLayerImage();
	data.addLayerImage();
	data.addLayerImage();

	size_t currentIndex = 0;

	while (System::Update())
	{

		auto& image = data.m_imageDataTable[data.m_layers[currentIndex].index];

		if (KeyUp.down())
		{
			currentIndex = (currentIndex + 1 == data.m_layers.size()) ? 0 : currentIndex + 1;
		}
		if (KeyDown.down())
		{
			currentIndex = (currentIndex == 0) ? data.m_layers.size() - 1 : currentIndex - 1;
		}

		if (MouseL.pressed())
		{
			//キャンバスの場合

			// 書き込む線の始点は直前のフレームのマウスカーソル座標
			// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
			const Point from = (MouseL.down() ? Cursor::Pos() / scaling : Cursor::PreviousPos() / scaling);

			// 書き込む線の終点は現在のマウスカーソル座標
			const Point to = Cursor::Pos() / scaling;

			// image に線を書き込む
			Line{ from, to }.overwrite(image, thickness, penColor, Antialiased::No);

			data.updateTexture();
		}

		if (MouseR.pressed())
		{
			// 書き込む線の終点は現在のマウスカーソル座標
			const Point to = Cursor::Pos() / scaling;

			penColor = image.getPixel(to, ImageAddressMode::Clamp);
		}


		if (KeyF.down())
		{
			const Point to = Cursor::Pos() / scaling;

			image.floodFill(to, penColor);

			data.updateTexture();
		}

		// 描いたものを消去するボタンが押されたら
		if (SimpleGUI::Button(U"Clear", Vec2{ 640, 500 }, 120))
		{
			data.clearAllImage();

			data.updateTexture();
		}


		//if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		//{
		//	Stopwatch stopwatch;
		//	stopwatch.start();
		//	Clipboard::SetText(EncodeImageToBase64(image));
		//	Print << stopwatch.usF();
		//}

		if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		{
			//PNG

			//ExportArtDataToJSON(data);

			//レイヤーを統合する必要がある。
			Image result = data.generateIntegratedImage();

			if (const auto filePath = Dialog::SaveImage())
			{
				result.savePNG(filePath.value());
			}
		}

		HSV color = penColor;

		if (SimpleGUI::ColorPicker(color, Vec2{ 640, 240 }, true))
		{
			penColor = color.toColor();
		}


		// テクスチャを表示

		{			
			data.draw(scaling);

			if (scaling >= 16)
			{
				DrawGrid(data.m_canvasSize, scaling);
			}
		}

		{
			const auto t = TranslateDrawingPos({ 600,0 });

			data.drawLayers(currentIndex);
		}

		{
			const auto t = TranslateDrawingPos({ 550,0 });

			data.draw(1);
		}

		Circle(Cursor::PosF(), 5.0).draw(penColor);

		
	}
}

せがわせがわ
タイトル


//達成
//アンドゥ・リドゥ history
//ドット絵としてぼやけないように const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
//拡大率を変えられるように
//レイヤーの機能は持たせるように 必須 半透明を
//グリッドの表示 8倍以降
//for (auto i : step(61))
//{
//	Rect{ 0, i * 10, 600, 1 }.draw(ColorF{ 0.4 });
//	Rect{ i * 10, 0, 1, 600 }.draw(ColorF{ 0.4 });
//}
//RGBパレットが必要
//右クリックでスポイト


//TODO
// 
//画面構成やレイアウトはJSONで変更できるように
// セーブデータはJSON+
//ドット絵のサイズは最初に選ばせるように 数値入力
//ドット絵の出力方法はいくつか選べるように(PNG、JSON、BMPなど)
//オリジナルのパレットも作れるように いくつかのプリセット スポイト
//範囲選択のコピー・カット・ペースト
//自作のポップアップウィンドウを使う
//import export new セッティング


# include <Siv3D.hpp>

String EncodeImageToBase64(const Image& image)
{
	const auto webp = image.encodeWebP(Lossless::Yes);
	const auto compressedData = Zlib::Compress(webp, Zlib::MaxCompressionLevel);
	return compressedData.base64Str();
}

Image DecodeImageFromBase64(StringView base64TextData)
{
	const auto decodedData = Base64::Decode(base64TextData);
	const auto decompressedData = Zlib::Decompress(decodedData);
	return Image(MemoryReader(decompressedData));
}

template<class Type>
Blob GenerateHistory(const Type& data)
{
	return Zlib::Compress(Serializer<MemoryWriter>{}(data)->getBlob());
}

template<class Type>
void RecoverHistory(Type& data, const Blob& blob)
{
	Deserializer<MemoryReader>{Zlib::Decompress(blob)}(data);
}

void DrawGrid(const Size& canvasSize, int32 scaling, const ColorF& color = ColorF{ 0.0, 0.1 })
{
	for (auto i : step(canvasSize.x))
	{
		Rect{ i * scaling, 0, 1, canvasSize.x * scaling }.draw(color);
	}

	for (auto i : step(canvasSize.y))
	{
		Rect{ 0, i * scaling, canvasSize.y * scaling, 1 }.draw(color);
	}
}


//半透明の表現が必要 灰色と白のチェック画像の自動生成
Image GenerateBackgroundImage(const Size& canvasSize)
{
	return Image(canvasSize, Arg::generator = [](const Point& p) {return (p.x + p.y) % 2 == 0 ? Color(255) : Color(230); });
}

class PixelArtData
{
public:

	struct Layer
	{
		String index;
		DynamicTexture texture;

		template <class Archive>
		void SIV3D_SERIALIZE(Archive& archive)
		{
			archive(index);
		}
	};


	PixelArtData()
	{
		m_backgroundTexture = Texture(GenerateBackgroundImage(m_canvasSize));
		m_palette = Image(32, 32, Palette::White);
	}

	Texture m_backgroundTexture;

	Size m_canvasSize{ 32, 32 };
	HashTable<String, Image> m_imageDataTable;
	Array<Layer> m_layers;
	size_t m_index = 0;
	Image m_palette;

	template <class Archive>
	void SIV3D_SERIALIZE(Archive& archive)
	{
		archive(m_canvasSize, m_imageDataTable, m_layers, m_index, m_palette);
	}


	String generateImageIndex()
	{
		return ToString(++m_index);
	}

	void draw() const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
		// テクスチャを表示

		m_backgroundTexture.draw();

		for (const auto& layer : m_layers)
		{
			layer.texture.draw();
		}
	}

	void addLayerImageWhite()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 255));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void addLayerImageTransparent()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 0));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void clearAllImage()
	{

		for (auto& image : m_imageDataTable)
		{
			image.second.fill(Color(255, 255, 255, 0));
		}

		if (m_layers.size() == 0)
		{
			return;
		}

		m_imageDataTable[m_layers[0].index].fill(Palette::White);
	}

	void updateTexture()
	{
		for (auto& layer : m_layers)
		{
			layer.texture.fill(m_imageDataTable[layer.index]);
		}
	}

	void drawLayers(size_t currentIndex) const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

		//もっとリストとしてきれいに配置したい。インタラクティブに
		for (size_t i = 0; i < m_layers.size(); ++i)
		{
			const size_t k = m_layers.size() - 1 - i;

			m_backgroundTexture.draw(Point(0, k * m_canvasSize.y));
			m_layers[i].texture.draw(Point(0, k * m_canvasSize.y));
		}


		const size_t j = m_layers.size() - 1 - currentIndex;

		Rect(m_canvasSize).setPos(0, j * m_canvasSize.y).drawFrame(2.0, Palette::Black);
	}

	Image generateIntegratedImage()
	{
		if (m_layers.size() == 0)
		{
			return Image();
		}

		Image result = m_imageDataTable[m_layers[0].index];

		for (size_t i = 1; i < m_layers.size(); ++i)
		{
			m_imageDataTable[m_layers[i].index].stamp(result, 0, 0);
		}

		return result;
	}
};

PixelArtData ImportArtDataFromJSON(const JSON& json)
{
	//バージョン管理

	//基本情報	

	//Image情報

	//Layer情報

}

JSON ExportArtDataToJSON(const PixelArtData& art)
{
	JSON json;

	json[U"test"] = U"wow";

	return json;
}

Transformer2D TranslateDrawingPos(const Vec2& pos)
{
	return Transformer2D(Mat3x2::Translate(pos));
}



void Main()
{
	// タイトル
	Window::SetTitle(U" pixrus ");

	int32 scaling = 16;

	Scene::SetBackground(Color(125, 162, 163));

	// ペンの太さ
	constexpr int32 thickness = 1;

	// ペンの色
	Color penColor = Palette::Orange;

	// 書き込み用の画像データを用意
	//Image image = DecodeImageFromBase64(U"eNoBrgBR/1JJRkamAAAAV0VCUFZQOEyaAAAALz/ADwAPMP/zP//zH3iAW2uboowOIPQ6CKcwZMogpBDPCLcAZM5GG+GyPg/yDfZLHNH/CeCjqSvFSsqCmjZ3OiP1TC/UsMeEoUZf8MJAo9YEYalr8SHc7DMM+Xbj5RSjfOtjmR0tPGChDMHDDHgQHaoQCVOL/LncvquRSf5ByjaCahlh4jBP9KXBRJq/ogJJ7yS2vOI5+o5/LvgzT50=");

	// 表示用のテクスチャ(内容を更新するので DynamicTexture)
	//DynamicTexture texture{ image };

	PixelArtData data;

	data.addLayerImageWhite();
	data.addLayerImageTransparent();
	data.addLayerImageTransparent();

	size_t currentIndex = 0;
	Point canvasPos(50, 50);
	const Point previewPos(0, 0);
	const Point layerPos(600, 0);
	Optional<Point> tCanvasMoveCursorPos;
	Optional<Point> tCanvasPos;
	Optional<String> lastAction;

	Array<Blob> histories;
	histories << GenerateHistory(data);
	const size_t maxHisoryNum = 1000;

	Font font(12);

	Point colorTextBoxPos(640, 360);
	TextEditState r;
	TextEditState g;
	TextEditState b;

	while (System::Update())
	{

		auto& image = data.m_imageDataTable[data.m_layers[currentIndex].index];

		if (KeyUp.down())
		{
			currentIndex = (currentIndex + 1 == data.m_layers.size()) ? 0 : currentIndex + 1;
		}
		if (KeyDown.down())
		{
			currentIndex = (currentIndex == 0) ? data.m_layers.size() - 1 : currentIndex - 1;
		}

		if (Mouse::Wheel() > 0.5)
		{
			scaling = Min(scaling + 2, 32);
		}
		if (Mouse::Wheel() < -0.5)
		{
			scaling = Max(scaling - 2, 1);
		}

		{
			//キャンバスの場合
			const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos), TransformCursor::Yes);

			if (MouseL.pressed())
			{
				// 書き込む線の始点は直前のフレームのマウスカーソル座標
				// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
				const Point from = (MouseL.down() ? Cursor::Pos()/* / scaling*/ : Cursor::PreviousPos()/* / scaling*/);

				// 書き込む線の終点は現在のマウスカーソル座標
				const Point to = Cursor::Pos();// / scaling;

				// image に線を書き込む
				Line{ from, to }.overwrite(image, thickness, penColor, Antialiased::No);

				data.updateTexture();
			}
			if (MouseL.up())
			{
				lastAction = U"Drawed";
			}

			if (MouseR.pressed())
			{
				// 書き込む線の終点は現在のマウスカーソル座標
				const Point to = Cursor::Pos();

				penColor = image.getPixel(to, ImageAddressMode::Clamp);
			}
			if (MouseR.up())
			{
				lastAction = U"Picked";
			}

			if (KeyF.down())
			{
				const Point to = Cursor::Pos();

				image.floodFill(to, penColor);

				data.updateTexture();

				lastAction = U"Filled";
			}
		}

		//移動
		if (MouseM.down())
		{
			tCanvasMoveCursorPos = Cursor::Pos();
			tCanvasPos = canvasPos;
		}
		if (MouseM.up())
		{
			tCanvasMoveCursorPos.reset();
			tCanvasPos.reset();

			lastAction = U"Moved";
		}
		if (tCanvasMoveCursorPos.has_value())
		{
			Cursor::RequestStyle(CursorStyle::Hand);

			const Point offset = Cursor::Pos() - tCanvasMoveCursorPos.value();

			canvasPos = tCanvasPos.value() + offset;
		}




		if (KeyR.down())
		{
			canvasPos = Point(50, 50);

			lastAction = U"Reset";
		}

		// 描いたものを消去するボタンが押されたら
		if (SimpleGUI::Button(U"Clear", Vec2{ 640, 500 }, 120))
		{
			data.clearAllImage();

			data.updateTexture();

			lastAction = U"Cleared";
		}


		//if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		//{
		//	Stopwatch stopwatch;
		//	stopwatch.start();
		//	Clipboard::SetText(EncodeImageToBase64(image));
		//	Print << stopwatch.usF();
		//}

		if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		{
			Image result = data.generateIntegratedImage();

			if (const auto filePath = Dialog::SaveImage())
			{
				result.savePNG(filePath.value());
			}

			lastAction = U"Saved";
		}

		HSV color = penColor;

		if (SimpleGUI::ColorPicker(color, Vec2{ 640, 240 }, true))
		{
			penColor = ColorF(color.toColor(), 1.0);

			r.text = ToString(penColor.r);
			g.text = ToString(penColor.g);
			b.text = ToString(penColor.b);
		}

		if (SimpleGUI::TextBox(r, colorTextBoxPos, 50, 3)
			|| SimpleGUI::TextBox(g, colorTextBoxPos + Vec2(55, 0), 50, 3)
			|| SimpleGUI::TextBox(b, colorTextBoxPos + Vec2(110, 0), 50, 3))
		{
			penColor = Color(ParseOr<int32>(r.text, 0), ParseOr<int32>(g.text, 0), ParseOr<int32>(b.text, 0));
		}


		if (lastAction.has_value())
		{
			histories << GenerateHistory<PixelArtData>(data);

			if (histories.size() > maxHisoryNum)
			{
				histories.pop_front();
			}

			//Print << histories.back().size_bytes();

			lastAction.reset();
		}

		if (KeyZ.down())
		{
			if (histories.size() >= 2)
			{
				histories.pop_back();

				RecoverHistory<PixelArtData>(data, histories.back());

				data.updateTexture();
			}
		}

		if (KeyC.down())
		{
			Clipboard::SetText(GenerateHistory<PixelArtData>(data).base64Str());
		}


		// テクスチャを表示

		{
			{
				const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos));

				data.draw();
			}

			if (scaling >= 16)
			{
				const auto t = Transformer2D(Mat3x2::Translate(canvasPos));

				DrawGrid(data.m_canvasSize, scaling);
			}
		}

		{
			const auto t = TranslateDrawingPos(layerPos);

			data.drawLayers(currentIndex);
		}

		{
			const auto t = TranslateDrawingPos(previewPos);

			data.draw();
		}

		Circle(Cursor::PosF(), 5.0).draw(penColor);


	}
}


せがわせがわ
たいとる


//達成
//アンドゥ・リドゥ history
//ドット絵としてぼやけないように const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
//拡大率を変えられるように
//レイヤーの機能は持たせるように 必須 半透明を
//グリッドの表示 8倍以降
//for (auto i : step(61))
//{
//	Rect{ 0, i * 10, 600, 1 }.draw(ColorF{ 0.4 });
//	Rect{ i * 10, 0, 1, 600 }.draw(ColorF{ 0.4 });
//}
//RGBパレットが必要
//右クリックでスポイト


//TODO
// 
//画面構成やレイアウトはJSONで変更できるように
// セーブデータはJSON+
//ドット絵のサイズは最初に選ばせるように 数値入力
//ドット絵の出力方法はいくつか選べるように(PNG、JSON、BMPなど)
//オリジナルのパレットも作れるように いくつかのプリセット スポイト
//範囲選択のコピー・カット・ペースト
//自作のポップアップウィンドウを使う
//import export new セッティング


# include <Siv3D.hpp>

String EncodeImageToBase64(const Image& image)
{
	const auto webp = image.encodeWebP(Lossless::Yes);
	const auto compressedData = Zlib::Compress(webp, Zlib::MaxCompressionLevel);
	return compressedData.base64Str();
}

Image DecodeImageFromBase64(StringView base64TextData)
{
	const auto decodedData = Base64::Decode(base64TextData);
	const auto decompressedData = Zlib::Decompress(decodedData);
	return Image(MemoryReader(decompressedData));
}

template<class Type>
Blob GenerateHistory(const Type& data)
{
	return Zlib::Compress(Serializer<MemoryWriter>{}(data)->getBlob());
}

template<class Type>
void RecoverHistory(Type& data, const Blob& blob)
{
	Deserializer<MemoryReader>{Zlib::Decompress(blob)}(data);
}

void DrawGrid(const Size& canvasSize, int32 scaling, const ColorF& color = ColorF{ 0.0, 0.1 })
{
	for (auto i : step(canvasSize.x))
	{
		Rect{ i * scaling, 0, 1, canvasSize.x * scaling }.draw(color);
	}

	for (auto i : step(canvasSize.y))
	{
		Rect{ 0, i * scaling, canvasSize.y * scaling, 1 }.draw(color);
	}
}


//半透明の表現が必要 灰色と白のチェック画像の自動生成
Image GenerateBackgroundImage(const Size& canvasSize)
{
	return Image(canvasSize, Arg::generator = [](const Point& p) {return (p.x + p.y) % 2 == 0 ? Color(255) : Color(230); });
}


Rect CalculateAABB(const Point& p1, const Point& p2)
{
	const int32 minX = std::min(p1.x, p2.x);
	const int32 maxX = std::max(p1.x, p2.x);
	const int32 minY = std::min(p1.y, p2.y);
	const int32 maxY = std::max(p1.y, p2.y);

	return Rect(minX, minY, maxX - minX, maxY - minY);
}

Image GetAABBImage(const Image& originalImage, const Point& p1, const Point& p2)
{
	const Rect aabb = CalculateAABB(p1, p2);
	return originalImage.clipped(aabb);
}




class PixelArtData
{
public:

	struct Layer
	{
		String index;
		DynamicTexture texture;

		template <class Archive>
		void SIV3D_SERIALIZE(Archive& archive)
		{
			archive(index);
		}
	};


	PixelArtData()
	{
		m_backgroundTexture = Texture(GenerateBackgroundImage(m_canvasSize));
		m_palette = Image(32, 32, Palette::White);
	}

	Texture m_backgroundTexture;

	Size m_canvasSize{ 32, 32 };
	HashTable<String, Image> m_imageDataTable;
	Array<Layer> m_layers;
	size_t m_index = 0;
	Image m_palette;

	template <class Archive>
	void SIV3D_SERIALIZE(Archive& archive)
	{
		archive(m_canvasSize, m_imageDataTable, m_layers, m_index, m_palette);
	}


	String generateImageIndex()
	{
		return ToString(++m_index);
	}

	void draw() const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
		// テクスチャを表示

		m_backgroundTexture.draw();

		for (const auto& layer : m_layers)
		{
			layer.texture.draw();
		}
	}

	void addLayerImageWhite()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 255));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void addLayerImageTransparent()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 0));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void clearAllImage()
	{

		for (auto& image : m_imageDataTable)
		{
			image.second.fill(Color(255, 255, 255, 0));
		}

		if (m_layers.size() == 0)
		{
			return;
		}

		m_imageDataTable[m_layers[0].index].fill(Palette::White);
	}

	void updateTexture()
	{
		for (auto& layer : m_layers)
		{
			layer.texture.fill(m_imageDataTable[layer.index]);
		}
	}

	void drawLayers(size_t currentIndex) const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

		//もっとリストとしてきれいに配置したい。インタラクティブに
		for (size_t i = 0; i < m_layers.size(); ++i)
		{
			const size_t k = m_layers.size() - 1 - i;

			m_backgroundTexture.draw(Point(0, k * m_canvasSize.y));
			m_layers[i].texture.draw(Point(0, k * m_canvasSize.y));
		}


		const size_t j = m_layers.size() - 1 - currentIndex;

		Rect(m_canvasSize).setPos(0, j * m_canvasSize.y).drawFrame(2.0, Palette::Black);
	}

	Image generateIntegratedImage()
	{
		if (m_layers.size() == 0)
		{
			return Image();
		}

		Image result = m_imageDataTable[m_layers[0].index];

		for (size_t i = 1; i < m_layers.size(); ++i)
		{
			m_imageDataTable[m_layers[i].index].stamp(result, 0, 0);
		}

		return result;
	}
};

PixelArtData ImportArtDataFromJSON(const JSON& json)
{
	//バージョン管理

	//基本情報	

	//Image情報

	//Layer情報

}

JSON ExportArtDataToJSON(const PixelArtData& art)
{
	JSON json;

	json[U"test"] = U"wow";

	return json;
}

Transformer2D TranslateDrawingPos(const Vec2& pos)
{
	return Transformer2D(Mat3x2::Translate(pos));
}



void Main()
{
	// タイトル
	Window::SetTitle(U" pixrus ");

	int32 scaling = 16;

	Scene::SetBackground(Color(125, 162, 163));

	// ペンの太さ
	constexpr int32 thickness = 1;

	// ペンの色
	Color penColor = Palette::Orange;

	// 書き込み用の画像データを用意
	//Image image = DecodeImageFromBase64(U"eNoBrgBR/1JJRkamAAAAV0VCUFZQOEyaAAAALz/ADwAPMP/zP//zH3iAW2uboowOIPQ6CKcwZMogpBDPCLcAZM5GG+GyPg/yDfZLHNH/CeCjqSvFSsqCmjZ3OiP1TC/UsMeEoUZf8MJAo9YEYalr8SHc7DMM+Xbj5RSjfOtjmR0tPGChDMHDDHgQHaoQCVOL/LncvquRSf5ByjaCahlh4jBP9KXBRJq/ogJJ7yS2vOI5+o5/LvgzT50=");

	// 表示用のテクスチャ(内容を更新するので DynamicTexture)
	//DynamicTexture texture{ image };

	PixelArtData data;

	data.addLayerImageWhite();
	data.addLayerImageTransparent();
	data.addLayerImageTransparent();

	size_t currentIndex = 0;
	Point canvasPos(50, 50);
	const Point previewPos(0, 0);
	const Point layerPos(600, 0);
	Optional<Point> tCanvasMoveCursorPos;
	Optional<Point> tCanvasPos;
	Optional<String> lastAction;

	Array<Blob> histories;
	histories << GenerateHistory(data);
	const size_t maxHisoryNum = 1000;

	Font font(12);

	Point colorTextBoxPos(640, 360);
	TextEditState r;
	TextEditState g;
	TextEditState b;

	Image clippedImage;
	DynamicTexture clippedImageTexture;
	Optional<Point> AABBPoint1;
	Optional<Point> AABBPoint2;

	while (System::Update())
	{

		auto& image = data.m_imageDataTable[data.m_layers[currentIndex].index];

		if (KeyUp.down())
		{
			currentIndex = (currentIndex + 1 == data.m_layers.size()) ? 0 : currentIndex + 1;
		}
		if (KeyDown.down())
		{
			currentIndex = (currentIndex == 0) ? data.m_layers.size() - 1 : currentIndex - 1;
		}

		if (Mouse::Wheel() > 0.5)
		{
			scaling = Min(scaling + 2, 32);
		}
		if (Mouse::Wheel() < -0.5)
		{
			scaling = Max(scaling - 2, 1);
		}

		{
			//キャンバスの場合
			const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos), TransformCursor::Yes);

			if (Key1.down())
			{
				AABBPoint1 = Cursor::Pos();
			}
			if (Key2.down())
			{
				AABBPoint2 = Cursor::Pos();
			}
			if (AABBPoint1.has_value() && AABBPoint2.has_value())
			{
				clippedImage = GetAABBImage(image, AABBPoint1.value(), AABBPoint2.value());
				clippedImageTexture = DynamicTexture(clippedImage);
				AABBPoint1.reset();
				AABBPoint2.reset();
			}




			if (MouseL.pressed())
			{
				// 書き込む線の始点は直前のフレームのマウスカーソル座標
				// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
				const Point from = (MouseL.down() ? Cursor::Pos()/* / scaling*/ : Cursor::PreviousPos()/* / scaling*/);

				// 書き込む線の終点は現在のマウスカーソル座標
				const Point to = Cursor::Pos();// / scaling;

				// image に線を書き込む
				Line{ from, to }.overwrite(image, thickness, penColor, Antialiased::No);

				data.updateTexture();
			}
			if (MouseL.up())
			{
				lastAction = U"Drawed";
			}

			if (MouseR.pressed())
			{
				// 書き込む線の終点は現在のマウスカーソル座標
				const Point to = Cursor::Pos();

				penColor = image.getPixel(to, ImageAddressMode::Clamp);
			}
			if (MouseR.up())
			{
				lastAction = U"Picked";
			}

			if (KeyF.down())
			{
				const Point to = Cursor::Pos();

				image.floodFill(to, penColor);

				data.updateTexture();

				lastAction = U"Filled";
			}
		}

		//移動
		if (MouseM.down())
		{
			tCanvasMoveCursorPos = Cursor::Pos();
			tCanvasPos = canvasPos;
		}
		if (MouseM.up())
		{
			tCanvasMoveCursorPos.reset();
			tCanvasPos.reset();

			lastAction = U"Moved";
		}
		if (tCanvasMoveCursorPos.has_value())
		{
			Cursor::RequestStyle(CursorStyle::Hand);

			const Point offset = Cursor::Pos() - tCanvasMoveCursorPos.value();

			canvasPos = tCanvasPos.value() + offset;
		}




		if (KeyR.down())
		{
			canvasPos = Point(50, 50);

			lastAction = U"Reset";
		}

		// 描いたものを消去するボタンが押されたら
		if (SimpleGUI::Button(U"Clear", Vec2{ 640, 500 }, 120))
		{
			data.clearAllImage();

			data.updateTexture();

			lastAction = U"Cleared";
		}


		//if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		//{
		//	Stopwatch stopwatch;
		//	stopwatch.start();
		//	Clipboard::SetText(EncodeImageToBase64(image));
		//	Print << stopwatch.usF();
		//}

		if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		{
			Image result = data.generateIntegratedImage();

			if (const auto filePath = Dialog::SaveImage())
			{
				result.savePNG(filePath.value());
			}

			lastAction = U"Saved";
		}

		HSV color = penColor;

		if (SimpleGUI::ColorPicker(color, Vec2{ 640, 240 }, true))
		{
			penColor = ColorF(color.toColor(), 1.0);

			r.text = ToString(penColor.r);
			g.text = ToString(penColor.g);
			b.text = ToString(penColor.b);
		}

		if (SimpleGUI::TextBox(r, colorTextBoxPos, 50, 3)
			|| SimpleGUI::TextBox(g, colorTextBoxPos + Vec2(55, 0), 50, 3)
			|| SimpleGUI::TextBox(b, colorTextBoxPos + Vec2(110, 0), 50, 3))
		{
			penColor = Color(ParseOr<int32>(r.text, 0), ParseOr<int32>(g.text, 0), ParseOr<int32>(b.text, 0));
		}

		clippedImageTexture.scaled(4).draw(colorTextBoxPos + Vec2(0, 100));

		if (lastAction.has_value())
		{
			histories << GenerateHistory<PixelArtData>(data);

			if (histories.size() > maxHisoryNum)
			{
				histories.pop_front();
			}

			//Print << histories.back().size_bytes();

			lastAction.reset();
		}

		if (KeyZ.down())
		{
			if (histories.size() >= 2)
			{
				histories.pop_back();

				RecoverHistory<PixelArtData>(data, histories.back());

				data.updateTexture();
			}
		}

		if (KeyC.down())
		{
			Clipboard::SetText(GenerateHistory<PixelArtData>(data).base64Str());
		}


		// テクスチャを表示

		{
			{
				const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos));

				data.draw();
			}

			if (scaling >= 16)
			{
				const auto t = Transformer2D(Mat3x2::Translate(canvasPos));

				DrawGrid(data.m_canvasSize, scaling);
			}
		}

		{
			const auto t = TranslateDrawingPos(layerPos);

			data.drawLayers(currentIndex);
		}

		{
			const auto t = TranslateDrawingPos(previewPos);

			data.draw();
		}

		Circle(Cursor::PosF(), 5.0).draw(penColor);


	}
}
せがわせがわ
# include <Siv3D.hpp>

void Main()
{
	//Twitter::OpenTweetWindow(U"yeah");

	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });

	Vec2 headlinePos{ 100, 60 };
	Vec2 inputPos{ 100, 100 };
	Vec2 presetPos{ 300, 60 };
	Vec2 selectedSizePos{ 100, 200 };
	Vec2 buttonPos{ 100, 300 };

	TextEditState widthInput;
	TextEditState heightInput;
	size_t presetIndex = 0;

	const Array<String> presets = { U"16x16", U"32x32", U"64x64", U"128x128" };
	constexpr std::array<Size, 4> presetSizes = { Size{16, 16}, Size{32, 32}, Size{64, 64}, Size{128, 128} };

	Size selectedSize{ 16, 16 };

	while (System::Update())
	{
		SimpleGUI::Headline(U"ドット絵サイズ設定", headlinePos);

		SimpleGUI::Headline(U"幅:", inputPos);
		SimpleGUI::TextBox(widthInput, inputPos + Vec2{ 40, 0 }, 100, 4);

		SimpleGUI::Headline(U"高さ:", inputPos + Vec2{ 0, 40 });
		SimpleGUI::TextBox(heightInput, inputPos + Vec2{ 40, 40 }, 100, 4);

		SimpleGUI::Headline(U"プリセット", presetPos);

		if (SimpleGUI::RadioButtons(presetIndex, presets, presetPos + Vec2{ 0, 40 }))
		{
			selectedSize = presetSizes[presetIndex];
			widthInput.text = Format(selectedSize.x);
			heightInput.text = Format(selectedSize.y);
		}

		SimpleGUI::Headline(U"選択されたサイズ", selectedSizePos);
		SimpleGUI::Headline(Format(U"幅: ", selectedSize.x, U", 高さ: ", selectedSize.y), selectedSizePos + Vec2{ 0, 40 });

		if (SimpleGUI::Button(U"新規作成", buttonPos, 200, true))
		{
			// 新規作成ボタンがクリックされたときの処理をここに記述
		}

		if (SimpleGUI::Button(U"キャンセル", buttonPos + Vec2{ 0, 50 }, 200, true))
		{
			// キャンセルボタンがクリックされたときの処理をここに記述
		}
	}
}
せがわせがわ
タイトル


//達成
//アンドゥ・リドゥ history
//ドット絵としてぼやけないように const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
//拡大率を変えられるように
//レイヤーの機能は持たせるように 必須 半透明を
//グリッドの表示 8倍以降
//for (auto i : step(61))
//{
//	Rect{ 0, i * 10, 600, 1 }.draw(ColorF{ 0.4 });
//	Rect{ i * 10, 0, 1, 600 }.draw(ColorF{ 0.4 });
//}
//RGBパレットが必要
//右クリックでスポイト


//TODO
// 
//画面構成やレイアウトはJSONで変更できるように
// セーブデータはJSON+
//ドット絵のサイズは最初に選ばせるように 数値入力
//ドット絵の出力方法はいくつか選べるように(PNG、JSON、BMPなど)
//オリジナルのパレットも作れるように いくつかのプリセット スポイト
//範囲選択のコピー・カット・ペースト
//自作のポップアップウィンドウを使う
//import export new セッティング


# include <Siv3D.hpp>

String EncodeImageToBase64(const Image& image)
{
	const auto webp = image.encodeWebP(Lossless::Yes);
	const auto compressedData = Zlib::Compress(webp, Zlib::MaxCompressionLevel);
	return compressedData.base64Str();
}

Image DecodeImageFromBase64(StringView base64TextData)
{
	const auto decodedData = Base64::Decode(base64TextData);
	const auto decompressedData = Zlib::Decompress(decodedData);
	return Image(MemoryReader(decompressedData));
}

template<class Type>
Blob GenerateHistory(const Type& data)
{
	return Zlib::Compress(Serializer<MemoryWriter>{}(data)->getBlob());
}

template<class Type>
void RecoverHistory(Type& data, const Blob& blob)
{
	Deserializer<MemoryReader>{Zlib::Decompress(blob)}(data);
}

void DrawGrid(const Size& canvasSize, int32 scaling, const ColorF& color = ColorF{ 0.0, 0.1 })
{
	for (auto i : step(canvasSize.x))
	{
		Rect{ i * scaling, 0, 1, canvasSize.x * scaling }.draw(color);
	}

	for (auto i : step(canvasSize.y))
	{
		Rect{ 0, i * scaling, canvasSize.y * scaling, 1 }.draw(color);
	}
}


//半透明の表現が必要 灰色と白のチェック画像の自動生成
Image GenerateBackgroundImage(const Size& canvasSize)
{
	return Image(canvasSize, Arg::generator = [](const Point& p) {return (p.x + p.y) % 2 == 0 ? Color(255) : Color(230); });
}


Rect CalculateAABB(const Point& p1, const Point& p2)
{
	const int32 minX = std::min(p1.x, p2.x);
	const int32 maxX = std::max(p1.x, p2.x);
	const int32 minY = std::min(p1.y, p2.y);
	const int32 maxY = std::max(p1.y, p2.y);

	return Rect(minX, minY, maxX - minX, maxY - minY);
}

Image GetAABBImage(const Image& originalImage, const Point& p1, const Point& p2)
{
	const Rect aabb = CalculateAABB(p1, p2);
	return originalImage.clipped(aabb);
}


class PixelArtData
{
public:

	struct Layer
	{
		String index;
		DynamicTexture texture;

		template <class Archive>
		void SIV3D_SERIALIZE(Archive& archive)
		{
			archive(index);
		}
	};


	PixelArtData(const Size& canvasSize = Size(32, 32))
		: m_canvasSize(canvasSize)
	{
		m_backgroundTexture = Texture(GenerateBackgroundImage(m_canvasSize));


		m_palette = Image(32, 32, Palette::White);
	}

	Texture m_backgroundTexture;

	Size m_canvasSize;
	HashTable<String, Image> m_imageDataTable;
	Array<Layer> m_layers;
	size_t m_index = 0;
	Image m_palette;

	template <class Archive>
	void SIV3D_SERIALIZE(Archive& archive)
	{
		archive(m_canvasSize, m_imageDataTable, m_layers, m_index, m_palette);
	}


	String generateImageIndex()
	{
		return ToString(++m_index);
	}

	void draw() const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
		// テクスチャを表示

		m_backgroundTexture.draw();

		for (const auto& layer : m_layers)
		{
			layer.texture.draw();
		}
	}

	void addLayerImage(const Image& image)
	{
		if (m_canvasSize != image.size())
		{
			return;
		}

		const String index = generateImageIndex();

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void addLayerImageWhite()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 255));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void addLayerImageTransparent()
	{
		const String index = generateImageIndex();

		const Image image = Image(m_canvasSize, Color(255, 255, 255, 0));

		m_imageDataTable[index] = image;

		Layer layer;
		layer.index = index;
		layer.texture = DynamicTexture(image);

		m_layers << layer;
	}

	void clearAllImage()
	{

		for (auto& image : m_imageDataTable)
		{
			image.second.fill(Color(255, 255, 255, 0));
		}

		if (m_layers.size() == 0)
		{
			return;
		}

		m_imageDataTable[m_layers[0].index].fill(Palette::White);
	}

	void updateTexture()
	{
		for (auto& layer : m_layers)
		{
			layer.texture.fill(m_imageDataTable[layer.index]);
		}
	}

	void drawLayers(size_t currentIndex) const
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

		//もっとリストとしてきれいに配置したい。インタラクティブに
		for (size_t i = 0; i < m_layers.size(); ++i)
		{
			const size_t k = m_layers.size() - 1 - i;

			m_backgroundTexture.draw(Point(0, k * m_canvasSize.y));
			m_layers[i].texture.draw(Point(0, k * m_canvasSize.y));
		}


		const size_t j = m_layers.size() - 1 - currentIndex;

		Rect(m_canvasSize).setPos(0, j * m_canvasSize.y).drawFrame(2.0, Palette::Black);
	}

	Image generateIntegratedImage()
	{
		if (m_layers.size() == 0)
		{
			return Image();
		}

		Image result = m_imageDataTable[m_layers[0].index];

		for (size_t i = 1; i < m_layers.size(); ++i)
		{
			m_imageDataTable[m_layers[i].index].stamp(result, 0, 0);
		}

		return result;
	}
};

class PopUpWindowBase
{
public:

	PopUpWindowBase() = default;
	virtual ~PopUpWindowBase() = default;

	virtual void update()
	{

	}

	virtual void draw() const
	{

	}

	bool m_isDestoyed = false;

private:

};



PixelArtData ImportArtDataFromJSON(const JSON& json)
{
	//バージョン管理

	//基本情報	

	//Image情報

	//Layer情報

}

JSON ExportArtDataToJSON(const PixelArtData& art)
{
	JSON json;

	json[U"test"] = U"wow";

	return json;
}

Transformer2D TranslateDrawingPos(const Vec2& pos)
{
	return Transformer2D(Mat3x2::Translate(pos));
}



class CommonData
{
public:
	// シングルトンインスタンスへのアクセスメソッド
	static CommonData& getInstance()
	{
		static CommonData instance;
		return instance;
	}

	// コピー・ムーブ操作の禁止
	CommonData(const CommonData&) = delete;
	CommonData& operator=(const CommonData&) = delete;
	CommonData(CommonData&&) = delete;
	CommonData& operator=(CommonData&&) = delete;

	// データの設定・取得メソッドなど、必要に応じて追加してください

	// ペンの色

	PixelArtData data;
	Array<Blob> histories;
	const size_t maxHisoryNum = 1000;

	Font font{ 12 };

	std::unique_ptr<PopUpWindowBase> popup;

private:
	// コンストラクタとデストラクタをprivateにする
	CommonData()
	{

	}


	~CommonData() = default;
};




class CreateNewImageWindow : public PopUpWindowBase
{
public:

	CreateNewImageWindow()
		: PopUpWindowBase()
	{
		selectedSize = presetSizes[presetIndex];
		widthInput.text = Format(selectedSize.x);
		heightInput.text = Format(selectedSize.y);
	}

	void update() override
	{
		Scene::Rect().stretched(-20, -20).rounded(30.0).draw(Palette::White);

		SimpleGUI::Headline(U"ドット絵サイズ設定 : ドラッグアンドドロップで読み込めるよ", headlinePos);

		SimpleGUI::Headline(U"幅:", inputPos);
		SimpleGUI::TextBox(widthInput, inputPos + Vec2{ 70, 0 }, 100, 4);

		SimpleGUI::Headline(U"高さ:", inputPos + Vec2{ 0, 40 });
		SimpleGUI::TextBox(heightInput, inputPos + Vec2{ 70, 40 }, 100, 4);

		SimpleGUI::Headline(U"プリセット", presetPos);

		if (SimpleGUI::RadioButtons(presetIndex, presets, presetPos + Vec2{ 0, 40 }))
		{
			selectedSize = presetSizes[presetIndex];
			widthInput.text = Format(selectedSize.x);
			heightInput.text = Format(selectedSize.y);
		}

		selectedSize.x = ParseOr<int32>(widthInput.text, 1);
		selectedSize.y = ParseOr<int32>(heightInput.text, 1);

		SimpleGUI::Headline(U"選択されたサイズ", selectedSizePos);
		SimpleGUI::Headline(Format(U"幅: ", selectedSize.x, U", 高さ: ", selectedSize.y), selectedSizePos + Vec2{ 0, 40 });

		if (SimpleGUI::Button(U"新規作成", buttonPos, 200, true))
		{
			// 新規作成ボタンがクリックされたときの処理をここに記述
			auto newPixelArtData = PixelArtData(selectedSize);
			CommonData::getInstance().data = newPixelArtData;


			auto& commonData = CommonData::getInstance();
			auto& data = commonData.data;
			auto& histories = commonData.histories;

			data.addLayerImageWhite();
			data.addLayerImageTransparent();
			data.addLayerImageTransparent();
			histories.clear();
			histories << GenerateHistory(data);

			m_isDestoyed = true;
		}

		if (SimpleGUI::Button(U"キャンセル", buttonPos + Vec2{ 0, 50 }, 200, true))
		{


			// キャンセルボタンがクリックされたときの処理をここに記述
			m_isDestoyed = true;
		}


		// ファイルがドロップされた
		if (DragDrop::HasNewFilePaths())
		{
			// ファイルを画像として読み込めた
			if (const Image image{ DragDrop::GetDroppedFilePaths().front().path })
			{
				selectedSize = image.size();
				widthInput.text = Format(selectedSize.x);
				heightInput.text = Format(selectedSize.y);

				// 画面のサイズに合うように画像を拡大縮小
				texture = Texture{ image };

				auto newPixelArtData = PixelArtData(selectedSize);
				CommonData::getInstance().data = newPixelArtData;

				auto& commonData = CommonData::getInstance();
				auto& data = commonData.data;
				auto& histories = commonData.histories;

				data.addLayerImage(image);
				data.addLayerImageTransparent();
				data.addLayerImageTransparent();
				histories.clear();
				histories << GenerateHistory(data);

				m_isDestoyed = true;
			}
		}
	}

	void draw() const override
	{
		if (texture)
		{
			texture.draw(Arg::topLeft = Point(400, 100));
		}
	}

private:
	Vec2 headlinePos{ 100, 60 };
	Vec2 inputPos{ 100, 100 };
	Vec2 presetPos{ 300, 100 };
	Vec2 selectedSizePos{ 100, 200 };
	Vec2 buttonPos{ 100, 450 };

	TextEditState widthInput;
	TextEditState heightInput;
	size_t presetIndex = 0;

	const Array<String> presets = { U"16x16", U"32x32", U"64x64", U"128x128" };
	std::array<Size, 4> presetSizes = { Size{16, 16}, Size{32, 32}, Size{64, 64}, Size{128, 128} };

	Size selectedSize{ 16, 16 };
	Texture texture;

};




class MainScene
{
public:

	MainScene()
	{
		// タイトル
		Window::SetTitle(U" pixrus ");

		Scene::SetBackground(Color(125, 162, 163));
		Scene::SetLetterbox(Scene::GetBackground());
		Scene::SetResizeMode(ResizeMode::Keep);
		Scene::SetTextureFilter(TextureFilter::Nearest);
		Window::SetStyle(WindowStyle::Sizable);


		init();


		auto& commonData = CommonData::getInstance();
		auto& data = commonData.data;		

		operationFunctionTable[U"pen"] = [&]()
		{
			auto& image = data.m_imageDataTable[data.m_layers[currentLayerIndex].index];

			if (Rect(image.size()).mouseOver() && MouseL.pressed())
			{
				drawingPos = Cursor::Pos();
			}
			if (drawingPos.has_value())
			{
				// 書き込む線の始点は直前のフレームのマウスカーソル座標
				// (初回はタッチ操作時の座標のジャンプを防ぐため、現在のマウスカーソル座標にする)
				const Point from = (MouseL.down() ? Cursor::Pos() : Cursor::PreviousPos());

				// 書き込む線の終点は現在のマウスカーソル座標
				const Point to = Cursor::Pos();// / scaling;

				// image に線を書き込む
				Line{ from, to }.overwrite(image, thickness, penColor, Antialiased::No);

				data.updateTexture();

				if (MouseL.up())
				{
					lastAction = U"Drawed";
					drawingPos.reset();
				}
			}

		};

		operationFunctionTable[U"fill"] = [&]()
		{
			auto& image = data.m_imageDataTable[data.m_layers[currentLayerIndex].index];

			if (Rect(image.size()).mouseOver() && MouseL.down())
			{
				const Point to = Cursor::Pos();

				image.floodFill(to, penColor);

				data.updateTexture();

				lastAction = U"Filled";
			}
		};

	}

	void init()
	{
		auto& commonData = CommonData::getInstance();
		auto& data = commonData.data;
		auto& histories = commonData.histories;

		data.addLayerImageWhite();
		data.addLayerImageTransparent();
		data.addLayerImageTransparent();
		histories << GenerateHistory(data);
	}

	void update()
	{
		auto& commonData = CommonData::getInstance();
		auto& data = commonData.data;
		auto& image = data.m_imageDataTable[data.m_layers[currentLayerIndex].index];
		auto& histories = commonData.histories;
		auto& maxHisoryNum = commonData.maxHisoryNum;

		if (KeyUp.down())
		{
			currentLayerIndex = (currentLayerIndex + 1 == data.m_layers.size()) ? 0 : currentLayerIndex + 1;
		}
		if (KeyDown.down())
		{
			currentLayerIndex = (currentLayerIndex == 0) ? data.m_layers.size() - 1 : currentLayerIndex - 1;
		}

		if (Mouse::Wheel() > 0.5)
		{
			scaling = Min(scaling + 2, 32);
		}
		if (Mouse::Wheel() < -0.5)
		{
			scaling = Max(scaling - 2, 1);
		}


		if (operationMode.has_value())
		{
			commonData.font(operationMode.value()).draw(Arg::topLeft = Point(80, 10));

			//キャンバスの場合
			const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos), TransformCursor::Yes);

			operationFunctionTable[operationMode.value()]();
		}



		{
			//キャンバスの場合
			const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos), TransformCursor::Yes);

			if (Key1.down())
			{
				AABBPoint1 = Cursor::Pos();
			}
			if (Key2.down())
			{
				AABBPoint2 = Cursor::Pos();
			}
			if (AABBPoint1.has_value() && AABBPoint2.has_value())
			{
				clippedImage = GetAABBImage(image, AABBPoint1.value(), AABBPoint2.value());
				clippedImageTexture = DynamicTexture(clippedImage);
				AABBPoint1.reset();
				AABBPoint2.reset();
			}
		}



		{
			Circle(Vec2(20.0, 50.0), 25.0).draw(Palette::White);
			undoTexture.drawAt(20, 50, ColorF{ 0.25 });

			if (Circle(Vec2(20.0, 50.0), 25).leftClicked())
			{
				undoFunction();
			}

			Circle(Vec2(20.0, 100.0), 25.0).draw(Palette::White);
			penTexture.drawAt(20, 100, ColorF{ 0.25 });

			if (Circle(Vec2(20.0, 100.0), 25).leftClicked())
			{
				operationMode = U"pen";
			}

			Circle(Vec2(20.0, 150.0), 25).draw(Palette::White);
			fillTexture.drawAt(20, 150, ColorF{ 0.25 });

			if (Circle(Vec2(20.0, 150.0), 25).leftClicked())
			{
				operationMode = U"fill";
			}
		}


		//移動
		if (MouseM.down())
		{
			tCanvasMoveCursorPos = Cursor::Pos();
			tCanvasPos = canvasPos;
		}
		if (MouseM.up())
		{
			tCanvasMoveCursorPos.reset();
			tCanvasPos.reset();

			lastAction = U"Moved";
		}
		if (tCanvasMoveCursorPos.has_value())
		{
			Cursor::RequestStyle(CursorStyle::Hand);

			const Point offset = Cursor::Pos() - tCanvasMoveCursorPos.value();

			canvasPos = tCanvasPos.value() + offset;
		}

		if (KeyR.down())
		{
			canvasPos = Point(50, 50);

			lastAction = U"Reset";
		}

		// 描いたものを消去するボタンが押されたら
		if (SimpleGUI::Button(U"Clear", Vec2{ 640, 500 }, 120))
		{
			data.clearAllImage();

			data.updateTexture();

			lastAction = U"Cleared";
		}

		if (SimpleGUI::Button(U"Save", Vec2{ 640, 140 }, 120))
		{
			Image result = data.generateIntegratedImage();

			if (const auto filePath = Dialog::SaveImage())
			{
				result.savePNG(filePath.value());
			}

			lastAction = U"Saved";
		}

		HSV color = penColor;

		if (SimpleGUI::ColorPicker(color, Vec2{ 640, 240 }, true))
		{
			penColor = ColorF(color.toColor(), 1.0);

			r.text = ToString(penColor.r);
			g.text = ToString(penColor.g);
			b.text = ToString(penColor.b);
		}

		if (SimpleGUI::TextBox(r, colorTextBoxPos, 50, 3)
			|| SimpleGUI::TextBox(g, colorTextBoxPos + Vec2(55, 0), 50, 3)
			|| SimpleGUI::TextBox(b, colorTextBoxPos + Vec2(110, 0), 50, 3))
		{
			penColor = Color(ParseOr<int32>(r.text, 0), ParseOr<int32>(g.text, 0), ParseOr<int32>(b.text, 0));
		}

		clippedImageTexture.scaled(4).draw(colorTextBoxPos + Vec2(0, 100));

		if (lastAction.has_value())
		{
			histories << GenerateHistory<PixelArtData>(data);

			if (histories.size() > maxHisoryNum)
			{
				histories.pop_front();
			}

			//Print << histories.back().size_bytes();

			lastAction.reset();
		}

		if (KeyZ.down())
		{
			undoFunction();
		}

		if (KeyC.down())
		{
			Clipboard::SetText(GenerateHistory<PixelArtData>(data).base64Str());
		}

		if (KeyN.down())
		{
			commonData.popup = std::make_unique<CreateNewImageWindow>();
		}

		// テクスチャを表示

	}

	void draw() const
	{
		auto& commonData = CommonData::getInstance();
		auto& data = commonData.data;

		{
			{
				const auto t = Transformer2D(Mat3x2::Scale(scaling) * Mat3x2::Translate(canvasPos));

				data.draw();
			}

			if (scaling >= 16)
			{
				const auto t = Transformer2D(Mat3x2::Translate(canvasPos));

				DrawGrid(data.m_canvasSize, scaling);
			}
		}

		{
			const auto t = TranslateDrawingPos(layerPos);

			data.drawLayers(currentLayerIndex);
		}

		{
			const auto t = TranslateDrawingPos(previewPos);

			data.draw();
		}

		Circle(Cursor::PosF(), 5.0).draw(penColor);
	}

	void undoFunction()
	{
		auto& commonData = CommonData::getInstance();
		auto& data = commonData.data;
		auto& histories = commonData.histories;

		if (histories.size() >= 2)
		{
			histories.pop_back();

			RecoverHistory<PixelArtData>(data, histories.back());

			data.updateTexture();
		}
	}


private:

	int32 scaling = 16;

	size_t currentLayerIndex = 0;
	Point canvasPos{ 50, 50 };

	const Point previewPos{ 0, 0 };
	const Point layerPos{ 600, 0 };

	Optional<Point> tCanvasMoveCursorPos;
	Optional<Point> tCanvasPos;
	Optional<String> lastAction;

	Point colorTextBoxPos{ 640, 360 };
	TextEditState r;
	TextEditState g;
	TextEditState b;

	Image clippedImage;
	DynamicTexture clippedImageTexture;
	Optional<Point> AABBPoint1;
	Optional<Point> AABBPoint2;

	Color penColor = Palette::Orange;
	int32 thickness = 1;

	bool drawing = false;

	// 歯車 (f013) のアイコン
	const Texture fillTexture{ 0xF0266_icon, 40 };
	const Texture penTexture{ 0xF03EB_icon, 40 };
	const Texture undoTexture{ 0xF054C_icon, 40 };

	Optional<String> operationMode = U"pen";
	HashTable<String, std::function<void()>> operationFunctionTable;
	Optional<Point> drawingPos;
};


void Main()
{
	MainScene scene;

	while (System::Update())
	{
		auto& popup = CommonData::getInstance().popup;

		if (!popup)
			scene.update();

		scene.draw();

		if (popup)
		{
			Scene::Rect().draw(ColorF(0.0, 0.5));
			popup->update();
			popup->draw();

			if (popup->m_isDestoyed)
			{
				popup.reset();
			}
		}
	}
}