feat(images): WebP decode via libwebp + more stb formats
Broaden avatar/image support: - Add libwebp (FetchContent, static, decode-only) so WebP loads. Built from source for Linux / mingw-Windows / macOS-osxcross identically — the cross sysroots have no webp, so vendoring from source is the one portable path; encode/tool builds are disabled to avoid pulling in libpng/zlib. Linked as webp + webpdemux (the latter for animated WebP, wired next). - texture_loader routes all decode through DecodeImageRGBA: sniffs the RIFF/ WEBP header and uses libwebp (WebPDecodeRGBAInto into a free()-able buffer), else stb — so every existing caller (avatars, QR, thumbnails) gains WebP for free with no allocator mismatch. - Enable stb's TGA / PSD / PNM / PIC decoders and add .webp/.tga/.psd/.pnm/ .ppm/.pgm/.pic to the image-picker + avatar-library extension lists. Verified: libwebp builds static and a real .webp decodes to correct RGBA. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -295,6 +295,29 @@ else()
|
||||
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# libwebp - WebP decode (still + animated via WebPAnimDecoder). Built from source, static, decode-only
|
||||
# so Linux / mingw-Windows / macOS-osxcross all build it identically (the mingw/osx sysroots have no
|
||||
# webp). Encode tools are disabled to avoid pulling in libpng/zlib that the cross sysroots lack.
|
||||
message(STATUS "Fetching libwebp (decode-only, static)...")
|
||||
FetchContent_Declare(
|
||||
libwebp
|
||||
GIT_REPOSITORY https://github.com/webmproject/libwebp.git
|
||||
GIT_TAG v1.4.0
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(WEBP_LINK_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_ANIM_UTILS OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_CWEBP OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_DWEBP OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_GIF2WEBP OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_IMG2WEBP OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_VWEBP OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_WEBPINFO OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_LIBWEBPMUX OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_WEBPMUX OFF CACHE BOOL "" FORCE)
|
||||
set(WEBP_BUILD_EXTRAS OFF CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(libwebp)
|
||||
|
||||
# libsodium - platform-specific
|
||||
# Search order per platform:
|
||||
# 1. Local pre-built in libs/libsodium{-mac,-win}/ (downloaded by scripts/fetch-libsodium.sh)
|
||||
@@ -735,6 +758,7 @@ target_include_directories(ObsidianDragon PRIVATE
|
||||
${GLAD_INCLUDE}
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${MINIZ_DIR}
|
||||
${libwebp_SOURCE_DIR}/src # <webp/decode.h>, <webp/demux.h> (FetchContent build tree)
|
||||
)
|
||||
|
||||
target_link_libraries(ObsidianDragon PRIVATE
|
||||
@@ -744,6 +768,8 @@ target_link_libraries(ObsidianDragon PRIVATE
|
||||
sqlite3_amalgamation
|
||||
${CURL_LIBRARIES}
|
||||
${SODIUM_LIBRARY}
|
||||
webp
|
||||
webpdemux # WebPAnimDecoder (animated WebP); transitively pulls in webp + sharpyuv
|
||||
)
|
||||
|
||||
if(DRAGONX_LITE_BACKEND_READY)
|
||||
|
||||
@@ -202,7 +202,8 @@ static bool s_avatarLibraryDirty = true;
|
||||
static bool isImageFileName(const std::string& name) {
|
||||
std::string lo = name;
|
||||
for (char& c : lo) c = static_cast<char>(std::tolower((unsigned char)c));
|
||||
for (const char* e : { ".png", ".jpg", ".jpeg", ".bmp", ".gif" }) {
|
||||
for (const char* e : { ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp",
|
||||
".tga", ".psd", ".pnm", ".ppm", ".pgm", ".pic" }) {
|
||||
const std::string ext(e);
|
||||
if (lo.size() > ext.size() && lo.compare(lo.size() - ext.size(), ext.size(), ext) == 0) return true;
|
||||
}
|
||||
|
||||
@@ -297,7 +297,8 @@ private:
|
||||
static bool isImageName(const std::string& name) {
|
||||
auto lower = name;
|
||||
for (char& c : lower) c = (char)std::tolower((unsigned char)c);
|
||||
static const char* kExt[] = { ".png", ".jpg", ".jpeg", ".bmp", ".gif" };
|
||||
static const char* kExt[] = { ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp",
|
||||
".tga", ".psd", ".pnm", ".ppm", ".pgm", ".pic" };
|
||||
for (const char* e : kExt) {
|
||||
const std::string ext(e);
|
||||
if (lower.size() > ext.size() && lower.compare(lower.size()-ext.size(), ext.size(), ext) == 0)
|
||||
|
||||
@@ -7,17 +7,25 @@
|
||||
// stb_image — single-file image loader (public domain)
|
||||
// Only compiled here; all other files just include the header.
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
// Formats we actually decode: PNG for app assets, plus JPEG/BMP/GIF so user-chosen contact avatars
|
||||
// (which are commonly JPEGs) load — must stay in sync with ImagePicker's accepted extensions.
|
||||
// Formats stb decodes for us: PNG for app assets, plus the common user-avatar formats. WebP is handled
|
||||
// separately (libwebp) below. Keep this list in sync with ImagePicker's accepted extensions.
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_ONLY_JPEG
|
||||
#define STBI_ONLY_BMP
|
||||
#define STBI_ONLY_GIF
|
||||
#define STBI_ONLY_TGA
|
||||
#define STBI_ONLY_PSD
|
||||
#define STBI_ONLY_PNM // .pnm/.ppm/.pgm/.pbm
|
||||
#define STBI_ONLY_PIC
|
||||
#define STBI_NO_STDIO // we do our own fread for portability
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <webp/decode.h>
|
||||
#include <webp/demux.h> // WebPAnimDecoder — animated WebP
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#ifdef DRAGONX_USE_DX11
|
||||
@@ -43,6 +51,30 @@ static ID3D11Device* GetImGuiD3D11Device()
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
// True if the buffer is a WebP (RIFF....WEBP) container.
|
||||
static bool IsWebP(const unsigned char* d, size_t n)
|
||||
{
|
||||
return n >= 12 && memcmp(d, "RIFF", 4) == 0 && memcmp(d + 8, "WEBP", 4) == 0;
|
||||
}
|
||||
|
||||
// Decode any supported image to a fresh RGBA8 buffer. WebP goes through libwebp; everything else
|
||||
// through stb. The returned buffer is always free()-able (stbi's default free is free(), and the WebP
|
||||
// path decodes into a malloc'd buffer), so FreeRawPixels(stbi_image_free) releases either. NULL on fail.
|
||||
static unsigned char* DecodeImageRGBA(const unsigned char* data, size_t len, int* outW, int* outH)
|
||||
{
|
||||
if (IsWebP(data, len)) {
|
||||
int w = 0, h = 0;
|
||||
if (!WebPGetInfo(data, len, &w, &h) || w <= 0 || h <= 0) return nullptr;
|
||||
unsigned char* buf = (unsigned char*)malloc((size_t)w * h * 4);
|
||||
if (!buf) return nullptr;
|
||||
if (!WebPDecodeRGBAInto(data, len, buf, (size_t)w * h * 4, w * 4)) { free(buf); return nullptr; }
|
||||
*outW = w; *outH = h;
|
||||
return buf;
|
||||
}
|
||||
int channels = 0;
|
||||
return stbi_load_from_memory(data, (int)len, outW, outH, &channels, 4);
|
||||
}
|
||||
|
||||
// Read entire file into memory
|
||||
static bool ReadFileToBuffer(const char* path, std::vector<unsigned char>& buf)
|
||||
{
|
||||
@@ -66,11 +98,10 @@ bool LoadTextureFromFile(const char* path, ImTextureID* outTexID, int* outW, int
|
||||
return false;
|
||||
}
|
||||
|
||||
int w = 0, h = 0, channels = 0;
|
||||
unsigned char* pixels = stbi_load_from_memory(
|
||||
fileData.data(), (int)fileData.size(), &w, &h, &channels, 4);
|
||||
int w = 0, h = 0;
|
||||
unsigned char* pixels = DecodeImageRGBA(fileData.data(), fileData.size(), &w, &h);
|
||||
if (!pixels) {
|
||||
DEBUG_LOGF("LoadTextureFromFile: stbi failed for '%s'\n", path);
|
||||
DEBUG_LOGF("LoadTextureFromFile: decode failed for '%s'\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -154,11 +185,10 @@ bool LoadTextureFromMemory(const unsigned char* data, size_t dataSize,
|
||||
return false;
|
||||
}
|
||||
|
||||
int w = 0, h = 0, channels = 0;
|
||||
unsigned char* pixels = stbi_load_from_memory(
|
||||
data, (int)dataSize, &w, &h, &channels, 4);
|
||||
int w = 0, h = 0;
|
||||
unsigned char* pixels = DecodeImageRGBA(data, dataSize, &w, &h);
|
||||
if (!pixels) {
|
||||
DEBUG_LOGF("LoadTextureFromMemory: stbi decode failed\n");
|
||||
DEBUG_LOGF("LoadTextureFromMemory: decode failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -298,16 +328,13 @@ unsigned char* LoadRawPixelsFromFile(const char* path, int* outW, int* outH)
|
||||
{
|
||||
std::vector<unsigned char> fileData;
|
||||
if (!ReadFileToBuffer(path, fileData)) return nullptr;
|
||||
int channels = 0;
|
||||
return stbi_load_from_memory(fileData.data(), (int)fileData.size(),
|
||||
outW, outH, &channels, 4);
|
||||
return DecodeImageRGBA(fileData.data(), fileData.size(), outW, outH);
|
||||
}
|
||||
|
||||
unsigned char* LoadRawPixelsFromMemory(const unsigned char* data, size_t dataSize, int* outW, int* outH)
|
||||
{
|
||||
if (!data || dataSize == 0) return nullptr;
|
||||
int channels = 0;
|
||||
return stbi_load_from_memory(data, (int)dataSize, outW, outH, &channels, 4);
|
||||
return DecodeImageRGBA(data, dataSize, outW, outH);
|
||||
}
|
||||
|
||||
void FreeRawPixels(unsigned char* pixels)
|
||||
|
||||
Reference in New Issue
Block a user