3327: Check skin/sprite images for correctness r=def- a=Jupeyy

I hope this helps with downloading things at wrong resolution again.

Note that this commit is smaller than it seems, bcs of renaming files and moving code to it.
Also this requires new translations.

In the skin database are invalid skins too:

![screenshot_2020-11-18_07-51-02](https://user-images.githubusercontent.com/6654924/99495409-e7637e80-2972-11eb-91ec-f8bb02a80e83.png)
![screenshot_2020-11-18_07-51-05](https://user-images.githubusercontent.com/6654924/99495410-e7fc1500-2972-11eb-94b8-aedc98e8ef67.png)


## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
This commit is contained in:
bors[bot] 2020-11-18 12:27:47 +00:00 committed by GitHub
commit 078247dd3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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)
@ -572,6 +574,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)