mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
[feat] write and load thumbnails supported
This commit is contained in:
@@ -99,4 +99,27 @@ std::string GetMac() {
|
||||
close(sock);
|
||||
#endif
|
||||
return mac_addr;
|
||||
}
|
||||
|
||||
std::string GetHostName() {
|
||||
char hostname[256];
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
std::cerr << "WSAStartup failed." << std::endl;
|
||||
return "";
|
||||
}
|
||||
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
|
||||
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
|
||||
WSACleanup();
|
||||
return "";
|
||||
}
|
||||
WSACleanup();
|
||||
#else
|
||||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||||
LOG_ERROR("gethostname failed");
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
return hostname;
|
||||
}
|
||||
@@ -10,5 +10,6 @@
|
||||
#include <iostream>
|
||||
|
||||
std::string GetMac();
|
||||
std::string GetHostName();
|
||||
|
||||
#endif
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef enum { mouse = 0, keyboard, audio_capture } ControlType;
|
||||
typedef enum { mouse = 0, keyboard, audio_capture, host_info } ControlType;
|
||||
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
|
||||
typedef enum { key_down = 0, key_up } KeyFlag;
|
||||
typedef struct {
|
||||
@@ -23,11 +23,17 @@ typedef struct {
|
||||
KeyFlag flag;
|
||||
} Key;
|
||||
|
||||
typedef struct {
|
||||
char host_name[64];
|
||||
size_t host_name_size;
|
||||
} HostInfo;
|
||||
|
||||
typedef struct {
|
||||
ControlType type;
|
||||
union {
|
||||
Mouse m;
|
||||
Key k;
|
||||
HostInfo i;
|
||||
bool a;
|
||||
};
|
||||
} RemoteAction;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "render.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@@ -15,12 +16,6 @@
|
||||
#include "rd_log.h"
|
||||
#include "screen_capturer_factory.h"
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "libyuv.h"
|
||||
#include "stb_image_write.h"
|
||||
|
||||
// Refresh Event
|
||||
#define REFRESH_EVENT (SDL_USEREVENT + 1)
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
@@ -75,61 +70,6 @@ SDL_HitTestResult Render::HitTestCallback(SDL_Window* window,
|
||||
return SDL_HITTEST_NORMAL;
|
||||
}
|
||||
|
||||
bool LoadTextureFromMemory(const void* data, size_t data_size,
|
||||
SDL_Renderer* renderer, SDL_Texture** out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
int image_width = 0;
|
||||
int image_height = 0;
|
||||
int channels = 4;
|
||||
unsigned char* image_data =
|
||||
stbi_load_from_memory((const unsigned char*)data, (int)data_size,
|
||||
&image_width, &image_height, NULL, 4);
|
||||
if (image_data == nullptr) {
|
||||
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
|
||||
(void*)image_data, image_width, image_height, channels * 8,
|
||||
channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
|
||||
if (surface == nullptr) {
|
||||
fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
if (texture == nullptr)
|
||||
fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());
|
||||
|
||||
*out_texture = texture;
|
||||
*out_width = image_width;
|
||||
*out_height = image_height;
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
stbi_image_free(image_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Open and read a file, then forward to LoadTextureFromMemory()
|
||||
bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer,
|
||||
SDL_Texture** out_texture, int* out_width,
|
||||
int* out_height) {
|
||||
FILE* f = fopen(file_name, "rb");
|
||||
if (f == NULL) return false;
|
||||
fseek(f, 0, SEEK_END);
|
||||
size_t file_size = (size_t)ftell(f);
|
||||
if (file_size == -1) return false;
|
||||
fseek(f, 0, SEEK_SET);
|
||||
void* file_data = IM_ALLOC(file_size);
|
||||
fread(file_data, 1, file_size, f);
|
||||
bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture,
|
||||
out_width, out_height);
|
||||
IM_FREE(file_data);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Render::Render() {}
|
||||
|
||||
Render::~Render() {}
|
||||
@@ -701,47 +641,6 @@ int Render::DrawMainWindow() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScaleYUV420pToABGR(char* dst_buffer_, int video_width_, int video_height_,
|
||||
int scaled_video_width_, int scaled_video_height_,
|
||||
char* argb_buffer_) {
|
||||
int src_y_size = video_width_ * video_height_;
|
||||
int src_uv_size = (video_width_ + 1) / 2 * (video_height_ + 1) / 2;
|
||||
int dst_y_size = scaled_video_width_ * scaled_video_height_;
|
||||
int dst_uv_size =
|
||||
(scaled_video_width_ + 1) / 2 * (scaled_video_height_ + 1) / 2;
|
||||
|
||||
uint8_t* src_y = reinterpret_cast<uint8_t*>(dst_buffer_);
|
||||
uint8_t* src_u = src_y + src_y_size;
|
||||
uint8_t* src_v = src_u + src_uv_size;
|
||||
|
||||
std::unique_ptr<uint8_t[]> dst_y(new uint8_t[dst_y_size]);
|
||||
std::unique_ptr<uint8_t[]> dst_u(new uint8_t[dst_uv_size]);
|
||||
std::unique_ptr<uint8_t[]> dst_v(new uint8_t[dst_uv_size]);
|
||||
|
||||
try {
|
||||
libyuv::I420Scale(src_y, video_width_, src_u, (video_width_ + 1) / 2, src_v,
|
||||
(video_width_ + 1) / 2, video_width_, video_height_,
|
||||
dst_y.get(), scaled_video_width_, dst_u.get(),
|
||||
(scaled_video_width_ + 1) / 2, dst_v.get(),
|
||||
(scaled_video_width_ + 1) / 2, scaled_video_width_,
|
||||
scaled_video_height_, libyuv::kFilterBilinear);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("I420Scale failed: %s", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
libyuv::I420ToABGR(
|
||||
dst_y.get(), scaled_video_width_, dst_u.get(),
|
||||
(scaled_video_width_ + 1) / 2, dst_v.get(),
|
||||
(scaled_video_width_ + 1) / 2, reinterpret_cast<uint8_t*>(argb_buffer_),
|
||||
scaled_video_width_ * 4, scaled_video_width_, scaled_video_height_);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("I420ToBGRA failed: %s", e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int Render::DrawStreamWindow() {
|
||||
if (!stream_ctx_) {
|
||||
LOG_ERROR("Stream context is null");
|
||||
@@ -842,8 +741,8 @@ int Render::Run() {
|
||||
CreateMainWindow();
|
||||
SetupMainWindow();
|
||||
|
||||
const int scaled_video_width_ = 128;
|
||||
const int scaled_video_height_ = 72;
|
||||
const int scaled_video_width_ = 160;
|
||||
const int scaled_video_height_ = 90;
|
||||
char* argb_buffer_ =
|
||||
new char[scaled_video_width_ * scaled_video_height_ * 40];
|
||||
|
||||
@@ -904,13 +803,9 @@ int Render::Run() {
|
||||
DestroyStreamWindowContext();
|
||||
|
||||
if (dst_buffer_) {
|
||||
ScaleYUV420pToABGR((char*)dst_buffer_, video_width_, video_height_,
|
||||
scaled_video_width_, scaled_video_height_,
|
||||
argb_buffer_);
|
||||
stbi_write_png("RecentConnectionImage01.png", scaled_video_width_,
|
||||
scaled_video_height_, 4, argb_buffer_,
|
||||
scaled_video_width_ * 4);
|
||||
LOG_ERROR("RecentConnectionImage01.png saved");
|
||||
thumbnail_.SaveToThumbnail((char*)dst_buffer_, video_width_,
|
||||
video_height_, host_name_, remote_id_);
|
||||
recent_connection_image_save_time_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
LOG_INFO("[{}] Leave connection [{}]", client_id_, remote_id_);
|
||||
@@ -1013,18 +908,17 @@ int Render::Run() {
|
||||
}
|
||||
|
||||
if (reload_recent_connections_ && main_renderer_) {
|
||||
bool ret = LoadTextureFromFile(
|
||||
"RecentConnectionImage01.png", main_renderer_,
|
||||
&recent_connection_texture_, &recent_connection_image_width_,
|
||||
&recent_connection_image_height_);
|
||||
if (!ret) {
|
||||
LOG_ERROR("Load recent connections image failed");
|
||||
} else {
|
||||
LOG_ERROR("Load recent connections image width: {}, height: {}",
|
||||
recent_connection_image_width_,
|
||||
recent_connection_image_height_);
|
||||
// loal recent connection thumbnails after saving for 1 second
|
||||
uint32_t now_time = SDL_GetTicks();
|
||||
if (now_time - recent_connection_image_save_time_ >= 1000) {
|
||||
int ret = thumbnail_.LoadThumbnail(
|
||||
main_renderer_, &recent_connection_texture_,
|
||||
&recent_connection_image_width_, &recent_connection_image_height_);
|
||||
if (!ret) {
|
||||
LOG_INFO("Load recent connection thumbnails");
|
||||
}
|
||||
reload_recent_connections_ = false;
|
||||
}
|
||||
reload_recent_connections_ = false;
|
||||
}
|
||||
|
||||
if (connection_established_ && streaming_) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "imgui_impl_sdlrenderer2.h"
|
||||
#include "screen_capturer_factory.h"
|
||||
#include "speaker_capturer_factory.h"
|
||||
#include "thumbnail.h"
|
||||
|
||||
class Render {
|
||||
public:
|
||||
@@ -227,11 +228,16 @@ class Render {
|
||||
SDL_Texture *recent_connection_texture_ = nullptr;
|
||||
int recent_connection_image_width_ = 128;
|
||||
int recent_connection_image_height_ = 72;
|
||||
uint32_t recent_connection_image_save_time_ = 0;
|
||||
|
||||
// video window
|
||||
SDL_Texture *stream_texture_ = nullptr;
|
||||
SDL_Rect stream_render_rect_;
|
||||
uint32_t stream_pixformat_ = 0;
|
||||
std::string host_name_ = "";
|
||||
std::string image_path_ = "thumbnails";
|
||||
|
||||
Thumbnail thumbnail_;
|
||||
|
||||
bool resizable_ = false;
|
||||
bool label_inited_ = false;
|
||||
@@ -268,6 +274,7 @@ class Render {
|
||||
bool is_control_bar_in_left_ = true;
|
||||
bool control_window_width_is_changing_ = false;
|
||||
bool reload_recent_connections_ = true;
|
||||
bool hostname_sent_ = false;
|
||||
|
||||
double copy_start_time_ = 0;
|
||||
double regenerate_password_start_time_ = 0;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "device_controller.h"
|
||||
#include "localization.h"
|
||||
#include "platform.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
@@ -209,6 +210,11 @@ void Render::OnReceiveDataBufferCb(const char *data, size_t size,
|
||||
} else {
|
||||
render->StopSpeakerCapture();
|
||||
}
|
||||
} else if (ControlType::keyboard == remote_action.type) {
|
||||
} else if (ControlType::host_info == remote_action.type) {
|
||||
render->host_name_ =
|
||||
std::string(remote_action.i.host_name, remote_action.i.host_name_size);
|
||||
LOG_INFO("Remote hostname: [{}]", render->host_name_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +267,18 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char *user_id,
|
||||
render->start_screen_capture_ = true;
|
||||
render->start_mouse_control_ = true;
|
||||
}
|
||||
if (!render->hostname_sent_) {
|
||||
std::string host_name = GetHostName();
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::host_info;
|
||||
memcpy(&remote_action.i.host_name, host_name.data(), host_name.size());
|
||||
remote_action.i.host_name_size = host_name.size();
|
||||
int ret = SendData(render->peer_, DATA_TYPE::DATA,
|
||||
(const char *)&remote_action, sizeof(remote_action));
|
||||
if (0 == ret) {
|
||||
render->hostname_sent_ = true;
|
||||
}
|
||||
}
|
||||
} else if (ConnectionStatus::Disconnected == status) {
|
||||
render->connection_status_str_ = "Disconnected";
|
||||
render->password_validating_time_ = 0;
|
||||
@@ -274,6 +292,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char *user_id,
|
||||
render->start_mouse_control_ = false;
|
||||
render->connection_established_ = false;
|
||||
render->control_mouse_ = false;
|
||||
render->hostname_sent_ = false;
|
||||
if (render->audio_capture_) {
|
||||
render->StopSpeakerCapture();
|
||||
render->audio_capture_ = false;
|
||||
|
||||
187
src/single_window/thumbnail.cpp
Normal file
187
src/single_window/thumbnail.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include "thumbnail.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
|
||||
void ScaleYUV420pToABGR(char* dst_buffer_, int video_width_, int video_height_,
|
||||
int scaled_video_width_, int scaled_video_height_,
|
||||
char* rgba_buffer_) {
|
||||
int src_y_size = video_width_ * video_height_;
|
||||
int src_uv_size = (video_width_ + 1) / 2 * (video_height_ + 1) / 2;
|
||||
int dst_y_size = scaled_video_width_ * scaled_video_height_;
|
||||
int dst_uv_size =
|
||||
(scaled_video_width_ + 1) / 2 * (scaled_video_height_ + 1) / 2;
|
||||
|
||||
uint8_t* src_y = reinterpret_cast<uint8_t*>(dst_buffer_);
|
||||
uint8_t* src_u = src_y + src_y_size;
|
||||
uint8_t* src_v = src_u + src_uv_size;
|
||||
|
||||
std::unique_ptr<uint8_t[]> dst_y(new uint8_t[dst_y_size]);
|
||||
std::unique_ptr<uint8_t[]> dst_u(new uint8_t[dst_uv_size]);
|
||||
std::unique_ptr<uint8_t[]> dst_v(new uint8_t[dst_uv_size]);
|
||||
|
||||
try {
|
||||
libyuv::I420Scale(src_y, video_width_, src_u, (video_width_ + 1) / 2, src_v,
|
||||
(video_width_ + 1) / 2, video_width_, video_height_,
|
||||
dst_y.get(), scaled_video_width_, dst_u.get(),
|
||||
(scaled_video_width_ + 1) / 2, dst_v.get(),
|
||||
(scaled_video_width_ + 1) / 2, scaled_video_width_,
|
||||
scaled_video_height_, libyuv::kFilterBilinear);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("I420Scale failed: %s", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
libyuv::I420ToABGR(
|
||||
dst_y.get(), scaled_video_width_, dst_u.get(),
|
||||
(scaled_video_width_ + 1) / 2, dst_v.get(),
|
||||
(scaled_video_width_ + 1) / 2, reinterpret_cast<uint8_t*>(rgba_buffer_),
|
||||
scaled_video_width_ * 4, scaled_video_width_, scaled_video_height_);
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("I420ToRGBA failed: %s", e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Thumbnail::Thumbnail() { std::filesystem::create_directory(image_path_); }
|
||||
|
||||
Thumbnail::~Thumbnail() {
|
||||
if (rgba_buffer_) {
|
||||
delete[] rgba_buffer_;
|
||||
rgba_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
|
||||
const std::string& host_name,
|
||||
const std::string& remote_id) {
|
||||
if (!rgba_buffer_) {
|
||||
rgba_buffer_ = new char[thumbnail_width_ * thumbnail_height_ * 4];
|
||||
}
|
||||
|
||||
if (yuv420p) {
|
||||
ScaleYUV420pToABGR((char*)yuv420p, width, height, thumbnail_width_,
|
||||
thumbnail_height_, rgba_buffer_);
|
||||
std::string image_name =
|
||||
image_path_ + "/" + host_name + "@" + remote_id + ".png";
|
||||
stbi_write_png(image_name.data(), thumbnail_width_, thumbnail_height_, 4,
|
||||
rgba_buffer_, thumbnail_width_ * 4);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool LoadTextureFromMemory(const void* data, size_t data_size,
|
||||
SDL_Renderer* renderer, SDL_Texture** out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
int image_width = 0;
|
||||
int image_height = 0;
|
||||
int channels = 4;
|
||||
unsigned char* image_data =
|
||||
stbi_load_from_memory((const unsigned char*)data, (int)data_size,
|
||||
&image_width, &image_height, NULL, 4);
|
||||
if (image_data == nullptr) {
|
||||
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
|
||||
return false;
|
||||
}
|
||||
|
||||
// ABGR
|
||||
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
|
||||
(void*)image_data, image_width, image_height, channels * 8,
|
||||
channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
|
||||
if (surface == nullptr) {
|
||||
fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
if (texture == nullptr)
|
||||
fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());
|
||||
|
||||
*out_texture = texture;
|
||||
*out_width = image_width;
|
||||
*out_height = image_height;
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
stbi_image_free(image_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer,
|
||||
SDL_Texture** out_texture, int* out_width,
|
||||
int* out_height) {
|
||||
std::filesystem::path file_path(file_name);
|
||||
if (!std::filesystem::exists(file_path)) return false;
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
if (!file) return false;
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (file_size == -1) return false;
|
||||
char* file_data = new char[file_size];
|
||||
if (!file_data) return false;
|
||||
file.read(file_data, file_size);
|
||||
bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture,
|
||||
out_width, out_height);
|
||||
delete[] file_data;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> Thumbnail::FindThumbnailPath(
|
||||
const std::filesystem::path& directory) {
|
||||
std::vector<std::filesystem::path> thumbnails_path;
|
||||
std::string image_extensions = ".png";
|
||||
|
||||
if (!std::filesystem::is_directory(directory)) {
|
||||
LOG_ERROR("No such directory [{}]", directory.string());
|
||||
return thumbnails_path;
|
||||
}
|
||||
|
||||
thumbnails_sorted_by_write_time_.clear();
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::time_t last_write_time = std::chrono::system_clock::to_time_t(
|
||||
time_point_cast<std::chrono::system_clock::duration>(
|
||||
entry.last_write_time() -
|
||||
std::filesystem::file_time_type::clock::now() +
|
||||
std::chrono::system_clock::now()));
|
||||
|
||||
if (entry.path().extension() == image_extensions) {
|
||||
thumbnails_sorted_by_write_time_[last_write_time] = entry.path();
|
||||
}
|
||||
|
||||
for (const auto& pair : thumbnails_sorted_by_write_time_) {
|
||||
thumbnails_path.push_back(pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return thumbnails_path;
|
||||
}
|
||||
|
||||
int Thumbnail::LoadThumbnail(SDL_Renderer* renderer, SDL_Texture** texture,
|
||||
int* width, int* height) {
|
||||
std::vector<std::filesystem::path> image_path =
|
||||
FindThumbnailPath(image_path_);
|
||||
|
||||
if (image_path.size() == 0) {
|
||||
LOG_INFO("No thumbnail saved", image_path_);
|
||||
return -1;
|
||||
} else {
|
||||
LoadTextureFromFile(image_path[0].string().c_str(), renderer, texture,
|
||||
width, height);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
40
src/single_window/thumbnail.h
Normal file
40
src/single_window/thumbnail.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-07
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _THUMBNAIL_H_
|
||||
#define _THUMBNAIL_H_
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
class Thumbnail {
|
||||
public:
|
||||
Thumbnail();
|
||||
~Thumbnail();
|
||||
|
||||
public:
|
||||
int SaveToThumbnail(const char* yuv420p, int width, int height,
|
||||
const std::string& host_name,
|
||||
const std::string& remote_id);
|
||||
|
||||
int LoadThumbnail(SDL_Renderer* renderer, SDL_Texture** texture, int* width,
|
||||
int* height);
|
||||
|
||||
private:
|
||||
std::vector<std::filesystem::path> FindThumbnailPath(
|
||||
const std::filesystem::path& directory);
|
||||
|
||||
private:
|
||||
int thumbnail_width_ = 160;
|
||||
int thumbnail_height_ = 90;
|
||||
char* rgba_buffer_ = nullptr;
|
||||
std::string image_path_ = "thumbnails";
|
||||
std::map<std::time_t, std::filesystem::path> thumbnails_sorted_by_write_time_;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user