fix(images): cap and stream image decoding in a GL-free decoder
Image decoding lived in the GL texture file and had no limits: a file of any size was read whole, stb and libwebp allocated whatever the header claimed (a WebP alone up to 1 GB), and a GIF was decoded at full size, every frame, before the 300-frame cap. A 720p 150-frame GIF avatar cost 0.2-1.5 s and 140-370 MB on the UI thread. stb's GIF loader also read before the start of its buffer on disposal-3 frames, and when a long GIF ran out of memory it freed the frame delays that the caller then freed again (an abort). Decoding, and the stb implementation, now live in the GL-free util/image_decode_core, safe on any thread: - the header is probed first; stills above 256 MP, any GIF or animated WebP canvas above 32 Mi px, and files above 256 MiB are rejected before anything is allocated (real photos, 200 MP phone shots and wide panoramas still load); - a GIF decodes one composited frame at a time, downscaled as it goes, and stops at the frame cap: peak memory is stb's working buffers, two canvases and the small frames; a truncated GIF keeps the frames decoded so far; - a header stb cannot read means "unsupported", never a blind decode; - decodes above 16 MP run one at a time; a decode can be cancelled between frames. util::StartImageDecode runs one decode on a detached worker that owns its job and its lane's in-flight counter, so nothing waits at exit; the next commits move avatars and the image picker onto it. The limits apply now to avatars and the picker's hover previews; thumbnails and avatar imports get them when they move to workers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,37 +6,17 @@
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
// GL-free decoding (AnimFrames, LoadAnimatedRGBA[Ex], IsAnimatedImageFile, LoadRawPixels*, FreeRawPixels,
|
||||
// StartImageDecode) lives in image_decode_core.h and is safe on worker threads. Everything declared below
|
||||
// creates or destroys GPU textures and must run on the UI (GL/DX) thread.
|
||||
#include "image_decode_core.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
/**
|
||||
* @brief Downscaled RGBA frame sequence for an image (1 frame for stills, N for animations).
|
||||
*/
|
||||
struct AnimFrames {
|
||||
std::vector<std::vector<unsigned char>> frames; ///< each is w*h*4 RGBA (already downscaled)
|
||||
std::vector<float> delaysSec; ///< per-frame duration in seconds (parallel to frames)
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Decode an image into 1+ downscaled RGBA frames plus per-frame durations.
|
||||
*
|
||||
* Animated GIF (via stb) and animated WebP (via libwebp WebPAnimDecoder) return every frame (capped at
|
||||
* maxFrames); static formats — and APNG, which stb reads as a single image — return one frame. Frames
|
||||
* are box-downscaled internally (smaller for animations to bound VRAM). The caller uploads each frame
|
||||
* with CreateRawTexture. Returns false on decode failure.
|
||||
*/
|
||||
bool LoadAnimatedRGBA(const char* path, int maxFrames, AnimFrames& out);
|
||||
|
||||
/**
|
||||
* @brief Cheaply report whether an image file is an animation (multi-frame GIF or animated WebP),
|
||||
* WITHOUT a full decode — a header/structure probe. False for stills + non-animatable formats.
|
||||
*/
|
||||
bool IsAnimatedImageFile(const char* path);
|
||||
|
||||
/**
|
||||
* @brief Load a PNG/JPG/BMP image from disk into an OpenGL or DX11 texture.
|
||||
* @param path File path (relative to working directory or absolute)
|
||||
@@ -74,30 +54,6 @@ bool LoadTextureFromMemory(const unsigned char* data, size_t dataSize,
|
||||
bool CreateRawTexture(const unsigned char* pixels, int w, int h,
|
||||
bool repeat, ImTextureID* outTexID);
|
||||
|
||||
/**
|
||||
* @brief Load raw RGBA pixel data from a PNG/JPG/BMP file (no GPU texture).
|
||||
* @param path File path
|
||||
* @param outW Receives image width
|
||||
* @param outH Receives image height
|
||||
* @return Pointer to RGBA pixels (caller must free with FreeRawPixels), or nullptr on failure
|
||||
*/
|
||||
unsigned char* LoadRawPixelsFromFile(const char* path, int* outW, int* outH);
|
||||
|
||||
/**
|
||||
* @brief Load raw RGBA pixel data from in-memory compressed image data (no GPU texture).
|
||||
* @param data Pointer to compressed image data (e.g. PNG bytes)
|
||||
* @param dataSize Size of the data in bytes
|
||||
* @param outW Receives image width
|
||||
* @param outH Receives image height
|
||||
* @return Pointer to RGBA pixels (caller must free with FreeRawPixels), or nullptr on failure
|
||||
*/
|
||||
unsigned char* LoadRawPixelsFromMemory(const unsigned char* data, size_t dataSize, int* outW, int* outH);
|
||||
|
||||
/**
|
||||
* @brief Free pixels returned by LoadRawPixelsFromFile / LoadRawPixelsFromMemory.
|
||||
*/
|
||||
void FreeRawPixels(unsigned char* pixels);
|
||||
|
||||
/**
|
||||
* @brief Destroy a GPU texture previously created by Load/CreateRaw functions.
|
||||
* @param texID Texture handle (GLuint on OpenGL, ID3D11ShaderResourceView* on DX11)
|
||||
|
||||
Reference in New Issue
Block a user