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