Merge pull request #8965 from Robyt3/Image-Loading-Refactoring

Refactor image loading, saving and manipulation
This commit is contained in:
Dennis Felsing 2024-09-16 16:08:06 +00:00 committed by GitHub
commit 7dc6346b2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 812 additions and 876 deletions

View file

@ -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

View file

@ -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));
}
}

View file

@ -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
View 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);
}
}

View file

@ -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;
}

View file

@ -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

View file

@ -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)

View file

@ -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);

View file

@ -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
View 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

View file

@ -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);
}
}

View file

@ -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));

View file

@ -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) {

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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)

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;