2020-10-26 16:05:57 +00:00
|
|
|
#include <algorithm>
|
2022-04-22 23:04:48 +00:00
|
|
|
#include <base/logger.h>
|
2020-10-26 16:05:57 +00:00
|
|
|
#include <base/system.h>
|
2022-02-14 23:32:04 +00:00
|
|
|
#include <cstdint>
|
2022-06-21 13:26:23 +00:00
|
|
|
#include <engine/gfx/image_manipulation.h>
|
2020-10-26 16:05:57 +00:00
|
|
|
#include <engine/shared/datafile.h>
|
|
|
|
#include <engine/storage.h>
|
|
|
|
#include <game/mapitems.h>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
void ClearTransparentPixels(uint8_t *pImg, int Width, int Height)
|
|
|
|
{
|
|
|
|
for(int y = 0; y < Height; ++y)
|
|
|
|
{
|
|
|
|
for(int x = 0; x < Width; ++x)
|
|
|
|
{
|
|
|
|
int Index = y * Width * 4 + x * 4;
|
|
|
|
if(pImg[Index + 3] == 0)
|
|
|
|
{
|
|
|
|
pImg[Index + 0] = 0;
|
|
|
|
pImg[Index + 1] = 0;
|
|
|
|
pImg[Index + 2] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CopyOpaquePixels(uint8_t *pDestImg, uint8_t *pSrcImg, int Width, int Height)
|
|
|
|
{
|
|
|
|
for(int y = 0; y < Height; ++y)
|
|
|
|
{
|
|
|
|
for(int x = 0; x < Width; ++x)
|
|
|
|
{
|
|
|
|
int Index = y * Width * 4 + x * 4;
|
2022-05-28 07:13:13 +00:00
|
|
|
if(pSrcImg[Index + 3] > 0)
|
2020-10-26 16:05:57 +00:00
|
|
|
mem_copy(&pDestImg[Index], &pSrcImg[Index], sizeof(uint8_t) * 4);
|
|
|
|
else
|
|
|
|
mem_zero(&pDestImg[Index], sizeof(uint8_t) * 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearPixelsTile(uint8_t *pImg, int Width, int Height, int TileIndex)
|
|
|
|
{
|
|
|
|
int WTile = Width / 16;
|
|
|
|
int HTile = Height / 16;
|
|
|
|
int xi = (TileIndex % 16) * WTile;
|
|
|
|
int yi = (TileIndex / 16) * HTile;
|
|
|
|
|
|
|
|
for(int y = yi; y < yi + HTile; ++y)
|
|
|
|
{
|
|
|
|
for(int x = xi; x < xi + WTile; ++x)
|
|
|
|
{
|
|
|
|
int Index = y * Width * 4 + x * 4;
|
|
|
|
pImg[Index + 0] = 0;
|
|
|
|
pImg[Index + 1] = 0;
|
|
|
|
pImg[Index + 2] = 0;
|
|
|
|
pImg[Index + 3] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-15 15:49:32 +00:00
|
|
|
void GetImageSHA256(uint8_t *pImgBuff, int ImgSize, int Width, int Height, char *pSHA256Str, size_t SHA256StrSize)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
uint8_t *pNewImgBuff = (uint8_t *)malloc(ImgSize);
|
|
|
|
|
2022-05-28 07:13:13 +00:00
|
|
|
// Clear fully transparent pixels, so the SHA is easier to identify with the original image
|
2020-10-26 16:05:57 +00:00
|
|
|
CopyOpaquePixels(pNewImgBuff, pImgBuff, Width, Height);
|
|
|
|
SHA256_DIGEST SHAStr = sha256(pNewImgBuff, (size_t)ImgSize);
|
|
|
|
|
2022-10-15 15:49:32 +00:00
|
|
|
sha256_str(SHAStr, pSHA256Str, SHA256StrSize);
|
2020-10-26 16:05:57 +00:00
|
|
|
|
|
|
|
free(pNewImgBuff);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, const char **argv)
|
|
|
|
{
|
2022-06-13 16:07:29 +00:00
|
|
|
CCmdlineFix CmdlineFix(&argc, &argv);
|
2022-04-22 23:04:48 +00:00
|
|
|
log_set_global_logger_default();
|
2020-10-26 16:05:57 +00:00
|
|
|
|
2021-12-21 16:19:42 +00:00
|
|
|
IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv);
|
2020-10-26 16:05:57 +00:00
|
|
|
if(!pStorage || argc <= 1 || argc > 3)
|
|
|
|
{
|
|
|
|
dbg_msg("map_optimize", "Invalid parameters or other unknown error.");
|
|
|
|
dbg_msg("map_optimize", "Usage: map_optimize <source map filepath> [<dest map filepath>]");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
char aFileName[IO_MAX_PATH_LENGTH];
|
2020-10-26 16:05:57 +00:00
|
|
|
if(argc == 3)
|
|
|
|
{
|
|
|
|
str_format(aFileName, sizeof(aFileName), "out/%s", argv[2]);
|
|
|
|
|
|
|
|
fs_makedir_rec_for(aFileName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fs_makedir("out");
|
2021-09-13 08:06:34 +00:00
|
|
|
char aBuff[IO_MAX_PATH_LENGTH];
|
2022-01-22 16:34:23 +00:00
|
|
|
IStorage::StripPathAndExtension(argv[1], aBuff, sizeof(aBuff));
|
2020-10-26 16:05:57 +00:00
|
|
|
str_format(aFileName, sizeof(aFileName), "out/%s.map", aBuff);
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
CDataFileReader Reader;
|
|
|
|
if(!Reader.Open(pStorage, argv[1], IStorage::TYPE_ABSOLUTE))
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
dbg_msg("map_optimize", "Failed to open source file.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
CDataFileWriter Writer;
|
|
|
|
if(!Writer.Open(pStorage, aFileName, IStorage::TYPE_ABSOLUTE))
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
dbg_msg("map_optimize", "Failed to open target file.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:36:28 +00:00
|
|
|
int aImageFlags[MAX_MAPIMAGES] = {
|
2020-10-26 16:05:57 +00:00
|
|
|
0,
|
|
|
|
};
|
|
|
|
|
2023-10-28 15:36:28 +00:00
|
|
|
bool aaImageTiles[MAX_MAPIMAGES][256]{
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
struct SMapOptimizeItem
|
|
|
|
{
|
|
|
|
CMapItemImage *m_pImage;
|
|
|
|
int m_Index;
|
|
|
|
int m_Data;
|
|
|
|
int m_Text;
|
|
|
|
};
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
std::vector<SMapOptimizeItem> vDataFindHelper;
|
2020-10-26 16:05:57 +00:00
|
|
|
|
|
|
|
// add all items
|
2022-05-31 20:45:17 +00:00
|
|
|
for(int Index = 0, i = 0; Index < Reader.NumItems(); Index++)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
2022-05-31 20:45:17 +00:00
|
|
|
int Type, ID;
|
|
|
|
void *pPtr = Reader.GetItem(Index, &Type, &ID);
|
|
|
|
int Size = Reader.GetItemSize(Index);
|
2021-05-02 15:22:15 +00:00
|
|
|
|
|
|
|
// filter ITEMTYPE_EX items, they will be automatically added again
|
2021-05-03 08:43:00 +00:00
|
|
|
if(Type == ITEMTYPE_EX)
|
2021-05-02 15:22:15 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2020-10-26 16:05:57 +00:00
|
|
|
// for all layers, check if it uses a image and set the corresponding flag
|
|
|
|
if(Type == MAPITEMTYPE_LAYER)
|
|
|
|
{
|
|
|
|
CMapItemLayer *pLayer = (CMapItemLayer *)pPtr;
|
|
|
|
if(pLayer->m_Type == LAYERTYPE_TILES)
|
|
|
|
{
|
|
|
|
CMapItemLayerTilemap *pTLayer = (CMapItemLayerTilemap *)pLayer;
|
2023-10-28 15:36:28 +00:00
|
|
|
if(pTLayer->m_Image >= 0 && pTLayer->m_Image < (int)MAX_MAPIMAGES && pTLayer->m_Flags == 0)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
aImageFlags[pTLayer->m_Image] |= 1;
|
|
|
|
// check tiles that are used in this image
|
2022-05-31 20:45:17 +00:00
|
|
|
unsigned int DataSize = Reader.GetDataSize(pTLayer->m_Data);
|
|
|
|
void *pTiles = Reader.GetData(pTLayer->m_Data);
|
2020-10-26 16:05:57 +00:00
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
if(DataSize >= (size_t)pTLayer->m_Width * pTLayer->m_Height * sizeof(CTile))
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
2022-05-31 20:45:17 +00:00
|
|
|
for(int y = 0; y < pTLayer->m_Height; ++y)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
2022-05-31 20:45:17 +00:00
|
|
|
for(int x = 0; x < pTLayer->m_Width; ++x)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
int TileIndex = ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Index;
|
|
|
|
if(TileIndex > 0)
|
|
|
|
{
|
2022-05-31 20:45:17 +00:00
|
|
|
aaImageTiles[pTLayer->m_Image][TileIndex] = true;
|
2020-10-26 16:05:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(pLayer->m_Type == LAYERTYPE_QUADS)
|
|
|
|
{
|
|
|
|
CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer;
|
2023-10-28 15:36:28 +00:00
|
|
|
if(pQLayer->m_Image >= 0 && pQLayer->m_Image < (int)MAX_MAPIMAGES)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
aImageFlags[pQLayer->m_Image] |= 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(Type == MAPITEMTYPE_IMAGE)
|
|
|
|
{
|
2023-04-27 15:13:35 +00:00
|
|
|
CMapItemImage_v2 *pImg = (CMapItemImage_v2 *)pPtr;
|
|
|
|
if(!pImg->m_External && pImg->m_Version < CMapItemImage_v2::CURRENT_VERSION)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
SMapOptimizeItem Item;
|
|
|
|
Item.m_pImage = pImg;
|
|
|
|
Item.m_Index = i;
|
|
|
|
Item.m_Data = pImg->m_ImageData;
|
|
|
|
Item.m_Text = pImg->m_ImageName;
|
2022-05-31 20:45:17 +00:00
|
|
|
vDataFindHelper.push_back(Item);
|
2020-10-26 16:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// found an image
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
Writer.AddItem(Type, ID, Size, pPtr);
|
2020-10-26 16:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add all data
|
2022-05-31 20:45:17 +00:00
|
|
|
for(int Index = 0; Index < Reader.NumData(); Index++)
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
bool DeletePtr = false;
|
2022-05-31 20:45:17 +00:00
|
|
|
void *pPtr = Reader.GetData(Index);
|
|
|
|
int Size = Reader.GetDataSize(Index);
|
|
|
|
auto it = std::find_if(vDataFindHelper.begin(), vDataFindHelper.end(), [Index](const SMapOptimizeItem &Other) -> bool { return Other.m_Data == Index || Other.m_Text == Index; });
|
|
|
|
if(it != vDataFindHelper.end())
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
int Width = it->m_pImage->m_Width;
|
|
|
|
int Height = it->m_pImage->m_Height;
|
|
|
|
|
|
|
|
int ImageIndex = it->m_Index;
|
|
|
|
if(it->m_Data == Index)
|
|
|
|
{
|
|
|
|
DeletePtr = true;
|
|
|
|
// optimize embedded images
|
|
|
|
// use a new pointer, to be safe, when using the original image data
|
|
|
|
void *pNewPtr = malloc(Size);
|
|
|
|
mem_copy(pNewPtr, pPtr, Size);
|
|
|
|
pPtr = pNewPtr;
|
|
|
|
uint8_t *pImgBuff = (uint8_t *)pPtr;
|
|
|
|
|
|
|
|
bool DoClearTransparentPixels = false;
|
|
|
|
bool DilateAs2DArray = false;
|
|
|
|
bool DoDilate = false;
|
|
|
|
|
|
|
|
// all tiles that aren't used are cleared(if image was only used by tilemap)
|
|
|
|
if(aImageFlags[ImageIndex] == 1)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < 256; ++i)
|
|
|
|
{
|
2022-05-31 20:45:17 +00:00
|
|
|
if(!aaImageTiles[ImageIndex][i])
|
2020-10-26 16:05:57 +00:00
|
|
|
{
|
|
|
|
ClearPixelsTile(pImgBuff, Width, Height, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DoClearTransparentPixels = true;
|
|
|
|
DilateAs2DArray = true;
|
|
|
|
DoDilate = true;
|
|
|
|
}
|
|
|
|
else if(aImageFlags[ImageIndex] == 0)
|
|
|
|
{
|
2023-02-19 12:45:48 +00:00
|
|
|
mem_zero(pImgBuff, (size_t)Width * Height * 4);
|
2020-10-26 16:05:57 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DoClearTransparentPixels = true;
|
|
|
|
DoDilate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(DoClearTransparentPixels)
|
|
|
|
{
|
|
|
|
// clear unused pixels and make a clean dilate for the compressor
|
|
|
|
ClearTransparentPixels(pImgBuff, Width, Height);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(DoDilate)
|
|
|
|
{
|
|
|
|
if(DilateAs2DArray)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < 256; ++i)
|
|
|
|
{
|
|
|
|
int ImgTileW = Width / 16;
|
|
|
|
int ImgTileH = Height / 16;
|
|
|
|
int x = (i % 16) * ImgTileW;
|
|
|
|
int y = (i / 16) * ImgTileH;
|
2023-10-03 11:45:50 +00:00
|
|
|
DilateImageSub(pImgBuff, Width, Height, x, y, ImgTileW, ImgTileH);
|
2020-10-26 16:05:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-10-03 11:45:50 +00:00
|
|
|
DilateImage(pImgBuff, Width, Height);
|
2020-10-26 16:05:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(it->m_Text == Index)
|
|
|
|
{
|
|
|
|
char *pImgName = (char *)pPtr;
|
2022-05-31 20:45:17 +00:00
|
|
|
uint8_t *pImgBuff = (uint8_t *)Reader.GetData(it->m_Data);
|
|
|
|
int ImgSize = Reader.GetDataSize(it->m_Data);
|
2020-10-26 16:05:57 +00:00
|
|
|
|
|
|
|
char aSHA256Str[SHA256_MAXSTRSIZE];
|
|
|
|
// This is the important function, that calculates the SHA256 in a special way
|
|
|
|
// Please read the comments inside the functions to understand it
|
2022-10-15 15:49:32 +00:00
|
|
|
GetImageSHA256(pImgBuff, ImgSize, Width, Height, aSHA256Str, sizeof(aSHA256Str));
|
2020-10-26 16:05:57 +00:00
|
|
|
|
2021-09-13 08:06:34 +00:00
|
|
|
char aNewName[IO_MAX_PATH_LENGTH];
|
2022-03-30 13:16:19 +00:00
|
|
|
int StrLen = str_format(aNewName, std::size(aNewName), "%s_cut_%s", pImgName, aSHA256Str);
|
2020-10-26 16:05:57 +00:00
|
|
|
|
|
|
|
DeletePtr = true;
|
|
|
|
// make the new name ready
|
|
|
|
char *pNewPtr = (char *)malloc(StrLen + 1);
|
|
|
|
str_copy(pNewPtr, aNewName, StrLen + 1);
|
|
|
|
pPtr = pNewPtr;
|
|
|
|
Size = StrLen + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
Writer.AddData(Size, pPtr, Z_BEST_COMPRESSION);
|
2020-10-26 16:05:57 +00:00
|
|
|
|
|
|
|
if(DeletePtr)
|
|
|
|
free(pPtr);
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:45:17 +00:00
|
|
|
Reader.Close();
|
|
|
|
Writer.Finish();
|
2020-10-26 16:05:57 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|