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