1#include "editor.h"
2#include "editor_actions.h"
3
4#include <game/editor/mapitems/image.h>
5
6#include <array>
7
8bool operator<(const ColorRGBA &Left, const ColorRGBA &Right)
9{
10 if(Left.r != Right.r)
11 return Left.r < Right.r;
12 else if(Left.g != Right.g)
13 return Left.g < Right.g;
14 else if(Left.b != Right.b)
15 return Left.b < Right.b;
16 else
17 return Left.a < Right.a;
18}
19
20static ColorRGBA GetPixelColor(const CImageInfo &Image, size_t x, size_t y)
21{
22 uint8_t *pData = Image.m_pData;
23 const size_t PixelSize = Image.PixelSize();
24 const size_t PixelStartIndex = x * PixelSize + (Image.m_Width * PixelSize * y);
25
26 ColorRGBA Color = {255, 255, 255, 255};
27 if(PixelSize == 1)
28 {
29 Color.a = pData[PixelStartIndex];
30 }
31 else
32 {
33 Color.r = pData[PixelStartIndex + 0];
34 Color.g = pData[PixelStartIndex + 1];
35 Color.b = pData[PixelStartIndex + 2];
36
37 if(PixelSize == 4)
38 Color.a = pData[PixelStartIndex + 3];
39 }
40
41 return Color;
42}
43
44static void SetPixelColor(CImageInfo *pImage, size_t x, size_t y, ColorRGBA Color)
45{
46 uint8_t *pData = pImage->m_pData;
47 const size_t PixelSize = pImage->PixelSize();
48 const size_t PixelStartIndex = x * PixelSize + (pImage->m_Width * PixelSize * y);
49
50 if(PixelSize == 1)
51 {
52 pData[PixelStartIndex] = Color.a;
53 }
54 else
55 {
56 pData[PixelStartIndex + 0] = Color.r;
57 pData[PixelStartIndex + 1] = Color.g;
58 pData[PixelStartIndex + 2] = Color.b;
59
60 if(PixelSize == 4)
61 pData[PixelStartIndex + 3] = Color.a;
62 }
63}
64
65static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image)
66{
67 std::set<ColorRGBA> ColorSet;
68 std::vector<ColorRGBA> vUniqueColors;
69 for(size_t x = 0; x < Image.m_Width; x++)
70 {
71 for(size_t y = 0; y < Image.m_Height; y++)
72 {
73 ColorRGBA Color = GetPixelColor(Image, x, y);
74 if(Color.a > 0 && ColorSet.insert(x: Color).second)
75 vUniqueColors.push_back(x: Color);
76 }
77 }
78 std::sort(first: vUniqueColors.begin(), last: vUniqueColors.end());
79
80 return vUniqueColors;
81}
82
83constexpr int NumTilesRow = 16;
84constexpr int NumTilesColumn = 16;
85constexpr int NumTiles = NumTilesRow * NumTilesColumn;
86constexpr int TileSize = 64;
87
88static int GetColorIndex(const std::array<ColorRGBA, NumTiles> &ColorGroup, ColorRGBA Color)
89{
90 std::array<ColorRGBA, NumTiles>::const_iterator Iterator = std::find(first: ColorGroup.begin(), last: ColorGroup.end(), val: Color);
91 if(Iterator == ColorGroup.end())
92 return 0;
93 return Iterator - ColorGroup.begin();
94}
95
96static std::vector<std::array<ColorRGBA, NumTiles>> GroupColors(const std::vector<ColorRGBA> &vColors)
97{
98 std::vector<std::array<ColorRGBA, NumTiles>> vaColorGroups;
99
100 for(size_t i = 0; i < vColors.size(); i += NumTiles - 1)
101 {
102 auto &Group = vaColorGroups.emplace_back();
103 std::copy_n(first: vColors.begin() + i, n: std::min<size_t>(a: NumTiles - 1, b: vColors.size() - i), result: Group.begin() + 1);
104 }
105
106 return vaColorGroups;
107}
108
109static void SetColorTile(CImageInfo *pImage, int x, int y, ColorRGBA Color)
110{
111 for(int i = 0; i < TileSize; i++)
112 {
113 for(int j = 0; j < TileSize; j++)
114 SetPixelColor(pImage, x: x * TileSize + i, y: y * TileSize + j, Color);
115 }
116}
117
118static CImageInfo ColorGroupToImage(const std::array<ColorRGBA, NumTiles> &aColorGroup)
119{
120 CImageInfo Image;
121 Image.m_Width = NumTilesRow * TileSize;
122 Image.m_Height = NumTilesColumn * TileSize;
123 Image.m_Format = CImageInfo::FORMAT_RGBA;
124 Image.m_pData = static_cast<uint8_t *>(malloc(size: Image.DataSize()));
125
126 for(int y = 0; y < NumTilesColumn; y++)
127 {
128 for(int x = 0; x < NumTilesRow; x++)
129 {
130 int ColorIndex = x + NumTilesRow * y;
131 SetColorTile(pImage: &Image, x, y, Color: aColorGroup[ColorIndex]);
132 }
133 }
134
135 return Image;
136}
137
138static std::vector<CImageInfo> ColorGroupsToImages(const std::vector<std::array<ColorRGBA, NumTiles>> &vaColorGroups)
139{
140 std::vector<CImageInfo> vImages;
141 vImages.reserve(n: vaColorGroups.size());
142 for(const auto &ColorGroup : vaColorGroups)
143 vImages.push_back(x: ColorGroupToImage(aColorGroup: ColorGroup));
144
145 return vImages;
146}
147
148static std::shared_ptr<CEditorImage> ImageInfoToEditorImage(CEditor *pEditor, const CImageInfo &Image, const char *pName)
149{
150 std::shared_ptr<CEditorImage> pEditorImage = std::make_shared<CEditorImage>(args&: pEditor);
151 pEditorImage->m_Width = Image.m_Width;
152 pEditorImage->m_Height = Image.m_Height;
153 pEditorImage->m_Format = Image.m_Format;
154 pEditorImage->m_pData = Image.m_pData;
155
156 int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
157 pEditorImage->m_Texture = pEditor->Graphics()->LoadTextureRaw(Image, Flags: TextureLoadFlag, pTexName: pName);
158 pEditorImage->m_External = 0;
159 str_copy(dst&: pEditorImage->m_aName, src: pName);
160
161 return pEditorImage;
162}
163
164static std::shared_ptr<CLayerTiles> AddLayerWithImage(CEditor *pEditor, const std::shared_ptr<CLayerGroup> &pGroup, int Width, int Height, const CImageInfo &Image, const char *pName)
165{
166 std::shared_ptr<CEditorImage> pEditorImage = ImageInfoToEditorImage(pEditor, Image, pName);
167 pEditor->m_Map.m_vpImages.push_back(x: pEditorImage);
168
169 std::shared_ptr<CLayerTiles> pLayer = std::make_shared<CLayerTiles>(args&: pEditor, args&: Width, args&: Height);
170 str_copy(dst&: pLayer->m_aName, src: pName);
171 pLayer->m_Image = pEditor->m_Map.m_vpImages.size() - 1;
172 pGroup->AddLayer(pLayer);
173
174 return pLayer;
175}
176
177static void SetTilelayerIndices(const std::shared_ptr<CLayerTiles> &pLayer, const std::array<ColorRGBA, NumTiles> &aColorGroup, const CImageInfo &Image)
178{
179 for(int x = 0; x < pLayer->m_Width; x++)
180 {
181 for(int y = 0; y < pLayer->m_Height; y++)
182 pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(ColorGroup: aColorGroup, Color: GetPixelColor(Image, x, y));
183 }
184}
185
186void CEditor::AddTileart(bool IgnoreHistory)
187{
188 char aTileArtFileName[IO_MAX_PATH_LENGTH];
189 IStorage::StripPathAndExtension(pFilename: m_aTileartFilename, pBuffer: aTileArtFileName, BufferSize: sizeof(aTileArtFileName));
190
191 std::shared_ptr<CLayerGroup> pGroup = m_Map.NewGroup();
192 str_copy(dst&: pGroup->m_aName, src: aTileArtFileName);
193
194 int ImageCount = m_Map.m_vpImages.size();
195
196 auto vUniqueColors = GetUniqueColors(Image: m_TileartImageInfo);
197 auto vaColorGroups = GroupColors(vColors: vUniqueColors);
198 auto vColorImages = ColorGroupsToImages(vaColorGroups);
199 char aImageName[IO_MAX_PATH_LENGTH];
200 for(size_t i = 0; i < vColorImages.size(); i++)
201 {
202 str_format(buffer: aImageName, buffer_size: sizeof(aImageName), format: "%s %" PRIzu, aTileArtFileName, i + 1);
203 std::shared_ptr<CLayerTiles> pLayer = AddLayerWithImage(pEditor: this, pGroup, Width: m_TileartImageInfo.m_Width, Height: m_TileartImageInfo.m_Height, Image: vColorImages[i], pName: aImageName);
204 SetTilelayerIndices(pLayer, aColorGroup: vaColorGroups[i], Image: m_TileartImageInfo);
205 }
206 auto IndexMap = SortImages();
207
208 if(!IgnoreHistory)
209 {
210 m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionTileArt>(args: this, args&: ImageCount, args&: m_aTileartFilename, args&: IndexMap));
211 }
212
213 m_TileartImageInfo.Free();
214 m_Map.OnModify();
215 m_Dialog = DIALOG_NONE;
216}
217
218void CEditor::TileartCheckColors()
219{
220 auto vUniqueColors = GetUniqueColors(Image: m_TileartImageInfo);
221 int NumColorGroups = std::ceil(x: vUniqueColors.size() / 255.0f);
222 if(m_Map.m_vpImages.size() + NumColorGroups >= 64)
223 {
224 m_PopupEventType = CEditor::POPEVENT_PIXELART_TOO_MANY_COLORS;
225 m_PopupEventActivated = true;
226 m_TileartImageInfo.Free();
227 }
228 else if(NumColorGroups > 1)
229 {
230 m_PopupEventType = CEditor::POPEVENT_PIXELART_MANY_COLORS;
231 m_PopupEventActivated = true;
232 }
233 else
234 AddTileart();
235}
236
237bool CEditor::CallbackAddTileart(const char *pFilepath, int StorageType, void *pUser)
238{
239 CEditor *pEditor = (CEditor *)pUser;
240
241 if(!pEditor->Graphics()->LoadPng(Image&: pEditor->m_TileartImageInfo, pFilename: pFilepath, StorageType))
242 {
243 pEditor->ShowFileDialogError(pFormat: "Failed to load image from file '%s'.", pFilepath);
244 return false;
245 }
246
247 str_copy(dst&: pEditor->m_aTileartFilename, src: pFilepath);
248 if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000)
249 {
250 pEditor->m_PopupEventType = CEditor::POPEVENT_PIXELART_BIG_IMAGE;
251 pEditor->m_PopupEventActivated = true;
252 return false;
253 }
254 else
255 {
256 pEditor->TileartCheckColors();
257 return false;
258 }
259}
260