From c989cd67f9aed0a180db3edb841768791d3b3d97 Mon Sep 17 00:00:00 2001 From: Jupeyy Date: Wed, 18 Nov 2020 07:36:19 +0100 Subject: [PATCH] Check skin/sprite images for correctness --- CMakeLists.txt | 4 +- src/engine/client/backend_sdl.cpp | 133 +------------- src/engine/client/graphics_threaded.cpp | 52 ++++++ src/engine/client/graphics_threaded.h | 2 + src/engine/graphics.h | 2 + src/engine/shared/dilate.cpp | 89 --------- src/engine/shared/dilate.h | 6 - src/engine/shared/image_manipulation.cpp | 222 +++++++++++++++++++++++ src/engine/shared/image_manipulation.h | 14 ++ src/game/client/components/skins.cpp | 2 +- src/game/client/gameclient.cpp | 6 +- src/game/editor/editor.cpp | 2 +- src/tools/dilate.cpp | 2 +- 13 files changed, 303 insertions(+), 233 deletions(-) delete mode 100644 src/engine/shared/dilate.cpp delete mode 100644 src/engine/shared/dilate.h create mode 100644 src/engine/shared/image_manipulation.cpp create mode 100644 src/engine/shared/image_manipulation.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 10efdedee..2f61f7a8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp index 05851a5c1..e7aabde64 100644 --- a/src/engine/client/backend_sdl.cpp +++ b/src/engine/client/backend_sdl.cpp @@ -36,6 +36,8 @@ #include "opengl_sl.h" #include "opengl_sl_program.h" +#include + #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(x, 0, (int)W - 1); - y = clamp(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(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) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index f0f3cc695..742f83ce0 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include // 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(HighestBit(Img.m_Width), DivX); + NewHeight = (NewWidth / DivX) * DivY; + } + else + { + NewHeight = maximum(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) diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 13e0c4188..63f9f40fc 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -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; diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 5c45f2d11..ad0b2261e 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -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; diff --git a/src/engine/shared/dilate.cpp b/src/engine/shared/dilate.cpp deleted file mode 100644 index 62b3a536a..000000000 --- a/src/engine/shared/dilate.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include -#include - -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]); -} diff --git a/src/engine/shared/dilate.h b/src/engine/shared/dilate.h deleted file mode 100644 index 368a6b35d..000000000 --- a/src/engine/shared/dilate.h +++ /dev/null @@ -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 diff --git a/src/engine/shared/image_manipulation.cpp b/src/engine/shared/image_manipulation.cpp new file mode 100644 index 000000000..40cd7b456 --- /dev/null +++ b/src/engine/shared/image_manipulation.cpp @@ -0,0 +1,222 @@ +#include "image_manipulation.h" +#include +#include + +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(x, 0, (int)W - 1); + y = clamp(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(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; +} diff --git a/src/engine/shared/image_manipulation.h b/src/engine/shared/image_manipulation.h new file mode 100644 index 000000000..b6464518a --- /dev/null +++ b/src/engine/shared/image_manipulation.h @@ -0,0 +1,14 @@ +#ifndef ENGINE_SHARED_IMAGE_MANIPULATION_H +#define ENGINE_SHARED_IMAGE_MANIPULATION_H + +#include +#include + +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 diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp index 73cae21f3..128cf43fb 100644 --- a/src/game/client/components/skins.cpp +++ b/src/game/client/components/skins.cpp @@ -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); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 9cb75354e..556d1f8a7 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -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]); diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 250a7d313..cfd54cfee 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -30,7 +30,7 @@ #include #include -#include +#include #include "auto_map.h" #include "editor.h" diff --git a/src/tools/dilate.cpp b/src/tools/dilate.cpp index cc568c9ba..f509a661a 100644 --- a/src/tools/dilate.cpp +++ b/src/tools/dilate.cpp @@ -2,7 +2,7 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include -#include +#include #include int DilateFile(const char *pFileName)