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