1#include <base/logger.h>
2#include <base/system.h>
3
4#include <engine/gfx/image_manipulation.h>
5#include <engine/shared/datafile.h>
6#include <engine/storage.h>
7
8#include <game/mapitems.h>
9
10#include <algorithm>
11#include <cstdint>
12#include <vector>
13
14static void ClearTransparentPixels(uint8_t *pImg, int Width, int Height)
15{
16 for(int y = 0; y < Height; ++y)
17 {
18 for(int x = 0; x < Width; ++x)
19 {
20 int Index = y * Width * 4 + x * 4;
21 if(pImg[Index + 3] == 0)
22 {
23 pImg[Index + 0] = 0;
24 pImg[Index + 1] = 0;
25 pImg[Index + 2] = 0;
26 }
27 }
28 }
29}
30
31static void CopyOpaquePixels(uint8_t *pDestImg, uint8_t *pSrcImg, int Width, int Height)
32{
33 for(int y = 0; y < Height; ++y)
34 {
35 for(int x = 0; x < Width; ++x)
36 {
37 int Index = y * Width * 4 + x * 4;
38 if(pSrcImg[Index + 3] > 0)
39 mem_copy(dest: &pDestImg[Index], source: &pSrcImg[Index], size: sizeof(uint8_t) * 4);
40 else
41 mem_zero(block: &pDestImg[Index], size: sizeof(uint8_t) * 4);
42 }
43 }
44}
45
46static void ClearPixelsTile(uint8_t *pImg, int Width, int Height, int TileIndex)
47{
48 int WTile = Width / 16;
49 int HTile = Height / 16;
50 int StartX = (TileIndex % 16) * WTile;
51 int StartY = (TileIndex / 16) * HTile;
52
53 for(int y = StartY; y < StartY + HTile; ++y)
54 {
55 for(int x = StartX; x < StartX + WTile; ++x)
56 {
57 int Index = y * Width * 4 + x * 4;
58 pImg[Index + 0] = 0;
59 pImg[Index + 1] = 0;
60 pImg[Index + 2] = 0;
61 pImg[Index + 3] = 0;
62 }
63 }
64}
65
66static void GetImageSHA256(uint8_t *pImgBuff, int ImgSize, int Width, int Height, char *pSHA256Str, size_t SHA256StrSize)
67{
68 uint8_t *pNewImgBuff = (uint8_t *)malloc(size: ImgSize);
69
70 // Clear fully transparent pixels, so the SHA is easier to identify with the original image
71 CopyOpaquePixels(pDestImg: pNewImgBuff, pSrcImg: pImgBuff, Width, Height);
72 SHA256_DIGEST SHAStr = sha256(message: pNewImgBuff, message_len: (size_t)ImgSize);
73
74 sha256_str(digest: SHAStr, str: pSHA256Str, max_len: SHA256StrSize);
75
76 free(ptr: pNewImgBuff);
77}
78
79int main(int argc, const char **argv)
80{
81 CCmdlineFix CmdlineFix(&argc, &argv);
82 log_set_global_logger_default();
83
84 std::unique_ptr<IStorage> pStorage = std::unique_ptr<IStorage>(CreateStorage(InitializationType: IStorage::EInitializationType::BASIC, NumArgs: argc, ppArguments: argv));
85 if(!pStorage)
86 {
87 log_error("map_optimize", "Error creating basic storage");
88 return -1;
89 }
90 if(argc <= 1 || argc > 3)
91 {
92 dbg_msg(sys: "map_optimize", fmt: "Usage: map_optimize <source map filepath> [<dest map filepath>]");
93 return -1;
94 }
95
96 char aFilename[IO_MAX_PATH_LENGTH];
97 if(argc == 3)
98 {
99 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "out/%s", argv[2]);
100
101 fs_makedir_rec_for(path: aFilename);
102 }
103 else
104 {
105 fs_makedir(path: "out");
106 char aBuff[IO_MAX_PATH_LENGTH];
107 IStorage::StripPathAndExtension(pFilename: argv[1], pBuffer: aBuff, BufferSize: sizeof(aBuff));
108 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "out/%s.map", aBuff);
109 }
110
111 CDataFileReader Reader;
112 if(!Reader.Open(pStorage: pStorage.get(), pFilename: argv[1], StorageType: IStorage::TYPE_ABSOLUTE))
113 {
114 dbg_msg(sys: "map_optimize", fmt: "Failed to open source file.");
115 return -1;
116 }
117
118 CDataFileWriter Writer;
119 if(!Writer.Open(pStorage: pStorage.get(), pFilename: aFilename, StorageType: IStorage::TYPE_ABSOLUTE))
120 {
121 dbg_msg(sys: "map_optimize", fmt: "Failed to open target file.");
122 return -1;
123 }
124
125 int aImageFlags[MAX_MAPIMAGES] = {
126 0,
127 };
128
129 bool aaImageTiles[MAX_MAPIMAGES][256]{
130 {
131 false,
132 },
133 };
134
135 struct SMapOptimizeItem
136 {
137 CMapItemImage *m_pImage;
138 int m_Index;
139 int m_Data;
140 int m_Text;
141 };
142
143 std::vector<SMapOptimizeItem> vDataFindHelper;
144
145 // add all items
146 for(int Index = 0, i = 0; Index < Reader.NumItems(); Index++)
147 {
148 int Type, Id;
149 CUuid Uuid;
150 void *pPtr = Reader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid);
151
152 // Filter ITEMTYPE_EX items, they will be automatically added again.
153 if(Type == ITEMTYPE_EX)
154 {
155 continue;
156 }
157
158 // for all layers, check if it uses a image and set the corresponding flag
159 if(Type == MAPITEMTYPE_LAYER)
160 {
161 CMapItemLayer *pLayer = (CMapItemLayer *)pPtr;
162 if(pLayer->m_Type == LAYERTYPE_TILES)
163 {
164 CMapItemLayerTilemap *pTLayer = (CMapItemLayerTilemap *)pLayer;
165 if(pTLayer->m_Image >= 0 && pTLayer->m_Image < (int)MAX_MAPIMAGES && pTLayer->m_Flags == 0)
166 {
167 aImageFlags[pTLayer->m_Image] |= 1;
168 // check tiles that are used in this image
169 unsigned int DataSize = Reader.GetDataSize(Index: pTLayer->m_Data);
170 void *pTiles = Reader.GetData(Index: pTLayer->m_Data);
171
172 if(DataSize >= (size_t)pTLayer->m_Width * pTLayer->m_Height * sizeof(CTile))
173 {
174 for(int y = 0; y < pTLayer->m_Height; ++y)
175 {
176 for(int x = 0; x < pTLayer->m_Width; ++x)
177 {
178 int TileIndex = ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Index;
179 if(TileIndex > 0)
180 {
181 aaImageTiles[pTLayer->m_Image][TileIndex] = true;
182 }
183 }
184 }
185 }
186 }
187 }
188 else if(pLayer->m_Type == LAYERTYPE_QUADS)
189 {
190 CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer;
191 if(pQLayer->m_Image >= 0 && pQLayer->m_Image < (int)MAX_MAPIMAGES)
192 {
193 aImageFlags[pQLayer->m_Image] |= 2;
194 }
195 }
196 }
197 else if(Type == MAPITEMTYPE_IMAGE)
198 {
199 CMapItemImage_v2 *pImg = (CMapItemImage_v2 *)pPtr;
200 if(!pImg->m_External && pImg->m_Version < 2)
201 {
202 SMapOptimizeItem Item;
203 Item.m_pImage = pImg;
204 Item.m_Index = i;
205 Item.m_Data = pImg->m_ImageData;
206 Item.m_Text = pImg->m_ImageName;
207 vDataFindHelper.push_back(x: Item);
208 }
209
210 // found an image
211 ++i;
212 }
213
214 int Size = Reader.GetItemSize(Index);
215 Writer.AddItem(Type, Id, Size, pData: pPtr, pUuid: &Uuid);
216 }
217
218 // add all data
219 for(int Index = 0; Index < Reader.NumData(); Index++)
220 {
221 bool DeletePtr = false;
222 void *pPtr = Reader.GetData(Index);
223 int Size = Reader.GetDataSize(Index);
224 auto MapDataItemIterator = std::find_if(first: vDataFindHelper.begin(), last: vDataFindHelper.end(), pred: [Index](const SMapOptimizeItem &Other) -> bool { return Other.m_Data == Index || Other.m_Text == Index; });
225 if(MapDataItemIterator != vDataFindHelper.end())
226 {
227 int Width = MapDataItemIterator->m_pImage->m_Width;
228 int Height = MapDataItemIterator->m_pImage->m_Height;
229
230 int ImageIndex = MapDataItemIterator->m_Index;
231 if(MapDataItemIterator->m_Data == Index)
232 {
233 DeletePtr = true;
234 // optimize embedded images
235 // use a new pointer, to be safe, when using the original image data
236 void *pNewPtr = malloc(size: Size);
237 mem_copy(dest: pNewPtr, source: pPtr, size: Size);
238 pPtr = pNewPtr;
239 uint8_t *pImgBuff = (uint8_t *)pPtr;
240
241 bool DoClearTransparentPixels = false;
242 bool DilateAs2DArray = false;
243 bool DoDilate = false;
244
245 // all tiles that aren't used are cleared(if image was only used by tilemap)
246 if(aImageFlags[ImageIndex] == 1)
247 {
248 for(int i = 0; i < 256; ++i)
249 {
250 if(!aaImageTiles[ImageIndex][i])
251 {
252 ClearPixelsTile(pImg: pImgBuff, Width, Height, TileIndex: i);
253 }
254 }
255
256 DoClearTransparentPixels = true;
257 DilateAs2DArray = true;
258 DoDilate = true;
259 }
260 else if(aImageFlags[ImageIndex] == 0)
261 {
262 mem_zero(block: pImgBuff, size: (size_t)Width * Height * 4);
263 }
264 else
265 {
266 DoClearTransparentPixels = true;
267 DoDilate = true;
268 }
269
270 if(DoClearTransparentPixels)
271 {
272 // clear unused pixels and make a clean dilate for the compressor
273 ClearTransparentPixels(pImg: pImgBuff, Width, Height);
274 }
275
276 if(DoDilate)
277 {
278 if(DilateAs2DArray)
279 {
280 for(int i = 0; i < 256; ++i)
281 {
282 int ImgTileW = Width / 16;
283 int ImgTileH = Height / 16;
284 int x = (i % 16) * ImgTileW;
285 int y = (i / 16) * ImgTileH;
286 DilateImageSub(pImageBuff: pImgBuff, w: Width, h: Height, x, y, SubWidth: ImgTileW, SubHeight: ImgTileH);
287 }
288 }
289 else
290 {
291 DilateImage(pImageBuff: pImgBuff, w: Width, h: Height);
292 }
293 }
294 }
295 else if(MapDataItemIterator->m_Text == Index)
296 {
297 char *pImgName = (char *)pPtr;
298 uint8_t *pImgBuff = (uint8_t *)Reader.GetData(Index: MapDataItemIterator->m_Data);
299 int ImgSize = Reader.GetDataSize(Index: MapDataItemIterator->m_Data);
300
301 char aSHA256Str[SHA256_MAXSTRSIZE];
302 // This is the important function, that calculates the SHA256 in a special way
303 // Please read the comments inside the functions to understand it
304 GetImageSHA256(pImgBuff, ImgSize, Width, Height, pSHA256Str: aSHA256Str, SHA256StrSize: sizeof(aSHA256Str));
305
306 char aNewName[IO_MAX_PATH_LENGTH];
307 int StrLen = str_format(buffer: aNewName, buffer_size: std::size(aNewName), format: "%s_cut_%s", pImgName, aSHA256Str);
308
309 DeletePtr = true;
310 // make the new name ready
311 char *pNewPtr = (char *)malloc(size: StrLen + 1);
312 str_copy(dst: pNewPtr, src: aNewName, dst_size: StrLen + 1);
313 pPtr = pNewPtr;
314 Size = StrLen + 1;
315 }
316 }
317
318 Writer.AddData(Size, pData: pPtr, CompressionLevel: CDataFileWriter::COMPRESSION_BEST);
319
320 if(DeletePtr)
321 free(ptr: pPtr);
322 }
323
324 Reader.Close();
325 Writer.Finish();
326
327 return 0;
328}
329