aboutsummaryrefslogtreecommitdiff
path: root/src/AsyncImageLoader.cpp
blob: bfef294e88661c932bb8e17aabecd2d82b780725 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#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 <assert.h>

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<ThumbnailData> 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<sf::Image>();
            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<sf::Image>();
            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<sf::Image>();
                    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;
        }
    }
}