libcmo21/LibCmo/CK2/DataHandlers/CKBitmapHandler.cpp

521 lines
18 KiB
C++
Raw Normal View History

#include "CKBitmapHandler.hpp"
#include "stb_image.h"
#include "stb_image_write.h"
namespace LibCmo::CK2::DataHandlers {
#pragma region Help Functions
2023-09-12 20:49:19 +08:00
/*
ABGR8888 is format used by std image.
The data is placed in buffer with RGBA order, so the format is ABGR.
2023-12-03 20:50:25 +08:00
BGR888 also is non-alpha format used by std image.
The data is placed in buffer with RGB order, so the format is BGR.
2023-09-12 20:49:19 +08:00
ARGB8888 is Virtools standard.
The data is placed in buffer with BGRA order.
*/
2023-12-03 20:50:25 +08:00
// MARK: for std-image size, we use `n * CKSizeof(CKBYTE)` to calc offset.
// for virtools size, we use `VxMath::VxImageDescEx::ColorFactorSize` and `VxMath::VxImageDescEx::PixelSize` to calc offset.
2023-09-16 18:31:25 +08:00
static void ABGRToARGB(CKDWORD count, const void* _abgr, void* _argb) {
2023-09-17 10:38:46 +08:00
const CKBYTE* abgr = static_cast<const CKBYTE*>(_abgr);
CKBYTE* argb = static_cast<CKBYTE*>(_argb);
2023-09-12 20:49:19 +08:00
// copy R
VxMath::VxCopyStructure(
count,
2023-09-12 20:49:19 +08:00
argb + (2u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize,
VxMath::VxImageDescEx::ColorFactorSize,
2023-12-03 20:50:25 +08:00
abgr + (0u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE)
2023-09-12 20:49:19 +08:00
);
// copy G
VxMath::VxCopyStructure(
count,
argb + (1u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize,
VxMath::VxImageDescEx::ColorFactorSize,
2023-12-03 20:50:25 +08:00
abgr + (1u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE)
2023-09-12 20:49:19 +08:00
);
// copy B
VxMath::VxCopyStructure(
count,
argb + (0u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize,
VxMath::VxImageDescEx::ColorFactorSize,
2023-12-03 20:50:25 +08:00
abgr + (2u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE)
);
// copy A
VxMath::VxCopyStructure(
count,
2023-09-12 20:49:19 +08:00
argb + (3u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize,
VxMath::VxImageDescEx::ColorFactorSize,
2023-12-03 20:50:25 +08:00
abgr + (3u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE)
);
}
2023-09-16 18:31:25 +08:00
static void ARGBToABGR(CKDWORD count, const void* _argb, void* _abgr) {
2023-09-17 10:38:46 +08:00
const CKBYTE* argb = static_cast<const CKBYTE*>(_argb);
CKBYTE* abgr = static_cast<CKBYTE*>(_abgr);
2023-09-12 20:49:19 +08:00
// copy R
VxMath::VxCopyStructure(
count,
2023-12-03 20:50:25 +08:00
abgr + (0u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE),
2023-09-12 20:49:19 +08:00
VxMath::VxImageDescEx::ColorFactorSize,
argb + (2u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
// copy G
VxMath::VxCopyStructure(
count,
2023-12-03 20:50:25 +08:00
abgr + (1u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE),
2023-09-12 20:49:19 +08:00
VxMath::VxImageDescEx::ColorFactorSize,
argb + (1u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
// copy B
VxMath::VxCopyStructure(
count,
2023-12-03 20:50:25 +08:00
abgr + (2u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE),
2023-09-12 20:49:19 +08:00
VxMath::VxImageDescEx::ColorFactorSize,
argb + (0u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
// copy A
VxMath::VxCopyStructure(
count,
2023-12-03 20:50:25 +08:00
abgr + (3u * CKSizeof(CKBYTE)),
4u * CKSizeof(CKBYTE),
2023-09-12 20:49:19 +08:00
VxMath::VxImageDescEx::ColorFactorSize,
argb + (3u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
}
2023-12-03 20:50:25 +08:00
static void ARGBToBGR(CKDWORD count, const void* _argb, void* _bgr) {
const CKBYTE* argb = static_cast<const CKBYTE*>(_argb);
CKBYTE* bgr = static_cast<CKBYTE*>(_bgr);
// copy R
VxMath::VxCopyStructure(
count,
bgr + (0u * CKSizeof(CKBYTE)),
3u * CKSizeof(CKBYTE),
VxMath::VxImageDescEx::ColorFactorSize,
argb + (2u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
// copy G
VxMath::VxCopyStructure(
count,
bgr + (1u * CKSizeof(CKBYTE)),
3u * CKSizeof(CKBYTE),
VxMath::VxImageDescEx::ColorFactorSize,
argb + (1u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
// copy B
VxMath::VxCopyStructure(
count,
bgr + (2u * CKSizeof(CKBYTE)),
3u * CKSizeof(CKBYTE),
VxMath::VxImageDescEx::ColorFactorSize,
argb + (0u * VxMath::VxImageDescEx::ColorFactorSize),
VxMath::VxImageDescEx::PixelSize
);
// skip A factor
}
static bool StbReadFile(CKSTRING u8filename, VxMath::VxImageDescEx* read_image) {
if (u8filename == nullptr || read_image == nullptr) return false;
FILE* fs = EncodingHelper::U8FOpen(u8filename, "rb");
if (fs == nullptr) return false;
// read data
int x, y, channels_in_file;
stbi_uc* data = stbi_load_from_file(fs, &x, &y, &channels_in_file, 4); // 4 == RGBA8888
std::fclose(fs);
if (data == nullptr) return false;
2023-09-11 14:39:07 +08:00
// create read image
read_image->CreateImage(static_cast<CKDWORD>(x), static_cast<CKDWORD>(y));
// copy data
2023-09-12 20:49:19 +08:00
ABGRToARGB(read_image->GetPixelCount(), data, read_image->GetMutableImage());
// clear data
stbi_image_free(data);
return true;
}
static bool StbReadMemory(const void* memory, CKDWORD size, VxMath::VxImageDescEx* read_image) {
// same like ReadFile
if (memory == nullptr || read_image == nullptr) return false;
// read data
int x, y, channels_in_file;
stbi_uc* data = stbi_load_from_memory(
2023-09-20 10:49:32 +08:00
static_cast<const stbi_uc*>(memory),
static_cast<int>(size),
&x, &y, &channels_in_file, 4 // 4 == RGBA8888
);
if (data == nullptr) return false;
2023-12-03 20:50:25 +08:00
2023-09-11 14:39:07 +08:00
// create read image
read_image->CreateImage(static_cast<CKDWORD>(x), static_cast<CKDWORD>(y));
2023-09-11 14:39:07 +08:00
// copy data
2023-09-12 20:49:19 +08:00
ABGRToARGB(read_image->GetPixelCount(), data, read_image->GetMutableImage());
// clear data
stbi_image_free(data);
return true;
}
struct FileSaveContext {
FileSaveContext(FILE* fs) :
m_Fs(fs), m_Counter(0) {}
FILE* m_Fs;
size_t m_Counter;
};
static void FileWriteFunction(void* context, void* data, int size) {
2023-09-20 10:49:32 +08:00
FileSaveContext* ctx = static_cast<FileSaveContext*>(context);
if (ctx->m_Fs != nullptr) {
2023-09-17 10:38:46 +08:00
std::fwrite(data, sizeof(CKBYTE), size, ctx->m_Fs);
}
ctx->m_Counter += size;
}
struct MemorySaveContext {
MemorySaveContext(void* mem) :
m_Mem(mem), m_Counter(0) {}
void* m_Mem;
size_t m_Counter;
};
static void MemoryWriteFunction(void* context, void* data, int size) {
2023-09-20 10:49:32 +08:00
MemorySaveContext* ctx = static_cast<MemorySaveContext*>(context);
if (ctx->m_Mem != nullptr) {
std::memcpy(ctx->m_Mem, data, size);
2023-09-17 10:38:46 +08:00
ctx->m_Mem = static_cast<CKBYTE*>(ctx->m_Mem) + size;
}
ctx->m_Counter += size;
}
using SaveOperation = std::function<int(stbi_write_func*, void*, int, int, int, const void*)>;
2023-12-03 20:50:25 +08:00
static bool StbSaveFile(CKSTRING u8filename, const VxMath::VxImageDescEx* write_image, bool save_alpha, SaveOperation oper) {
if (u8filename == nullptr || write_image == nullptr) return false;
2023-09-11 22:20:45 +08:00
if (!write_image->IsValid()) return false;
FILE* fs = EncodingHelper::U8FOpen(u8filename, "wb");
if (fs == nullptr) return false;
2023-12-03 20:50:25 +08:00
// allocate buffer and convert data from ARGB to RGBA or RGB
CKBYTE* data = nullptr;
int channel_count = 0;
if (save_alpha) {
// save with alpha
data = new CKBYTE[CKSizeof(CKBYTE) * 4u * write_image->GetWidth() * write_image->GetHeight()];
ARGBToABGR(write_image->GetPixelCount(), write_image->GetImage(), data);
channel_count = 4;
} else {
// save without alpha
data = new CKBYTE[CKSizeof(CKBYTE) * 3u * write_image->GetWidth() * write_image->GetHeight()];
ARGBToBGR(write_image->GetPixelCount(), write_image->GetImage(), data);
channel_count = 3;
}
// write data
FileSaveContext* ctx = new FileSaveContext(fs);
int ret = oper(
&FileWriteFunction, ctx,
static_cast<int>(write_image->GetWidth()), static_cast<int>(write_image->GetHeight()),
2023-12-03 20:50:25 +08:00
channel_count, data // 4 == RGBA8888
);
// free data
delete ctx;
delete[] data;
std::fclose(fs);
2023-11-10 14:58:07 +08:00
// ret is 0 mean failed.
return ret != 0;
}
2023-12-03 20:50:25 +08:00
static CKDWORD StbSaveMemory(void* memory, const VxMath::VxImageDescEx* write_image, bool save_alpha, SaveOperation oper) {
if (write_image == nullptr) return 0;
2023-09-11 22:20:45 +08:00
if (!write_image->IsValid()) return 0;
2023-12-03 20:50:25 +08:00
// allocate buffer and convert data from ARGB to RGBA or RGB
CKBYTE* data = nullptr;
int channel_count = 0;
if (save_alpha) {
// save with alpha
data = new CKBYTE[CKSizeof(CKBYTE) * 4u * write_image->GetWidth() * write_image->GetHeight()];
ARGBToABGR(write_image->GetPixelCount(), write_image->GetImage(), data);
channel_count = 4;
} else {
// save without alpha
data = new CKBYTE[CKSizeof(CKBYTE) * 3u * write_image->GetWidth() * write_image->GetHeight()];
ARGBToBGR(write_image->GetPixelCount(), write_image->GetImage(), data);
channel_count = 3;
}
// write data
MemorySaveContext* ctx = new MemorySaveContext(memory);
int ret = oper(
&MemoryWriteFunction, ctx,
static_cast<int>(write_image->GetWidth()), static_cast<int>(write_image->GetHeight()),
2023-12-03 20:50:25 +08:00
channel_count, data // 4 == RGBA8888
);
// free data
CKDWORD expected = static_cast<CKDWORD>(ctx->m_Counter);
delete ctx;
delete[] data;
2023-11-10 14:58:07 +08:00
// ret is 0 mean failed. return zero size.
if (ret == 0) return 0;
else return expected;
}
2023-12-03 20:50:25 +08:00
#pragma endregion
#pragma region CKBitmapBMPHandler
static const CKBitmapProperties g_BMPProperties(CKGUID(0xBCA97223u, 0x48578BCAu), "Bmp");
CKBitmapBMPHandler::CKBitmapBMPHandler() :
CKBitmapHandler() {}
CKBitmapBMPHandler::~CKBitmapBMPHandler() {}
const CKBitmapProperties& CKBitmapBMPHandler::GetBitmapDefaultProperties() {
return g_BMPProperties;
}
bool CKBitmapBMPHandler::ReadFile(CKSTRING u8filename, VxMath::VxImageDescEx* read_image) {
return StbReadFile(u8filename, read_image);
}
bool CKBitmapBMPHandler::ReadMemory(const void* memory, CKDWORD size, VxMath::VxImageDescEx* read_image) {
return StbReadMemory(memory, size, read_image);
}
2023-12-03 20:50:25 +08:00
// MARK: when stb-image writing bmp file with alpha channel, it will create a very rare bmp file supporting alpha channel.
// this format is not supported by virtools and will result blank image.
// so we create an alpha option to forcely change channel count to 3 (RGB) then the bmp writer can work normally.
bool CKBitmapBMPHandler::SaveFile(CKSTRING u8filename, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
2023-12-03 20:50:25 +08:00
return StbSaveFile(u8filename, write_image, false, // bmp do not support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
return stbi_write_bmp_to_func(func, context, w, h, comp, data);
});
}
CKDWORD CKBitmapBMPHandler::SaveMemory(void* memory, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
2023-12-03 20:50:25 +08:00
return StbSaveMemory(memory, write_image, false, // bmp do not support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
return stbi_write_bmp_to_func(func, context, w, h, comp, data);
});
}
bool CKBitmapBMPHandler::CanSaveAlpha() {
return false;
}
#pragma endregion
#pragma region CKBitmapTGAHandler
2023-12-03 20:50:25 +08:00
static const CKBitmapProperties g_TGAProperties(CKGUID(0x585C7216u, 0x33302657u), "Tga");
CKBitmapTGAHandler::CKBitmapTGAHandler() :
CKBitmapHandler() {}
CKBitmapTGAHandler::~CKBitmapTGAHandler() {}
const CKBitmapProperties& CKBitmapTGAHandler::GetBitmapDefaultProperties() {
return g_TGAProperties;
}
bool CKBitmapTGAHandler::ReadFile(CKSTRING u8filename, VxMath::VxImageDescEx* read_image) {
return StbReadFile(u8filename, read_image);
}
bool CKBitmapTGAHandler::ReadMemory(const void* memory, CKDWORD size, VxMath::VxImageDescEx* read_image) {
return StbReadMemory(memory, size, read_image);
}
bool CKBitmapTGAHandler::SaveFile(CKSTRING u8filename, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
2023-12-03 20:50:25 +08:00
return StbSaveFile(u8filename, write_image, false, // tga support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
return stbi_write_tga_to_func(func, context, w, h, comp, data);
});
}
CKDWORD CKBitmapTGAHandler::SaveMemory(void* memory, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
2023-12-03 20:50:25 +08:00
return StbSaveMemory(memory, write_image, false, // tga support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
return stbi_write_tga_to_func(func, context, w, h, comp, data);
});
}
bool CKBitmapTGAHandler::CanSaveAlpha() {
return true;
}
#pragma endregion
#pragma region CKBitmapJPGHandler
// MARK: this GUID is gotten from Virtools 3.5 Plugins.
static const CKBitmapProperties g_JPGProperties(CKGUID(0x4AE51AC4u, 0x04587D76u), "jpg");
// MARK: this quality is gotten from default value of virtools.
constexpr int g_JPGDefaultQuality = 75;
CKBitmapJPGHandler::CKBitmapJPGHandler() :
CKBitmapHandler() {}
CKBitmapJPGHandler::~CKBitmapJPGHandler() {}
const CKBitmapProperties& CKBitmapJPGHandler::GetBitmapDefaultProperties() {
return g_JPGProperties;
}
bool CKBitmapJPGHandler::ReadFile(CKSTRING u8filename, VxMath::VxImageDescEx* read_image) {
return StbReadFile(u8filename, read_image);
}
bool CKBitmapJPGHandler::ReadMemory(const void* memory, CKDWORD size, VxMath::VxImageDescEx* read_image) {
return StbReadMemory(memory, size, read_image);
}
bool CKBitmapJPGHandler::SaveFile(CKSTRING u8filename, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
return StbSaveFile(u8filename, write_image, false, // jpg do not support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
return stbi_write_jpg_to_func(func, context, w, h, comp, data, g_JPGDefaultQuality);
});
}
CKDWORD CKBitmapJPGHandler::SaveMemory(void* memory, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
return StbSaveMemory(memory, write_image, false, // jpg do not support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
return stbi_write_jpg_to_func(func, context, w, h, comp, data, g_JPGDefaultQuality);
});
}
bool CKBitmapJPGHandler::CanSaveAlpha() {
return false;
}
#pragma endregion
#pragma region CKBitmapPNGHandler
// MARK: this GUID is gotten from Virtools 3.5 Plugins.
static const CKBitmapProperties g_PNGProperties(CKGUID(0x02D45C7Bu, 0x4AAC16ECu), "png");
// MARK: this is compress level gotten from default value of virtools.
constexpr int g_PNGDefaultCompressLevel = 3;
/**
* @brief A helper function to get stride parameter passed to png writer.
* @param width[in] The width given by general stb writer wrapper.
* @param comp[in] The comp given by general stb writer wrapper.
* @return The stride data passed to real stb writer.
*/
static int StbPngStrideGetter(int width, int comp) {
return width * comp;
}
CKBitmapPNGHandler::CKBitmapPNGHandler() :
CKBitmapHandler() {}
CKBitmapPNGHandler::~CKBitmapPNGHandler() {}
const CKBitmapProperties& CKBitmapPNGHandler::GetBitmapDefaultProperties() {
return g_PNGProperties;
}
bool CKBitmapPNGHandler::ReadFile(CKSTRING u8filename, VxMath::VxImageDescEx* read_image) {
return StbReadFile(u8filename, read_image);
}
bool CKBitmapPNGHandler::ReadMemory(const void* memory, CKDWORD size, VxMath::VxImageDescEx* read_image) {
return StbReadMemory(memory, size, read_image);
}
bool CKBitmapPNGHandler::SaveFile(CKSTRING u8filename, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
return StbSaveFile(u8filename, write_image, false, // png support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
// set default compress level
stbi_write_png_compression_level = g_PNGDefaultCompressLevel;
// write data
return stbi_write_png_to_func(func, context, w, h, comp, data, StbPngStrideGetter(w, comp));
});
}
CKDWORD CKBitmapPNGHandler::SaveMemory(void* memory, const VxMath::VxImageDescEx* write_image, const CKBitmapProperties& codec_param) {
return StbSaveMemory(memory, write_image, false, // png support alpha
[&codec_param](stbi_write_func* func, void* context, int w, int h, int comp, const void* data) -> int {
stbi_write_png_compression_level = g_PNGDefaultCompressLevel;
return stbi_write_png_to_func(func, context, w, h, comp, data, StbPngStrideGetter(w, comp));
});
}
bool CKBitmapPNGHandler::CanSaveAlpha() {
return true;
}
#pragma endregion
2023-12-03 20:50:25 +08:00
#pragma region General Getter Freer
static CKBitmapHandler* FindHandlerByExt(const CKFileExtension& ext) {
if (ext == g_BMPProperties.m_Ext) return new CKBitmapBMPHandler();
if (ext == g_TGAProperties.m_Ext) return new CKBitmapTGAHandler();
if (ext == g_JPGProperties.m_Ext) return new CKBitmapJPGHandler();
if (ext == g_PNGProperties.m_Ext) return new CKBitmapPNGHandler();
return nullptr;
}
static CKBitmapHandler* FindHandlerByGuid(const CKGUID& guid) {
if (guid == g_BMPProperties.m_ReaderGuid) return new CKBitmapBMPHandler();
if (guid == g_TGAProperties.m_ReaderGuid) return new CKBitmapTGAHandler();
if (guid == g_JPGProperties.m_ReaderGuid) return new CKBitmapJPGHandler();
if (guid == g_PNGProperties.m_ReaderGuid) return new CKBitmapPNGHandler();
return nullptr;
}
CKBitmapHandler* CKBitmapHandler::GetBitmapHandler(const CKFileExtension& ext, const CKGUID& guid) {
CKBitmapHandler* handler = nullptr;
// check ext first
handler = FindHandlerByExt(ext);
if (handler != nullptr) return handler;
2023-12-03 20:50:25 +08:00
// check guid
handler = FindHandlerByGuid(guid);
if (handler != nullptr) return handler;
return nullptr;
}
2023-09-28 23:29:52 +08:00
CKBitmapHandlerWrapper_t CKBitmapHandler::GetBitmapHandlerWrapper(const CKFileExtension& ext, const CKGUID& guid) {
return CKBitmapHandlerWrapper_t(GetBitmapHandler(ext, guid));
2023-09-11 22:20:45 +08:00
}
void CKBitmapHandlerDeleter::operator()(CKBitmapHandler* handler) {
CKBitmapHandler::ReleaseBitmapHandler(handler);
}
void CKBitmapHandler::ReleaseBitmapHandler(CKBitmapHandler* handler) {
if (handler != nullptr) delete handler;
}
#pragma endregion
}