mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Map optimizer
This commit is contained in:
parent
b3bbba8f48
commit
259629db9f
|
@ -2066,6 +2066,7 @@ set_src(TOOLS GLOB src/tools
|
|||
map_convert_07.cpp
|
||||
map_diff.cpp
|
||||
map_extract.cpp
|
||||
map_optimize.cpp
|
||||
map_replace_image.cpp
|
||||
map_resave.cpp
|
||||
packetgen.cpp
|
||||
|
@ -2078,7 +2079,7 @@ foreach(ABS_T ${TOOLS})
|
|||
string(REGEX REPLACE "\\.cpp$" "" TOOL "${T}")
|
||||
set(TOOL_DEPS ${DEPS})
|
||||
set(TOOL_LIBS ${LIBS})
|
||||
if(TOOL MATCHES "^(tileset_.*|dilate|map_convert_07|map_extract|map_replace_image)$")
|
||||
if(TOOL MATCHES "^(tileset_.*|dilate|map_convert_07|map_optimize|map_extract|map_replace_image)$")
|
||||
list(APPEND TOOL_DEPS ${PNGLITE_DEP})
|
||||
list(APPEND TOOL_LIBS ${PNGLITE_LIBRARIES})
|
||||
list(APPEND TOOL_INCLUDE_DIRS ${PNGLITE_INCLUDE_DIRS})
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
|
||||
#include "uuid_manager.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
static const int DEBUG = 0;
|
||||
|
||||
enum
|
||||
|
@ -692,7 +690,7 @@ int CDataFileWriter::AddItem(int Type, int ID, int Size, void *pData)
|
|||
return m_NumItems - 1;
|
||||
}
|
||||
|
||||
int CDataFileWriter::AddData(int Size, void *pData)
|
||||
int CDataFileWriter::AddData(int Size, void *pData, int CompressionLevel)
|
||||
{
|
||||
dbg_assert(m_NumDatas < 1024, "too much data");
|
||||
|
||||
|
@ -700,7 +698,7 @@ int CDataFileWriter::AddData(int Size, void *pData)
|
|||
unsigned long s = compressBound(Size);
|
||||
void *pCompData = malloc(s); // temporary buffer that we use during compression
|
||||
|
||||
int Result = compress((Bytef *)pCompData, &s, (Bytef *)pData, Size); // ignore_convention
|
||||
int Result = compress2((Bytef *)pCompData, &s, (Bytef *)pData, Size, CompressionLevel); // ignore_convention
|
||||
if(Result != Z_OK)
|
||||
{
|
||||
dbg_msg("datafile", "compression error %d", Result);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <base/hash.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
// raw datafile access
|
||||
class CDataFileReader
|
||||
{
|
||||
|
@ -100,7 +102,7 @@ public:
|
|||
void Init();
|
||||
bool OpenFile(class IStorage *pStorage, const char *pFilename, int StorageType = IStorage::TYPE_SAVE);
|
||||
bool Open(class IStorage *pStorage, const char *pFilename, int StorageType = IStorage::TYPE_SAVE);
|
||||
int AddData(int Size, void *pData);
|
||||
int AddData(int Size, void *pData, int CompressionLevel = Z_DEFAULT_COMPRESSION);
|
||||
int AddDataSwapped(int Size, void *pData);
|
||||
int AddItem(int Type, int ID, int Size, void *pData);
|
||||
int Finish();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
|
||||
static void Dilate(int w, int h, int BPP, unsigned char *pSrc, unsigned char *pDest, unsigned char AlphaThreshold = 30)
|
||||
static void Dilate(int w, int h, int BPP, unsigned char *pSrc, unsigned char *pDest, unsigned char AlphaThreshold = TW_DILATE_ALPHA_THRESHOLD)
|
||||
{
|
||||
int ix, iy;
|
||||
const int aDirX[] = {0, -1, 1, 0};
|
||||
|
@ -67,26 +67,50 @@ static void CopyColorValues(int w, int h, int BPP, unsigned char *pSrc, unsigned
|
|||
}
|
||||
|
||||
void DilateImage(unsigned char *pImageBuff, int w, int h, int BPP)
|
||||
{
|
||||
DilateImageSub(pImageBuff, w, h, BPP, 0, 0, w, h);
|
||||
}
|
||||
|
||||
void DilateImageSub(unsigned char *pImageBuff, int w, int h, int BPP, int x, int y, int sw, int sh)
|
||||
{
|
||||
unsigned char *apBuffer[2] = {NULL, NULL};
|
||||
|
||||
apBuffer[0] = (unsigned char *)malloc((size_t)w * h * sizeof(unsigned char) * BPP);
|
||||
apBuffer[1] = (unsigned char *)malloc((size_t)w * h * sizeof(unsigned char) * BPP);
|
||||
apBuffer[0] = (unsigned char *)malloc((size_t)sw * sh * sizeof(unsigned char) * BPP);
|
||||
apBuffer[1] = (unsigned char *)malloc((size_t)sw * sh * sizeof(unsigned char) * BPP);
|
||||
unsigned char *pBufferOriginal = (unsigned char *)malloc((size_t)sw * sh * sizeof(unsigned char) * BPP);
|
||||
|
||||
unsigned char *pPixelBuff = (unsigned char *)pImageBuff;
|
||||
|
||||
Dilate(w, h, BPP, pPixelBuff, apBuffer[0]);
|
||||
for(int Y = 0; Y < sh; ++Y)
|
||||
{
|
||||
int SrcImgOffset = ((y + Y) * w * BPP) + (x * BPP);
|
||||
int DstImgOffset = (Y * sw * BPP);
|
||||
int CopySize = sw * BPP;
|
||||
mem_copy(&pBufferOriginal[DstImgOffset], &pPixelBuff[SrcImgOffset], CopySize);
|
||||
}
|
||||
|
||||
Dilate(sw, sh, BPP, pBufferOriginal, apBuffer[0]);
|
||||
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
Dilate(w, h, BPP, apBuffer[0], apBuffer[1]);
|
||||
Dilate(w, h, BPP, apBuffer[1], apBuffer[0]);
|
||||
Dilate(sw, sh, BPP, apBuffer[0], apBuffer[1]);
|
||||
Dilate(sw, sh, BPP, apBuffer[1], apBuffer[0]);
|
||||
}
|
||||
|
||||
CopyColorValues(w, h, BPP, apBuffer[0], pPixelBuff);
|
||||
CopyColorValues(sw, sh, BPP, apBuffer[0], pBufferOriginal);
|
||||
|
||||
free(apBuffer[0]);
|
||||
free(apBuffer[1]);
|
||||
|
||||
for(int Y = 0; Y < sh; ++Y)
|
||||
{
|
||||
int SrcImgOffset = ((y + Y) * w * BPP) + (x * BPP);
|
||||
int DstImgOffset = (Y * sw * BPP);
|
||||
int CopySize = sw * BPP;
|
||||
mem_copy(&pPixelBuff[SrcImgOffset], &pBufferOriginal[DstImgOffset], CopySize);
|
||||
}
|
||||
|
||||
free(pBufferOriginal);
|
||||
}
|
||||
|
||||
static float CubicHermite(float A, float B, float C, float D, float t)
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TW_DILATE_ALPHA_THRESHOLD 30
|
||||
|
||||
void DilateImage(unsigned char *pImageBuff, int w, int h, int BPP);
|
||||
void DilateImageSub(unsigned char *pImageBuff, int w, int h, int BPP, int x, int y, int sw, int sh);
|
||||
|
||||
// returned pointer is allocated with malloc
|
||||
uint8_t *ResizeImage(const uint8_t *pImgData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
|
||||
|
|
322
src/tools/map_optimize.cpp
Normal file
322
src/tools/map_optimize.cpp
Normal file
|
@ -0,0 +1,322 @@
|
|||
#include <algorithm>
|
||||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/shared/image_manipulation.h>
|
||||
#include <engine/storage.h>
|
||||
#include <game/mapitems.h>
|
||||
#include <stdint.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)
|
||||
{
|
||||
dbg_logger_stdout();
|
||||
|
||||
IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_BASIC, argc, argv);
|
||||
int Index, ID = 0, Type = 0, Size;
|
||||
void *pPtr;
|
||||
char aFileName[1024];
|
||||
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[MAX_PATH_LENGTH];
|
||||
pStorage->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
|
||||
int i = 0;
|
||||
for(int Index = 0; Index < DataFile.NumItems(); Index++)
|
||||
{
|
||||
pPtr = DataFile.GetItem(Index, &Type, &ID);
|
||||
Size = DataFile.GetItemSize(Index);
|
||||
// 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 Size = DataFile.GetDataSize(DataIndex);
|
||||
void *pTiles = DataFile.GetData(DataIndex);
|
||||
unsigned int TileSize = sizeof(CTile);
|
||||
|
||||
if(Size >= 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
|
||||
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[MAX_PATH_LENGTH];
|
||||
int StrLen = str_format(aNewName, sizeof(aNewName) / sizeof(aNewName[0]), "%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();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue