diff --git a/libvisual/CMakeLists.txt b/libvisual/CMakeLists.txt index 4409400c..c1706c96 100644 --- a/libvisual/CMakeLists.txt +++ b/libvisual/CMakeLists.txt @@ -82,8 +82,11 @@ IF(NOT HAVE_STDC_MATH) MESSAGE(FATAL_ERROR "Libvisual requires libm to build") ENDIF() +# Check for Zlib +FIND_PACKAGE(ZLIB 1.2 REQUIRED) + # Check for libpng -FIND_PACKAGE(PNG REQUIRED) +FIND_PACKAGE(PNG 1.4 REQUIRED) # Internationalization SET(GETTEXT_PACKAGE "libvisual-${LV_VERSION_SUFFIX}") diff --git a/libvisual/libvisual/CMakeLists.txt b/libvisual/libvisual/CMakeLists.txt index c784cf71..cf089d28 100644 --- a/libvisual/libvisual/CMakeLists.txt +++ b/libvisual/libvisual/CMakeLists.txt @@ -176,6 +176,7 @@ TARGET_LINK_LIBRARIES(libvisual PkgConfig::ORC PNG::PNG Threads::Threads + ZLIB::ZLIB ) IF(WIN32) diff --git a/libvisual/libvisual/lv_video.cpp b/libvisual/libvisual/lv_video.cpp index 9bb5f1b1..b97af6a7 100644 --- a/libvisual/libvisual/lv_video.cpp +++ b/libvisual/libvisual/lv_video.cpp @@ -29,6 +29,7 @@ #include "lv_color.h" #include "lv_common.h" #include "lv_cpu.h" +#include "private/lv_string_hash.hpp" #include "private/lv_video_private.hpp" #include "private/lv_video_blit.hpp" #include "private/lv_video_convert.hpp" @@ -36,19 +37,79 @@ #include "private/lv_video_transform.hpp" #include "private/lv_video_bmp.hpp" #include "private/lv_video_png.hpp" +#include +#include #include +#include #include +#include +#include +#include +#include +#include namespace LV { + namespace fs = std::filesystem; + namespace { + using BitmapReader = std::function; + using BitmapWriter = std::function; + + struct ImageFormatInfo + { + std::string name; + BitmapReader reader; + BitmapWriter writer; + std::vector extensions; + }; + + using ImageFormatExtensionLookup = std::unordered_map< + std::string, ImageFormat, StringHash, std::equal_to<>>; + + std::unordered_map const supported_image_formats + { + {ImageFormat::BMP, {"BMP", bitmap_load_bmp, nullptr, {".bmp"}}}, + {ImageFormat::PNG, {"PNG", bitmap_load_png, bitmap_save_png, {".png"}}} + }; + + ImageFormatExtensionLookup build_image_format_extension_lookup () + { + ImageFormatExtensionLookup map; + + for (auto const& entry : supported_image_formats) + for (auto const& extension : entry.second.extensions) + map.emplace (extension, entry.first); + + return map; + } + + std::optional find_image_format_by_extension (std::string_view extension) + { + static auto const lookup {build_image_format_extension_lookup ()}; + + auto match {lookup.find (extension)}; + if (match == lookup.end ()) + return std::nullopt; + return match->second; + } + bool is_valid_scale_method (VisVideoScaleMethod scale_method) { return scale_method == VISUAL_VIDEO_SCALE_NEAREST || scale_method == VISUAL_VIDEO_SCALE_BILINEAR; } + // TODO: Factor this out into a string utility library + std::string to_lower_ascii (std::string const& s) + { + auto result {s}; + for (auto& c : result) + c = std::tolower (c); + return result; + } + } // anonymous namespace @@ -191,17 +252,16 @@ namespace LV { VideoPtr Video::create_from_stream (std::istream& input) { - auto image = bitmap_load_bmp (input); - if (image) { - return image; - } - - image = bitmap_load_png (input); - if (image) { - return image; + for (auto entry : supported_image_formats) { + if (entry.second.reader) { + auto image {entry.second.reader (input)}; + if (image) { + return image; + } + } } - return {}; + return nullptr; } VideoPtr Video::create_scale_depth (VideoConstPtr const& src, @@ -260,6 +320,43 @@ namespace LV { return m_impl->buffer->is_allocated (); } + bool Video::save_to_file (std::string const& path) const + { + auto extension {fs::path {path}.extension ()}; + auto extension_lower (to_lower_ascii (extension.string ())); + + auto format {find_image_format_by_extension (extension_lower)}; + if (!format.has_value ()) { + visual_log (VISUAL_LOG_ERROR, "Could not deduce format from filename (%s)", path.c_str ()); + return false; + } + + std::ofstream output {path}; + if (!output) { + visual_log (VISUAL_LOG_ERROR, "Could not create file '%s'", path.c_str ()); + return false; + } + + return save_to_stream (output, format.value ()); + } + + bool Video::save_to_stream (std::ostream& output, ImageFormat format) const + { + auto entry {supported_image_formats.find (format)}; + + if (entry == supported_image_formats.end ()) { + visual_log (VISUAL_LOG_ERROR, "Saving to unknown format (%d).", static_cast (format)); + return false; + } + + if (!entry->second.writer) { + visual_log (VISUAL_LOG_ERROR, "Saving to %s is unsupported.", entry->second.name.c_str ()); + return false; + } + + return entry->second.writer (*this, output); + } + void Video::copy_attrs (VideoConstPtr const& src) { set_depth (src->m_impl->depth); diff --git a/libvisual/libvisual/lv_video.h b/libvisual/libvisual/lv_video.h index 793375d0..3c1f3fec 100644 --- a/libvisual/libvisual/lv_video.h +++ b/libvisual/libvisual/lv_video.h @@ -96,6 +96,14 @@ typedef enum { VISUAL_VIDEO_COMPOSE_TYPE_CUSTOM /**< Custom compose function (looks up on the source VisVideo. */ } VisVideoComposeType; +/** + * The set of known and supported image formats. + */ +typedef enum { + VISUAL_IMAGE_FORMAT_BMP = 0, + VISUAL_IMAGE_FORMAT_PNG +} VisImageFormat; + typedef struct _VisVideoAttrOptions VisVideoAttrOptions; #ifdef __cplusplus @@ -125,6 +133,15 @@ struct _VisVideoAttrOptions { namespace LV { + /** + * The set of known and supported image formats. + */ + enum class ImageFormat + { + BMP = VISUAL_IMAGE_FORMAT_BMP, + PNG = VISUAL_IMAGE_FORMAT_PNG + }; + class Video; typedef IntrusivePtr