mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-09 09:38:19 +00:00
Merge pull request #8965 from Robyt3/Image-Loading-Refactoring
Refactor image loading, saving and manipulation
This commit is contained in:
commit
7dc6346b2d
|
@ -2033,6 +2033,7 @@ set_src(ENGINE_INTERFACE GLOB src/engine
|
|||
ghost.h
|
||||
graphics.h
|
||||
http.h
|
||||
image.h
|
||||
input.h
|
||||
kernel.h
|
||||
keys.h
|
||||
|
@ -2139,6 +2140,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
|
|||
websockets.h
|
||||
)
|
||||
set_src(ENGINE_GFX GLOB src/engine/gfx
|
||||
image.cpp
|
||||
image_loader.cpp
|
||||
image_loader.h
|
||||
image_manipulation.cpp
|
||||
|
|
|
@ -300,54 +300,6 @@ void CGraphics_Threaded::UnloadTexture(CTextureHandle *pIndex)
|
|||
FreeTextureIndex(pIndex);
|
||||
}
|
||||
|
||||
bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage)
|
||||
{
|
||||
if(SrcImage.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
mem_copy(pDest, SrcImage.m_pData, SrcImage.DataSize());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t SrcChannelCount = CImageInfo::PixelSize(SrcImage.m_Format);
|
||||
const size_t DstChannelCount = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA);
|
||||
for(size_t Y = 0; Y < SrcImage.m_Height; ++Y)
|
||||
{
|
||||
for(size_t X = 0; X < SrcImage.m_Width; ++X)
|
||||
{
|
||||
size_t ImgOffsetSrc = (Y * SrcImage.m_Width * SrcChannelCount) + (X * SrcChannelCount);
|
||||
size_t ImgOffsetDest = (Y * SrcImage.m_Width * DstChannelCount) + (X * DstChannelCount);
|
||||
size_t CopySize = SrcChannelCount;
|
||||
if(SrcImage.m_Format == CImageInfo::FORMAT_RGB)
|
||||
{
|
||||
mem_copy(&pDest[ImgOffsetDest], &SrcImage.m_pData[ImgOffsetSrc], CopySize);
|
||||
pDest[ImgOffsetDest + 3] = 255;
|
||||
}
|
||||
else if(SrcImage.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT)
|
||||
{
|
||||
pDest[ImgOffsetDest + 0] = 255;
|
||||
pDest[ImgOffsetDest + 1] = 255;
|
||||
pDest[ImgOffsetDest + 2] = 255;
|
||||
pDest[ImgOffsetDest + 3] = SrcImage.m_pData[ImgOffsetSrc];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTextureImpl(const CImageInfo &FromImageInfo, int x, int y, size_t w, size_t h, const char *pName)
|
||||
{
|
||||
m_vSpriteHelper.resize(w * h * FromImageInfo.PixelSize());
|
||||
CopyTextureFromTextureBufferSub(m_vSpriteHelper.data(), w, h, FromImageInfo, x, y, w, h);
|
||||
CImageInfo SpriteInfo;
|
||||
SpriteInfo.m_Width = w;
|
||||
SpriteInfo.m_Height = h;
|
||||
SpriteInfo.m_Format = FromImageInfo.m_Format;
|
||||
SpriteInfo.m_pData = m_vSpriteHelper.data();
|
||||
return LoadTextureRaw(SpriteInfo, 0, pName);
|
||||
}
|
||||
|
||||
IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTexture(const CImageInfo &FromImageInfo, const CDataSprite *pSprite)
|
||||
{
|
||||
int ImageGridX = FromImageInfo.m_Width / pSprite->m_pSet->m_Gridx;
|
||||
|
@ -356,12 +308,19 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTexture(const CImageInfo
|
|||
int y = pSprite->m_Y * ImageGridY;
|
||||
int w = pSprite->m_W * ImageGridX;
|
||||
int h = pSprite->m_H * ImageGridY;
|
||||
return LoadSpriteTextureImpl(FromImageInfo, x, y, w, h, pSprite->m_pName);
|
||||
|
||||
CImageInfo SpriteInfo;
|
||||
SpriteInfo.m_Width = w;
|
||||
SpriteInfo.m_Height = h;
|
||||
SpriteInfo.m_Format = FromImageInfo.m_Format;
|
||||
SpriteInfo.m_pData = static_cast<uint8_t *>(malloc(SpriteInfo.DataSize()));
|
||||
SpriteInfo.CopyRectFrom(FromImageInfo, x, y, w, h, 0, 0);
|
||||
return LoadTextureRawMove(SpriteInfo, 0, pSprite->m_pName);
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::IsImageSubFullyTransparent(const CImageInfo &FromImageInfo, int x, int y, int w, int h)
|
||||
{
|
||||
if(FromImageInfo.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT || FromImageInfo.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
if(FromImageInfo.m_Format == CImageInfo::FORMAT_R || FromImageInfo.m_Format == CImageInfo::FORMAT_RA || FromImageInfo.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
const uint8_t *pImgData = FromImageInfo.m_pData;
|
||||
const size_t PixelSize = FromImageInfo.PixelSize();
|
||||
|
@ -435,8 +394,8 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(const CImageInfo &I
|
|||
CCommandBuffer::SCommand_Texture_Create Cmd = LoadTextureCreateCommand(TextureHandle.Id(), Image.m_Width, Image.m_Height, Flags);
|
||||
|
||||
// Copy texture data and convert if necessary
|
||||
uint8_t *pTmpData = static_cast<uint8_t *>(malloc(Image.m_Width * Image.m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
if(!ConvertToRGBA(pTmpData, Image))
|
||||
uint8_t *pTmpData;
|
||||
if(!ConvertToRgbaAlloc(pTmpData, Image))
|
||||
{
|
||||
dbg_msg("graphics", "converted image '%s' to RGBA, consider making its file format RGBA", pTexName ? pTexName : "(no name)");
|
||||
}
|
||||
|
@ -472,7 +431,6 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRawMove(CImageInfo &Ima
|
|||
return TextureHandle;
|
||||
}
|
||||
|
||||
// simple uncompressed RGBA loaders
|
||||
IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int Flags)
|
||||
{
|
||||
dbg_assert(pFilename[0] != '\0', "Cannot load texture from file with empty filename"); // would cause Valgrind to crash otherwise
|
||||
|
@ -544,81 +502,56 @@ bool CGraphics_Threaded::UpdateTextTexture(CTextureHandle TextureId, int x, int
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const char *pFilename, int StorageType)
|
||||
static SWarning FormatPngliteIncompatibilityWarning(int PngliteIncompatible, const char *pContextName)
|
||||
{
|
||||
char aCompleteFilename[IO_MAX_PATH_LENGTH];
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename));
|
||||
if(File)
|
||||
SWarning Warning;
|
||||
str_format(Warning.m_aWarningMsg, sizeof(Warning.m_aWarningMsg), Localize("\"%s\" is not compatible with pnglite and cannot be loaded by old DDNet versions: "), pContextName);
|
||||
static const int FLAGS[] = {CImageLoader::PNGLITE_COLOR_TYPE, CImageLoader::PNGLITE_BIT_DEPTH, CImageLoader::PNGLITE_INTERLACE_TYPE, CImageLoader::PNGLITE_COMPRESSION_TYPE, CImageLoader::PNGLITE_FILTER_TYPE};
|
||||
static const char *const EXPLANATION[] = {"color type", "bit depth", "interlace type", "compression type", "filter type"};
|
||||
|
||||
bool First = true;
|
||||
for(size_t i = 0; i < std::size(FLAGS); ++i)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
if((PngliteIncompatible & FLAGS[i]) != 0)
|
||||
{
|
||||
io_close(File);
|
||||
log_error("game/png", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(::LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, Image.m_Width, Image.m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
if(ImageFormat == IMAGE_FORMAT_RGB)
|
||||
Image.m_Format = CImageInfo::FORMAT_RGB;
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA)
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
else
|
||||
if(!First)
|
||||
{
|
||||
free(pImgBuffer);
|
||||
log_error("game/png", "image had unsupported image format. filename='%s' format='%d'", pFilename, (int)ImageFormat);
|
||||
return false;
|
||||
str_append(Warning.m_aWarningMsg, ", ");
|
||||
}
|
||||
Image.m_pData = pImgBuffer;
|
||||
|
||||
if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
|
||||
{
|
||||
SWarning Warning;
|
||||
str_format(Warning.m_aWarningMsg, sizeof(Warning.m_aWarningMsg), Localize("\"%s\" is not compatible with pnglite and cannot be loaded by old DDNet versions: "), pFilename);
|
||||
static const int FLAGS[] = {PNGLITE_COLOR_TYPE, PNGLITE_BIT_DEPTH, PNGLITE_INTERLACE_TYPE, PNGLITE_COMPRESSION_TYPE, PNGLITE_FILTER_TYPE};
|
||||
static const char *const EXPLANATION[] = {"color type", "bit depth", "interlace type", "compression type", "filter type"};
|
||||
|
||||
bool First = true;
|
||||
for(size_t i = 0; i < std::size(FLAGS); ++i)
|
||||
{
|
||||
if((PngliteIncompatible & FLAGS[i]) != 0)
|
||||
{
|
||||
if(!First)
|
||||
{
|
||||
str_append(Warning.m_aWarningMsg, ", ");
|
||||
}
|
||||
str_append(Warning.m_aWarningMsg, EXPLANATION[i]);
|
||||
First = false;
|
||||
}
|
||||
}
|
||||
str_append(Warning.m_aWarningMsg, " unsupported");
|
||||
m_vWarnings.emplace_back(Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("game/png", "failed to load file. filename='%s'", pFilename);
|
||||
return false;
|
||||
str_append(Warning.m_aWarningMsg, EXPLANATION[i]);
|
||||
First = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log_error("game/png", "failed to open file. filename='%s'", pFilename);
|
||||
str_append(Warning.m_aWarningMsg, " unsupported");
|
||||
return Warning;
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const char *pFilename, int StorageType)
|
||||
{
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
|
||||
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(File, pFilename, Image, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
|
||||
{
|
||||
m_vWarnings.emplace_back(FormatPngliteIncompatibilityWarning(PngliteIncompatible, pFilename));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName)
|
||||
{
|
||||
CByteBufferReader Reader(pData, DataSize);
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(Reader, pContextName, Image, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
|
||||
{
|
||||
m_vWarnings.emplace_back(FormatPngliteIncompatibilityWarning(PngliteIncompatible, pContextName));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -655,12 +588,7 @@ bool CGraphics_Threaded::CheckImageDivisibility(const char *pContextName, CImage
|
|||
NewHeight = maximum<int>(HighestBit(Image.m_Height), DivY);
|
||||
NewWidth = (NewHeight / DivY) * DivX;
|
||||
}
|
||||
|
||||
uint8_t *pNewImage = ResizeImage(Image.m_pData, Image.m_Width, Image.m_Height, NewWidth, NewHeight, Image.PixelSize());
|
||||
free(Image.m_pData);
|
||||
Image.m_pData = pNewImage;
|
||||
Image.m_Width = NewWidth;
|
||||
Image.m_Height = NewHeight;
|
||||
ResizeImage(Image, NewWidth, NewHeight);
|
||||
ImageIsValid = true;
|
||||
}
|
||||
|
||||
|
@ -682,29 +610,6 @@ bool CGraphics_Threaded::IsImageFormatRgba(const char *pContextName, const CImag
|
|||
return true;
|
||||
}
|
||||
|
||||
void CGraphics_Threaded::CopyTextureBufferSub(uint8_t *pDestBuffer, const CImageInfo &SourceImage, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight)
|
||||
{
|
||||
const size_t PixelSize = SourceImage.PixelSize();
|
||||
for(size_t Y = 0; Y < SubCopyHeight; ++Y)
|
||||
{
|
||||
const size_t ImgOffset = ((SubOffsetY + Y) * SourceImage.m_Width * PixelSize) + (SubOffsetX * PixelSize);
|
||||
const size_t CopySize = SubCopyWidth * PixelSize;
|
||||
mem_copy(&pDestBuffer[ImgOffset], &SourceImage.m_pData[ImgOffset], CopySize);
|
||||
}
|
||||
}
|
||||
|
||||
void CGraphics_Threaded::CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, const CImageInfo &SourceImage, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight)
|
||||
{
|
||||
const size_t PixelSize = SourceImage.PixelSize();
|
||||
for(size_t Y = 0; Y < SrcSubCopyHeight; ++Y)
|
||||
{
|
||||
const size_t SrcImgOffset = ((SrcSubOffsetY + Y) * SourceImage.m_Width * PixelSize) + (SrcSubOffsetX * PixelSize);
|
||||
const size_t DstImgOffset = (Y * DestWidth * PixelSize);
|
||||
const size_t CopySize = SrcSubCopyWidth * PixelSize;
|
||||
mem_copy(&pDestBuffer[DstImgOffset], &SourceImage.m_pData[SrcImgOffset], CopySize);
|
||||
}
|
||||
}
|
||||
|
||||
void CGraphics_Threaded::KickCommandBuffer()
|
||||
{
|
||||
m_pBackend->RunBuffer(m_pCommandBuffer);
|
||||
|
@ -731,24 +636,14 @@ class CScreenshotSaveJob : public IJob
|
|||
IStorage *m_pStorage;
|
||||
IConsole *m_pConsole;
|
||||
char m_aName[IO_MAX_PATH_LENGTH];
|
||||
int m_Width;
|
||||
int m_Height;
|
||||
uint8_t *m_pData;
|
||||
CImageInfo m_Image;
|
||||
|
||||
void Run() override
|
||||
{
|
||||
char aWholePath[IO_MAX_PATH_LENGTH];
|
||||
char aBuf[64 + IO_MAX_PATH_LENGTH];
|
||||
IOHANDLE File = m_pStorage->OpenFile(m_aName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
|
||||
if(File)
|
||||
if(CImageLoader::SavePng(m_pStorage->OpenFile(m_aName, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath)), m_aName, m_Image))
|
||||
{
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
if(SavePng(IMAGE_FORMAT_RGBA, m_pData, ImageByteBuffer, m_Width, m_Height))
|
||||
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
|
||||
io_close(File);
|
||||
|
||||
str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
|
||||
}
|
||||
else
|
||||
|
@ -759,19 +654,17 @@ class CScreenshotSaveJob : public IJob
|
|||
}
|
||||
|
||||
public:
|
||||
CScreenshotSaveJob(IStorage *pStorage, IConsole *pConsole, const char *pName, int Width, int Height, uint8_t *pData) :
|
||||
CScreenshotSaveJob(IStorage *pStorage, IConsole *pConsole, const char *pName, CImageInfo Image) :
|
||||
m_pStorage(pStorage),
|
||||
m_pConsole(pConsole),
|
||||
m_Width(Width),
|
||||
m_Height(Height),
|
||||
m_pData(pData)
|
||||
m_Image(Image)
|
||||
{
|
||||
str_copy(m_aName, pName);
|
||||
}
|
||||
|
||||
~CScreenshotSaveJob() override
|
||||
{
|
||||
free(m_pData);
|
||||
m_Image.Free();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -795,7 +688,7 @@ void CGraphics_Threaded::ScreenshotDirect(bool *pSwapped)
|
|||
|
||||
if(Image.m_pData)
|
||||
{
|
||||
m_pEngine->AddJob(std::make_shared<CScreenshotSaveJob>(m_pStorage, m_pConsole, m_aScreenshotName, Image.m_Width, Image.m_Height, Image.m_pData));
|
||||
m_pEngine->AddJob(std::make_shared<CScreenshotSaveJob>(m_pStorage, m_pConsole, m_aScreenshotName, Image));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -798,8 +798,6 @@ class CGraphics_Threaded : public IEngineGraphics
|
|||
size_t m_FirstFreeTexture;
|
||||
int m_TextureMemoryUsage;
|
||||
|
||||
std::vector<uint8_t> m_vSpriteHelper;
|
||||
|
||||
bool m_WarnPngliteIncompatibleImages = false;
|
||||
|
||||
std::vector<SWarning> m_vWarnings;
|
||||
|
@ -954,7 +952,6 @@ public:
|
|||
bool UnloadTextTextures(CTextureHandle &TextTexture, CTextureHandle &TextOutlineTexture) override;
|
||||
bool UpdateTextTexture(CTextureHandle TextureId, int x, int y, size_t Width, size_t Height, const uint8_t *pData) override;
|
||||
|
||||
CTextureHandle LoadSpriteTextureImpl(const CImageInfo &FromImageInfo, int x, int y, size_t w, size_t h, const char *pName);
|
||||
CTextureHandle LoadSpriteTexture(const CImageInfo &FromImageInfo, const struct CDataSprite *pSprite) override;
|
||||
|
||||
bool IsImageSubFullyTransparent(const CImageInfo &FromImageInfo, int x, int y, int w, int h) override;
|
||||
|
@ -963,13 +960,11 @@ public:
|
|||
// simple uncompressed RGBA loaders
|
||||
IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int Flags = 0) override;
|
||||
bool LoadPng(CImageInfo &Image, const char *pFilename, int StorageType) override;
|
||||
bool LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName) override;
|
||||
|
||||
bool CheckImageDivisibility(const char *pContextName, CImageInfo &Image, int DivX, int DivY, bool AllowResize) override;
|
||||
bool IsImageFormatRgba(const char *pContextName, const CImageInfo &Image) override;
|
||||
|
||||
void CopyTextureBufferSub(uint8_t *pDestBuffer, const CImageInfo &SourceImage, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) override;
|
||||
void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, const CImageInfo &SourceImage, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) override;
|
||||
|
||||
void TextureSet(CTextureHandle TextureId) override;
|
||||
|
||||
void Clear(float r, float g, float b, bool ForceClearNow = false) override;
|
||||
|
|
120
src/engine/gfx/image.cpp
Normal file
120
src/engine/gfx/image.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include <base/system.h>
|
||||
|
||||
#include <engine/image.h>
|
||||
|
||||
void CImageInfo::Free()
|
||||
{
|
||||
m_Width = 0;
|
||||
m_Height = 0;
|
||||
m_Format = FORMAT_UNDEFINED;
|
||||
free(m_pData);
|
||||
m_pData = nullptr;
|
||||
}
|
||||
|
||||
size_t CImageInfo::PixelSize(EImageFormat Format)
|
||||
{
|
||||
dbg_assert(Format != FORMAT_UNDEFINED, "Format undefined");
|
||||
static const size_t s_aSizes[] = {3, 4, 1, 2};
|
||||
return s_aSizes[(int)Format];
|
||||
}
|
||||
|
||||
const char *CImageInfo::FormatName(EImageFormat Format)
|
||||
{
|
||||
static const char *s_apNames[] = {"UNDEFINED", "RGBA", "RGB", "R", "RA"};
|
||||
return s_apNames[(int)Format + 1];
|
||||
}
|
||||
|
||||
size_t CImageInfo::PixelSize() const
|
||||
{
|
||||
return PixelSize(m_Format);
|
||||
}
|
||||
|
||||
const char *CImageInfo::FormatName() const
|
||||
{
|
||||
return FormatName(m_Format);
|
||||
}
|
||||
|
||||
size_t CImageInfo::DataSize() const
|
||||
{
|
||||
return m_Width * m_Height * PixelSize(m_Format);
|
||||
}
|
||||
|
||||
bool CImageInfo::DataEquals(const CImageInfo &Other) const
|
||||
{
|
||||
if(m_Width != Other.m_Width || m_Height != Other.m_Height || m_Format != Other.m_Format)
|
||||
return false;
|
||||
if(m_pData == nullptr && Other.m_pData == nullptr)
|
||||
return true;
|
||||
if(m_pData == nullptr || Other.m_pData == nullptr)
|
||||
return false;
|
||||
return mem_comp(m_pData, Other.m_pData, DataSize()) == 0;
|
||||
}
|
||||
|
||||
ColorRGBA CImageInfo::PixelColor(size_t x, size_t y) const
|
||||
{
|
||||
const size_t PixelStartIndex = (x + m_Width * y) * PixelSize();
|
||||
|
||||
ColorRGBA Color;
|
||||
if(m_Format == FORMAT_R)
|
||||
{
|
||||
Color.r = Color.g = Color.b = 1.0f;
|
||||
Color.a = m_pData[PixelStartIndex] / 255.0f;
|
||||
}
|
||||
else if(m_Format == FORMAT_RA)
|
||||
{
|
||||
Color.r = Color.g = Color.b = m_pData[PixelStartIndex] / 255.0f;
|
||||
Color.a = m_pData[PixelStartIndex + 1] / 255.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.r = m_pData[PixelStartIndex + 0] / 255.0f;
|
||||
Color.g = m_pData[PixelStartIndex + 1] / 255.0f;
|
||||
Color.b = m_pData[PixelStartIndex + 2] / 255.0f;
|
||||
if(m_Format == FORMAT_RGBA)
|
||||
{
|
||||
Color.a = m_pData[PixelStartIndex + 3] / 255.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.a = 1.0f;
|
||||
}
|
||||
}
|
||||
return Color;
|
||||
}
|
||||
|
||||
void CImageInfo::SetPixelColor(size_t x, size_t y, ColorRGBA Color) const
|
||||
{
|
||||
const size_t PixelStartIndex = (x + m_Width * y) * PixelSize();
|
||||
|
||||
if(m_Format == FORMAT_R)
|
||||
{
|
||||
m_pData[PixelStartIndex] = round_to_int(Color.a * 255.0f);
|
||||
}
|
||||
else if(m_Format == FORMAT_RA)
|
||||
{
|
||||
m_pData[PixelStartIndex] = round_to_int((Color.r + Color.g + Color.b) / 3.0f * 255.0f);
|
||||
m_pData[PixelStartIndex + 1] = round_to_int(Color.a * 255.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pData[PixelStartIndex + 0] = round_to_int(Color.r * 255.0f);
|
||||
m_pData[PixelStartIndex + 1] = round_to_int(Color.g * 255.0f);
|
||||
m_pData[PixelStartIndex + 2] = round_to_int(Color.b * 255.0f);
|
||||
if(m_Format == FORMAT_RGBA)
|
||||
{
|
||||
m_pData[PixelStartIndex + 3] = round_to_int(Color.a * 255.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CImageInfo::CopyRectFrom(const CImageInfo &SrcImage, size_t SrcX, size_t SrcY, size_t Width, size_t Height, size_t DestX, size_t DestY) const
|
||||
{
|
||||
const size_t PixelSize = SrcImage.PixelSize();
|
||||
const size_t CopySize = Width * PixelSize;
|
||||
for(size_t Y = 0; Y < Height; ++Y)
|
||||
{
|
||||
const size_t SrcOffset = ((SrcY + Y) * SrcImage.m_Width + SrcX) * PixelSize;
|
||||
const size_t DestOffset = ((DestY + Y) * m_Width + DestX) * PixelSize;
|
||||
mem_copy(&m_pData[DestOffset], &SrcImage.m_pData[SrcOffset], CopySize);
|
||||
}
|
||||
}
|
|
@ -1,89 +1,94 @@
|
|||
#include "image_loader.h"
|
||||
|
||||
#include <base/log.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
struct SLibPNGWarningItem
|
||||
bool CByteBufferReader::Read(void *pData, size_t Size)
|
||||
{
|
||||
SImageByteBuffer *m_pByteLoader;
|
||||
const char *m_pFileName;
|
||||
std::jmp_buf m_Buf;
|
||||
};
|
||||
if(m_Error)
|
||||
return false;
|
||||
|
||||
[[noreturn]] static void LibPNGError(png_structp png_ptr, png_const_charp error_msg)
|
||||
{
|
||||
SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
|
||||
pUserStruct->m_pByteLoader->m_Err = -1;
|
||||
dbg_msg("png", "error for file \"%s\": %s", pUserStruct->m_pFileName, error_msg);
|
||||
std::longjmp(pUserStruct->m_Buf, 1);
|
||||
}
|
||||
|
||||
static void LibPNGWarning(png_structp png_ptr, png_const_charp warning_msg)
|
||||
{
|
||||
SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
|
||||
dbg_msg("png", "warning for file \"%s\": %s", pUserStruct->m_pFileName, warning_msg);
|
||||
}
|
||||
|
||||
static bool FileMatchesImageType(SImageByteBuffer &ByteLoader)
|
||||
{
|
||||
if(ByteLoader.m_pvLoadedImageBytes->size() >= 8)
|
||||
return png_sig_cmp((png_bytep)ByteLoader.m_pvLoadedImageBytes->data(), 0, 8) == 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ReadDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToRead)
|
||||
{
|
||||
png_voidp pIO_Ptr = png_get_io_ptr(pPNGStruct);
|
||||
|
||||
SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr;
|
||||
|
||||
if(pByteLoader->m_pvLoadedImageBytes->size() >= pByteLoader->m_LoadOffset + (size_t)ByteCountToRead)
|
||||
if(m_ReadOffset + Size <= m_Size)
|
||||
{
|
||||
mem_copy(pOutBytes, &(*pByteLoader->m_pvLoadedImageBytes)[pByteLoader->m_LoadOffset], (size_t)ByteCountToRead);
|
||||
|
||||
pByteLoader->m_LoadOffset += (size_t)ByteCountToRead;
|
||||
mem_copy(pData, &m_pData[m_ReadOffset], Size);
|
||||
m_ReadOffset += Size;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pByteLoader->m_Err = -1;
|
||||
dbg_msg("png", "could not read bytes, file was too small.");
|
||||
m_Error = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static EImageFormat LibPNGGetImageFormat(int ColorChannelCount)
|
||||
void CByteBufferWriter::Write(const void *pData, size_t Size)
|
||||
{
|
||||
if(!Size)
|
||||
return;
|
||||
|
||||
const size_t WriteOffset = m_vBuffer.size();
|
||||
m_vBuffer.resize(WriteOffset + Size);
|
||||
mem_copy(&m_vBuffer[WriteOffset], pData, Size);
|
||||
}
|
||||
|
||||
class CUserErrorStruct
|
||||
{
|
||||
public:
|
||||
CByteBufferReader *m_pReader;
|
||||
const char *m_pContextName;
|
||||
std::jmp_buf m_JmpBuf;
|
||||
};
|
||||
|
||||
[[noreturn]] static void PngErrorCallback(png_structp png_ptr, png_const_charp error_msg)
|
||||
{
|
||||
CUserErrorStruct *pUserStruct = static_cast<CUserErrorStruct *>(png_get_error_ptr(png_ptr));
|
||||
log_error("png", "error for file \"%s\": %s", pUserStruct->m_pContextName, error_msg);
|
||||
std::longjmp(pUserStruct->m_JmpBuf, 1);
|
||||
}
|
||||
|
||||
static void PngWarningCallback(png_structp png_ptr, png_const_charp warning_msg)
|
||||
{
|
||||
CUserErrorStruct *pUserStruct = static_cast<CUserErrorStruct *>(png_get_error_ptr(png_ptr));
|
||||
log_warn("png", "warning for file \"%s\": %s", pUserStruct->m_pContextName, warning_msg);
|
||||
}
|
||||
|
||||
static void PngReadDataCallback(png_structp pPngStruct, png_bytep pOutBytes, png_size_t ByteCountToRead)
|
||||
{
|
||||
CByteBufferReader *pReader = static_cast<CByteBufferReader *>(png_get_io_ptr(pPngStruct));
|
||||
if(!pReader->Read(pOutBytes, ByteCountToRead))
|
||||
{
|
||||
png_error(pPngStruct, "Could not read all bytes, file was too small");
|
||||
}
|
||||
}
|
||||
|
||||
static CImageInfo::EImageFormat ImageFormatFromChannelCount(int ColorChannelCount)
|
||||
{
|
||||
switch(ColorChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return IMAGE_FORMAT_R;
|
||||
return CImageInfo::FORMAT_R;
|
||||
case 2:
|
||||
return IMAGE_FORMAT_RA;
|
||||
return CImageInfo::FORMAT_RA;
|
||||
case 3:
|
||||
return IMAGE_FORMAT_RGB;
|
||||
return CImageInfo::FORMAT_RGB;
|
||||
case 4:
|
||||
return IMAGE_FORMAT_RGBA;
|
||||
return CImageInfo::FORMAT_RGBA;
|
||||
default:
|
||||
dbg_assert(false, "ColorChannelCount invalid");
|
||||
dbg_break();
|
||||
}
|
||||
}
|
||||
|
||||
static void LibPNGDeleteReadStruct(png_structp pPNGStruct, png_infop pPNGInfo)
|
||||
static int PngliteIncompatibility(png_structp pPngStruct, png_infop pPngInfo)
|
||||
{
|
||||
if(pPNGInfo != nullptr)
|
||||
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
}
|
||||
|
||||
static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
|
||||
{
|
||||
int ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
|
||||
int BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
|
||||
int InterlaceType = png_get_interlace_type(pPNGStruct, pPNGInfo);
|
||||
int Result = 0;
|
||||
|
||||
const int ColorType = png_get_color_type(pPngStruct, pPngInfo);
|
||||
switch(ColorType)
|
||||
{
|
||||
case PNG_COLOR_TYPE_GRAY:
|
||||
|
@ -93,9 +98,10 @@ static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
|
|||
break;
|
||||
default:
|
||||
log_debug("png", "color type %d unsupported by pnglite", ColorType);
|
||||
Result |= PNGLITE_COLOR_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_COLOR_TYPE;
|
||||
}
|
||||
|
||||
const int BitDepth = png_get_bit_depth(pPngStruct, pPngInfo);
|
||||
switch(BitDepth)
|
||||
{
|
||||
case 8:
|
||||
|
@ -103,258 +109,294 @@ static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
|
|||
break;
|
||||
default:
|
||||
log_debug("png", "bit depth %d unsupported by pnglite", BitDepth);
|
||||
Result |= PNGLITE_BIT_DEPTH;
|
||||
Result |= CImageLoader::PNGLITE_BIT_DEPTH;
|
||||
}
|
||||
|
||||
const int InterlaceType = png_get_interlace_type(pPngStruct, pPngInfo);
|
||||
if(InterlaceType != PNG_INTERLACE_NONE)
|
||||
{
|
||||
log_debug("png", "interlace type %d unsupported by pnglite", InterlaceType);
|
||||
Result |= PNGLITE_INTERLACE_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_INTERLACE_TYPE;
|
||||
}
|
||||
if(png_get_compression_type(pPNGStruct, pPNGInfo) != PNG_COMPRESSION_TYPE_BASE)
|
||||
|
||||
if(png_get_compression_type(pPngStruct, pPngInfo) != PNG_COMPRESSION_TYPE_BASE)
|
||||
{
|
||||
log_debug("png", "non-default compression type unsupported by pnglite");
|
||||
Result |= PNGLITE_COMPRESSION_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_COMPRESSION_TYPE;
|
||||
}
|
||||
if(png_get_filter_type(pPNGStruct, pPNGInfo) != PNG_FILTER_TYPE_BASE)
|
||||
|
||||
if(png_get_filter_type(pPngStruct, pPngInfo) != PNG_FILTER_TYPE_BASE)
|
||||
{
|
||||
log_debug("png", "non-default filter type unsupported by pnglite");
|
||||
Result |= PNGLITE_FILTER_TYPE;
|
||||
Result |= CImageLoader::PNGLITE_FILTER_TYPE;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool LoadPng(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, size_t &Width, size_t &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat)
|
||||
bool CImageLoader::LoadPng(CByteBufferReader &Reader, const char *pContextName, CImageInfo &Image, int &PngliteIncompatible)
|
||||
{
|
||||
SLibPNGWarningItem UserErrorStruct = {&ByteLoader, pFileName, {}};
|
||||
CUserErrorStruct UserErrorStruct = {&Reader, pContextName, {}};
|
||||
|
||||
png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
|
||||
if(pPNGStruct == nullptr)
|
||||
png_structp pPngStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if(pPngStruct == nullptr)
|
||||
{
|
||||
dbg_msg("png", "libpng internal failure: png_create_read_struct failed.");
|
||||
log_error("png", "libpng internal failure: png_create_read_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
png_infop pPNGInfo = nullptr;
|
||||
png_infop pPngInfo = nullptr;
|
||||
png_bytepp pRowPointers = nullptr;
|
||||
Height = 0; // ensure this is not undefined for the error handler
|
||||
if(setjmp(UserErrorStruct.m_Buf))
|
||||
{
|
||||
int Height = 0; // ensure this is not undefined for the Cleanup function
|
||||
const auto &&Cleanup = [&]() {
|
||||
if(pRowPointers != nullptr)
|
||||
{
|
||||
for(size_t i = 0; i < Height; ++i)
|
||||
for(int y = 0; y < Height; ++y)
|
||||
{
|
||||
delete[] pRowPointers[i];
|
||||
delete[] pRowPointers[y];
|
||||
}
|
||||
}
|
||||
delete[] pRowPointers;
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
return false;
|
||||
}
|
||||
png_set_error_fn(pPNGStruct, &UserErrorStruct, LibPNGError, LibPNGWarning);
|
||||
|
||||
pPNGInfo = png_create_info_struct(pPNGStruct);
|
||||
|
||||
if(pPNGInfo == nullptr)
|
||||
if(pPngInfo != nullptr)
|
||||
{
|
||||
png_destroy_info_struct(pPngStruct, &pPngInfo);
|
||||
}
|
||||
png_destroy_read_struct(&pPngStruct, nullptr, nullptr);
|
||||
};
|
||||
if(setjmp(UserErrorStruct.m_JmpBuf))
|
||||
{
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
dbg_msg("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
png_set_error_fn(pPngStruct, &UserErrorStruct, PngErrorCallback, PngWarningCallback);
|
||||
|
||||
if(!FileMatchesImageType(ByteLoader))
|
||||
pPngInfo = png_create_info_struct(pPngStruct);
|
||||
if(pPngInfo == nullptr)
|
||||
{
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
dbg_msg("png", "file does not match image type.");
|
||||
Cleanup();
|
||||
log_error("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteLoader.m_LoadOffset = 8;
|
||||
|
||||
png_set_read_fn(pPNGStruct, (png_bytep)&ByteLoader, ReadDataFromLoadedBytes);
|
||||
|
||||
png_set_sig_bytes(pPNGStruct, 8);
|
||||
|
||||
png_read_info(pPNGStruct, pPNGInfo);
|
||||
|
||||
if(ByteLoader.m_Err != 0)
|
||||
png_byte aSignature[8];
|
||||
if(!Reader.Read(aSignature, sizeof(aSignature)) || png_sig_cmp(aSignature, 0, sizeof(aSignature)) != 0)
|
||||
{
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
dbg_msg("png", "byte loader error.");
|
||||
Cleanup();
|
||||
log_error("png", "file is not a valid PNG file (signature mismatch).");
|
||||
return false;
|
||||
}
|
||||
|
||||
Width = png_get_image_width(pPNGStruct, pPNGInfo);
|
||||
Height = png_get_image_height(pPNGStruct, pPNGInfo);
|
||||
const int ColorType = png_get_color_type(pPNGStruct, pPNGInfo);
|
||||
const png_byte BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo);
|
||||
PngliteIncompatible = PngliteIncompatibility(pPNGStruct, pPNGInfo);
|
||||
png_set_read_fn(pPngStruct, (png_bytep)&Reader, PngReadDataCallback);
|
||||
png_set_sig_bytes(pPngStruct, sizeof(aSignature));
|
||||
|
||||
png_read_info(pPngStruct, pPngInfo);
|
||||
|
||||
if(Reader.Error())
|
||||
{
|
||||
// error already logged
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
const int Width = png_get_image_width(pPngStruct, pPngInfo);
|
||||
Height = png_get_image_height(pPngStruct, pPngInfo);
|
||||
const png_byte BitDepth = png_get_bit_depth(pPngStruct, pPngInfo);
|
||||
const int ColorType = png_get_color_type(pPngStruct, pPngInfo);
|
||||
|
||||
if(Width == 0 || Height == 0)
|
||||
{
|
||||
log_error("png", "image has width (%d) or height (%d) of 0.", Width, Height);
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(BitDepth == 16)
|
||||
{
|
||||
png_set_strip_16(pPNGStruct);
|
||||
png_set_strip_16(pPngStruct);
|
||||
}
|
||||
else if(BitDepth > 8)
|
||||
else if(BitDepth > 8 || BitDepth == 0)
|
||||
{
|
||||
dbg_msg("png", "non supported bit depth.");
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Width == 0 || Height == 0 || BitDepth == 0)
|
||||
{
|
||||
dbg_msg("png", "image had width, height or bit depth of 0.");
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
log_error("png", "bit depth %d not supported.", BitDepth);
|
||||
Cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ColorType == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(pPNGStruct);
|
||||
{
|
||||
png_set_palette_to_rgb(pPngStruct);
|
||||
}
|
||||
|
||||
if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(pPNGStruct);
|
||||
{
|
||||
png_set_expand_gray_1_2_4_to_8(pPngStruct);
|
||||
}
|
||||
|
||||
if(png_get_valid(pPNGStruct, pPNGInfo, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(pPNGStruct);
|
||||
if(png_get_valid(pPngStruct, pPngInfo, PNG_INFO_tRNS))
|
||||
{
|
||||
png_set_tRNS_to_alpha(pPngStruct);
|
||||
}
|
||||
|
||||
png_read_update_info(pPNGStruct, pPNGInfo);
|
||||
png_read_update_info(pPngStruct, pPngInfo);
|
||||
|
||||
const size_t ColorChannelCount = png_get_channels(pPNGStruct, pPNGInfo);
|
||||
const size_t BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo);
|
||||
const int ColorChannelCount = png_get_channels(pPngStruct, pPngInfo);
|
||||
const int BytesInRow = png_get_rowbytes(pPngStruct, pPngInfo);
|
||||
dbg_assert(BytesInRow == Width * ColorChannelCount, "bytes in row incorrect.");
|
||||
|
||||
pRowPointers = new png_bytep[Height];
|
||||
for(size_t y = 0; y < Height; ++y)
|
||||
for(int y = 0; y < Height; ++y)
|
||||
{
|
||||
pRowPointers[y] = new png_byte[BytesInRow];
|
||||
}
|
||||
|
||||
png_read_image(pPNGStruct, pRowPointers);
|
||||
png_read_image(pPngStruct, pRowPointers);
|
||||
|
||||
if(ByteLoader.m_Err == 0)
|
||||
pImageBuff = (uint8_t *)malloc(Height * Width * ColorChannelCount * sizeof(uint8_t));
|
||||
|
||||
for(size_t i = 0; i < Height; ++i)
|
||||
if(!Reader.Error())
|
||||
{
|
||||
if(ByteLoader.m_Err == 0)
|
||||
mem_copy(&pImageBuff[i * BytesInRow], pRowPointers[i], BytesInRow);
|
||||
delete[] pRowPointers[i];
|
||||
Image.m_Width = Width;
|
||||
Image.m_Height = Height;
|
||||
Image.m_Format = ImageFormatFromChannelCount(ColorChannelCount);
|
||||
Image.m_pData = static_cast<uint8_t *>(malloc(Image.DataSize()));
|
||||
for(int y = 0; y < Height; ++y)
|
||||
{
|
||||
mem_copy(&Image.m_pData[y * BytesInRow], pRowPointers[y], BytesInRow);
|
||||
}
|
||||
PngliteIncompatible = PngliteIncompatibility(pPngStruct, pPngInfo);
|
||||
}
|
||||
delete[] pRowPointers;
|
||||
pRowPointers = nullptr;
|
||||
|
||||
if(ByteLoader.m_Err != 0)
|
||||
Cleanup();
|
||||
|
||||
return !Reader.Error();
|
||||
}
|
||||
|
||||
bool CImageLoader::LoadPng(IOHANDLE File, const char *pFilename, CImageInfo &Image, int &PngliteIncompatible)
|
||||
{
|
||||
if(!File)
|
||||
{
|
||||
LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
|
||||
dbg_msg("png", "byte loader error.");
|
||||
log_error("png", "failed to open file for reading. filename='%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageFormat = LibPNGGetImageFormat(ColorChannelCount);
|
||||
void *pFileData;
|
||||
unsigned FileDataSize;
|
||||
io_read_all(File, &pFileData, &FileDataSize);
|
||||
io_close(File);
|
||||
|
||||
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
CByteBufferReader ImageReader(static_cast<const uint8_t *>(pFileData), FileDataSize);
|
||||
|
||||
const bool LoadResult = CImageLoader::LoadPng(ImageReader, pFilename, Image, PngliteIncompatible);
|
||||
free(pFileData);
|
||||
if(!LoadResult)
|
||||
{
|
||||
log_error("png", "failed to load image from file. filename='%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Image.m_Format != CImageInfo::FORMAT_RGB && Image.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
log_error("png", "image has unsupported format. filename='%s' format='%s'", pFilename, Image.FormatName());
|
||||
Image.Free();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WriteDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite)
|
||||
static void PngWriteDataCallback(png_structp pPngStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite)
|
||||
{
|
||||
if(ByteCountToWrite > 0)
|
||||
{
|
||||
png_voidp pIO_Ptr = png_get_io_ptr(pPNGStruct);
|
||||
|
||||
SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr;
|
||||
|
||||
size_t NewSize = pByteLoader->m_LoadOffset + (size_t)ByteCountToWrite;
|
||||
pByteLoader->m_pvLoadedImageBytes->resize(NewSize);
|
||||
|
||||
mem_copy(&(*pByteLoader->m_pvLoadedImageBytes)[pByteLoader->m_LoadOffset], pOutBytes, (size_t)ByteCountToWrite);
|
||||
pByteLoader->m_LoadOffset = NewSize;
|
||||
}
|
||||
CByteBufferWriter *pWriter = static_cast<CByteBufferWriter *>(png_get_io_ptr(pPngStruct));
|
||||
pWriter->Write(pOutBytes, ByteCountToWrite);
|
||||
}
|
||||
|
||||
static void FlushPNGWrite(png_structp png_ptr) {}
|
||||
static void PngOutputFlushCallback(png_structp png_ptr)
|
||||
{
|
||||
// no need to flush memory buffer
|
||||
}
|
||||
|
||||
static size_t ImageLoaderHelperFormatToColorChannel(EImageFormat Format)
|
||||
static int PngColorTypeFromFormat(CImageInfo::EImageFormat Format)
|
||||
{
|
||||
switch(Format)
|
||||
{
|
||||
case IMAGE_FORMAT_R:
|
||||
return 1;
|
||||
case IMAGE_FORMAT_RA:
|
||||
return 2;
|
||||
case IMAGE_FORMAT_RGB:
|
||||
return 3;
|
||||
case IMAGE_FORMAT_RGBA:
|
||||
return 4;
|
||||
case CImageInfo::FORMAT_R:
|
||||
return PNG_COLOR_TYPE_GRAY;
|
||||
case CImageInfo::FORMAT_RA:
|
||||
return PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
case CImageInfo::FORMAT_RGB:
|
||||
return PNG_COLOR_TYPE_RGB;
|
||||
case CImageInfo::FORMAT_RGBA:
|
||||
return PNG_COLOR_TYPE_RGBA;
|
||||
default:
|
||||
dbg_assert(false, "Format invalid");
|
||||
dbg_break();
|
||||
}
|
||||
}
|
||||
|
||||
bool SavePng(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, size_t Width, size_t Height)
|
||||
bool CImageLoader::SavePng(CByteBufferWriter &Writer, const CImageInfo &Image)
|
||||
{
|
||||
png_structp pPNGStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
|
||||
if(pPNGStruct == nullptr)
|
||||
png_structp pPngStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if(pPngStruct == nullptr)
|
||||
{
|
||||
dbg_msg("png", "libpng internal failure: png_create_write_struct failed.");
|
||||
log_error("png", "libpng internal failure: png_create_write_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
png_infop pPNGInfo = png_create_info_struct(pPNGStruct);
|
||||
|
||||
if(pPNGInfo == nullptr)
|
||||
png_infop pPngInfo = png_create_info_struct(pPngStruct);
|
||||
if(pPngInfo == nullptr)
|
||||
{
|
||||
png_destroy_read_struct(&pPNGStruct, nullptr, nullptr);
|
||||
dbg_msg("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
png_destroy_read_struct(&pPngStruct, nullptr, nullptr);
|
||||
log_error("png", "libpng internal failure: png_create_info_struct failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
WrittenBytes.m_LoadOffset = 0;
|
||||
WrittenBytes.m_pvLoadedImageBytes->clear();
|
||||
png_set_write_fn(pPngStruct, (png_bytep)&Writer, PngWriteDataCallback, PngOutputFlushCallback);
|
||||
|
||||
png_set_write_fn(pPNGStruct, (png_bytep)&WrittenBytes, WriteDataFromLoadedBytes, FlushPNGWrite);
|
||||
png_set_IHDR(pPngStruct, pPngInfo, Image.m_Width, Image.m_Height, 8, PngColorTypeFromFormat(Image.m_Format), PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
png_write_info(pPngStruct, pPngInfo);
|
||||
|
||||
int ColorType = PNG_COLOR_TYPE_RGB;
|
||||
size_t WriteBytesPerPixel = ImageLoaderHelperFormatToColorChannel(ImageFormat);
|
||||
if(ImageFormat == IMAGE_FORMAT_R)
|
||||
{
|
||||
ColorType = PNG_COLOR_TYPE_GRAY;
|
||||
}
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA)
|
||||
{
|
||||
ColorType = PNG_COLOR_TYPE_RGBA;
|
||||
}
|
||||
|
||||
png_set_IHDR(pPNGStruct, pPNGInfo, Width, Height, 8, ColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
|
||||
png_write_info(pPNGStruct, pPNGInfo);
|
||||
|
||||
png_bytepp pRowPointers = new png_bytep[Height];
|
||||
size_t WidthBytes = Width * WriteBytesPerPixel;
|
||||
png_bytepp pRowPointers = new png_bytep[Image.m_Height];
|
||||
const int WidthBytes = Image.m_Width * Image.PixelSize();
|
||||
ptrdiff_t BufferOffset = 0;
|
||||
for(size_t y = 0; y < Height; ++y)
|
||||
for(size_t y = 0; y < Image.m_Height; ++y)
|
||||
{
|
||||
pRowPointers[y] = new png_byte[WidthBytes];
|
||||
mem_copy(pRowPointers[y], pRawBuffer + BufferOffset, WidthBytes);
|
||||
mem_copy(pRowPointers[y], Image.m_pData + BufferOffset, WidthBytes);
|
||||
BufferOffset += (ptrdiff_t)WidthBytes;
|
||||
}
|
||||
png_write_image(pPNGStruct, pRowPointers);
|
||||
png_write_image(pPngStruct, pRowPointers);
|
||||
png_write_end(pPngStruct, pPngInfo);
|
||||
|
||||
png_write_end(pPNGStruct, pPNGInfo);
|
||||
|
||||
for(size_t y = 0; y < Height; ++y)
|
||||
for(size_t y = 0; y < Image.m_Height; ++y)
|
||||
{
|
||||
delete[](pRowPointers[y]);
|
||||
delete[] pRowPointers[y];
|
||||
}
|
||||
delete[](pRowPointers);
|
||||
delete[] pRowPointers;
|
||||
|
||||
png_destroy_info_struct(pPNGStruct, &pPNGInfo);
|
||||
png_destroy_write_struct(&pPNGStruct, nullptr);
|
||||
png_destroy_info_struct(pPngStruct, &pPngInfo);
|
||||
png_destroy_write_struct(&pPngStruct, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CImageLoader::SavePng(IOHANDLE File, const char *pFilename, const CImageInfo &Image)
|
||||
{
|
||||
if(!File)
|
||||
{
|
||||
log_error("png", "failed to open file for writing. filename='%s'", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
CByteBufferWriter Writer;
|
||||
if(!CImageLoader::SavePng(Writer, Image))
|
||||
{
|
||||
// error already logged
|
||||
io_close(File);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool WriteSuccess = io_write(File, Writer.Data(), Writer.Size()) == Writer.Size();
|
||||
if(!WriteSuccess)
|
||||
{
|
||||
log_error("png", "failed to write PNG data to file. filename='%s'", pFilename);
|
||||
}
|
||||
io_close(File);
|
||||
return WriteSuccess;
|
||||
}
|
||||
|
|
|
@ -1,38 +1,57 @@
|
|||
#ifndef ENGINE_GFX_IMAGE_LOADER_H
|
||||
#define ENGINE_GFX_IMAGE_LOADER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <base/types.h>
|
||||
|
||||
#include <engine/image.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
enum EImageFormat
|
||||
class CByteBufferReader
|
||||
{
|
||||
IMAGE_FORMAT_R = 0,
|
||||
IMAGE_FORMAT_RA,
|
||||
IMAGE_FORMAT_RGB,
|
||||
IMAGE_FORMAT_RGBA,
|
||||
const uint8_t *m_pData;
|
||||
size_t m_Size;
|
||||
size_t m_ReadOffset = 0;
|
||||
bool m_Error = false;
|
||||
|
||||
public:
|
||||
CByteBufferReader(const uint8_t *pData, size_t Size) :
|
||||
m_pData(pData),
|
||||
m_Size(Size) {}
|
||||
|
||||
bool Read(void *pData, size_t Size);
|
||||
bool Error() const { return m_Error; }
|
||||
};
|
||||
|
||||
typedef std::vector<uint8_t> TImageByteBuffer;
|
||||
struct SImageByteBuffer
|
||||
class CByteBufferWriter
|
||||
{
|
||||
SImageByteBuffer(std::vector<uint8_t> *pvBuff) :
|
||||
m_LoadOffset(0), m_pvLoadedImageBytes(pvBuff), m_Err(0) {}
|
||||
size_t m_LoadOffset;
|
||||
std::vector<uint8_t> *m_pvLoadedImageBytes;
|
||||
int m_Err;
|
||||
std::vector<uint8_t> m_vBuffer;
|
||||
|
||||
public:
|
||||
void Write(const void *pData, size_t Size);
|
||||
const uint8_t *Data() const { return m_vBuffer.data(); }
|
||||
size_t Size() const { return m_vBuffer.size(); }
|
||||
};
|
||||
|
||||
enum
|
||||
class CImageLoader
|
||||
{
|
||||
PNGLITE_COLOR_TYPE = 1 << 0,
|
||||
PNGLITE_BIT_DEPTH = 1 << 1,
|
||||
PNGLITE_INTERLACE_TYPE = 1 << 2,
|
||||
PNGLITE_COMPRESSION_TYPE = 1 << 3,
|
||||
PNGLITE_FILTER_TYPE = 1 << 4,
|
||||
};
|
||||
CImageLoader() = delete;
|
||||
|
||||
bool LoadPng(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, size_t &Width, size_t &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat);
|
||||
bool SavePng(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, size_t Width, size_t Height);
|
||||
public:
|
||||
enum
|
||||
{
|
||||
PNGLITE_COLOR_TYPE = 1 << 0,
|
||||
PNGLITE_BIT_DEPTH = 1 << 1,
|
||||
PNGLITE_INTERLACE_TYPE = 1 << 2,
|
||||
PNGLITE_COMPRESSION_TYPE = 1 << 3,
|
||||
PNGLITE_FILTER_TYPE = 1 << 4,
|
||||
};
|
||||
|
||||
static bool LoadPng(CByteBufferReader &Reader, const char *pContextName, CImageInfo &Image, int &PngliteIncompatible);
|
||||
static bool LoadPng(IOHANDLE File, const char *pFilename, CImageInfo &Image, int &PngliteIncompatible);
|
||||
|
||||
static bool SavePng(CByteBufferWriter &Writer, const CImageInfo &Image);
|
||||
static bool SavePng(IOHANDLE File, const char *pFilename, const CImageInfo &Image);
|
||||
};
|
||||
|
||||
#endif // ENGINE_GFX_IMAGE_LOADER_H
|
||||
|
|
|
@ -1,7 +1,88 @@
|
|||
#include "image_manipulation.h"
|
||||
|
||||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
|
||||
bool ConvertToRgba(uint8_t *pDest, const CImageInfo &SourceImage)
|
||||
{
|
||||
if(SourceImage.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
mem_copy(pDest, SourceImage.m_pData, SourceImage.DataSize());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t SrcChannelCount = CImageInfo::PixelSize(SourceImage.m_Format);
|
||||
const size_t DstChannelCount = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA);
|
||||
for(size_t Y = 0; Y < SourceImage.m_Height; ++Y)
|
||||
{
|
||||
for(size_t X = 0; X < SourceImage.m_Width; ++X)
|
||||
{
|
||||
size_t ImgOffsetSrc = (Y * SourceImage.m_Width * SrcChannelCount) + (X * SrcChannelCount);
|
||||
size_t ImgOffsetDest = (Y * SourceImage.m_Width * DstChannelCount) + (X * DstChannelCount);
|
||||
if(SourceImage.m_Format == CImageInfo::FORMAT_RGB)
|
||||
{
|
||||
mem_copy(&pDest[ImgOffsetDest], &SourceImage.m_pData[ImgOffsetSrc], SrcChannelCount);
|
||||
pDest[ImgOffsetDest + 3] = 255;
|
||||
}
|
||||
else if(SourceImage.m_Format == CImageInfo::FORMAT_RA)
|
||||
{
|
||||
pDest[ImgOffsetDest + 0] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
pDest[ImgOffsetDest + 1] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
pDest[ImgOffsetDest + 2] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
pDest[ImgOffsetDest + 3] = SourceImage.m_pData[ImgOffsetSrc + 1];
|
||||
}
|
||||
else if(SourceImage.m_Format == CImageInfo::FORMAT_R)
|
||||
{
|
||||
pDest[ImgOffsetDest + 0] = 255;
|
||||
pDest[ImgOffsetDest + 1] = 255;
|
||||
pDest[ImgOffsetDest + 2] = 255;
|
||||
pDest[ImgOffsetDest + 3] = SourceImage.m_pData[ImgOffsetSrc];
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_assert(false, "SourceImage.m_Format invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConvertToRgbaAlloc(uint8_t *&pDest, const CImageInfo &SourceImage)
|
||||
{
|
||||
pDest = static_cast<uint8_t *>(malloc(SourceImage.m_Width * SourceImage.m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
return ConvertToRgba(pDest, SourceImage);
|
||||
}
|
||||
|
||||
bool ConvertToRgba(CImageInfo &Image)
|
||||
{
|
||||
if(Image.m_Format == CImageInfo::FORMAT_RGBA)
|
||||
return true;
|
||||
|
||||
uint8_t *pRgbaData;
|
||||
ConvertToRgbaAlloc(pRgbaData, Image);
|
||||
free(Image.m_pData);
|
||||
Image.m_pData = pRgbaData;
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConvertToGrayscale(const CImageInfo &Image)
|
||||
{
|
||||
if(Image.m_Format == CImageInfo::FORMAT_R || Image.m_Format == CImageInfo::FORMAT_RA)
|
||||
return;
|
||||
|
||||
const size_t Step = Image.PixelSize();
|
||||
for(size_t i = 0; i < Image.m_Width * Image.m_Height; ++i)
|
||||
{
|
||||
const int Average = (Image.m_pData[i * Step] + Image.m_pData[i * Step + 1] + Image.m_pData[i * Step + 2]) / 3;
|
||||
Image.m_pData[i * Step] = Average;
|
||||
Image.m_pData[i * Step + 1] = Average;
|
||||
Image.m_pData[i * Step + 2] = Average;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr int DILATE_BPP = 4; // RGBA assumed
|
||||
static constexpr uint8_t DILATE_ALPHA_THRESHOLD = 10;
|
||||
|
||||
|
@ -70,6 +151,12 @@ void DilateImage(uint8_t *pImageBuff, int w, int h)
|
|||
DilateImageSub(pImageBuff, w, h, 0, 0, w, h);
|
||||
}
|
||||
|
||||
void DilateImage(const CImageInfo &Image)
|
||||
{
|
||||
dbg_assert(Image.m_Format == CImageInfo::FORMAT_RGBA, "Dilate requires RGBA format");
|
||||
DilateImage(Image.m_pData, Image.m_Width, Image.m_Height);
|
||||
}
|
||||
|
||||
void DilateImageSub(uint8_t *pImageBuff, int w, int h, int x, int y, int sw, int sh)
|
||||
{
|
||||
uint8_t *apBuffer[2] = {nullptr, nullptr};
|
||||
|
@ -181,6 +268,15 @@ uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWi
|
|||
return pTmpData;
|
||||
}
|
||||
|
||||
void ResizeImage(CImageInfo &Image, int NewWidth, int NewHeight)
|
||||
{
|
||||
uint8_t *pNewData = ResizeImage(Image.m_pData, Image.m_Width, Image.m_Height, NewWidth, NewHeight, Image.PixelSize());
|
||||
free(Image.m_pData);
|
||||
Image.m_pData = pNewData;
|
||||
Image.m_Width = NewWidth;
|
||||
Image.m_Height = NewHeight;
|
||||
}
|
||||
|
||||
int HighestBit(int OfVar)
|
||||
{
|
||||
if(!OfVar)
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
#ifndef ENGINE_GFX_IMAGE_MANIPULATION_H
|
||||
#define ENGINE_GFX_IMAGE_MANIPULATION_H
|
||||
|
||||
#include <engine/image.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Destination must have appropriate size for RGBA data
|
||||
bool ConvertToRgba(uint8_t *pDest, const CImageInfo &SourceImage);
|
||||
// Allocates appropriate buffer with malloc, must be freed by caller
|
||||
bool ConvertToRgbaAlloc(uint8_t *&pDest, const CImageInfo &SourceImage);
|
||||
// Replaces existing image data with RGBA data (unless already RGBA)
|
||||
bool ConvertToRgba(CImageInfo &Image);
|
||||
|
||||
// Changes the image data (not the format)
|
||||
void ConvertToGrayscale(const CImageInfo &Image);
|
||||
|
||||
// These functions assume that the image data is 4 bytes per pixel RGBA
|
||||
void DilateImage(uint8_t *pImageBuff, int w, int h);
|
||||
void DilateImage(const CImageInfo &Image);
|
||||
void DilateImageSub(uint8_t *pImageBuff, int w, int h, int x, int y, int sw, int sh);
|
||||
|
||||
// returned pointer is allocated with malloc
|
||||
// Returned buffer is allocated with malloc, must be freed by caller
|
||||
uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
|
||||
// Replaces existing image data with resized buffer
|
||||
void ResizeImage(CImageInfo &Image, int NewWidth, int NewHeight);
|
||||
|
||||
int HighestBit(int OfVar);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#ifndef ENGINE_GRAPHICS_H
|
||||
#define ENGINE_GRAPHICS_H
|
||||
|
||||
#include "image.h"
|
||||
#include "kernel.h"
|
||||
#include "warning.h"
|
||||
|
||||
|
@ -64,68 +65,6 @@ struct SGraphicTileTexureCoords
|
|||
ubvec4 m_TexCoordBottomLeft;
|
||||
};
|
||||
|
||||
class CImageInfo
|
||||
{
|
||||
public:
|
||||
enum EImageFormat
|
||||
{
|
||||
FORMAT_ERROR = -1,
|
||||
FORMAT_RGB = 0,
|
||||
FORMAT_RGBA = 1,
|
||||
FORMAT_SINGLE_COMPONENT = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the width of the image
|
||||
*/
|
||||
size_t m_Width = 0;
|
||||
|
||||
/**
|
||||
* Contains the height of the image
|
||||
*/
|
||||
size_t m_Height = 0;
|
||||
|
||||
/**
|
||||
* Contains the format of the image.
|
||||
*
|
||||
* @see EImageFormat
|
||||
*/
|
||||
EImageFormat m_Format = FORMAT_ERROR;
|
||||
|
||||
/**
|
||||
* Pointer to the image data.
|
||||
*/
|
||||
uint8_t *m_pData = nullptr;
|
||||
|
||||
void Free()
|
||||
{
|
||||
m_Width = 0;
|
||||
m_Height = 0;
|
||||
m_Format = FORMAT_ERROR;
|
||||
free(m_pData);
|
||||
m_pData = nullptr;
|
||||
}
|
||||
|
||||
static size_t PixelSize(EImageFormat Format)
|
||||
{
|
||||
dbg_assert(Format != FORMAT_ERROR, "Format invalid");
|
||||
static const size_t s_aSizes[] = {3, 4, 1};
|
||||
return s_aSizes[(int)Format];
|
||||
}
|
||||
|
||||
size_t PixelSize() const
|
||||
{
|
||||
return PixelSize(m_Format);
|
||||
}
|
||||
|
||||
size_t DataSize() const
|
||||
{
|
||||
return m_Width * m_Height * PixelSize(m_Format);
|
||||
}
|
||||
};
|
||||
|
||||
bool ConvertToRGBA(uint8_t *pDest, const CImageInfo &SrcImage);
|
||||
|
||||
/*
|
||||
Structure: CVideoMode
|
||||
*/
|
||||
|
@ -330,16 +269,11 @@ public:
|
|||
virtual const TTwGraphicsGpuList &GetGpus() const = 0;
|
||||
|
||||
virtual bool LoadPng(CImageInfo &Image, const char *pFilename, int StorageType) = 0;
|
||||
virtual bool LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName) = 0;
|
||||
|
||||
virtual bool CheckImageDivisibility(const char *pContextName, CImageInfo &Image, int DivX, int DivY, bool AllowResize) = 0;
|
||||
virtual bool IsImageFormatRgba(const char *pContextName, const CImageInfo &Image) = 0;
|
||||
|
||||
// destination and source buffer require to have the same width and height
|
||||
virtual void CopyTextureBufferSub(uint8_t *pDestBuffer, const CImageInfo &SourceImage, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) = 0;
|
||||
|
||||
// destination width must be equal to the subwidth of the source
|
||||
virtual void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, const CImageInfo &SourceImage, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) = 0;
|
||||
|
||||
virtual void UnloadTexture(CTextureHandle *pIndex) = 0;
|
||||
virtual CTextureHandle LoadTextureRaw(const CImageInfo &Image, int Flags, const char *pTexName = nullptr) = 0;
|
||||
virtual CTextureHandle LoadTextureRawMove(CImageInfo &Image, int Flags, const char *pTexName = nullptr) = 0;
|
||||
|
|
137
src/engine/image.h
Normal file
137
src/engine/image.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
#ifndef ENGINE_IMAGE_H
|
||||
#define ENGINE_IMAGE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <base/color.h>
|
||||
|
||||
/**
|
||||
* Represents an image that has been loaded into main memory.
|
||||
*/
|
||||
class CImageInfo
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Defines the format of image data.
|
||||
*/
|
||||
enum EImageFormat
|
||||
{
|
||||
FORMAT_UNDEFINED = -1,
|
||||
FORMAT_RGB = 0,
|
||||
FORMAT_RGBA = 1,
|
||||
FORMAT_R = 2,
|
||||
FORMAT_RA = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Width of the image.
|
||||
*/
|
||||
size_t m_Width = 0;
|
||||
|
||||
/**
|
||||
* Height of the image.
|
||||
*/
|
||||
size_t m_Height = 0;
|
||||
|
||||
/**
|
||||
* Format of the image.
|
||||
*
|
||||
* @see EImageFormat
|
||||
*/
|
||||
EImageFormat m_Format = FORMAT_UNDEFINED;
|
||||
|
||||
/**
|
||||
* Pointer to the image data.
|
||||
*/
|
||||
uint8_t *m_pData = nullptr;
|
||||
|
||||
/**
|
||||
* Frees the image data and clears all info.
|
||||
*/
|
||||
void Free();
|
||||
|
||||
/**
|
||||
* Returns the pixel size in bytes for the given image format.
|
||||
*
|
||||
* @param Format Image format, must not be `FORMAT_UNDEFINED`.
|
||||
*
|
||||
* @return Size of one pixel with the given image format.
|
||||
*/
|
||||
static size_t PixelSize(EImageFormat Format);
|
||||
|
||||
/**
|
||||
* Returns a readable name for the given image format.
|
||||
*
|
||||
* @param Format Image format.
|
||||
*
|
||||
* @return Readable name for the given image format.
|
||||
*/
|
||||
static const char *FormatName(EImageFormat Format);
|
||||
|
||||
/**
|
||||
* Returns the pixel size in bytes for the format of this image.
|
||||
*
|
||||
* @return Size of one pixel with the format of this image.
|
||||
*
|
||||
* @remark The format must not be `FORMAT_UNDEFINED`.
|
||||
*/
|
||||
size_t PixelSize() const;
|
||||
|
||||
/**
|
||||
* Returns a readable name for the format of this image.
|
||||
*
|
||||
* @return Readable name for the format of this image.
|
||||
*/
|
||||
const char *FormatName() const;
|
||||
|
||||
/**
|
||||
* Returns the size of the data, as derived from the width, height and pixel size.
|
||||
*
|
||||
* @return Expected size of the image data.
|
||||
*/
|
||||
size_t DataSize() const;
|
||||
|
||||
/**
|
||||
* Returns whether this image is equal to the given image
|
||||
* in width, height, format and data.
|
||||
*
|
||||
* @param Other The image to compare with.
|
||||
*
|
||||
* @return `true` if the images are identical, `false` otherwise.
|
||||
*/
|
||||
bool DataEquals(const CImageInfo &Other) const;
|
||||
|
||||
/**
|
||||
* Returns the color of the pixel at the specified position.
|
||||
*
|
||||
* @param x The x-coordinate to read from.
|
||||
* @param y The y-coordinate to read from.
|
||||
*
|
||||
* @return Pixel color converted to normalized RGBA.
|
||||
*/
|
||||
ColorRGBA PixelColor(size_t x, size_t y) const;
|
||||
|
||||
/**
|
||||
* Sets the color of the pixel at the specified position.
|
||||
*
|
||||
* @param x The x-coordinate to write to.
|
||||
* @param y The y-coordinate to write to.
|
||||
* @param Color The normalized RGBA color to write.
|
||||
*/
|
||||
void SetPixelColor(size_t x, size_t y, ColorRGBA Color) const;
|
||||
|
||||
/**
|
||||
* Copies a rectangle of image data from the given image to this image.
|
||||
*
|
||||
* @param SrcImage The image to copy data from.
|
||||
* @param SrcX The x-offset in the source image.
|
||||
* @param SrcY The y-offset in the source image.
|
||||
* @param Width The width of the rectangle to copy.
|
||||
* @param Height The height of the rectangle to copy.
|
||||
* @param DestX The x-offset in the destination image (this).
|
||||
* @param DestY The y-offset in the destination image (this).
|
||||
*/
|
||||
void CopyRectFrom(const CImageInfo &SrcImage, size_t SrcX, size_t SrcY, size_t Width, size_t Height, size_t DestX, size_t DestY) const;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -301,7 +301,7 @@ IGraphics::CTextureHandle CMapImages::GetEntities(EMapImageEntityLayerType Entit
|
|||
const size_t CopyHeight = ImgInfo.m_Height / 16;
|
||||
const size_t OffsetX = (size_t)(TileIndex % 16) * CopyWidth;
|
||||
const size_t OffsetY = (size_t)(TileIndex / 16) * CopyHeight;
|
||||
Graphics()->CopyTextureBufferSub(BuildImageInfo.m_pData, ImgInfo, OffsetX, OffsetY, CopyWidth, CopyHeight);
|
||||
BuildImageInfo.CopyRectFrom(ImgInfo, OffsetX, OffsetY, CopyWidth, CopyHeight, OffsetX, OffsetY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <engine/config.h>
|
||||
#include <engine/editor.h>
|
||||
#include <engine/friends.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/keys.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
|
@ -2396,16 +2397,7 @@ int CMenus::MenuImageScan(const char *pName, int IsDir, int DirType, void *pUser
|
|||
|
||||
MenuImage.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aPath);
|
||||
|
||||
// create gray scale version
|
||||
unsigned char *pData = static_cast<unsigned char *>(Info.m_pData);
|
||||
const size_t Step = Info.PixelSize();
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
int v = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||||
pData[i * Step] = v;
|
||||
pData[i * Step + 1] = v;
|
||||
pData[i * Step + 2] = v;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
MenuImage.m_GreyTexture = pSelf->Graphics()->LoadTextureRawMove(Info, 0, aPath);
|
||||
|
||||
str_truncate(MenuImage.m_aName, sizeof(MenuImage.m_aName), pName, str_length(pName) - str_length(pExtension));
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <engine/engine.h>
|
||||
#include <engine/favorites.h>
|
||||
#include <engine/friends.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/keys.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
#include <engine/shared/config.h>
|
||||
|
@ -1992,16 +1993,7 @@ void CMenus::LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &Info,
|
|||
CommunityIcon.m_Sha256 = Sha256;
|
||||
CommunityIcon.m_OrgTexture = Graphics()->LoadTextureRaw(Info, 0, pCommunityId);
|
||||
|
||||
// create gray scale version
|
||||
unsigned char *pData = static_cast<unsigned char *>(Info.m_pData);
|
||||
const size_t Step = Info.PixelSize();
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
int v = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||||
pData[i * Step] = v;
|
||||
pData[i * Step + 1] = v;
|
||||
pData[i * Step + 2] = v;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
CommunityIcon.m_GreyTexture = Graphics()->LoadTextureRawMove(Info, 0, pCommunityId);
|
||||
|
||||
auto ExistingIcon = std::find_if(m_vCommunityIcons.begin(), m_vCommunityIcons.end(), [pCommunityId](const SCommunityIcon &Element) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <base/system.h>
|
||||
|
||||
#include <engine/engine.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/storage.h>
|
||||
|
@ -237,14 +238,7 @@ const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info)
|
|||
// get feet outline size
|
||||
CheckMetrics(Skin.m_Metrics.m_Feet, pData, Pitch, FeetOutlineOffsetX, FeetOutlineOffsetY, FeetOutlineWidth, FeetOutlineHeight);
|
||||
|
||||
// make the texture gray scale
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
int v = (pData[i * PixelStep] + pData[i * PixelStep + 1] + pData[i * PixelStep + 2]) / 3;
|
||||
pData[i * PixelStep] = v;
|
||||
pData[i * PixelStep + 1] = v;
|
||||
pData[i * PixelStep + 2] = v;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
|
||||
int aFreq[256] = {0};
|
||||
int OrgWeight = 0;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <base/system.h>
|
||||
|
||||
#include <engine/external/json-parser/json.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/config.h>
|
||||
#include <engine/shared/jsonwriter.h>
|
||||
|
@ -92,14 +93,7 @@ int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser
|
|||
Part.m_BloodColor = ColorRGBA(normalize(vec3(aColors[0], aColors[1], aColors[2])));
|
||||
}
|
||||
|
||||
// create colorless version
|
||||
for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||||
{
|
||||
const int Average = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||||
pData[i * Step] = Average;
|
||||
pData[i * Step + 1] = Average;
|
||||
pData[i * Step + 2] = Average;
|
||||
}
|
||||
ConvertToGrayscale(Info);
|
||||
|
||||
Part.m_ColorTexture = pSelf->Graphics()->LoadTextureRawMove(Info, 0, aFilename);
|
||||
|
||||
|
|
|
@ -850,41 +850,14 @@ bool CEditor::CallbackSaveImage(const char *pFileName, int StorageType, void *pU
|
|||
|
||||
std::shared_ptr<CEditorImage> pImg = pEditor->m_Map.m_vpImages[pEditor->m_SelectedImage];
|
||||
|
||||
EImageFormat OutputFormat;
|
||||
switch(pImg->m_Format)
|
||||
if(CImageLoader::SavePng(pEditor->Storage()->OpenFile(pFileName, IOFLAG_WRITE, StorageType), pFileName, *pImg))
|
||||
{
|
||||
case CImageInfo::FORMAT_RGB:
|
||||
OutputFormat = IMAGE_FORMAT_RGB;
|
||||
break;
|
||||
case CImageInfo::FORMAT_RGBA:
|
||||
OutputFormat = IMAGE_FORMAT_RGBA;
|
||||
break;
|
||||
case CImageInfo::FORMAT_SINGLE_COMPONENT:
|
||||
OutputFormat = IMAGE_FORMAT_R;
|
||||
break;
|
||||
default:
|
||||
dbg_assert(false, "Image has invalid format.");
|
||||
return false;
|
||||
};
|
||||
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
if(SavePng(OutputFormat, pImg->m_pData, ImageByteBuffer, pImg->m_Width, pImg->m_Height))
|
||||
{
|
||||
IOHANDLE File = pEditor->Storage()->OpenFile(pFileName, IOFLAG_WRITE, StorageType);
|
||||
if(File)
|
||||
{
|
||||
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
|
||||
io_close(File);
|
||||
pEditor->m_Dialog = DIALOG_NONE;
|
||||
return true;
|
||||
}
|
||||
pEditor->ShowFileDialogError("Failed to open file '%s'.", pFileName);
|
||||
return false;
|
||||
pEditor->m_Dialog = DIALOG_NONE;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEditor->ShowFileDialogError("Failed to write image to file.");
|
||||
pEditor->ShowFileDialogError("Failed to write image to file '%s'.", pFileName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -4401,18 +4374,13 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup
|
|||
str_copy(pImg->m_aName, aBuf);
|
||||
pImg->m_External = IsVanillaImage(pImg->m_aName);
|
||||
|
||||
if(!pImg->m_External && pImg->m_Format != CImageInfo::FORMAT_RGBA)
|
||||
if(!pImg->m_External)
|
||||
{
|
||||
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
ConvertToRGBA(pRgbaData, *pImg);
|
||||
free(pImg->m_pData);
|
||||
pImg->m_pData = pRgbaData;
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
|
||||
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(pImg->m_pData, pImg->m_Width, pImg->m_Height);
|
||||
ConvertToRgba(*pImg);
|
||||
if(g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(*pImg);
|
||||
}
|
||||
}
|
||||
|
||||
pImg->m_AutoMapper.Load(pImg->m_aName);
|
||||
|
@ -4473,18 +4441,13 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser)
|
|||
pImg->m_pData = ImgInfo.m_pData;
|
||||
pImg->m_External = IsVanillaImage(aBuf);
|
||||
|
||||
if(pImg->m_Format != CImageInfo::FORMAT_RGBA)
|
||||
if(!pImg->m_External)
|
||||
{
|
||||
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
ConvertToRGBA(pRgbaData, *pImg);
|
||||
free(pImg->m_pData);
|
||||
pImg->m_pData = pRgbaData;
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
|
||||
if(!pImg->m_External && g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(pImg->m_pData, pImg->m_Width, pImg->m_Height);
|
||||
ConvertToRgba(*pImg);
|
||||
if(g_Config.m_ClEditorDilate == 1)
|
||||
{
|
||||
DilateImage(*pImg);
|
||||
}
|
||||
}
|
||||
|
||||
int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
|
|
|
@ -52,29 +52,3 @@ void CEditorImage::AnalyseTileFlags()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CEditorImage::DataEquals(const CEditorImage &Other) const
|
||||
{
|
||||
// If height, width or pixel size don't match, then data cannot be equal
|
||||
const size_t ImgPixelSize = PixelSize();
|
||||
|
||||
if(Other.m_Height != m_Height || Other.m_Width != m_Width || Other.PixelSize() != ImgPixelSize)
|
||||
return false;
|
||||
|
||||
const auto &&GetPixel = [&](uint8_t *pData, int x, int y, size_t p) -> uint8_t {
|
||||
return pData[x * ImgPixelSize + (m_Width * ImgPixelSize * y) + p];
|
||||
};
|
||||
|
||||
// Look through every pixel and check if there are any difference
|
||||
for(size_t y = 0; y < m_Height; y += ImgPixelSize)
|
||||
{
|
||||
for(size_t x = 0; x < m_Width; x += ImgPixelSize)
|
||||
{
|
||||
for(size_t p = 0; p < ImgPixelSize; p++)
|
||||
if(GetPixel(m_pData, x, y, p) != GetPixel(Other.m_pData, x, y, p))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ public:
|
|||
|
||||
void OnInit(CEditor *pEditor) override;
|
||||
void AnalyseTileFlags();
|
||||
bool DataEquals(const CEditorImage &Other) const;
|
||||
|
||||
IGraphics::CTextureHandle m_Texture;
|
||||
int m_External = 0;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <engine/client.h>
|
||||
#include <engine/console.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/serverbrowser.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
|
@ -509,14 +510,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
|
|||
pImg->m_Height = ImgInfo.m_Height;
|
||||
pImg->m_Format = ImgInfo.m_Format;
|
||||
pImg->m_pData = ImgInfo.m_pData;
|
||||
if(pImg->m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
uint8_t *pRgbaData = static_cast<uint8_t *>(malloc((size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)));
|
||||
ConvertToRGBA(pRgbaData, *pImg);
|
||||
free(pImg->m_pData);
|
||||
pImg->m_pData = pRgbaData;
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
ConvertToRgba(*pImg);
|
||||
|
||||
int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
|
||||
if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0)
|
||||
|
|
|
@ -17,51 +17,6 @@ bool operator<(const ColorRGBA &Left, const ColorRGBA &Right)
|
|||
return Left.a < Right.a;
|
||||
}
|
||||
|
||||
static ColorRGBA GetPixelColor(const CImageInfo &Image, size_t x, size_t y)
|
||||
{
|
||||
uint8_t *pData = Image.m_pData;
|
||||
const size_t PixelSize = Image.PixelSize();
|
||||
const size_t PixelStartIndex = x * PixelSize + (Image.m_Width * PixelSize * y);
|
||||
|
||||
ColorRGBA Color = {255, 255, 255, 255};
|
||||
if(PixelSize == 1)
|
||||
{
|
||||
Color.a = pData[PixelStartIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
Color.r = pData[PixelStartIndex + 0];
|
||||
Color.g = pData[PixelStartIndex + 1];
|
||||
Color.b = pData[PixelStartIndex + 2];
|
||||
|
||||
if(PixelSize == 4)
|
||||
Color.a = pData[PixelStartIndex + 3];
|
||||
}
|
||||
|
||||
return Color;
|
||||
}
|
||||
|
||||
static void SetPixelColor(CImageInfo *pImage, size_t x, size_t y, ColorRGBA Color)
|
||||
{
|
||||
uint8_t *pData = pImage->m_pData;
|
||||
const size_t PixelSize = pImage->PixelSize();
|
||||
const size_t PixelStartIndex = x * PixelSize + (pImage->m_Width * PixelSize * y);
|
||||
|
||||
if(PixelSize == 1)
|
||||
{
|
||||
pData[PixelStartIndex] = Color.a;
|
||||
}
|
||||
else
|
||||
{
|
||||
pData[PixelStartIndex + 0] = Color.r;
|
||||
pData[PixelStartIndex + 1] = Color.g;
|
||||
pData[PixelStartIndex + 2] = Color.b;
|
||||
|
||||
if(PixelSize == 4)
|
||||
pData[PixelStartIndex + 3] = Color.a;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image)
|
||||
{
|
||||
std::set<ColorRGBA> ColorSet;
|
||||
|
@ -70,7 +25,7 @@ static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image)
|
|||
{
|
||||
for(size_t y = 0; y < Image.m_Height; y++)
|
||||
{
|
||||
ColorRGBA Color = GetPixelColor(Image, x, y);
|
||||
ColorRGBA Color = Image.PixelColor(x, y);
|
||||
if(Color.a > 0 && ColorSet.insert(Color).second)
|
||||
vUniqueColors.push_back(Color);
|
||||
}
|
||||
|
@ -106,12 +61,12 @@ static std::vector<std::array<ColorRGBA, NumTiles>> GroupColors(const std::vecto
|
|||
return vaColorGroups;
|
||||
}
|
||||
|
||||
static void SetColorTile(CImageInfo *pImage, int x, int y, ColorRGBA Color)
|
||||
static void SetColorTile(CImageInfo &Image, int x, int y, ColorRGBA Color)
|
||||
{
|
||||
for(int i = 0; i < TileSize; i++)
|
||||
{
|
||||
for(int j = 0; j < TileSize; j++)
|
||||
SetPixelColor(pImage, x * TileSize + i, y * TileSize + j, Color);
|
||||
Image.SetPixelColor(x * TileSize + i, y * TileSize + j, Color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +83,7 @@ static CImageInfo ColorGroupToImage(const std::array<ColorRGBA, NumTiles> &aColo
|
|||
for(int x = 0; x < NumTilesRow; x++)
|
||||
{
|
||||
int ColorIndex = x + NumTilesRow * y;
|
||||
SetColorTile(&Image, x, y, aColorGroup[ColorIndex]);
|
||||
SetColorTile(Image, x, y, aColorGroup[ColorIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +134,7 @@ static void SetTilelayerIndices(const std::shared_ptr<CLayerTiles> &pLayer, cons
|
|||
for(int x = 0; x < pLayer->m_Width; x++)
|
||||
{
|
||||
for(int y = 0; y < pLayer->m_Height; y++)
|
||||
pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(aColorGroup, GetPixelColor(Image, x, y));
|
||||
pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(aColorGroup, Image.PixelColor(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,88 +1,49 @@
|
|||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/gfx/image_manipulation.h>
|
||||
#include <engine/graphics.h>
|
||||
|
||||
int DilateFile(const char *pFilename)
|
||||
static bool DilateFile(const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(File)
|
||||
CImageInfo Image;
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(pFilename, IOFLAG_READ), pFilename, Image, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
if(Image.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
dbg_msg("dilate", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
CImageInfo Img;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, Img.m_Width, Img.m_Height, Img.m_pData, ImageFormat))
|
||||
{
|
||||
if(ImageFormat != IMAGE_FORMAT_RGBA)
|
||||
{
|
||||
free(Img.m_pData);
|
||||
dbg_msg("dilate", "%s: not an RGBA image", pFilename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DilateImage(Img.m_pData, Img.m_Width, Img.m_Height);
|
||||
|
||||
// save here
|
||||
IOHANDLE SaveFile = io_open(pFilename, IOFLAG_WRITE);
|
||||
if(SaveFile)
|
||||
{
|
||||
TImageByteBuffer ByteBuffer2;
|
||||
SImageByteBuffer ImageByteBuffer2(&ByteBuffer2);
|
||||
|
||||
if(SavePng(IMAGE_FORMAT_RGBA, Img.m_pData, ImageByteBuffer2, Img.m_Width, Img.m_Height))
|
||||
io_write(SaveFile, &ByteBuffer2.front(), ByteBuffer2.size());
|
||||
io_close(SaveFile);
|
||||
|
||||
free(Img.m_pData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("dilate", "failed unknown image format: %s", pFilename);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("dilate", "failed to open image file. filename='%s'", pFilename);
|
||||
return -1;
|
||||
log_error("dilate", "ERROR: only RGBA PNG images are supported");
|
||||
Image.Free();
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
DilateImage(Image);
|
||||
|
||||
const bool SaveResult = CImageLoader::SavePng(io_open(pFilename, IOFLAG_WRITE), pFilename, Image);
|
||||
Image.Free();
|
||||
|
||||
return SaveResult;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
CCmdlineFix CmdlineFix(&argc, &argv);
|
||||
log_set_global_logger_default();
|
||||
|
||||
if(argc == 1)
|
||||
{
|
||||
dbg_msg("usage", "%s FILE1 [ FILE2... ]", argv[0]);
|
||||
log_error("dilate", "Usage: %s <image1.png> [<image2.png> ...]", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Success = true;
|
||||
for(int i = 1; i < argc; i++)
|
||||
DilateFile(argv[i]);
|
||||
|
||||
return 0;
|
||||
{
|
||||
Success &= DilateFile(argv[i]);
|
||||
}
|
||||
return Success ? 0 : -1;
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/gamecore.h>
|
||||
#include <game/mapitems.h>
|
||||
|
||||
/*
|
||||
Usage: map_convert_07 <source map filepath> <dest map filepath>
|
||||
*/
|
||||
|
@ -25,53 +27,6 @@ int g_NextDataItemId = -1;
|
|||
|
||||
int g_aImageIds[MAX_MAPIMAGES];
|
||||
|
||||
int LoadPng(CImageInfo *pImg, const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(File)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
dbg_msg("map_convert_07", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
pImg->m_pData = pImgBuffer;
|
||||
|
||||
if(ImageFormat == IMAGE_FORMAT_RGBA && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
|
||||
{
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("map_convert_07", "invalid image format. filename='%s'", pFilename);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename)
|
||||
{
|
||||
if(LayerType != MAPITEMTYPE_LAYER)
|
||||
|
@ -117,15 +72,19 @@ void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, CMapItemImage *pNewIm
|
|||
|
||||
dbg_msg("map_convert_07", "embedding image '%s'", pName);
|
||||
|
||||
CImageInfo ImgInfo;
|
||||
char aStr[IO_MAX_PATH_LENGTH];
|
||||
str_format(aStr, sizeof(aStr), "data/mapres/%s.png", pName);
|
||||
if(!LoadPng(&ImgInfo, aStr))
|
||||
|
||||
CImageInfo ImgInfo;
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(aStr, IOFLAG_READ), aStr, ImgInfo, PngliteIncompatible))
|
||||
return pImgItem; // keep as external if we don't have a mapres to replace
|
||||
|
||||
if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
const size_t MaxImageDimension = 1 << 13;
|
||||
if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA || ImgInfo.m_Width > MaxImageDimension || ImgInfo.m_Height > MaxImageDimension)
|
||||
{
|
||||
dbg_msg("map_convert_07", "image '%s' is not in RGBA format", aStr);
|
||||
dbg_msg("map_convert_07", "ERROR: only RGBA PNG images with maximum width/height %" PRIzu " are supported", MaxImageDimension);
|
||||
ImgInfo.Free();
|
||||
return pImgItem;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/mapitems.h>
|
||||
|
||||
bool CreatePixelArt(const char[3][IO_MAX_PATH_LENGTH], const int[2], const int[2], int[2], const bool[2]);
|
||||
void InsertCurrentQuads(CDataFileReader &, CMapItemLayerQuads *, CQuad *);
|
||||
int InsertPixelArtQuads(CQuad *, int &, const CImageInfo &, const int[2], const int[2], const bool[2]);
|
||||
|
||||
bool LoadPng(CImageInfo *, const char *);
|
||||
bool OpenMaps(const char[2][IO_MAX_PATH_LENGTH], CDataFileReader &, CDataFileWriter &);
|
||||
void SaveOutputMap(CDataFileReader &, CDataFileWriter &, CMapItemLayerQuads *, int, CQuad *, int);
|
||||
|
||||
|
@ -73,7 +73,8 @@ int main(int argc, const char **argv)
|
|||
bool CreatePixelArt(const char aFilenames[3][IO_MAX_PATH_LENGTH], const int aLayerId[2], const int aStartingPos[2], int aPixelSizes[2], const bool aArtOptions[2])
|
||||
{
|
||||
CImageInfo Img;
|
||||
if(!LoadPng(&Img, aFilenames[2]))
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(aFilenames[2], IOFLAG_READ), aFilenames[2], Img, PngliteIncompatible))
|
||||
return false;
|
||||
|
||||
aPixelSizes[0] = aPixelSizes[0] ? aPixelSizes[0] : GetImagePixelSize(Img);
|
||||
|
@ -227,7 +228,7 @@ bool GetPixelClamped(const CImageInfo &Img, size_t x, size_t y, uint8_t aPixel[4
|
|||
aPixel[3] = 255;
|
||||
|
||||
const size_t PixelSize = Img.PixelSize();
|
||||
for(size_t i = 0; i < PixelSize; i++)
|
||||
for(size_t i = 0; i < minimum<size_t>(4, PixelSize); i++) // minimum is used here to avoid false positive stringop-overflow warning
|
||||
aPixel[i] = Img.m_pData[x * PixelSize + (Img.m_Width * PixelSize * y) + i];
|
||||
|
||||
return aPixel[3] > 0;
|
||||
|
@ -315,54 +316,6 @@ CQuad CreateNewQuad(const float PosX, const float PosY, const int Width, const i
|
|||
return Quad;
|
||||
}
|
||||
|
||||
bool LoadPng(CImageInfo *pImg, const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(!File)
|
||||
{
|
||||
dbg_msg("map_create_pixelart", "ERROR: Unable to open file %s", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
dbg_msg("map_create_pixelart", "ERROR: Failed to get file size (%ld). filename='%s'", FileSize, pFilename);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
|
||||
if(!LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
dbg_msg("map_create_pixelart", "ERROR: Unable to load a valid PNG from file %s", pFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ImageFormat != IMAGE_FORMAT_RGBA && ImageFormat != IMAGE_FORMAT_RGB)
|
||||
{
|
||||
dbg_msg("map_create_pixelart", "ERROR: only RGB and RGBA PNG images are supported");
|
||||
free(pImgBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
pImg->m_pData = pImgBuffer;
|
||||
pImg->m_Format = ImageFormat == IMAGE_FORMAT_RGB ? CImageInfo::FORMAT_RGB : CImageInfo::FORMAT_RGBA;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenMaps(const char pMapNames[2][IO_MAX_PATH_LENGTH], CDataFileReader &InputMap, CDataFileWriter &OutputMap)
|
||||
{
|
||||
IStorage *pStorage = CreateLocalStorage();
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Adapted from TWMapImagesRecovery by Tardo: https://github.com/Tardo/TWMapImagesRecovery
|
||||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/mapitems.h>
|
||||
|
||||
static void PrintMapInfo(CDataFileReader &Reader)
|
||||
|
@ -50,22 +52,18 @@ static void ExtractMapImages(CDataFileReader &Reader, const char *pPathSave)
|
|||
continue;
|
||||
}
|
||||
|
||||
IOHANDLE File = io_open(aBuf, IOFLAG_WRITE);
|
||||
if(File)
|
||||
{
|
||||
log_info("map_extract", "writing image: %s (%dx%d)", aBuf, pItem->m_Width, pItem->m_Height);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
CImageInfo Image;
|
||||
Image.m_Width = pItem->m_Width;
|
||||
Image.m_Height = pItem->m_Height;
|
||||
Image.m_Format = CImageInfo::FORMAT_RGBA;
|
||||
Image.m_pData = static_cast<uint8_t *>(Reader.GetData(pItem->m_ImageData));
|
||||
|
||||
if(SavePng(IMAGE_FORMAT_RGBA, (const uint8_t *)Reader.GetData(pItem->m_ImageData), ImageByteBuffer, pItem->m_Width, pItem->m_Height))
|
||||
io_write(File, &ByteBuffer.front(), ByteBuffer.size());
|
||||
io_close(File);
|
||||
Reader.UnloadData(pItem->m_ImageData);
|
||||
}
|
||||
else
|
||||
log_info("map_extract", "writing image: %s (%dx%d)", aBuf, pItem->m_Width, pItem->m_Height);
|
||||
if(!CImageLoader::SavePng(io_open(aBuf, IOFLAG_WRITE), aBuf, Image))
|
||||
{
|
||||
log_error("map_extract", "failed to open image file for writing. filename='%s'", aBuf);
|
||||
log_error("map_extract", "failed to write image file. filename='%s'", aBuf);
|
||||
}
|
||||
Reader.UnloadData(pItem->m_ImageData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
#include <base/logger.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/gfx/image_loader.h>
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/shared/datafile.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <game/mapitems.h>
|
||||
|
||||
/*
|
||||
Usage: map_replace_image <source map filepath> <dest map filepath> <current image name> <new image filepath>
|
||||
Notes: map filepath must be relative to user default teeworlds folder
|
||||
|
@ -23,55 +25,6 @@ int g_NewDataId = -1;
|
|||
int g_NewDataSize = 0;
|
||||
void *g_pNewData = nullptr;
|
||||
|
||||
bool LoadPng(CImageInfo *pImg, const char *pFilename)
|
||||
{
|
||||
IOHANDLE File = io_open(pFilename, IOFLAG_READ);
|
||||
if(File)
|
||||
{
|
||||
io_seek(File, 0, IOSEEK_END);
|
||||
long int FileSize = io_tell(File);
|
||||
if(FileSize <= 0)
|
||||
{
|
||||
io_close(File);
|
||||
return false;
|
||||
}
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
TImageByteBuffer ByteBuffer;
|
||||
SImageByteBuffer ImageByteBuffer(&ByteBuffer);
|
||||
|
||||
ByteBuffer.resize(FileSize);
|
||||
io_read(File, &ByteBuffer.front(), FileSize);
|
||||
|
||||
io_close(File);
|
||||
|
||||
uint8_t *pImgBuffer = NULL;
|
||||
EImageFormat ImageFormat;
|
||||
int PngliteIncompatible;
|
||||
if(LoadPng(ImageByteBuffer, pFilename, PngliteIncompatible, pImg->m_Width, pImg->m_Height, pImgBuffer, ImageFormat))
|
||||
{
|
||||
if((ImageFormat == IMAGE_FORMAT_RGBA || ImageFormat == IMAGE_FORMAT_RGB) && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
|
||||
{
|
||||
pImg->m_pData = pImgBuffer;
|
||||
|
||||
if(ImageFormat == IMAGE_FORMAT_RGB)
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGB;
|
||||
else if(ImageFormat == IMAGE_FORMAT_RGBA)
|
||||
pImg->m_Format = CImageInfo::FORMAT_RGBA;
|
||||
else
|
||||
{
|
||||
free(pImgBuffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName, const char *pImgFile, CMapItemImage *pNewImgItem)
|
||||
{
|
||||
const char *pName = g_DataReader.GetDataString(pImgItem->m_ImageName);
|
||||
|
@ -87,13 +40,15 @@ void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, const char *pImgName,
|
|||
dbg_msg("map_replace_image", "found image '%s'", pImgName);
|
||||
|
||||
CImageInfo ImgInfo;
|
||||
if(!LoadPng(&ImgInfo, pImgFile))
|
||||
return 0;
|
||||
int PngliteIncompatible;
|
||||
if(!CImageLoader::LoadPng(io_open(pImgName, IOFLAG_READ), pImgName, ImgInfo, PngliteIncompatible))
|
||||
return nullptr;
|
||||
|
||||
if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
|
||||
{
|
||||
dbg_msg("map_replace_image", "image '%s' is not in RGBA format", pImgName);
|
||||
return 0;
|
||||
dbg_msg("map_replace_image", "ERROR: only RGBA PNG images are supported");
|
||||
ImgInfo.Free();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*pNewImgItem = *pImgItem;
|
||||
|
|
Loading…
Reference in a new issue