ddnet/src/tools/map_optimize.cpp
heinrich5991 fa4bcd5ec0 Unify logging infrastructure between IConsole and dbg_msg
This makes the "black console window" less important on Windows (or
anywhere else, for that matter), lets you see logs from other threads in
the f1 console, and removes the distinction between `IConsole::Print`
and `dbg_msg`.
2022-04-29 15:21:26 +02:00

332 lines
8.2 KiB
C++

#include <algorithm>
#include <base/logger.h>
#include <base/math.h>
#include <base/system.h>
#include <cstdint>
#include <engine/shared/datafile.h>
#include <engine/shared/image_manipulation.h>
#include <engine/storage.h>
#include <game/mapitems.h>
#include <utility>
#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;
if(pSrcImg[Index + 3] > TW_DILATE_ALPHA_THRESHOLD)
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;
}
}
}
void GetImageSHA256(uint8_t *pImgBuff, int ImgSize, int Width, int Height, char *pSHA256Str)
{
uint8_t *pNewImgBuff = (uint8_t *)malloc(ImgSize);
// Get all image pixels, that have a alpha threshold over the default threshold,
// all other pixels are cleared to zero.
// This is required since dilate modifies pixels under the alpha threshold, which would alter the SHA.
CopyOpaquePixels(pNewImgBuff, pImgBuff, Width, Height);
SHA256_DIGEST SHAStr = sha256(pNewImgBuff, (size_t)ImgSize);
sha256_str(SHAStr, pSHA256Str, SHA256_MAXSTRSIZE * sizeof(char));
free(pNewImgBuff);
}
int main(int argc, const char **argv)
{
cmdline_fix(&argc, &argv);
log_set_global_logger_default();
IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_BASIC, argc, argv);
int ID = 0, Type = 0, Size;
void *pPtr;
char aFileName[IO_MAX_PATH_LENGTH];
CDataFileReader DataFile;
CDataFileWriter df;
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;
}
if(argc == 3)
{
str_format(aFileName, sizeof(aFileName), "out/%s", argv[2]);
fs_makedir_rec_for(aFileName);
}
else
{
fs_makedir("out");
char aBuff[IO_MAX_PATH_LENGTH];
IStorage::StripPathAndExtension(argv[1], aBuff, sizeof(aBuff));
str_format(aFileName, sizeof(aFileName), "out/%s.map", aBuff);
}
if(!DataFile.Open(pStorage, argv[1], IStorage::TYPE_ABSOLUTE))
{
dbg_msg("map_optimize", "Failed to open source file.");
return -1;
}
if(!df.Open(pStorage, aFileName, IStorage::TYPE_ABSOLUTE))
{
dbg_msg("map_optimize", "Failed to open target file.");
return -1;
}
int aImageFlags[64] = {
0,
};
bool aImageTiles[64][256]{
{
false,
},
};
struct SMapOptimizeItem
{
CMapItemImage *m_pImage;
int m_Index;
int m_Data;
int m_Text;
};
std::vector<SMapOptimizeItem> DataFindHelper;
// add all items
for(int Index = 0, i = 0; Index < DataFile.NumItems(); Index++)
{
pPtr = DataFile.GetItem(Index, &Type, &ID);
Size = DataFile.GetItemSize(Index);
// filter ITEMTYPE_EX items, they will be automatically added again
if(Type == ITEMTYPE_EX)
{
continue;
}
// 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;
if(pTLayer->m_Image >= 0 && pTLayer->m_Image < 64 && pTLayer->m_Flags == 0)
{
aImageFlags[pTLayer->m_Image] |= 1;
// check tiles that are used in this image
int DataIndex = pTLayer->m_Data;
unsigned int DataSize = DataFile.GetDataSize(DataIndex);
void *pTiles = DataFile.GetData(DataIndex);
unsigned int TileSize = sizeof(CTile);
if(DataSize >= pTLayer->m_Width * pTLayer->m_Height * TileSize)
{
int x = 0;
int y = 0;
for(y = 0; y < pTLayer->m_Height; ++y)
{
for(x = 0; x < pTLayer->m_Width; ++x)
{
int TileIndex = ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Index;
if(TileIndex > 0)
{
aImageTiles[pTLayer->m_Image][TileIndex] = true;
}
}
}
}
}
}
else if(pLayer->m_Type == LAYERTYPE_QUADS)
{
CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer;
if(pQLayer->m_Image >= 0 && pQLayer->m_Image < 64)
{
aImageFlags[pQLayer->m_Image] |= 2;
}
}
}
else if(Type == MAPITEMTYPE_IMAGE)
{
CMapItemImage *pImg = (CMapItemImage *)pPtr;
if(!pImg->m_External)
{
SMapOptimizeItem Item;
Item.m_pImage = pImg;
Item.m_Index = i;
Item.m_Data = pImg->m_ImageData;
Item.m_Text = pImg->m_ImageName;
DataFindHelper.push_back(Item);
}
// found an image
++i;
}
df.AddItem(Type, ID, Size, pPtr);
}
// add all data
int Index;
for(Index = 0; Index < DataFile.NumData(); Index++)
{
bool DeletePtr = false;
pPtr = DataFile.GetData(Index);
Size = DataFile.GetDataSize(Index);
std::vector<SMapOptimizeItem>::iterator it = std::find_if(DataFindHelper.begin(), DataFindHelper.end(), [Index](const SMapOptimizeItem &Other) -> bool { return Other.m_Data == Index || Other.m_Text == Index; });
if(it != DataFindHelper.end())
{
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)
{
if(!aImageTiles[ImageIndex][i])
{
ClearPixelsTile(pImgBuff, Width, Height, i);
}
}
DoClearTransparentPixels = true;
DilateAs2DArray = true;
DoDilate = true;
}
else if(aImageFlags[ImageIndex] == 0)
{
mem_zero(pImgBuff, Width * Height * 4);
}
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;
DilateImageSub(pImgBuff, Width, Height, 4, x, y, ImgTileW, ImgTileH);
}
}
else
{
DilateImage(pImgBuff, Width, Height, 4);
}
}
}
else if(it->m_Text == Index)
{
char *pImgName = (char *)pPtr;
uint8_t *pImgBuff = (uint8_t *)DataFile.GetData(it->m_Data);
int ImgSize = DataFile.GetDataSize(it->m_Data);
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
GetImageSHA256(pImgBuff, ImgSize, Width, Height, aSHA256Str);
char aNewName[IO_MAX_PATH_LENGTH];
int StrLen = str_format(aNewName, std::size(aNewName), "%s_cut_%s", pImgName, aSHA256Str);
DeletePtr = true;
// make the new name ready
char *pNewPtr = (char *)malloc(StrLen + 1);
str_copy(pNewPtr, aNewName, StrLen + 1);
pPtr = pNewPtr;
Size = StrLen + 1;
}
}
df.AddData(Size, pPtr, Z_BEST_COMPRESSION);
if(DeletePtr)
free(pPtr);
}
DataFile.Close();
df.Finish();
cmdline_free(argc, argv);
return 0;
}