Files
dl_old/dl/render/dl_image.cpp
T
2026-08-25 15:49:23 +08:00

470 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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, &params);
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, &params);
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);
}
}
}
}
}