Files
ObsidianDragon/src/util/texture_loader.h
T
DanSandClaude Opus 5.5 c6fc1ac168 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>
2026-10-09 21:09:48 -05:00

65 lines
2.3 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#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 Load a PNG/JPG/BMP image from disk into an OpenGL or DX11 texture.
* @param path File path (relative to working directory or absolute)
* @param outTexID Receives the ImTextureID for ImGui rendering
* @param outW Receives image width
* @param outH Receives image height
* @return true on success
*
* The caller is responsible for destroying the texture.
* On OpenGL, cast outTexID to GLuint and call glDeleteTextures.
*/
bool LoadTextureFromFile(const char* path, ImTextureID* outTexID, int* outW, int* outH);
/**
* @brief Load a PNG/JPG/BMP image from memory into an OpenGL or DX11 texture.
* @param data Pointer to compressed image data (e.g. PNG bytes)
* @param dataSize Size of the data in bytes
* @param outTexID Receives the ImTextureID for ImGui rendering
* @param outW Receives image width
* @param outH Receives image height
* @return true on success
*/
bool LoadTextureFromMemory(const unsigned char* data, size_t dataSize,
ImTextureID* outTexID, int* outW, int* outH);
/**
* @brief Create a texture from raw RGBA pixel data (no decoding needed).
* @param pixels Pointer to RGBA pixel data (4 bytes per pixel, caller-owned)
* @param w Image width
* @param h Image height
* @param repeat If true, set wrap mode to GL_REPEAT (for tiling); else CLAMP_TO_EDGE
* @param outTexID Receives the ImTextureID
* @return true on success
*/
bool CreateRawTexture(const unsigned char* pixels, int w, int h,
bool repeat, ImTextureID* outTexID);
/**
* @brief Destroy a GPU texture previously created by Load/CreateRaw functions.
* @param texID Texture handle (GLuint on OpenGL, ID3D11ShaderResourceView* on DX11)
*/
void DestroyTexture(ImTextureID texID);
} // namespace util
} // namespace dragonx