From a8e0846a7c111a8d5b5cf8592ecb9b9bbd15ce26 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 22 Sep 2020 22:46:29 +0200 Subject: Initial file manager implementation, with thumbnail caching --- src/Body.cpp | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 13 deletions(-) (limited to 'src/Body.cpp') diff --git a/src/Body.cpp b/src/Body.cpp index 20911de..ae60da2 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -1,5 +1,8 @@ #include "../include/Body.hpp" #include "../include/QuickMedia.hpp" +#include "../include/Scale.hpp" +#include "../include/base64_url.hpp" +#include "../include/ImageUtils.hpp" #include "../plugins/Plugin.hpp" #include #include @@ -11,7 +14,7 @@ const sf::Color front_color(43, 45, 47); const sf::Color back_color(33, 35, 37); namespace QuickMedia { - BodyItem::BodyItem(std::string _title): visible(true), dirty(false), dirty_description(false), background_color(front_color) { + BodyItem::BodyItem(std::string _title): visible(true), dirty(false), dirty_description(false), thumbnail_is_local(false), background_color(front_color) { set_title(std::move(_title)); } @@ -25,6 +28,7 @@ namespace QuickMedia { visible = other.visible; dirty = other.dirty; dirty_description = other.dirty_description; + thumbnail_is_local = other.thumbnail_is_local; if(other.title_text) title_text = std::make_unique(*other.title_text); else @@ -52,6 +56,10 @@ namespace QuickMedia { progress_text.setFillColor(sf::Color::White); author_text.setFillColor(sf::Color::White); replies_text.setFillColor(sf::Color(129, 162, 190)); + thumbnail_resize_target_size.x = 200; + thumbnail_resize_target_size.y = 119; + thumbnail_fallback_size.x = 50.0f; + thumbnail_fallback_size.y = 100.0f; } bool Body::select_previous_item() { @@ -176,18 +184,121 @@ namespace QuickMedia { } } - std::shared_ptr Body::load_thumbnail_from_url(const std::string &url) { + static sf::Vector2f to_vec2f(const sf::Vector2u &vec) { + return sf::Vector2f(vec.x, vec.y); + } + + static sf::Vector2f to_vec2f(const sf::Vector2i &vec) { + return sf::Vector2f(vec.x, vec.y); + } + + static sf::Vector2u to_vec2u(const sf::Vector2f &vec) { + return sf::Vector2u(vec.x, vec.y); + } + + 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) + return; + + //float width_ratio = (float)source_size.x / (float)destination_size.x; + //float height_ratio = (float)source_size.y / (float)destination_size.y; + + const sf::Uint8 *source_pixels = source.getPixelsPtr(); + // TODO: Remove this somehow. Right now we need to allocate this and also allocate the same array in the destination image + sf::Uint32 *destination_pixels = new sf::Uint32[destination_size.x * destination_size.y]; + 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; + //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; + ++destination_pixel; + } + } + destination.create(destination_size.x, destination_size.y, (sf::Uint8*)destination_pixels); + delete []destination_pixels; + } + + static bool save_image_as_thumbnail_atomic(const sf::Image &image, const Path &thumbnail_path, const char *ext) { + Path tmp_path = thumbnail_path; + tmp_path.append(".tmp"); + const char *thumbnail_path_ext = thumbnail_path.ext(); + if(is_image_ext(ext)) + tmp_path.append(ext); + else if(is_image_ext(thumbnail_path_ext)) + tmp_path.append(thumbnail_path_ext); + else + tmp_path.append(".png"); + return image.saveToFile(tmp_path.data) && (rename(tmp_path.data.c_str(), thumbnail_path.data.c_str()) == 0); + } + + // Returns empty string if no extension + static const char* get_ext(const std::string &path) { + size_t index = path.rfind('.'); + if(index == std::string::npos) + return ""; + return path.c_str() + index; + } + + // TODO: Do not load thumbnails for images larger than 30mb + std::shared_ptr Body::load_thumbnail_from_url(const std::string &url, bool local, sf::Vector2i thumbnail_resize_target_size) { auto result = std::make_shared(); result->setSmooth(true); assert(!loading_thumbnail); loading_thumbnail = true; - thumbnail_load_thread = std::thread([this, result, url]() { + thumbnail_load_thread = std::thread([this, result, url, local, thumbnail_resize_target_size]() { + // TODO: Use sha256 instead of base64_url encoding + Path thumbnail_path = get_cache_dir().join("thumbnails").join(base64_url::encode(url)); + std::string texture_data; - if(download_to_string_cache(url, texture_data, {}, program->get_current_plugin()->use_tor, true) == DownloadResult::OK) { - if(result->loadFromMemory(texture_data.data(), texture_data.size())) { - //result->generateMipmap(); + if(file_get_content(thumbnail_path, texture_data) == 0) { + fprintf(stderr, "Loaded %s from thumbnail cache\n", url.c_str()); + result->loadFromMemory(texture_data.data(), texture_data.size()); + loading_thumbnail = false; + return; + } else { + if(local) { + if(file_get_content(url, texture_data) != 0) { + loading_thumbnail = false; + return; + } + } else { + if(download_to_string_cache(url, texture_data, {}, program->get_current_plugin()->use_tor, true) != DownloadResult::OK) { + loading_thumbnail = false; + return; + } } } + + if(thumbnail_resize_target_size.x != 0 && thumbnail_resize_target_size.y != 0) { + auto image = std::make_unique(); + // TODO: Load from file instead? decreases ram usage and we save to file above anyways + if(image->loadFromMemory(texture_data.data(), texture_data.size())) { + texture_data.resize(0); + sf::Vector2u new_image_size = to_vec2u(clamp_to_size(to_vec2f(image->getSize()), to_vec2f(thumbnail_resize_target_size))); + if(new_image_size.x < image->getSize().x || new_image_size.y < image->getSize().y) { + sf::Image destination_image; + copy_resize(*image, destination_image, new_image_size); + image.reset(); + if(save_image_as_thumbnail_atomic(destination_image, thumbnail_path, get_ext(url))) + result->loadFromImage(destination_image); + loading_thumbnail = false; + return; + } else { + result->loadFromImage(*image); + loading_thumbnail = false; + return; + } + } + } + + result->loadFromMemory(texture_data.data(), texture_data.size()); loading_thumbnail = false; }); thumbnail_load_thread.detach(); @@ -207,16 +318,20 @@ namespace QuickMedia { sf::Vector2f scissor_size = size; size.x = std::max(0.0f, size.x - 5); - const float image_max_height = 100.0f; + float image_max_height = 100.0f; const float spacing_y = 15.0f; const float padding_x = 10.0f; const float image_padding_x = 5.0f; const float padding_y = 5.0f; const float start_y = pos.y; - sf::RectangleShape image_fallback(sf::Vector2f(50, image_max_height)); + sf::RectangleShape image_fallback(thumbnail_fallback_size); image_fallback.setFillColor(sf::Color::White); + if(thumbnail_resize_target_size.x != 0 && thumbnail_resize_target_size.y != 0) { + image_max_height = thumbnail_resize_target_size.y; + } + sf::Sprite image; sf::RectangleShape item_background; @@ -278,7 +393,7 @@ namespace QuickMedia { if(draw_thumbnails && !item->thumbnail_url.empty()) { auto &item_thumbnail = item_thumbnail_textures[item->thumbnail_url]; item_thumbnail.referenced = false; - float image_height = image_max_height; + float image_height = image_fallback.getSize().y; if(item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { auto image_size = item_thumbnail.texture->getSize(); image_height = std::min(image_max_height, (float)image_size.y); @@ -325,8 +440,8 @@ namespace QuickMedia { item_height += item->description_text->getHeight(); } if(draw_thumbnails && !item->thumbnail_url.empty()) { - float image_height = image_max_height; - if(item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { + float image_height = image_fallback.getSize().y; + if(item_thumbnail.loaded && item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { auto image_size = item_thumbnail.texture->getSize(); image_height = std::min(image_max_height, (float)image_size.y); } @@ -335,8 +450,9 @@ namespace QuickMedia { item_height += (padding_y * 2.0f); if(draw_thumbnails) { - if(!item->thumbnail_url.empty() && !loading_thumbnail && !item_thumbnail.texture) { - item_thumbnail.texture = load_thumbnail_from_url(item->thumbnail_url); + if(!item->thumbnail_url.empty() && !loading_thumbnail && !item_thumbnail.loaded && !item_thumbnail.texture) { + item_thumbnail.loaded = true; + item_thumbnail.texture = load_thumbnail_from_url(item->thumbnail_url, item->thumbnail_is_local, thumbnail_resize_target_size); } } -- cgit v1.2.3