Check skin/sprite images for correctness

This commit is contained in:
Jupeyy 2020-11-18 07:36:19 +01:00
parent f8b07f705e
commit c989cd67f9
13 changed files with 303 additions and 233 deletions

View file

@ -1490,8 +1490,6 @@ set_src(ENGINE_SHARED GLOB src/engine/shared
datafile.h
demo.cpp
demo.h
dilate.cpp
dilate.h
econ.cpp
econ.h
engine.cpp
@ -1502,6 +1500,8 @@ set_src(ENGINE_SHARED GLOB src/engine/shared
global_uuid_manager.cpp
huffman.cpp
huffman.h
image_manipulation.cpp
image_manipulation.h
jobs.cpp
jobs.h
json.cpp

View file

@ -36,6 +36,8 @@
#include "opengl_sl.h"
#include "opengl_sl_program.h"
#include <engine/shared/image_manipulation.h>
#ifdef __MINGW32__
extern "C" {
int putenv(const char *);
@ -177,19 +179,6 @@ bool CCommandProcessorFragment_General::RunCommand(const CCommandBuffer::SComman
// ------------ CCommandProcessorFragment_OpenGL
static int HighestBit(int OfVar)
{
if(!OfVar)
return 0;
int RetV = 1;
while(OfVar >>= 1)
RetV <<= 1;
return RetV;
}
int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat)
{
if(TexFormat == CCommandBuffer::TEXFORMAT_RGB)
@ -212,127 +201,11 @@ int CCommandProcessorFragment_OpenGL::TexFormatToImageColorChannelCount(int TexF
return 4;
}
static float CubicHermite(float A, float B, float C, float D, float t)
{
float a = -A / 2.0f + (3.0f * B) / 2.0f - (3.0f * C) / 2.0f + D / 2.0f;
float b = A - (5.0f * B) / 2.0f + 2.0f * C - D / 2.0f;
float c = -A / 2.0f + C / 2.0f;
float d = B;
return (a * t * t * t) + (b * t * t) + (c * t) + d;
}
static void GetPixelClamped(uint8_t *pSourceImage, int x, int y, uint32_t W, uint32_t H, size_t BPP, uint8_t aTmp[])
{
x = clamp<int>(x, 0, (int)W - 1);
y = clamp<int>(y, 0, (int)H - 1);
for(size_t i = 0; i < BPP; i++)
{
aTmp[i] = pSourceImage[x * BPP + (W * BPP * y) + i];
}
}
static void SampleBicubic(uint8_t *pSourceImage, float u, float v, uint32_t W, uint32_t H, size_t BPP, uint8_t aSample[])
{
float X = (u * W) - 0.5f;
int xInt = (int)X;
float xFract = X - floorf(X);
float Y = (v * H) - 0.5f;
int yInt = (int)Y;
float yFract = Y - floorf(Y);
uint8_t PX00[4];
uint8_t PX10[4];
uint8_t PX20[4];
uint8_t PX30[4];
uint8_t PX01[4];
uint8_t PX11[4];
uint8_t PX21[4];
uint8_t PX31[4];
uint8_t PX02[4];
uint8_t PX12[4];
uint8_t PX22[4];
uint8_t PX32[4];
uint8_t PX03[4];
uint8_t PX13[4];
uint8_t PX23[4];
uint8_t PX33[4];
GetPixelClamped(pSourceImage, xInt - 1, yInt - 1, W, H, BPP, PX00);
GetPixelClamped(pSourceImage, xInt + 0, yInt - 1, W, H, BPP, PX10);
GetPixelClamped(pSourceImage, xInt + 1, yInt - 1, W, H, BPP, PX20);
GetPixelClamped(pSourceImage, xInt + 2, yInt - 1, W, H, BPP, PX30);
GetPixelClamped(pSourceImage, xInt - 1, yInt + 0, W, H, BPP, PX01);
GetPixelClamped(pSourceImage, xInt + 0, yInt + 0, W, H, BPP, PX11);
GetPixelClamped(pSourceImage, xInt + 1, yInt + 0, W, H, BPP, PX21);
GetPixelClamped(pSourceImage, xInt + 2, yInt + 0, W, H, BPP, PX31);
GetPixelClamped(pSourceImage, xInt - 1, yInt + 1, W, H, BPP, PX02);
GetPixelClamped(pSourceImage, xInt + 0, yInt + 1, W, H, BPP, PX12);
GetPixelClamped(pSourceImage, xInt + 1, yInt + 1, W, H, BPP, PX22);
GetPixelClamped(pSourceImage, xInt + 2, yInt + 1, W, H, BPP, PX32);
GetPixelClamped(pSourceImage, xInt - 1, yInt + 2, W, H, BPP, PX03);
GetPixelClamped(pSourceImage, xInt + 0, yInt + 2, W, H, BPP, PX13);
GetPixelClamped(pSourceImage, xInt + 1, yInt + 2, W, H, BPP, PX23);
GetPixelClamped(pSourceImage, xInt + 2, yInt + 2, W, H, BPP, PX33);
for(size_t i = 0; i < BPP; i++)
{
float Clmn0 = CubicHermite(PX00[i], PX10[i], PX20[i], PX30[i], xFract);
float Clmn1 = CubicHermite(PX01[i], PX11[i], PX21[i], PX31[i], xFract);
float Clmn2 = CubicHermite(PX02[i], PX12[i], PX22[i], PX32[i], xFract);
float Clmn3 = CubicHermite(PX03[i], PX13[i], PX23[i], PX33[i], xFract);
float Valuef = CubicHermite(Clmn0, Clmn1, Clmn2, Clmn3, yFract);
Valuef = clamp<float>(Valuef, 0.0f, 255.0f);
aSample[i] = (uint8_t)Valuef;
}
}
static void ResizeImage(uint8_t *pSourceImage, uint32_t SW, uint32_t SH, uint8_t *pDestinationImage, uint32_t W, uint32_t H, size_t BPP)
{
uint8_t aSample[4];
int y, x;
for(y = 0; y < (int)H; ++y)
{
float v = (float)y / (float)(H - 1);
for(x = 0; x < (int)W; ++x)
{
float u = (float)x / (float)(W - 1);
SampleBicubic(pSourceImage, u, v, SW, SH, BPP, aSample);
for(size_t i = 0; i < BPP; ++i)
{
pDestinationImage[x * BPP + ((W * BPP) * y) + i] = aSample[i];
}
}
}
}
void *CCommandProcessorFragment_OpenGL::Resize(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData)
{
unsigned char *pTmpData;
int Bpp = TexFormatToImageColorChannelCount(Format);
// All calls to Resize() ensure width & height are > 0, Bpp is always > 0,
// thus no allocation size 0 possible.
pTmpData = (unsigned char *)malloc((size_t)NewWidth * NewHeight * Bpp); // NOLINT(clang-analyzer-optin.portability.UnixAPI)
ResizeImage((uint8_t *)pData, Width, Height, (uint8_t *)pTmpData, NewWidth, NewHeight, Bpp);
return pTmpData;
return ResizeImage((const uint8_t *)pData, Width, Height, NewWidth, NewHeight, Bpp);
}
bool CCommandProcessorFragment_OpenGL::IsTexturedState(const CCommandBuffer::SState &State)

View file

@ -22,6 +22,8 @@
#include <game/generated/client_data7.h>
#include <game/localization.h>
#include <engine/shared/image_manipulation.h>
#include <math.h> // cosf, sinf, log2f
#if defined(CONF_VIDEORECORDER)
@ -560,6 +562,56 @@ int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int Sto
return 1;
}
bool CGraphics_Threaded::CheckImageDivisibility(const char *pFileName, CImageInfo &Img, int DivX, int DivY, bool AllowResize)
{
dbg_assert(DivX != 0 && DivY != 0, "Passing 0 to this function is not allowed.");
bool ImageIsValid = true;
bool WidthBroken = Img.m_Width == 0 || (Img.m_Width % DivX) != 0;
bool HeightBroken = Img.m_Height == 0 || (Img.m_Height % DivY) != 0;
if(WidthBroken || HeightBroken)
{
SWarning NewWarning;
str_format(NewWarning.m_aWarningMsg, sizeof(NewWarning.m_aWarningMsg), Localize("The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs."), pFileName, DivX, DivY);
m_Warnings.emplace_back(NewWarning);
ImageIsValid = false;
}
if(AllowResize && !ImageIsValid && Img.m_Width > 0 && Img.m_Height > 0)
{
int NewWidth = DivX;
int NewHeight = DivY;
if(WidthBroken)
{
NewWidth = maximum<int>(HighestBit(Img.m_Width), DivX);
NewHeight = (NewWidth / DivX) * DivY;
}
else
{
NewHeight = maximum<int>(HighestBit(Img.m_Height), DivY);
NewWidth = (NewHeight / DivY) * DivX;
}
int ColorChannelCount = 4;
if(Img.m_Format == CImageInfo::FORMAT_ALPHA)
ColorChannelCount = 1;
else if(Img.m_Format == CImageInfo::FORMAT_RGB)
ColorChannelCount = 3;
else if(Img.m_Format == CImageInfo::FORMAT_RGBA)
ColorChannelCount = 4;
uint8_t *pNewImg = ResizeImage((uint8_t *)Img.m_pData, Img.m_Width, Img.m_Height, NewWidth, NewHeight, ColorChannelCount);
free(Img.m_pData);
Img.m_pData = pNewImg;
Img.m_Width = NewWidth;
Img.m_Height = NewHeight;
ImageIsValid = true;
}
return ImageIsValid;
}
void CGraphics_Threaded::CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, int FullWidth, int FullHeight, int ColorChannelCount, int SubOffsetX, int SubOffsetY, int SubCopyWidth, int SubCopyHeight)
{
for(int Y = 0; Y < SubCopyHeight; ++Y)

View file

@ -845,6 +845,8 @@ public:
IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) override;
int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) override;
bool CheckImageDivisibility(const char *pFileName, CImageInfo &Img, int DivX, int DivY, bool AllowResize) override;
void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, int FullWidth, int FullHeight, int ColorChannelCount, int SubOffsetX, int SubOffsetY, int SubCopyWidth, int SubCopyHeight) override;
void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, int DestWidth, int DestHeight, uint8_t *pSourceBuffer, int SrcWidth, int SrcHeight, int ColorChannelCount, int SrcSubOffsetX, int SrcSubOffsetY, int SrcSubCopyWidth, int SrcSubCopyHeight) override;

View file

@ -222,6 +222,8 @@ public:
virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) = 0;
virtual bool CheckImageDivisibility(const char *pFileName, CImageInfo &Img, int DivX, int DivY, bool AllowResize) = 0;
// destination and source buffer require to have the same width and height
virtual void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, int FullWidth, int FullHeight, int ColorChannelCount, int SubOffsetX, int SubOffsetY, int SubCopyWidth, int SubCopyHeight) = 0;

View file

@ -1,89 +0,0 @@
#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)
{
int ix, iy;
const int aDirX[] = {0, -1, 1, 0};
const int aDirY[] = {-1, 0, 0, 1};
int AlphaCompIndex = BPP - 1;
int m = 0;
for(int y = 0; y < h; y++)
{
for(int x = 0; x < w; x++, m += BPP)
{
for(int i = 0; i < BPP; ++i)
pDest[m + i] = pSrc[m + i];
if(pSrc[m + AlphaCompIndex] > AlphaThreshold)
continue;
int aSumOfOpaque[] = {0, 0, 0};
int Counter = 0;
for(int c = 0; c < 4; c++)
{
ix = clamp(x + aDirX[c], 0, w - 1);
iy = clamp(y + aDirY[c], 0, h - 1);
int k = iy * w * BPP + ix * BPP;
if(pSrc[k + AlphaCompIndex] > AlphaThreshold)
{
for(int p = 0; p < BPP - 1; ++p)
// Seems safe for BPP = 3, 4 which we use. clang-analyzer seems to
// asssume being called with larger value. TODO: Can make this
// safer anyway.
aSumOfOpaque[p] += pSrc[k + p]; // NOLINT(clang-analyzer-core.uninitialized.Assign)
++Counter;
break;
}
}
if(Counter > 0)
{
for(int i = 0; i < BPP - 1; ++i)
{
aSumOfOpaque[i] /= Counter;
pDest[m + i] = (unsigned char)aSumOfOpaque[i];
}
pDest[m + AlphaCompIndex] = 255;
}
}
}
}
static void CopyColorValues(int w, int h, int BPP, unsigned char *pSrc, unsigned char *pDest)
{
int m = 0;
for(int y = 0; y < h; y++)
{
for(int x = 0; x < w; x++, m += BPP)
{
for(int i = 0; i < BPP - 1; ++i)
pDest[m + i] = pSrc[m + i];
}
}
}
void DilateImage(unsigned char *pImageBuff, int w, int h, int BPP)
{
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);
unsigned char *pPixelBuff = (unsigned char *)pImageBuff;
Dilate(w, h, BPP, pPixelBuff, 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]);
}
CopyColorValues(w, h, BPP, apBuffer[0], pPixelBuff);
free(apBuffer[0]);
free(apBuffer[1]);
}

View file

@ -1,6 +0,0 @@
#ifndef ENGINE_SHARED_DILATE_H
#define ENGINE_SHARED_DILATE_H
void DilateImage(unsigned char *pImageBuff, int w, int h, int BPP);
#endif

View file

@ -0,0 +1,222 @@
#include "image_manipulation.h"
#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)
{
int ix, iy;
const int aDirX[] = {0, -1, 1, 0};
const int aDirY[] = {-1, 0, 0, 1};
int AlphaCompIndex = BPP - 1;
int m = 0;
for(int y = 0; y < h; y++)
{
for(int x = 0; x < w; x++, m += BPP)
{
for(int i = 0; i < BPP; ++i)
pDest[m + i] = pSrc[m + i];
if(pSrc[m + AlphaCompIndex] > AlphaThreshold)
continue;
int aSumOfOpaque[] = {0, 0, 0};
int Counter = 0;
for(int c = 0; c < 4; c++)
{
ix = clamp(x + aDirX[c], 0, w - 1);
iy = clamp(y + aDirY[c], 0, h - 1);
int k = iy * w * BPP + ix * BPP;
if(pSrc[k + AlphaCompIndex] > AlphaThreshold)
{
for(int p = 0; p < BPP - 1; ++p)
// Seems safe for BPP = 3, 4 which we use. clang-analyzer seems to
// asssume being called with larger value. TODO: Can make this
// safer anyway.
aSumOfOpaque[p] += pSrc[k + p]; // NOLINT(clang-analyzer-core.uninitialized.Assign)
++Counter;
break;
}
}
if(Counter > 0)
{
for(int i = 0; i < BPP - 1; ++i)
{
aSumOfOpaque[i] /= Counter;
pDest[m + i] = (unsigned char)aSumOfOpaque[i];
}
pDest[m + AlphaCompIndex] = 255;
}
}
}
}
static void CopyColorValues(int w, int h, int BPP, unsigned char *pSrc, unsigned char *pDest)
{
int m = 0;
for(int y = 0; y < h; y++)
{
for(int x = 0; x < w; x++, m += BPP)
{
for(int i = 0; i < BPP - 1; ++i)
pDest[m + i] = pSrc[m + i];
}
}
}
void DilateImage(unsigned char *pImageBuff, int w, int h, int BPP)
{
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);
unsigned char *pPixelBuff = (unsigned char *)pImageBuff;
Dilate(w, h, BPP, pPixelBuff, 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]);
}
CopyColorValues(w, h, BPP, apBuffer[0], pPixelBuff);
free(apBuffer[0]);
free(apBuffer[1]);
}
static float CubicHermite(float A, float B, float C, float D, float t)
{
float a = -A / 2.0f + (3.0f * B) / 2.0f - (3.0f * C) / 2.0f + D / 2.0f;
float b = A - (5.0f * B) / 2.0f + 2.0f * C - D / 2.0f;
float c = -A / 2.0f + C / 2.0f;
float d = B;
return (a * t * t * t) + (b * t * t) + (c * t) + d;
}
static void GetPixelClamped(const uint8_t *pSourceImage, int x, int y, uint32_t W, uint32_t H, size_t BPP, uint8_t aTmp[])
{
x = clamp<int>(x, 0, (int)W - 1);
y = clamp<int>(y, 0, (int)H - 1);
for(size_t i = 0; i < BPP; i++)
{
aTmp[i] = pSourceImage[x * BPP + (W * BPP * y) + i];
}
}
static void SampleBicubic(const uint8_t *pSourceImage, float u, float v, uint32_t W, uint32_t H, size_t BPP, uint8_t aSample[])
{
float X = (u * W) - 0.5f;
int xInt = (int)X;
float xFract = X - floorf(X);
float Y = (v * H) - 0.5f;
int yInt = (int)Y;
float yFract = Y - floorf(Y);
uint8_t PX00[4];
uint8_t PX10[4];
uint8_t PX20[4];
uint8_t PX30[4];
uint8_t PX01[4];
uint8_t PX11[4];
uint8_t PX21[4];
uint8_t PX31[4];
uint8_t PX02[4];
uint8_t PX12[4];
uint8_t PX22[4];
uint8_t PX32[4];
uint8_t PX03[4];
uint8_t PX13[4];
uint8_t PX23[4];
uint8_t PX33[4];
GetPixelClamped(pSourceImage, xInt - 1, yInt - 1, W, H, BPP, PX00);
GetPixelClamped(pSourceImage, xInt + 0, yInt - 1, W, H, BPP, PX10);
GetPixelClamped(pSourceImage, xInt + 1, yInt - 1, W, H, BPP, PX20);
GetPixelClamped(pSourceImage, xInt + 2, yInt - 1, W, H, BPP, PX30);
GetPixelClamped(pSourceImage, xInt - 1, yInt + 0, W, H, BPP, PX01);
GetPixelClamped(pSourceImage, xInt + 0, yInt + 0, W, H, BPP, PX11);
GetPixelClamped(pSourceImage, xInt + 1, yInt + 0, W, H, BPP, PX21);
GetPixelClamped(pSourceImage, xInt + 2, yInt + 0, W, H, BPP, PX31);
GetPixelClamped(pSourceImage, xInt - 1, yInt + 1, W, H, BPP, PX02);
GetPixelClamped(pSourceImage, xInt + 0, yInt + 1, W, H, BPP, PX12);
GetPixelClamped(pSourceImage, xInt + 1, yInt + 1, W, H, BPP, PX22);
GetPixelClamped(pSourceImage, xInt + 2, yInt + 1, W, H, BPP, PX32);
GetPixelClamped(pSourceImage, xInt - 1, yInt + 2, W, H, BPP, PX03);
GetPixelClamped(pSourceImage, xInt + 0, yInt + 2, W, H, BPP, PX13);
GetPixelClamped(pSourceImage, xInt + 1, yInt + 2, W, H, BPP, PX23);
GetPixelClamped(pSourceImage, xInt + 2, yInt + 2, W, H, BPP, PX33);
for(size_t i = 0; i < BPP; i++)
{
float Clmn0 = CubicHermite(PX00[i], PX10[i], PX20[i], PX30[i], xFract);
float Clmn1 = CubicHermite(PX01[i], PX11[i], PX21[i], PX31[i], xFract);
float Clmn2 = CubicHermite(PX02[i], PX12[i], PX22[i], PX32[i], xFract);
float Clmn3 = CubicHermite(PX03[i], PX13[i], PX23[i], PX33[i], xFract);
float Valuef = CubicHermite(Clmn0, Clmn1, Clmn2, Clmn3, yFract);
Valuef = clamp<float>(Valuef, 0.0f, 255.0f);
aSample[i] = (uint8_t)Valuef;
}
}
static void ResizeImage(const uint8_t *pSourceImage, uint32_t SW, uint32_t SH, uint8_t *pDestinationImage, uint32_t W, uint32_t H, size_t BPP)
{
uint8_t aSample[4];
int y, x;
for(y = 0; y < (int)H; ++y)
{
float v = (float)y / (float)(H - 1);
for(x = 0; x < (int)W; ++x)
{
float u = (float)x / (float)(W - 1);
SampleBicubic(pSourceImage, u, v, SW, SH, BPP, aSample);
for(size_t i = 0; i < BPP; ++i)
{
pDestinationImage[x * BPP + ((W * BPP) * y) + i] = aSample[i];
}
}
}
}
uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWidth, int NewHeight, int BPP)
{
// All calls to Resize() ensure width & height are > 0, BPP is always > 0,
// thus no allocation size 0 possible.
uint8_t *pTmpData = (uint8_t *)malloc((size_t)NewWidth * NewHeight * BPP); // NOLINT(clang-analyzer-optin.portability.UnixAPI)
ResizeImage(pImageData, Width, Height, pTmpData, NewWidth, NewHeight, BPP);
return pTmpData;
}
int HighestBit(int OfVar)
{
if(!OfVar)
return 0;
int RetV = 1;
while(OfVar >>= 1)
RetV <<= 1;
return RetV;
}

View file

@ -0,0 +1,14 @@
#ifndef ENGINE_SHARED_IMAGE_MANIPULATION_H
#define ENGINE_SHARED_IMAGE_MANIPULATION_H
#include <stddef.h>
#include <stdint.h>
void DilateImage(unsigned char *pImageBuff, int w, int h, int BPP);
// returned pointer is allocated with malloc
uint8_t *ResizeImage(const uint8_t *pImgData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
int HighestBit(int OfVar);
#endif

View file

@ -94,7 +94,7 @@ int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
{
char aBuf[512];
CImageInfo Info;
if(!Graphics()->LoadPNG(&Info, pPath, DirType))
if(!Graphics()->LoadPNG(&Info, pPath, DirType) || !Graphics()->CheckImageDivisibility(pPath, Info, g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy, true))
{
str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);

View file

@ -2659,7 +2659,7 @@ void CGameClient::LoadGameSkin(const char *pPath, bool AsDir)
else
LoadGameSkin(pPath, true);
}
else if(PngLoaded)
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_HEALTH_FULL].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_HEALTH_FULL].m_pSet->m_Gridy, true))
{
m_GameSkin.m_SpriteHealthFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HEALTH_FULL]);
m_GameSkin.m_SpriteHealthEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HEALTH_EMPTY]);
@ -2812,7 +2812,7 @@ void CGameClient::LoadEmoticonsSkin(const char *pPath, bool AsDir)
else
LoadEmoticonsSkin(pPath, true);
}
else if(PngLoaded)
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_OOP].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_OOP].m_pSet->m_Gridy, true))
{
for(int i = 0; i < 16; ++i)
m_EmoticonsSkin.m_SpriteEmoticons[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_OOP + i]);
@ -2866,7 +2866,7 @@ void CGameClient::LoadParticlesSkin(const char *pPath, bool AsDir)
else
LoadParticlesSkin(pPath, true);
}
else if(PngLoaded)
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_PART_SLICE].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_PART_SLICE].m_pSet->m_Gridy, true))
{
m_ParticlesSkin.m_SpriteParticleSlice = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SLICE]);
m_ParticlesSkin.m_SpriteParticleBall = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_BALL]);

View file

@ -30,7 +30,7 @@
#include <game/generated/client_data.h>
#include <game/localization.h>
#include <engine/shared/dilate.h>
#include <engine/shared/image_manipulation.h>
#include "auto_map.h"
#include "editor.h"

View file

@ -2,7 +2,7 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/math.h>
#include <base/system.h>
#include <engine/shared/dilate.h>
#include <engine/shared/image_manipulation.h>
#include <pnglite.h>
int DilateFile(const char *pFileName)