Files
ObsidianDragon/src/util/texture_loader.h
DanS 4b8d80b2fc feat(contacts): animated avatars (GIF/WebP) with a Settings toggle
Animate contact avatars end to end:

- texture_loader gains LoadAnimatedRGBA: decodes an image into a downscaled
  RGBA frame sequence + per-frame durations — animated GIF via stb
  (stbi_load_gif_from_memory) and animated WebP via libwebp's WebPAnimDecoder;
  stills (and APNG, which stb reads as one image) return a single frame. Frames
  are box-downscaled (smaller cap for animations) to bound VRAM, capped at 300.
- The contacts avatar cache now holds a frame sequence; currentAvatarFrame()
  advances animated avatars by the ImGui clock and is used everywhere avatars
  draw (list, cards, table, grid, preview). When a live animated frame is drawn
  it flags the render loop (ConsumeContactsAvatarAnimation, clear-on-read) so
  main.cpp keeps producing frames while animation plays and idles when it stops
  or the contacts view is hidden.
- New animate_avatars setting (default on) + a Settings appearance toggle
  ("Animate avatars"); off shows the first frame only. currentAvatarFrame
  honors it. +i18n (8 langs, CJK subset rebuilt for 帧/播/첫).

Verified: a 3-frame GIF and a 3-frame animated WebP both decode to 3 frames
with correct 120ms delays through the exact libwebp/stb calls used here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 11:11:06 -05:00

103 lines
3.8 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "imgui.h"
#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 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 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)
*/
void DestroyTexture(ImTextureID texID);
} // namespace util
} // namespace dragonx