1 | #include "editor.h" |
2 | #include "editor_actions.h" |
3 | |
4 | #include <game/editor/mapitems/image.h> |
5 | |
6 | #include <array> |
7 | |
8 | bool 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 | |
20 | static 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 | |
44 | static 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 | |
65 | static 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 | |
83 | constexpr int NumTilesRow = 16; |
84 | constexpr int NumTilesColumn = 16; |
85 | constexpr int NumTiles = NumTilesRow * NumTilesColumn; |
86 | constexpr int TileSize = 64; |
87 | |
88 | static 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 | |
96 | static 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 | |
109 | static 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 | |
118 | static 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 | |
138 | static 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 | |
148 | static 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 | |
164 | static 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 | |
177 | static 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 | |
186 | void 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 | |
218 | void 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 | |
237 | bool 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 | |