#include "../include/AsyncImageLoader.hpp" #include "../include/base64_url.hpp" #include "../include/Storage.hpp" #include "../include/DownloadUtils.hpp" #include "../include/ImageUtils.hpp" #include "../include/Scale.hpp" #include namespace QuickMedia { 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 slash_index = path.rfind('/'); size_t index = path.rfind('.'); if(index != std::string::npos && (slash_index == std::string::npos || index > slash_index)) return path.c_str() + index; return ""; } // TODO: Run in 5 different threads, and add download_to_file(_cache) to reduce memory usage in each (use -O curl option) bool AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, bool use_tor, std::shared_ptr thumbnail_data) { update(); if(loading_image) return false; loading_image = true; assert(thumbnail_data->loading_state == LoadingState::NOT_LOADED); thumbnail_data->loading_state = LoadingState::LOADING; if(url.empty()) { thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; loading_image = false; return true; } load_image_future = std::async(std::launch::async, [url, local, resize_target_size, thumbnail_data, use_tor]() mutable { // TODO: Use sha256 instead of base64_url encoding Path thumbnail_path = get_cache_dir().join("thumbnails").join(base64_url::encode(url)); thumbnail_data->image = std::make_unique(); if(thumbnail_data->image->loadFromFile(thumbnail_path.data)) { fprintf(stderr, "Loaded %s from thumbnail cache\n", url.c_str()); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; return; } else { if(local) { if(!thumbnail_data->image->loadFromFile(url)) { thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; return; } } else { std::string texture_data; if(download_to_string_cache(url, texture_data, {}, use_tor, true) != DownloadResult::OK || !thumbnail_data->image->loadFromMemory(texture_data.data(), texture_data.size())) { thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; return; } } } if(resize_target_size.x != 0 && resize_target_size.y != 0) { sf::Vector2u new_image_size = to_vec2u(clamp_to_size(to_vec2f(thumbnail_data->image->getSize()), to_vec2f(resize_target_size))); if(new_image_size.x < thumbnail_data->image->getSize().x || new_image_size.y < thumbnail_data->image->getSize().y) { auto destination_image = std::make_unique(); copy_resize(*thumbnail_data->image, *destination_image, new_image_size); thumbnail_data->image = std::move(destination_image); save_image_as_thumbnail_atomic(*thumbnail_data->image, thumbnail_path, get_ext(url)); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; return; } } thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; return; }); return true; } void AsyncImageLoader::update() { if(loading_image && load_image_future.valid() && load_image_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { load_image_future.get(); loading_image = false; } } }