Compare commits
7 Commits
v0.1
...
95b77cb696
Author | SHA1 | Date | |
---|---|---|---|
95b77cb696 | |||
2e28ad7bb9 | |||
75f78f8c7f | |||
ef59386e5c | |||
c0b57c30bd | |||
42b3866753 | |||
aff239377d |
2
external/solanaceae_contact
vendored
2
external/solanaceae_contact
vendored
Submodule external/solanaceae_contact updated: 65d253afea...738d2abe7b
@@ -1,24 +1,34 @@
|
|||||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||||
|
|
||||||
add_executable(tomato
|
add_executable(tomato
|
||||||
main.cpp
|
./main.cpp
|
||||||
icon.rc
|
./icon.rc
|
||||||
|
|
||||||
screen.hpp
|
./screen.hpp
|
||||||
start_screen.hpp
|
./start_screen.hpp
|
||||||
start_screen.cpp
|
./start_screen.cpp
|
||||||
main_screen.hpp
|
./main_screen.hpp
|
||||||
main_screen.cpp
|
./main_screen.cpp
|
||||||
|
|
||||||
./tox_client.hpp
|
./tox_client.hpp
|
||||||
./tox_client.cpp
|
./tox_client.cpp
|
||||||
./auto_dirty.hpp
|
./auto_dirty.hpp
|
||||||
./auto_dirty.cpp
|
./auto_dirty.cpp
|
||||||
|
|
||||||
theme.hpp
|
./image_loader.hpp
|
||||||
texture_uploader.hpp
|
./image_loader_sdl_bmp.hpp
|
||||||
|
./image_loader_sdl_bmp.cpp
|
||||||
|
|
||||||
|
./theme.hpp
|
||||||
|
./texture_uploader.hpp
|
||||||
./sdlrenderer_texture_uploader.hpp
|
./sdlrenderer_texture_uploader.hpp
|
||||||
./sdlrenderer_texture_uploader.cpp
|
./sdlrenderer_texture_uploader.cpp
|
||||||
|
./texture_cache.hpp
|
||||||
|
./texture_cache.cpp
|
||||||
|
./tox_avatar_loader.hpp
|
||||||
|
./tox_avatar_loader.cpp
|
||||||
|
./sdl_clipboard_utils.hpp
|
||||||
|
./sdl_clipboard_utils.cpp
|
||||||
./file_selector.hpp
|
./file_selector.hpp
|
||||||
./file_selector.cpp
|
./file_selector.cpp
|
||||||
|
|
||||||
|
@@ -11,6 +11,10 @@
|
|||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include "./sdl_clipboard_utils.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -23,10 +27,22 @@ ChatGui4::ChatGui4(
|
|||||||
RegistryMessageModel& rmm,
|
RegistryMessageModel& rmm,
|
||||||
Contact3Registry& cr,
|
Contact3Registry& cr,
|
||||||
TextureUploaderI& tu
|
TextureUploaderI& tu
|
||||||
) : _conf(conf), _rmm(rmm), _cr(cr) {
|
) : _conf(conf), _rmm(rmm), _cr(cr), _tal(_cr), _contact_tc(_tal, tu) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatGui4::render(void) {
|
void ChatGui4::render(void) {
|
||||||
|
if (!_cr.storage<Contact::Components::TagAvatarInvalidate>().empty()) { // handle force-reloads for avatars
|
||||||
|
std::vector<Contact3> to_purge;
|
||||||
|
_cr.view<Contact::Components::TagAvatarInvalidate>().each([&to_purge](const Contact3 c) {
|
||||||
|
to_purge.push_back(c);
|
||||||
|
});
|
||||||
|
_cr.remove<Contact::Components::TagAvatarInvalidate>(to_purge.cbegin(), to_purge.cend());
|
||||||
|
_contact_tc.invalidate(to_purge);
|
||||||
|
}
|
||||||
|
// ACTUALLY NOT IF RENDERED, MOVED LOGIC TO ABOVE
|
||||||
|
// it might unload textures, so it needs to be done before rendering
|
||||||
|
_contact_tc.update();
|
||||||
|
|
||||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||||
ImGui::SetNextWindowSize(viewport->WorkSize);
|
ImGui::SetNextWindowSize(viewport->WorkSize);
|
||||||
@@ -118,6 +134,11 @@ void ChatGui4::render(void) {
|
|||||||
if (ImGui::BeginChild("message_log", {0, -100}, false, ImGuiWindowFlags_MenuBar)) {
|
if (ImGui::BeginChild("message_log", {0, -100}, false, ImGuiWindowFlags_MenuBar)) {
|
||||||
if (ImGui::BeginMenuBar()) {
|
if (ImGui::BeginMenuBar()) {
|
||||||
ImGui::Checkbox("show extra info", &_show_chat_extra_info);
|
ImGui::Checkbox("show extra info", &_show_chat_extra_info);
|
||||||
|
if (ImGui::SmallButton("test")) {
|
||||||
|
_cr.emplace_or_replace<Contact::Components::AvatarFile>(*_selected_contact, "tomato_v1_256.bmp");
|
||||||
|
_cr.emplace_or_replace<Contact::Components::TagAvatarInvalidate>(*_selected_contact);
|
||||||
|
std::cout << "DEBUG: added AvatarFile comp to contact\n";
|
||||||
|
}
|
||||||
ImGui::EndMenuBar();
|
ImGui::EndMenuBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +277,14 @@ void ChatGui4::render(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_V) && ImGui::IsKeyPressed(ImGuiMod_Shortcut)) {
|
||||||
|
if (const auto* mime_type = clipboardHasImage(); mime_type != nullptr) {
|
||||||
|
size_t data_size = 0;
|
||||||
|
const auto* data = SDL_GetClipboardData(mime_type, &data_size);
|
||||||
|
// open file send preview.rawpixels
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
@@ -264,8 +293,7 @@ void ChatGui4::render(void) {
|
|||||||
|
|
||||||
_fss.render();
|
_fss.render();
|
||||||
|
|
||||||
//_tc.update();
|
_contact_tc.workLoadQueue();
|
||||||
//_tc.workLoadQueue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// has MessageText
|
// has MessageText
|
||||||
@@ -501,8 +529,8 @@ bool ChatGui4::renderContactListContactBig(const Contact3 c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// avatar
|
// avatar
|
||||||
#if 0
|
#if 1
|
||||||
const auto [id, width, height] = _tc.get("test");
|
const auto [id, width, height] = _contact_tc.get(c);
|
||||||
ImGui::Image(
|
ImGui::Image(
|
||||||
id,
|
id,
|
||||||
ImVec2{img_y, img_y},
|
ImVec2{img_y, img_y},
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
#include <solanaceae/util/config_model.hpp>
|
#include <solanaceae/util/config_model.hpp>
|
||||||
|
|
||||||
#include "./texture_uploader.hpp"
|
#include "./texture_uploader.hpp"
|
||||||
|
#include "./texture_cache.hpp"
|
||||||
|
#include "./tox_avatar_loader.hpp"
|
||||||
#include "./file_selector.hpp"
|
#include "./file_selector.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -14,6 +16,10 @@ class ChatGui4 {
|
|||||||
RegistryMessageModel& _rmm;
|
RegistryMessageModel& _rmm;
|
||||||
Contact3Registry& _cr;
|
Contact3Registry& _cr;
|
||||||
|
|
||||||
|
ToxAvatarLoader _tal;
|
||||||
|
TextureCache<void*, Contact3, ToxAvatarLoader> _contact_tc;
|
||||||
|
//TextureCache<Message3Handle> _msg_tc;
|
||||||
|
|
||||||
FileSelector _fss;
|
FileSelector _fss;
|
||||||
|
|
||||||
std::optional<Contact3> _selected_contact;
|
std::optional<Contact3> _selected_contact;
|
||||||
|
26
src/image_loader.hpp
Normal file
26
src/image_loader.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct ImageLoaderI {
|
||||||
|
virtual ~ImageLoaderI(void) {}
|
||||||
|
|
||||||
|
struct ImageInfo {
|
||||||
|
uint32_t width {0};
|
||||||
|
uint32_t height {0};
|
||||||
|
};
|
||||||
|
virtual ImageInfo loadInfoFromMemory(const uint8_t* data, uint64_t data_size) = 0;
|
||||||
|
|
||||||
|
struct ImageResult {
|
||||||
|
uint32_t width {0};
|
||||||
|
uint32_t height {0};
|
||||||
|
struct Frame {
|
||||||
|
int32_t ms {0};
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
std::vector<Frame> frames;
|
||||||
|
};
|
||||||
|
virtual ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) = 0;
|
||||||
|
};
|
||||||
|
|
53
src/image_loader_sdl_bmp.cpp
Normal file
53
src/image_loader_sdl_bmp.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "./image_loader_sdl_bmp.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
ImageLoaderSDLBMP::ImageInfo ImageLoaderSDLBMP::loadInfoFromMemory(const uint8_t* data, uint64_t data_size) {
|
||||||
|
ImageInfo res;
|
||||||
|
|
||||||
|
auto* rw_ctx = SDL_RWFromConstMem(data, data_size);
|
||||||
|
|
||||||
|
SDL_Surface* surf = SDL_LoadBMP_RW(rw_ctx, SDL_TRUE);
|
||||||
|
if (surf == nullptr) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.width = surf->w;
|
||||||
|
res.height = surf->h;
|
||||||
|
|
||||||
|
SDL_DestroySurface(surf);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageLoaderSDLBMP::ImageResult ImageLoaderSDLBMP::loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) {
|
||||||
|
ImageResult res;
|
||||||
|
|
||||||
|
auto* rw_ctx = SDL_RWFromConstMem(data, data_size);
|
||||||
|
|
||||||
|
SDL_Surface* surf = SDL_LoadBMP_RW(rw_ctx, SDL_TRUE);
|
||||||
|
if (surf == nullptr) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Surface* conv_surf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_RGBA32);
|
||||||
|
SDL_DestroySurface(surf);
|
||||||
|
if (conv_surf == nullptr) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.width = surf->w;
|
||||||
|
res.height = surf->h;
|
||||||
|
|
||||||
|
SDL_LockSurface(conv_surf);
|
||||||
|
|
||||||
|
auto& new_frame = res.frames.emplace_back();
|
||||||
|
new_frame.ms = 0;
|
||||||
|
new_frame.data.insert(new_frame.data.cbegin(), (const uint8_t*)conv_surf->pixels, ((const uint8_t*)conv_surf->pixels) + (surf->w*surf->h*4));
|
||||||
|
|
||||||
|
SDL_UnlockSurface(conv_surf);
|
||||||
|
SDL_DestroySurface(conv_surf);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
9
src/image_loader_sdl_bmp.hpp
Normal file
9
src/image_loader_sdl_bmp.hpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "./image_loader.hpp"
|
||||||
|
|
||||||
|
struct ImageLoaderSDLBMP : public ImageLoaderI {
|
||||||
|
ImageInfo loadInfoFromMemory(const uint8_t* data, uint64_t data_size) override;
|
||||||
|
ImageResult loadFromMemoryRGBA(const uint8_t* data, uint64_t data_size) override;
|
||||||
|
};
|
||||||
|
|
@@ -6,7 +6,6 @@
|
|||||||
#include <imgui/backends/imgui_impl_sdlrenderer3.h>
|
#include <imgui/backends/imgui_impl_sdlrenderer3.h>
|
||||||
|
|
||||||
#include "./theme.hpp"
|
#include "./theme.hpp"
|
||||||
#include "./sdlrenderer_texture_uploader.hpp"
|
|
||||||
|
|
||||||
#include "./start_screen.hpp"
|
#include "./start_screen.hpp"
|
||||||
|
|
||||||
@@ -63,6 +62,11 @@ int main(int argc, char** argv) {
|
|||||||
quit = true;
|
quit = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (screen->handleEvent(event)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui_ImplSDL3_ProcessEvent(&event);
|
ImGui_ImplSDL3_ProcessEvent(&event);
|
||||||
}
|
}
|
||||||
if (quit) {
|
if (quit) {
|
||||||
|
@@ -55,6 +55,10 @@ MainScreen::MainScreen(SDL_Renderer* renderer_, std::string save_path) :
|
|||||||
MainScreen::~MainScreen(void) {
|
MainScreen::~MainScreen(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MainScreen::handleEvent(SDL_Event& e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Screen* MainScreen::poll(bool& quit) {
|
Screen* MainScreen::poll(bool& quit) {
|
||||||
auto new_time = std::chrono::high_resolution_clock::now();
|
auto new_time = std::chrono::high_resolution_clock::now();
|
||||||
const float time_delta {std::chrono::duration<float, std::chrono::seconds::period>(new_time - last_time).count()};
|
const float time_delta {std::chrono::duration<float, std::chrono::seconds::period>(new_time - last_time).count()};
|
||||||
|
@@ -55,6 +55,8 @@ struct MainScreen final : public Screen {
|
|||||||
MainScreen(SDL_Renderer* renderer_, std::string save_path);
|
MainScreen(SDL_Renderer* renderer_, std::string save_path);
|
||||||
~MainScreen(void);
|
~MainScreen(void);
|
||||||
|
|
||||||
|
bool handleEvent(SDL_Event& e) override;
|
||||||
|
|
||||||
// return nullptr if not next
|
// return nullptr if not next
|
||||||
// sets bool quit to true if exit
|
// sets bool quit to true if exit
|
||||||
Screen* poll(bool&) override;
|
Screen* poll(bool&) override;
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
struct Screen {
|
struct Screen {
|
||||||
virtual ~Screen(void) = default;
|
virtual ~Screen(void) = default;
|
||||||
|
|
||||||
|
// return true if handled
|
||||||
|
virtual bool handleEvent(SDL_Event& e) { return false; }
|
||||||
|
|
||||||
// return nullptr if not next
|
// return nullptr if not next
|
||||||
// sets bool quit to true if exit
|
// sets bool quit to true if exit
|
||||||
virtual Screen* poll(bool& quit) = 0;
|
virtual Screen* poll(bool& quit) = 0;
|
||||||
|
23
src/sdl_clipboard_utils.cpp
Normal file
23
src/sdl_clipboard_utils.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "./sdl_clipboard_utils.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
const char* clipboardHasImage(void) {
|
||||||
|
const static std::vector<const char*> image_mime_types {
|
||||||
|
"image/webp",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/bmp",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* mime_type : image_mime_types) {
|
||||||
|
if (SDL_HasClipboardData(mime_type) == SDL_TRUE) {
|
||||||
|
return mime_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
6
src/sdl_clipboard_utils.hpp
Normal file
6
src/sdl_clipboard_utils.hpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// returns the mimetype (c-string) of the image in the clipboard
|
||||||
|
// or nullptr, if there is none
|
||||||
|
const char* clipboardHasImage(void);
|
||||||
|
|
@@ -13,8 +13,7 @@ uint64_t SDLRendererTextureUploader::uploadRGBA(const uint8_t* data, uint32_t wi
|
|||||||
(void*)data,
|
(void*)data,
|
||||||
width, height,
|
width, height,
|
||||||
4*width,
|
4*width,
|
||||||
//SDL_PIXELFORMAT_RGBA8888
|
SDL_PIXELFORMAT_RGBA32 // auto big/little
|
||||||
SDL_PIXELFORMAT_ABGR8888 // little endian
|
|
||||||
);
|
);
|
||||||
assert(surf); // TODO: add error reporting
|
assert(surf); // TODO: add error reporting
|
||||||
|
|
||||||
|
60
src/texture_cache.cpp
Normal file
60
src/texture_cache.cpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#include "./texture_cache.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
void TextureEntry::doAnimation(const uint64_t ts_now) {
|
||||||
|
if (frame_duration.size() > 1) { // is animation
|
||||||
|
do { // why is this loop so ugly
|
||||||
|
const uint64_t duration = getDuration();
|
||||||
|
if (ts_now - timestamp_last_rendered >= duration) {
|
||||||
|
timestamp_last_rendered += duration;
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(true);
|
||||||
|
} else {
|
||||||
|
timestamp_last_rendered = ts_now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureEntry generateTestAnim(TextureUploaderI& tu) {
|
||||||
|
TextureEntry new_entry;
|
||||||
|
new_entry.timestamp_last_rendered = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
new_entry.current_texture = 0;
|
||||||
|
for (size_t i = 0; i < 4; i++) {
|
||||||
|
// hack
|
||||||
|
// generate frame
|
||||||
|
const size_t width {2};
|
||||||
|
const size_t height {2};
|
||||||
|
std::array<uint8_t, width*height*4> pixels;
|
||||||
|
for (size_t pen = 0; pen < width*height; pen++) {
|
||||||
|
if (pen == i) {
|
||||||
|
pixels[pen*4+0] = 0xff;
|
||||||
|
pixels[pen*4+1] = 0xff;
|
||||||
|
pixels[pen*4+2] = 0xff;
|
||||||
|
pixels[pen*4+3] = 0xff;
|
||||||
|
} else {
|
||||||
|
pixels[pen*4+0] = 0x00;
|
||||||
|
pixels[pen*4+1] = 0x00;
|
||||||
|
pixels[pen*4+2] = 0x00;
|
||||||
|
pixels[pen*4+3] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto n_t = tu.uploadRGBA(pixels.data(), width, height);
|
||||||
|
|
||||||
|
// this is so ugly
|
||||||
|
new_entry.textures.emplace_back(n_t);
|
||||||
|
new_entry.frame_duration.emplace_back(250);
|
||||||
|
}
|
||||||
|
new_entry.width = 2;
|
||||||
|
new_entry.height = 2;
|
||||||
|
return new_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t getNowMS(void) {
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
165
src/texture_cache.hpp
Normal file
165
src/texture_cache.hpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "./texture_uploader.hpp"
|
||||||
|
|
||||||
|
#include <entt/container/dense_map.hpp>
|
||||||
|
#include <entt/container/dense_set.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
struct TextureEntry {
|
||||||
|
uint32_t width {0};
|
||||||
|
uint32_t height {0};
|
||||||
|
std::vector<uint64_t> textures;
|
||||||
|
std::vector<uint32_t> frame_duration; // ms
|
||||||
|
size_t current_texture {0};
|
||||||
|
|
||||||
|
bool rendered_this_frame {false};
|
||||||
|
// or flipped for animations
|
||||||
|
uint64_t timestamp_last_rendered {0}; // ms
|
||||||
|
|
||||||
|
TextureEntry(void) = default;
|
||||||
|
TextureEntry(const TextureEntry& other) :
|
||||||
|
width(other.width),
|
||||||
|
height(other.height),
|
||||||
|
textures(other.textures),
|
||||||
|
frame_duration(other.frame_duration),
|
||||||
|
current_texture(other.current_texture),
|
||||||
|
|
||||||
|
rendered_this_frame(other.rendered_this_frame),
|
||||||
|
timestamp_last_rendered(other.timestamp_last_rendered)
|
||||||
|
{}
|
||||||
|
|
||||||
|
TextureEntry& operator=(const TextureEntry& other) {
|
||||||
|
width = other.width;
|
||||||
|
height = other.height;
|
||||||
|
textures = other.textures;
|
||||||
|
frame_duration = other.frame_duration;
|
||||||
|
current_texture = other.current_texture;
|
||||||
|
|
||||||
|
rendered_this_frame = other.rendered_this_frame;
|
||||||
|
timestamp_last_rendered = other.timestamp_last_rendered;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getDuration(void) const {
|
||||||
|
return frame_duration.at(current_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void next(void) {
|
||||||
|
current_texture = (current_texture + 1) % frame_duration.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void doAnimation(const uint64_t ts_now);
|
||||||
|
|
||||||
|
template<typename TextureType>
|
||||||
|
TextureType getID(void) {
|
||||||
|
static_assert(
|
||||||
|
sizeof(TextureType) == sizeof(uint64_t) ||
|
||||||
|
sizeof(TextureType) == sizeof(uint32_t)
|
||||||
|
);
|
||||||
|
|
||||||
|
rendered_this_frame = true;
|
||||||
|
assert(current_texture < textures.size());
|
||||||
|
if constexpr (sizeof(TextureType) == sizeof(uint64_t)) {
|
||||||
|
return reinterpret_cast<TextureType>(textures.at(current_texture));
|
||||||
|
} else if constexpr (sizeof(TextureType) == sizeof(uint32_t)) {
|
||||||
|
return reinterpret_cast<TextureType>(static_cast<uint32_t>(textures.at(current_texture)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TextureEntry generateTestAnim(TextureUploaderI& tu);
|
||||||
|
|
||||||
|
// TODO: move to utils or something
|
||||||
|
uint64_t getNowMS(void);
|
||||||
|
|
||||||
|
template<typename TextureType, typename KeyType, class Loader>
|
||||||
|
struct TextureCache {
|
||||||
|
static_assert(
|
||||||
|
sizeof(TextureType) == sizeof(uint64_t) ||
|
||||||
|
sizeof(TextureType) == sizeof(uint32_t)
|
||||||
|
);
|
||||||
|
|
||||||
|
Loader& _l;
|
||||||
|
TextureUploaderI& _tu;
|
||||||
|
|
||||||
|
TextureEntry _default_texture;
|
||||||
|
|
||||||
|
entt::dense_map<KeyType, TextureEntry> _cache;
|
||||||
|
entt::dense_set<KeyType> _to_load;
|
||||||
|
|
||||||
|
const uint64_t ms_before_purge {60 * 1000ull};
|
||||||
|
const size_t min_count_before_purge {0}; // starts purging after that
|
||||||
|
|
||||||
|
TextureCache(Loader& l, TextureUploaderI& tu) : _l(l), _tu(tu) {
|
||||||
|
//_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||||
|
//_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
|
||||||
|
_default_texture = generateTestAnim(_tu);
|
||||||
|
//_default_texture = loadTestWebPAnim();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GetInfo {
|
||||||
|
TextureType id;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
};
|
||||||
|
GetInfo get(const KeyType& key) {
|
||||||
|
auto it = _cache.find(key);
|
||||||
|
|
||||||
|
if (it != _cache.end()) {
|
||||||
|
return {
|
||||||
|
it->second.template getID<TextureType>(),
|
||||||
|
it->second.width,
|
||||||
|
it->second.height
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
_to_load.insert(key);
|
||||||
|
return {
|
||||||
|
_default_texture.getID<TextureType>(),
|
||||||
|
_default_texture.width,
|
||||||
|
_default_texture.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(void) {
|
||||||
|
const uint64_t ts_now = getNowMS();
|
||||||
|
|
||||||
|
std::vector<KeyType> to_purge;
|
||||||
|
for (auto&& [key, te] : _cache) {
|
||||||
|
if (te.rendered_this_frame) {
|
||||||
|
te.doAnimation(ts_now);
|
||||||
|
te.rendered_this_frame = false;
|
||||||
|
} else if (_cache.size() > min_count_before_purge && ts_now - te.timestamp_last_rendered >= ms_before_purge) {
|
||||||
|
to_purge.push_back(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate(to_purge);
|
||||||
|
|
||||||
|
_default_texture.doAnimation(ts_now);
|
||||||
|
}
|
||||||
|
|
||||||
|
void invalidate(const std::vector<KeyType>& to_purge) {
|
||||||
|
for (const auto& key : to_purge) {
|
||||||
|
for (const auto& tex_id : _cache.at(key).textures) {
|
||||||
|
_tu.destroy(tex_id);
|
||||||
|
}
|
||||||
|
_cache.erase(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void workLoadQueue(void) {
|
||||||
|
for (auto it = _to_load.begin(); it != _to_load.end(); it++) {
|
||||||
|
auto new_entry_opt = _l.load(_tu, *it);
|
||||||
|
if (new_entry_opt.has_value()) {
|
||||||
|
_cache.emplace(*it, new_entry_opt.value());
|
||||||
|
_to_load.erase(it);
|
||||||
|
// TODO: not a good idea
|
||||||
|
break; // end load from queue/onlyload 1 per update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
210
src/tox_avatar_loader.cpp
Normal file
210
src/tox_avatar_loader.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
#include "./tox_avatar_loader.hpp"
|
||||||
|
|
||||||
|
#include "./image_loader_sdl_bmp.hpp"
|
||||||
|
|
||||||
|
#include <solanaceae/contact/components.hpp>
|
||||||
|
#include <solanaceae/tox_contacts/components.hpp>
|
||||||
|
|
||||||
|
#include <sodium/crypto_hash_sha256.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cassert>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
ToxAvatarLoader::ToxAvatarLoader(Contact3Registry& cr) : _cr(cr) {
|
||||||
|
_image_loaders.push_back(std::make_unique<ImageLoaderSDLBMP>());
|
||||||
|
//_image_loaders.push_back(std::make_unique<ImageLoaderWebP>());
|
||||||
|
//_image_loaders.push_back(std::make_unique<ImageLoaderSTB>());
|
||||||
|
}
|
||||||
|
|
||||||
|
static float getHue_6bytes(const uint8_t* data) {
|
||||||
|
uint64_t hue_uint = 0x00;
|
||||||
|
for (size_t i = 0; i < 6; i++) {
|
||||||
|
hue_uint = hue_uint << 8;
|
||||||
|
hue_uint += data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 48bit max (6bytes)
|
||||||
|
return float(hue_uint / 281474976710655.);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float hue2Rgb(float p, float q, float t) {
|
||||||
|
while (t < 0.f) { t += 1.f; }
|
||||||
|
while (t > 1.f) { t -= 1.f; }
|
||||||
|
if (t < 1.f/6.f) { return p + (q - p) * 6.f * t; }
|
||||||
|
if (t < 1.f/2.f) { return q; }
|
||||||
|
if (t < 2.f/3.f) { return p + (q - p) * (4.f - 6.f * t); }
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://github.com/Tox/Tox-Client-Standard/blob/master/appendix/ToxIdenticons.md
|
||||||
|
// creates a 5x5 pix texture RGBA8888
|
||||||
|
static std::vector<uint8_t> generateToxIdenticon(const ToxKey& key) {
|
||||||
|
// first hash key
|
||||||
|
std::array<uint8_t, 32> hashed_key;
|
||||||
|
assert(hashed_key.size() == key.data.size());
|
||||||
|
crypto_hash_sha256(hashed_key.data(), key.data.data(), key.data.size());
|
||||||
|
|
||||||
|
const float hue_color1 = getHue_6bytes(hashed_key.data()+26);
|
||||||
|
const float hue_color2 = getHue_6bytes(hashed_key.data()+20);
|
||||||
|
|
||||||
|
const float sat_color1 = 0.5f;
|
||||||
|
const float lig_color1 = 0.3f;
|
||||||
|
|
||||||
|
const float sat_color2 = 0.5f;
|
||||||
|
const float lig_color2 = 0.8f;
|
||||||
|
|
||||||
|
// TODO: refactor this mess
|
||||||
|
|
||||||
|
#define Q(LIG, SAT) (LIG) < 0.5f ? (LIG) * (1 + (SAT)) : (LIG) + (SAT) - (LIG) * (SAT)
|
||||||
|
const float q_color1 = Q(lig_color1, sat_color1);
|
||||||
|
const float q_color2 = Q(lig_color2, sat_color2);
|
||||||
|
#undef Q
|
||||||
|
|
||||||
|
#define P(LIG, Q) 2.f * (LIG) - (Q)
|
||||||
|
const float p_color1 = P(lig_color1, q_color1);
|
||||||
|
const float p_color2 = P(lig_color2, q_color2);
|
||||||
|
#undef P
|
||||||
|
|
||||||
|
const uint8_t color_1_r = hue2Rgb(p_color1, q_color1, hue_color1 + 1.f/3.f) * 255.f + 0.499f;
|
||||||
|
const uint8_t color_1_g = hue2Rgb(p_color1, q_color1, hue_color1) * 255.f + 0.499f;
|
||||||
|
const uint8_t color_1_b = hue2Rgb(p_color1, q_color1, hue_color1 - 1.f/3.f) * 255.f + 0.499f;
|
||||||
|
|
||||||
|
const uint8_t color_2_r = hue2Rgb(p_color2, q_color2, hue_color2 + 1.f/3.f) * 255.f + 0.499f;
|
||||||
|
const uint8_t color_2_g = hue2Rgb(p_color2, q_color2, hue_color2) * 255.f + 0.499f;
|
||||||
|
const uint8_t color_2_b = hue2Rgb(p_color2, q_color2, hue_color2 - 1.f/3.f) * 255.f + 0.499f;
|
||||||
|
|
||||||
|
// start drawing
|
||||||
|
|
||||||
|
std::vector<uint8_t> pixels(5*5*4, 0xff); // fill with white
|
||||||
|
#define R(x,y) pixels[(y*4*5) + (4*x+0)]
|
||||||
|
#define G(x,y) pixels[(y*4*5) + (4*x+1)]
|
||||||
|
#define B(x,y) pixels[(y*4*5) + (4*x+2)]
|
||||||
|
#define COLOR1(x,y) R(x, y) = color_1_r; G(x, y) = color_1_g; B(x, y) = color_1_b;
|
||||||
|
#define COLOR2(x,y) R(x, y) = color_2_r; G(x, y) = color_2_g; B(x, y) = color_2_b;
|
||||||
|
|
||||||
|
for (int64_t y = 0; y < 5; y++) {
|
||||||
|
for (int64_t x = 0; x < 5; x++) {
|
||||||
|
// mirrored index
|
||||||
|
//int64_t m_x = x <= 3 ? 3 : x - (x-2)*2;
|
||||||
|
int64_t m_x = ((x*2)-4)/2;
|
||||||
|
m_x = m_x < 0 ? -m_x : m_x;
|
||||||
|
|
||||||
|
int64_t pos = y * 3 + m_x;
|
||||||
|
const uint8_t byte = hashed_key[pos];
|
||||||
|
const uint8_t color = byte % 2;
|
||||||
|
if (color == 0) {
|
||||||
|
COLOR1(x, y)
|
||||||
|
} else {
|
||||||
|
COLOR2(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef COLOR1
|
||||||
|
#undef COLOR2
|
||||||
|
#undef R
|
||||||
|
#undef G
|
||||||
|
#undef B
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TextureEntry> ToxAvatarLoader::load(TextureUploaderI& tu, Contact3 c) {
|
||||||
|
if (!_cr.valid(c)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cr.all_of<Contact::Components::AvatarMemory>(c)) {
|
||||||
|
const auto& a_m = _cr.get<Contact::Components::AvatarMemory>(c);
|
||||||
|
|
||||||
|
TextureEntry new_entry;
|
||||||
|
new_entry.timestamp_last_rendered = getNowMS();
|
||||||
|
new_entry.current_texture = 0;
|
||||||
|
|
||||||
|
new_entry.width = a_m.width;
|
||||||
|
new_entry.height = a_m.height;
|
||||||
|
|
||||||
|
const auto n_t = tu.uploadRGBA(a_m.data.data(), a_m.width, a_m.height);
|
||||||
|
new_entry.textures.push_back(n_t);
|
||||||
|
new_entry.frame_duration.push_back(250);
|
||||||
|
|
||||||
|
std::cout << "TAL: loaded memory buffer\n";
|
||||||
|
|
||||||
|
return new_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cr.all_of<Contact::Components::AvatarFile>(c)) {
|
||||||
|
const auto& a_f = _cr.get<Contact::Components::AvatarFile>(c);
|
||||||
|
|
||||||
|
std::ifstream file(a_f.file_path, std::ios::binary);
|
||||||
|
if (file.is_open()) {
|
||||||
|
std::vector<uint8_t> tmp_buffer;
|
||||||
|
while (file.good()) {
|
||||||
|
auto ch = file.get();
|
||||||
|
if (ch == EOF) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tmp_buffer.push_back(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try all loaders after another
|
||||||
|
for (auto& il : _image_loaders) {
|
||||||
|
auto res = il->loadFromMemoryRGBA(tmp_buffer.data(), tmp_buffer.size());
|
||||||
|
if (res.frames.empty() || res.height == 0 || res.width == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureEntry new_entry;
|
||||||
|
new_entry.timestamp_last_rendered = getNowMS();
|
||||||
|
new_entry.current_texture = 0;
|
||||||
|
for (const auto& [ms, data] : res.frames) {
|
||||||
|
const auto n_t = tu.uploadRGBA(data.data(), res.width, res.height);
|
||||||
|
new_entry.textures.push_back(n_t);
|
||||||
|
new_entry.frame_duration.push_back(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entry.width = res.width;
|
||||||
|
new_entry.height = res.height;
|
||||||
|
|
||||||
|
std::cout << "TAL: loaded image file " << a_f.file_path << "\n";
|
||||||
|
|
||||||
|
return new_entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_cr.any_of<
|
||||||
|
Contact::Components::ToxFriendPersistent,
|
||||||
|
Contact::Components::ToxGroupPersistent,
|
||||||
|
Contact::Components::ToxGroupPeerPersistent
|
||||||
|
>(c)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> pixels;
|
||||||
|
if (_cr.all_of<Contact::Components::ToxFriendPersistent>(c)) {
|
||||||
|
pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxFriendPersistent>(c).key);
|
||||||
|
} else if (_cr.all_of<Contact::Components::ToxGroupPersistent>(c)) {
|
||||||
|
pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxGroupPersistent>(c).chat_id);
|
||||||
|
} else if (_cr.all_of<Contact::Components::ToxGroupPeerPersistent>(c)) {
|
||||||
|
pixels = generateToxIdenticon(_cr.get<Contact::Components::ToxGroupPeerPersistent>(c).peer_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureEntry new_entry;
|
||||||
|
new_entry.timestamp_last_rendered = getNowMS();
|
||||||
|
new_entry.current_texture = 0;
|
||||||
|
|
||||||
|
const auto n_t = tu.uploadRGBA(pixels.data(), 5, 5);
|
||||||
|
new_entry.textures.push_back(n_t);
|
||||||
|
new_entry.frame_duration.push_back(250);
|
||||||
|
|
||||||
|
new_entry.width = 5;
|
||||||
|
new_entry.height = 5;
|
||||||
|
|
||||||
|
std::cout << "TAL: generated ToxIdenticon\n";
|
||||||
|
|
||||||
|
return new_entry;
|
||||||
|
}
|
||||||
|
|
19
src/tox_avatar_loader.hpp
Normal file
19
src/tox_avatar_loader.hpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <solanaceae/contact/contact_model3.hpp>
|
||||||
|
|
||||||
|
#include "./image_loader.hpp"
|
||||||
|
#include "./texture_cache.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
class ToxAvatarLoader {
|
||||||
|
Contact3Registry& _cr;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<ImageLoaderI>> _image_loaders;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ToxAvatarLoader(Contact3Registry& cr);
|
||||||
|
std::optional<TextureEntry> load(TextureUploaderI& tu, Contact3 c);
|
||||||
|
};
|
||||||
|
|
Reference in New Issue
Block a user