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 | |
11 | void 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 | |
28 | void 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 | |
43 | void 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 | |
63 | void 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 | |
76 | int 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 | |