From b233f24fe103a745eb6487e236b7cb08269a6cb3 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 19 Oct 2020 11:02:26 +0200 Subject: Change thumbnail creation from nearest neighbor to linear interpolation, set body thumbnail size for 4chan and matrix (if available) --- TODO | 3 ++- plugins/Matrix.hpp | 1 + src/AsyncImageLoader.cpp | 28 ++++++++++++++++++++++++---- src/QuickMedia.cpp | 20 +++++++++++++++----- src/plugins/Fourchan.cpp | 14 ++++++++++++++ src/plugins/Matrix.cpp | 31 +++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 10 deletions(-) diff --git a/TODO b/TODO index 80051fc..04f3ffa 100644 --- a/TODO +++ b/TODO @@ -110,4 +110,5 @@ Add button to skip to next video. MPV has this feature when setting "next" video Handle room leave in matrix and remove rooms from the list. Use a custom allocator that replaces malloc/realloc/free/new/delete to release memory properly, using munmap in free/delete. The C allocator doesn't do that! memory usage remains high after one large allocation. The C allocator only marks it as free. Ignore timestamp ordering for messages in matrix? element seems to do that (or only for new messages???), and also we need the latest message to be last I guess to set read markers properly? -Merge |Page::search| and |Page::get_page|. get_page with page 0 should be the same as search. \ No newline at end of file +Merge |Page::search| and |Page::get_page|. get_page with page 0 should be the same as search. +Disable posting in 4chan thread if closed (thread json contains "closed" field for OP). \ No newline at end of file diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 135549f..c953364 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -59,6 +59,7 @@ namespace QuickMedia { std::string url; std::string thumbnail_url; std::string related_event_id; + sf::Vector2i thumbnail_size; // Set to {0, 0} if not specified RelatedEventType related_event_type; bool mentions_me = false; time_t timestamp = 0; diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index 8f771e7..00ca7ee 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -19,6 +19,7 @@ namespace QuickMedia { return sf::Vector2u(vec.x, vec.y); } + // Linear interpolation static void copy_resize(const sf::Image &source, sf::Image &destination, sf::Vector2u destination_size) { const sf::Vector2u source_size = source.getSize(); if(source_size.x == 0 || source_size.y == 0 || destination_size.x == 0 || destination_size.y == 0) @@ -33,14 +34,33 @@ namespace QuickMedia { sf::Uint32 *destination_pixel = destination_pixels; for(unsigned int y = 0; y < destination_size.y; ++y) { for(unsigned int x = 0; x < destination_size.x; ++x) { - int scaled_x = ((float)x / (float)destination_size.x) * source_size.x; - int scaled_y = ((float)y / (float)destination_size.y) * source_size.y; + int scaled_x_start = ((float)x / (float)destination_size.x) * source_size.x; + int scaled_y_start = ((float)y / (float)destination_size.y) * source_size.y; + int scaled_x_end = ((float)(x + 1) / (float)destination_size.x) * source_size.x; + int scaled_y_end = ((float)(y + 1) / (float)destination_size.y) * source_size.y; //float scaled_x = x * width_ratio; //float scaled_y = y * height_ratio; //sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (int)(scaled_x + scaled_y * source_size.x) * 4); - sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (scaled_x + scaled_y * source_size.x) * 4); - *destination_pixel = *source_pixel; + sf::Uint32 sum_red = 0; + sf::Uint32 sum_green = 0; + sf::Uint32 sum_blue = 0; + sf::Uint32 sum_alpha = 0; + sf::Uint32 num_colors = (scaled_x_end - scaled_x_start) * (scaled_y_end - scaled_y_start); + for(int yy = scaled_y_start; yy < scaled_y_end; ++yy) { + for(int xx = scaled_x_start; xx < scaled_x_end; ++xx) { + sf::Uint32 *source_pixel = (sf::Uint32*)(source_pixels + (xx + yy * source_size.x) * 4); + sum_red += (*source_pixel >> 24); + sum_green += ((*source_pixel >> 16) & 0xFF); + sum_blue += ((*source_pixel >> 8) & 0xFF); + sum_alpha += (*source_pixel & 0xFF); + } + } + sum_red /= num_colors; + sum_green /= num_colors; + sum_blue /= num_colors; + sum_alpha /= num_colors; + *destination_pixel = (sum_red << 24) | (sum_green << 16) | (sum_blue << 8) | sum_alpha; ++destination_pixel; } } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 6e4ee51..0b39673 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2894,6 +2894,16 @@ namespace QuickMedia { return str; } + static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { + return sf::Vector2f(vec.x, vec.y); + } + + static sf::Vector2i to_vec2i(const sf::Vector2f &vec) { + return sf::Vector2i(vec.x, vec.y); + } + + static const sf::Vector2i CHAT_MESSAGE_THUMBNAIL_MAX_SIZE(600, 337); + static std::shared_ptr message_to_body_item(Message *message, UserInfo *me) { auto body_item = BodyItem::create(""); body_item->set_author(message->user->display_name); @@ -2902,11 +2912,12 @@ namespace QuickMedia { text = remove_reply_formatting(text); body_item->set_description(std::move(text)); body_item->set_timestamp(message->timestamp); - if(!message->thumbnail_url.empty()) + if(!message->thumbnail_url.empty()) { body_item->thumbnail_url = message->thumbnail_url; - else if(!message->url.empty() && message->type == MessageType::IMAGE) + body_item->thumbnail_size = to_vec2i(clamp_to_size(to_vec2f(message->thumbnail_size), to_vec2f(CHAT_MESSAGE_THUMBNAIL_MAX_SIZE))); + } else if(!message->url.empty() && message->type == MessageType::IMAGE) { body_item->thumbnail_url = message->url; - else { + } else { body_item->thumbnail_url = message->user->avatar_url; body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that @@ -2943,8 +2954,7 @@ namespace QuickMedia { messages_tab.type = ChatTabType::MESSAGES; messages_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get()); messages_tab.body->draw_thumbnails = true; - messages_tab.body->thumbnail_resize_target_size.x = 600; - messages_tab.body->thumbnail_resize_target_size.y = 337; + messages_tab.body->thumbnail_resize_target_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; messages_tab.body->thumbnail_mask_shader = &circle_mask_shader; //messages_tab.body->line_separator_color = sf::Color::Transparent; messages_tab.text = sf::Text("Messages", *font, tab_text_size); diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index 2938319..7f8008c 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -252,6 +252,13 @@ namespace QuickMedia { // "s" means small, that's the url 4chan uses for thumbnails. // thumbnails always has .jpg extension even if they are gifs or webm. body_item->thumbnail_url = fourchan_image_url + url + "/" + std::to_string(tim.asInt64()) + "s.jpg"; + + sf::Vector2i thumbnail_size(64, 64); + const Json::Value &tn_w = thread["tn_w"]; + const Json::Value &tn_h = thread["tn_h"]; + if(tn_w.isNumeric() && tn_h.isNumeric()) + thumbnail_size = sf::Vector2i(tn_w.asInt() * 0.5, tn_h.asInt() * 0.5); + body_item->thumbnail_size = std::move(thumbnail_size); } result_items.push_back(std::move(body_item)); @@ -438,6 +445,13 @@ namespace QuickMedia { body_item->thumbnail_url = fourchan_image_url + board_id + "/" + tim_str + "s.jpg"; body_item->attached_content_url = fourchan_image_url + board_id + "/" + tim_str + ext_str; cached_media_urls.push_back(body_item->attached_content_url); + + sf::Vector2i thumbnail_size(64, 64); + const Json::Value &tn_w = post["tn_w"]; + const Json::Value &tn_h = post["tn_h"]; + if(tn_w.isNumeric() && tn_h.isNumeric()) + thumbnail_size = sf::Vector2i(tn_w.asInt(), tn_h.asInt()); + body_item->thumbnail_size = std::move(thumbnail_size); } ++body_item_index; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index f7364d1..bfdd496 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -364,6 +364,34 @@ namespace QuickMedia { return ""; } + static bool message_content_extract_thumbnail_size(const rapidjson::Value &content_json, sf::Vector2i &thumbnail_size) { + const rapidjson::Value &info_json = GetMember(content_json, "info"); + if(!info_json.IsObject()) + return false; + + bool found_resolution = false; + const rapidjson::Value &w_json = GetMember(info_json, "w"); + const rapidjson::Value &h_json = GetMember(info_json, "h"); + if(w_json.IsNumber() && h_json.IsNumber()) { + thumbnail_size.x = w_json.GetInt(); + thumbnail_size.y = h_json.GetInt(); + found_resolution = true; + } + + const rapidjson::Value &thumbnail_info_json = GetMember(info_json, "thumbnail_info"); + if(thumbnail_info_json.IsObject()) { + const rapidjson::Value &w_json = GetMember(thumbnail_info_json, "w"); + const rapidjson::Value &h_json = GetMember(thumbnail_info_json, "h"); + if(w_json.IsNumber() && h_json.IsNumber()) { + thumbnail_size.x = w_json.GetInt(); + thumbnail_size.y = h_json.GetInt(); + found_resolution = true; + } + } + + return found_resolution; + } + // TODO: Is this really the proper way to check for username mentions? static bool is_username_seperating_character(char c) { switch(c) { @@ -551,6 +579,7 @@ namespace QuickMedia { message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); + message_content_extract_thumbnail_size(*content_json, message->thumbnail_size); message->type = MessageType::IMAGE; } else if(strcmp(content_type.GetString(), "m.video") == 0) { const rapidjson::Value &url_json = GetMember(*content_json, "url"); @@ -559,6 +588,7 @@ namespace QuickMedia { message->url = homeserver + "/_matrix/media/r0/download/" + (url_json.GetString() + 6); message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); + message_content_extract_thumbnail_size(*content_json, message->thumbnail_size); message->type = MessageType::VIDEO; } else if(strcmp(content_type.GetString(), "m.audio") == 0) { const rapidjson::Value &url_json = GetMember(*content_json, "url"); @@ -587,6 +617,7 @@ namespace QuickMedia { message->type = MessageType::TEXT; message->thumbnail_url = message_content_extract_thumbnail_url(*content_json, homeserver); + message_content_extract_thumbnail_size(*content_json, message->thumbnail_size); } else { return nullptr; } -- cgit v1.2.3