|
29 | 29 | #include "lv_color.h" |
30 | 30 | #include "lv_common.h" |
31 | 31 | #include "lv_cpu.h" |
| 32 | +#include "private/lv_string_hash.hpp" |
32 | 33 | #include "private/lv_video_private.hpp" |
33 | 34 | #include "private/lv_video_blit.hpp" |
34 | 35 | #include "private/lv_video_convert.hpp" |
35 | 36 | #include "private/lv_video_fill.hpp" |
36 | 37 | #include "private/lv_video_transform.hpp" |
37 | 38 | #include "private/lv_video_bmp.hpp" |
38 | 39 | #include "private/lv_video_png.hpp" |
| 40 | +#include <algorithm> |
| 41 | +#include <cctype> |
39 | 42 | #include <cstring> |
| 43 | +#include <filesystem> |
40 | 44 | #include <fstream> |
| 45 | +#include <functional> |
| 46 | +#include <optional> |
| 47 | +#include <string> |
| 48 | +#include <unordered_map> |
| 49 | +#include <vector> |
41 | 50 |
|
42 | 51 | namespace LV { |
43 | 52 |
|
| 53 | + namespace fs = std::filesystem; |
| 54 | + |
44 | 55 | namespace { |
45 | 56 |
|
| 57 | + using BitmapReader = std::function<VideoPtr (std::istream&)>; |
| 58 | + using BitmapWriter = std::function<bool (Video const&, std::ostream&)>; |
| 59 | + |
| 60 | + struct ImageFormatInfo |
| 61 | + { |
| 62 | + std::string name; |
| 63 | + BitmapReader reader; |
| 64 | + BitmapWriter writer; |
| 65 | + std::vector<std::string> extensions; |
| 66 | + }; |
| 67 | + |
| 68 | + using ImageFormatExtensionLookup = std::unordered_map< |
| 69 | + std::string, ImageFormat, StringHash, std::equal_to<>>; |
| 70 | + |
| 71 | + std::unordered_map<ImageFormat, ImageFormatInfo> const supported_image_formats |
| 72 | + { |
| 73 | + {ImageFormat::BMP, {"BMP", bitmap_load_bmp, nullptr, {".bmp"}}}, |
| 74 | + {ImageFormat::PNG, {"PNG", bitmap_load_png, bitmap_save_png, {".png"}}} |
| 75 | + }; |
| 76 | + |
| 77 | + ImageFormatExtensionLookup build_image_format_extension_lookup () |
| 78 | + { |
| 79 | + ImageFormatExtensionLookup map; |
| 80 | + |
| 81 | + for (auto const& entry : supported_image_formats) |
| 82 | + for (auto const& extension : entry.second.extensions) |
| 83 | + map.emplace (extension, entry.first); |
| 84 | + |
| 85 | + return map; |
| 86 | + } |
| 87 | + |
| 88 | + std::optional<ImageFormat> find_image_format_by_extension (std::string_view extension) |
| 89 | + { |
| 90 | + static auto const lookup {build_image_format_extension_lookup ()}; |
| 91 | + |
| 92 | + auto match {lookup.find (extension)}; |
| 93 | + if (match == lookup.end ()) |
| 94 | + return std::nullopt; |
| 95 | + return match->second; |
| 96 | + } |
| 97 | + |
46 | 98 | bool is_valid_scale_method (VisVideoScaleMethod scale_method) |
47 | 99 | { |
48 | 100 | return scale_method == VISUAL_VIDEO_SCALE_NEAREST |
49 | 101 | || scale_method == VISUAL_VIDEO_SCALE_BILINEAR; |
50 | 102 | } |
51 | 103 |
|
| 104 | + // TODO: Factor this out into a string utility library |
| 105 | + std::string to_lower_ascii (std::string const& s) |
| 106 | + { |
| 107 | + auto result {s}; |
| 108 | + for (auto& c : result) |
| 109 | + c = std::tolower (c); |
| 110 | + return result; |
| 111 | + } |
| 112 | + |
52 | 113 | } // anonymous namespace |
53 | 114 |
|
54 | 115 |
|
@@ -191,17 +252,16 @@ namespace LV { |
191 | 252 |
|
192 | 253 | VideoPtr Video::create_from_stream (std::istream& input) |
193 | 254 | { |
194 | | - auto image = bitmap_load_bmp (input); |
195 | | - if (image) { |
196 | | - return image; |
197 | | - } |
198 | | - |
199 | | - image = bitmap_load_png (input); |
200 | | - if (image) { |
201 | | - return image; |
| 255 | + for (auto entry : supported_image_formats) { |
| 256 | + if (entry.second.reader) { |
| 257 | + auto image {entry.second.reader (input)}; |
| 258 | + if (image) { |
| 259 | + return image; |
| 260 | + } |
| 261 | + } |
202 | 262 | } |
203 | 263 |
|
204 | | - return {}; |
| 264 | + return nullptr; |
205 | 265 | } |
206 | 266 |
|
207 | 267 | VideoPtr Video::create_scale_depth (VideoConstPtr const& src, |
@@ -260,6 +320,43 @@ namespace LV { |
260 | 320 | return m_impl->buffer->is_allocated (); |
261 | 321 | } |
262 | 322 |
|
| 323 | + bool Video::save_to_file (std::string const& path) const |
| 324 | + { |
| 325 | + auto extension {fs::path {path}.extension ()}; |
| 326 | + auto extension_lower (to_lower_ascii (extension.string ())); |
| 327 | + |
| 328 | + auto format {find_image_format_by_extension (extension_lower)}; |
| 329 | + if (!format.has_value ()) { |
| 330 | + visual_log (VISUAL_LOG_ERROR, "Could not deduce format from filename (%s)", path.c_str ()); |
| 331 | + return false; |
| 332 | + } |
| 333 | + |
| 334 | + std::ofstream output {path}; |
| 335 | + if (!output) { |
| 336 | + visual_log (VISUAL_LOG_ERROR, "Could not create file '%s'", path.c_str ()); |
| 337 | + return false; |
| 338 | + } |
| 339 | + |
| 340 | + return save_to_stream (output, format.value ()); |
| 341 | + } |
| 342 | + |
| 343 | + bool Video::save_to_stream (std::ostream& output, ImageFormat format) const |
| 344 | + { |
| 345 | + auto entry {supported_image_formats.find (format)}; |
| 346 | + |
| 347 | + if (entry == supported_image_formats.end ()) { |
| 348 | + visual_log (VISUAL_LOG_ERROR, "Saving to unknown format (%d).", static_cast<int> (format)); |
| 349 | + return false; |
| 350 | + } |
| 351 | + |
| 352 | + if (!entry->second.writer) { |
| 353 | + visual_log (VISUAL_LOG_ERROR, "Saving to %s is unsupported.", entry->second.name.c_str ()); |
| 354 | + return false; |
| 355 | + } |
| 356 | + |
| 357 | + return entry->second.writer (*this, output); |
| 358 | + } |
| 359 | + |
263 | 360 | void Video::copy_attrs (VideoConstPtr const& src) |
264 | 361 | { |
265 | 362 | set_depth (src->m_impl->depth); |
|
0 commit comments