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