mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Check skin/sprite images for correctness
This commit is contained in:
parent
f8b07f705e
commit
c989cd67f9
|
@ -1490,8 +1490,6 @@ set_src(ENGINE_SHARED GLOB src/engine/shared
|
||||||
datafile.h
|
datafile.h
|
||||||
demo.cpp
|
demo.cpp
|
||||||
demo.h
|
demo.h
|
||||||
dilate.cpp
|
|
||||||
dilate.h
|
|
||||||
econ.cpp
|
econ.cpp
|
||||||
econ.h
|
econ.h
|
||||||
engine.cpp
|
engine.cpp
|
||||||
|
@ -1502,6 +1500,8 @@ set_src(ENGINE_SHARED GLOB src/engine/shared
|
||||||
global_uuid_manager.cpp
|
global_uuid_manager.cpp
|
||||||
huffman.cpp
|
huffman.cpp
|
||||||
huffman.h
|
huffman.h
|
||||||
|
image_manipulation.cpp
|
||||||
|
image_manipulation.h
|
||||||
jobs.cpp
|
jobs.cpp
|
||||||
jobs.h
|
jobs.h
|
||||||
json.cpp
|
json.cpp
|
||||||
|
|
|
@ -36,6 +36,8 @@
|
||||||
#include "opengl_sl.h"
|
#include "opengl_sl.h"
|
||||||
#include "opengl_sl_program.h"
|
#include "opengl_sl_program.h"
|
||||||
|
|
||||||
|
#include <engine/shared/image_manipulation.h>
|
||||||
|
|
||||||
#ifdef __MINGW32__
|
#ifdef __MINGW32__
|
||||||
extern "C" {
|
extern "C" {
|
||||||
int putenv(const char *);
|
int putenv(const char *);
|
||||||
|
@ -177,19 +179,6 @@ bool CCommandProcessorFragment_General::RunCommand(const CCommandBuffer::SComman
|
||||||
|
|
||||||
// ------------ CCommandProcessorFragment_OpenGL
|
// ------------ 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)
|
int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat)
|
||||||
{
|
{
|
||||||
if(TexFormat == CCommandBuffer::TEXFORMAT_RGB)
|
if(TexFormat == CCommandBuffer::TEXFORMAT_RGB)
|
||||||
|
@ -212,127 +201,11 @@ int CCommandProcessorFragment_OpenGL::TexFormatToImageColorChannelCount(int TexF
|
||||||
return 4;
|
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)
|
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);
|
int Bpp = TexFormatToImageColorChannelCount(Format);
|
||||||
|
|
||||||
// All calls to Resize() ensure width & height are > 0, Bpp is always > 0,
|
return ResizeImage((const uint8_t *)pData, Width, Height, NewWidth, NewHeight, Bpp);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCommandProcessorFragment_OpenGL::IsTexturedState(const CCommandBuffer::SState &State)
|
bool CCommandProcessorFragment_OpenGL::IsTexturedState(const CCommandBuffer::SState &State)
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
#include <game/generated/client_data7.h>
|
#include <game/generated/client_data7.h>
|
||||||
#include <game/localization.h>
|
#include <game/localization.h>
|
||||||
|
|
||||||
|
#include <engine/shared/image_manipulation.h>
|
||||||
|
|
||||||
#include <math.h> // cosf, sinf, log2f
|
#include <math.h> // cosf, sinf, log2f
|
||||||
|
|
||||||
#if defined(CONF_VIDEORECORDER)
|
#if defined(CONF_VIDEORECORDER)
|
||||||
|
@ -560,6 +562,56 @@ int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int Sto
|
||||||
return 1;
|
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)
|
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)
|
for(int Y = 0; Y < SubCopyHeight; ++Y)
|
||||||
|
|
|
@ -845,6 +845,8 @@ public:
|
||||||
IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) override;
|
IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) override;
|
||||||
int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) 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 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;
|
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;
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,8 @@ public:
|
||||||
|
|
||||||
virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) = 0;
|
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
|
// 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;
|
virtual void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, int FullWidth, int FullHeight, int ColorChannelCount, int SubOffsetX, int SubOffsetY, int SubCopyWidth, int SubCopyHeight) = 0;
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
|
||||||
}
|
|
|
@ -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
|
|
222
src/engine/shared/image_manipulation.cpp
Normal file
222
src/engine/shared/image_manipulation.cpp
Normal 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;
|
||||||
|
}
|
14
src/engine/shared/image_manipulation.h
Normal file
14
src/engine/shared/image_manipulation.h
Normal 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
|
|
@ -94,7 +94,7 @@ int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
|
||||||
{
|
{
|
||||||
char aBuf[512];
|
char aBuf[512];
|
||||||
CImageInfo Info;
|
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);
|
str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName);
|
||||||
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
|
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
|
||||||
|
|
|
@ -2659,7 +2659,7 @@ void CGameClient::LoadGameSkin(const char *pPath, bool AsDir)
|
||||||
else
|
else
|
||||||
LoadGameSkin(pPath, true);
|
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_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]);
|
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
|
else
|
||||||
LoadEmoticonsSkin(pPath, true);
|
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)
|
for(int i = 0; i < 16; ++i)
|
||||||
m_EmoticonsSkin.m_SpriteEmoticons[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_OOP + 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
|
else
|
||||||
LoadParticlesSkin(pPath, true);
|
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_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]);
|
m_ParticlesSkin.m_SpriteParticleBall = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_BALL]);
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
#include <game/generated/client_data.h>
|
#include <game/generated/client_data.h>
|
||||||
#include <game/localization.h>
|
#include <game/localization.h>
|
||||||
|
|
||||||
#include <engine/shared/dilate.h>
|
#include <engine/shared/image_manipulation.h>
|
||||||
|
|
||||||
#include "auto_map.h"
|
#include "auto_map.h"
|
||||||
#include "editor.h"
|
#include "editor.h"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||||
#include <base/math.h>
|
#include <base/math.h>
|
||||||
#include <base/system.h>
|
#include <base/system.h>
|
||||||
#include <engine/shared/dilate.h>
|
#include <engine/shared/image_manipulation.h>
|
||||||
#include <pnglite.h>
|
#include <pnglite.h>
|
||||||
|
|
||||||
int DilateFile(const char *pFileName)
|
int DilateFile(const char *pFileName)
|
||||||
|
|
Loading…
Reference in a new issue