470 lines
11 KiB
C++
470 lines
11 KiB
C++
#include "render/dl_image.h"
|
||
|
||
//#define STB_IMAGE_IMPLEMENTATION
|
||
#include <stb_image.h>
|
||
//#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||
#include <stb_image_write.h>
|
||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||
#include <stb_image_resize.h>
|
||
|
||
#include <lunasvg.h>
|
||
#include <webp/decode.h>
|
||
|
||
#include <ktx.h>
|
||
|
||
#include "base/dl_color.h"
|
||
#include "math/dl_align.h"
|
||
#include "math/dl_math.h"
|
||
#include "io/dl_file.h"
|
||
#include "io/dl_log.h"
|
||
#include "base/dl_string.h"
|
||
|
||
namespace dl
|
||
{
|
||
|
||
|
||
|
||
namespace Img
|
||
{
|
||
|
||
|
||
dl::Image* Create(const Size& size, const Color& col)
|
||
{
|
||
Image* ret = new Image(size);
|
||
ret->Fill(ABGR::Create(col));
|
||
return ret;
|
||
}
|
||
|
||
dl::Image* Create(std::string_view path_name)
|
||
{
|
||
Buffer buf = File::GetBuffer(path_name);
|
||
if (buf.empty())
|
||
return nullptr;
|
||
|
||
int texWidth, texHeight, texChannels;
|
||
stbi_uc* pixels = stbi_load_from_memory((const stbi_uc*)buf.data(), (int)buf.size(),
|
||
&texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
|
||
if (!pixels)
|
||
{
|
||
log_err("加载图像失败:{}", path_name);
|
||
return nullptr;
|
||
}
|
||
Image* ret = new Image(
|
||
Size{ (unsigned)texWidth, (unsigned)texHeight },
|
||
(uint32_t*)pixels);
|
||
stbi_image_free(pixels);
|
||
return ret;
|
||
}
|
||
|
||
Image* CreateSvg(std::string_view path_name, const Size& size)
|
||
{
|
||
using namespace lunasvg;
|
||
auto doc = Document::loadFromFile(std::string{ path_name });
|
||
if (!doc)
|
||
{
|
||
log_err("加载svg图像失败:{}", path_name);
|
||
return nullptr;
|
||
}
|
||
auto bitmap = doc->renderToBitmap(size[0], size[1], 0);
|
||
if (!bitmap.valid())
|
||
{
|
||
log_err("加载svg图像失败1:{}", path_name);
|
||
return nullptr;
|
||
}
|
||
bitmap.convertToRGBA();
|
||
Image* ret = new Image(size, (uint32_t*)bitmap.data());
|
||
return ret;
|
||
}
|
||
|
||
Image* CreateWebp(std::string_view path_name)
|
||
{
|
||
Buffer buf = File::GetBuffer(path_name);
|
||
if (buf.empty())
|
||
return nullptr;
|
||
Image* ret = Img::CreateWebp(buf);
|
||
if(!ret)
|
||
log_err("webp:解析失败:{}", path_name);
|
||
return ret;
|
||
}
|
||
Image* CreateWebp(BufferView buf)
|
||
{
|
||
WebPDecoderConfig config;
|
||
if (WebPInitDecoderConfig(&config) == 0)
|
||
{
|
||
log_err0("webp:InitDecoderConfig失败!");
|
||
return nullptr;
|
||
}
|
||
if (WebPGetFeatures((uint8_t*)buf.data(), buf.size(), &config.input) != VP8_STATUS_OK)
|
||
{
|
||
log_err0("webp:GetFeatures失败!");
|
||
return nullptr;
|
||
}
|
||
if (config.input.width == 0 || config.input.height == 0)
|
||
{
|
||
log_err0("webp:宽高某值为0!");
|
||
return nullptr;
|
||
}
|
||
|
||
config.output.colorspace = MODE_rgbA;// config.input.has_alpha ? MODE_rgbA : MODE_RGB;
|
||
|
||
int w = config.input.width;
|
||
int h = config.input.height;
|
||
Image* ret = new Image(toSize(w, h));
|
||
|
||
config.output.u.RGBA.rgba = (uint8_t*)ret->GetData();
|
||
config.output.u.RGBA.stride = w * 4;// w* (config.input.has_alpha ? 4 : 3);
|
||
config.output.u.RGBA.size = ret->GetSizeByte();
|
||
config.output.is_external_memory = 1;
|
||
|
||
if (WebPDecode((uint8_t*)buf.data(), buf.size(), &config) != VP8_STATUS_OK)
|
||
{
|
||
log_err0("webp:Decode失败!");
|
||
delete ret;
|
||
return nullptr;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
bool Save(const Image* img, std::string_view path_name)
|
||
{
|
||
#if DL_PLATFORM == DL_PLATFORM_WIN32
|
||
return stbi_write_png(CodeCvt::Utf8ToMultiByte(path_name).c_str(),
|
||
img->GetWidth(), img->GetHeight(),
|
||
4, img->GetBuffer(), 0);
|
||
#else
|
||
return stbi_write_png(std::string{ path_name }.c_str(),
|
||
img->GetWidth(), img->GetHeight(),
|
||
4, img->GetBuffer(), 0);
|
||
#endif
|
||
}
|
||
|
||
bool SaveKtx(std::span<Image*> vec_img, std::string_view path_name)
|
||
{
|
||
Image* img_big = vec_img[0];
|
||
// 1.准备纹理创建信息
|
||
ktxTextureCreateInfo createInfo;
|
||
createInfo.vkFormat = 43;// 37; // VK_FORMAT_R8G8B8A8_UNORM; // 指定纹理格式
|
||
createInfo.baseWidth = img_big->GetWidth();
|
||
createInfo.baseHeight = img_big->GetHeight();
|
||
createInfo.baseDepth = 1;
|
||
createInfo.numDimensions = 2; // 2D纹理
|
||
createInfo.numLevels = (ktx_uint32_t)vec_img.size(); // 只有基础mip级别
|
||
createInfo.numLayers = 1;
|
||
createInfo.numFaces = 1;
|
||
createInfo.isArray = KTX_FALSE;
|
||
createInfo.generateMipmaps = KTX_FALSE;
|
||
|
||
ktxTexture2* texture;
|
||
KTX_error_code result;
|
||
|
||
// 2.创建空的 ktxTexture2 对象
|
||
result = ktxTexture2_Create(&createInfo,
|
||
KTX_TEXTURE_CREATE_ALLOC_STORAGE, // 自动分配存储空间
|
||
&texture);
|
||
|
||
if (result != KTX_SUCCESS)
|
||
{
|
||
log_err("创建ktx对象失败:{}", path_name);
|
||
return false;
|
||
}
|
||
|
||
// 3.将图像数据(例如从PNG解码后的RGBA数据)设置到纹理对象
|
||
|
||
for (size_t i = 0; i < vec_img.size(); ++i)
|
||
{
|
||
Image* img = vec_img[i];
|
||
ktx_uint8_t* imageData = (ktx_uint8_t*)img->GetBuffer();
|
||
ktx_size_t imageSize = img->GetSizeByte();
|
||
|
||
// level=0, layer=0, faceSlice=0 表示基础层的第一个图像
|
||
result = ktxTexture_SetImageFromMemory(ktxTexture(texture),
|
||
(ktx_uint32_t)i, 0, 0,
|
||
imageData, imageSize);
|
||
if (result != KTX_SUCCESS)
|
||
{
|
||
log_err("写入ktx内存失败:{}", path_name);
|
||
return false;
|
||
}
|
||
}
|
||
#if 0
|
||
// 速度太慢,且是有损
|
||
// Debug太慢了,不使用
|
||
// 压缩
|
||
//@param[in] quality 压缩质量,取值范围为 1 ~ 255。默认值为 128(当传入 0 时即使用默认值)。
|
||
// 值越低 → 压缩率更高 / 质量更低 / 速度更快
|
||
// 值越高 → 压缩率更低 / 质量更高 / 速度更慢
|
||
ktxBasisParams params = {};
|
||
params.structSize = sizeof(params);
|
||
params.threadCount = 1;
|
||
params.compressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL;
|
||
params.qualityLevel = 255;
|
||
|
||
result = ktxTexture2_CompressBasisEx(texture, ¶ms);
|
||
if (result != KTX_SUCCESS)
|
||
{
|
||
log_err("Basis编码失败:{}", path_name);
|
||
return false;
|
||
}
|
||
#else
|
||
// 第一步:编码为 UASTC(高质量,向 GPU 格式转码时接近无损)
|
||
ktxBasisParams params{};
|
||
params.structSize = sizeof(params);
|
||
params.threadCount = 1; // 外部是多线程,这里一个线程即可
|
||
params.uastc = KTX_TRUE;
|
||
params.qualityLevel = 255; // 最高质量,1-255,255最佳
|
||
|
||
result = ktxTexture2_CompressBasisEx(texture, ¶ms);
|
||
if (result != KTX_SUCCESS)
|
||
{
|
||
log_err("Basis编码失败:{}", path_name);
|
||
ktxTexture_Destroy(ktxTexture(texture));
|
||
return false;
|
||
}
|
||
|
||
// 第二步:对 UASTC 数据进行 Zstandard 无损压缩
|
||
result = ktxTexture2_DeflateZstd(texture, 1); // 压缩级别 1-22,越高压缩率越大但越慢
|
||
if (result != KTX_SUCCESS)
|
||
{
|
||
log_warn("Zstd压缩失败:{}", path_name);
|
||
}
|
||
#endif
|
||
// 4. 将纹理对象写入到文件
|
||
result = ktxTexture_WriteToNamedFile(ktxTexture(texture), path_name.data());
|
||
if (result != KTX_SUCCESS)
|
||
{
|
||
log_err("保存ktx文件失败:{}", path_name);
|
||
return false;
|
||
}
|
||
// 5. 清理资源
|
||
ktxTexture_Destroy(ktxTexture(texture));
|
||
return true;
|
||
}
|
||
|
||
void CalcLeastRect(const Image* img, RectU& rect)
|
||
{
|
||
using namespace Align;
|
||
assert(img);
|
||
|
||
if(Math::IsRectEmpty(rect))
|
||
rect = createRectU(img->GetSize());
|
||
|
||
Math::GetRectTrim([img](unsigned x, unsigned y)
|
||
{
|
||
return ABGR::GetA(img->Get(y, x));
|
||
}, rect);
|
||
}
|
||
|
||
bool IsHaveTranslucent(const Image* img, const RectU& rect)
|
||
{
|
||
if (!Math::IsRectInSize(rect, img->GetSize()))
|
||
{
|
||
log_err("判断区域越界:rect:{},size:{}", to_string(rect), to_string(img->GetSize()));
|
||
return false;
|
||
}
|
||
unsigned length = Accumulate::Mul(img->GetSize());
|
||
for (unsigned j = rect[1]; j < rect[3]; ++j)
|
||
{
|
||
for (unsigned i = rect[0]; i < rect[2]; ++i)
|
||
{
|
||
unsigned index = j * img->GetWidth() + i;
|
||
|
||
uint8_t A = ABGR::GetA(img->GetData()[index]);
|
||
if (A != 255 && A != 0)
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void AddRect(Image* img_tar, const Image* img_src, const RectU& rect, const PointU& tar_xy)
|
||
{
|
||
assert(img_tar && img_src);
|
||
if (!img_src || img_src->IsEmpty())
|
||
{
|
||
log_err0("传入img无效!");
|
||
return;
|
||
}
|
||
|
||
if (!Math::IsRectInSize(rect, img_src->GetSize()))
|
||
{
|
||
log_err0("源图像区域无效!");
|
||
return;
|
||
}
|
||
|
||
Size img_size = img_src->GetSize();
|
||
Size s_size = Align::GetSize(rect);
|
||
|
||
if ((tar_xy[0] + s_size[0] > img_tar->GetWidth())
|
||
|| (tar_xy[1] + s_size[1] > img_tar->GetHeight()))
|
||
{
|
||
log_err0("目标图像过小!");
|
||
return;
|
||
}
|
||
|
||
uint32_t* buf_this = img_tar->GetData();
|
||
const uint32_t* buf_b = img_src->GetData();
|
||
//copy
|
||
for (size_t j = 0; j < s_size[1]; ++j)
|
||
{
|
||
memcpy(&(buf_this[(tar_xy[1] + j) * img_tar->GetWidth() + tar_xy[0]]),
|
||
&(buf_b[(rect[1] + j) * img_size[0] + rect[0]]),
|
||
size_t(s_size[0] * sizeof(uint32_t)));
|
||
}
|
||
}
|
||
|
||
void SetAlphaZero(Image* img)
|
||
{
|
||
size_t length = (size_t)img->GetSizeMul();
|
||
for (size_t i = 0; i != length; ++i)
|
||
{
|
||
uint32_t& col = img->GetData()[i];
|
||
if (ABGR::GetA(col) == 0)
|
||
col = 0;
|
||
}
|
||
}
|
||
|
||
void SetAlphaZero(Image* img, uint8_t a, uint32_t col_new)
|
||
{
|
||
size_t length = (size_t)img->GetSizeMul();
|
||
for (size_t i = 0; i != length; ++i)
|
||
{
|
||
uint32_t& col = img->GetData()[i];
|
||
if ((col >> 24) <= a)
|
||
col = col_new;
|
||
}
|
||
}
|
||
|
||
Image* Resize(const Image* img, const Size& size)
|
||
{
|
||
//FLAG_ALPHA_PREMULTIPLIED
|
||
if (Any::Zero(size))
|
||
{
|
||
log_err("目标大小错误:{}", to_string(size));
|
||
return nullptr;
|
||
}
|
||
Image* ret = Img::Create(size, ColorDef::BLUE);
|
||
int i = stbir_resize(
|
||
(unsigned char*)img->GetBuffer(), img->GetWidth(), img->GetHeight(), 0,
|
||
(unsigned char*)ret->GetBuffer(), ret->GetWidth(), ret->GetHeight(), 0,
|
||
STBIR_TYPE_UINT8, 4, 3, 0,
|
||
STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP,
|
||
STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
|
||
STBIR_COLORSPACE_SRGB, nullptr);
|
||
if (i == 0)
|
||
{
|
||
delete ret;
|
||
log_err("缩放图像失败:{}", to_string(size));
|
||
return nullptr;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
dl::Image* Resize(const Image* img, Float scale)
|
||
{
|
||
Size size = toSize(toPosition(img->GetSize()) * scale);
|
||
if (Any::Zero(size))
|
||
{
|
||
log_err("目标大小错误:{}", to_string(size));
|
||
return nullptr;
|
||
}
|
||
return Resize(img, size);
|
||
}
|
||
|
||
dl::Image* Extend(const Image* img, unsigned i, ExtendMode mode /*= ExtendMode::ZERO*/)
|
||
{
|
||
Size size = img->GetSize();
|
||
size[0] += i * 2;
|
||
size[1] += i * 2;
|
||
Image* ret = Img::Create(size, ColorDef::NONE);
|
||
AddRect(ret, img, createRectU(img->GetSize()), { i, i });
|
||
if (mode == ExtendMode::COPY)
|
||
{
|
||
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
void BuildBorder(Image* img, unsigned i)
|
||
{
|
||
if (Any::Less(img->GetSize(), i * 2))
|
||
{
|
||
log_err("图像大小过小,至少为 边界*2 大小:{}", to_string(img->GetSize()));
|
||
return;
|
||
}
|
||
|
||
unsigned w = img->GetWidth();
|
||
unsigned h = img->GetHeight();
|
||
|
||
// 上 左中右
|
||
for (unsigned y = 0; y < i; ++y)
|
||
{
|
||
for (unsigned x = 0; x < i; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[i][i];
|
||
}
|
||
for (unsigned x = i; x < w - i; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[i][x];
|
||
}
|
||
for (unsigned x = w - i; x < w; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[i][w - i - 1];
|
||
}
|
||
}
|
||
// 中 左右
|
||
for (unsigned y = i; y < h - i; ++y)
|
||
{
|
||
for (unsigned x = 0; x < i; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[y][i];
|
||
}
|
||
for (unsigned x = w - i; x < w; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[y][w - i - 1];
|
||
}
|
||
}
|
||
// 下 左中右
|
||
for (unsigned y = h - i; y < h; ++y)
|
||
{
|
||
for (unsigned x = 0; x < i; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[h - i - 1][i];
|
||
}
|
||
for (unsigned x = i; x < w - i; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[h - i - 1][x];
|
||
}
|
||
for (unsigned x = w - i; x < w; ++x)
|
||
{
|
||
(*img)[y][x] = (*img)[h - i - 1][w - i - 1];
|
||
}
|
||
}
|
||
}
|
||
|
||
void CvtEffect2Normal(Image* img)
|
||
{
|
||
for (unsigned y = 0; y < img->GetHeight(); ++y)
|
||
{
|
||
for (unsigned x = 0; x < img->GetWidth(); ++x)
|
||
{
|
||
uint32_t& col = img->GetRef(y, x);
|
||
|
||
auto arr = GetColorChannel(col);
|
||
|
||
uint8_t a = std::max({ arr[1], arr[2], arr[3] });
|
||
arr[0] = a;
|
||
if (a != 0)
|
||
{
|
||
arr[1] = arr[1] * 255 / a;
|
||
arr[2] = arr[2] * 255 / a;
|
||
arr[3] = arr[3] * 255 / a;
|
||
}
|
||
col = AssembleColor(arr);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
} |