From c02ab708137156dcb1de95374fe7a2750d8cf0e9 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 8 Oct 2021 03:02:45 -0400 Subject: [PATCH 1/4] FindResvg: Updates for discovery, version-parsing --- cmake/Modules/FindResvg.cmake | 37 +++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/cmake/Modules/FindResvg.cmake b/cmake/Modules/FindResvg.cmake index 7a03c33e..320e2e44 100644 --- a/cmake/Modules/FindResvg.cmake +++ b/cmake/Modules/FindResvg.cmake @@ -41,6 +41,28 @@ Copyright (c) 2020, FeRD (Frank Dana) #]=======================================================================] include(FindPackageHandleStandardArgs) +### Macro: parse_resvg_version +# +# Read the resvg.h file and extract the definition +# for RESVG_VERSION, to use as our version string. +macro (parse_resvg_version) + set(_header "${Resvg_INCLUDE_DIRS}/resvg.h") + if(EXISTS "${_header}") + #message(STATUS "Parsing Resvg version from ${_header}") + file(STRINGS "${_header}" _version_def + REGEX "^#define[ \t]+RESVG_VERSION[ \t]+\".*\"[ \t]*$") + string(REGEX REPLACE + "^.*RESVG_VERSION[ \t]+\"(.*)\".*$" + "\\1" + Resvg_VERSION "${_version_def}") + #message(STATUS "Found Resvg version: ${Resvg_VERSION}") + endif() +endmacro() + +### +### Begin discovery +### + # CMake 3.4+ only: Convert relative paths to absolute if(DEFINED RESVGDIR AND CMAKE_VERSION VERSION_GREATER 3.4) get_filename_component(RESVGDIR "${RESVGDIR}" ABSOLUTE @@ -58,8 +80,11 @@ find_path(Resvg_INCLUDE_DIRS /usr/include /usr/local/include PATH_SUFFIXES - resvg + c-api capi/include + resvg + resvg/include + resvg/c-api resvg/capi/include ) @@ -82,20 +107,24 @@ find_library(Resvg_LIBRARIES if (Resvg_INCLUDE_DIRS AND Resvg_LIBRARIES) set(Resvg_FOUND TRUE) endif() +parse_resvg_version() + set(Resvg_LIBRARIES ${Resvg_LIBRARIES} CACHE STRING "The Resvg library link path") set(Resvg_INCLUDE_DIRS ${Resvg_INCLUDE_DIRS} CACHE STRING "The Resvg include directories") set(Resvg_DEFINITIONS "" CACHE STRING "The Resvg CFLAGS") +set(Resvg_VERSION ${Resvg_VERSION} CACHE STRING "The Resvg version in use") mark_as_advanced(Resvg_LIBRARIES Resvg_INCLUDE_DIRS Resvg_DEFINITIONS) # Give a nice error message if some of the required vars are missing. find_package_handle_standard_args(Resvg - "Could NOT find Resvg, using Qt SVG parsing instead" - Resvg_LIBRARIES Resvg_INCLUDE_DIRS ) + REQUIRED_VARS Resvg_LIBRARIES Resvg_INCLUDE_DIRS + VERSION_VAR Resvg_VERSION +) # Export target if(Resvg_FOUND AND NOT TARGET Resvg::Resvg) - message(STATUS "Creating IMPORTED target Resvg::Resvg") + #message(STATUS "Creating IMPORTED target Resvg::Resvg") if (WIN32) # Windows mis-links SHARED library targets add_library(Resvg::Resvg UNKNOWN IMPORTED) From e3ca106f2054945f5d187070e84c641281cc20b4 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 8 Oct 2021 03:20:02 -0400 Subject: [PATCH 2/4] CI: Add cached Resvg build (Linux clang) --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7c8bab8..55e6b2e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,14 @@ jobs: repository: OpenShot/libopenshot-audio path: audio + - name: Checkout Resvg + if: ${{ matrix.compiler.cc == 'clang' && runner.os == 'linux' }} + uses: actions/checkout@v2 + with: + repository: RazrFalcon/resvg + path: resvg + branch: v0.11.0 + - uses: haya14busa/action-cond@v1 id: generator with: @@ -54,6 +62,12 @@ jobs: cond: ${{ matrix.compiler.cc == 'gcc' && runner.os == 'linux' }} if_true: "-DENABLE_COVERAGE:BOOL=1" + - uses: haya14busa/action-cond@v1 + id: use-resvg + with: + cond: ${{ matrix.compiler.cc == 'clang' && runner.os == 'linux' }} + if_true: "-DResvg_ROOT:PATH=./resvg" + - name: Install Linux dependencies if: ${{ runner.os == 'linux' }} run: | @@ -63,9 +77,11 @@ jobs: cmake swig doxygen graphviz curl lcov \ libasound2-dev \ qtbase5-dev qtbase5-dev-tools libqt5svg5-dev \ - libfdk-aac-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev \ + libfdk-aac-dev libavcodec-dev libavformat-dev \ + libavutil-dev libswscale-dev libswresample-dev \ libzmq3-dev libmagick++-dev libbabl-dev \ - libopencv-dev libprotobuf-dev protobuf-compiler + libopencv-dev libprotobuf-dev protobuf-compiler \ + cargo # Install catch2 package from Ubuntu 20.10, since for some reason # even 20.04 only has Catch 1.12.1 available. wget https://launchpad.net/ubuntu/+archive/primary/+files/catch2_2.13.0-1_all.deb @@ -103,13 +119,20 @@ jobs: mingw-w64-x86_64-swig - uses: actions/cache@v2 - id: cache + id: cache-audio with: path: audio/build key: audio-${{ runner.os }}-${{ matrix.compiler.cxx }}-${{ hashFiles('audio/CMakeLists.txt') }} + - uses: actions/cache@v2 + if: ${{ steps.use-resvg.outputs.value }} + id: cache-resvg + with: + path: resvg/target + key: resvg-${{ runner.os }}-${{ matrix.compiler.cxx }}-${{ hashFiles('resvg/Cargo.toml') }} + - name: Build OpenShotAudio (if not cached) - if: steps.cache.outputs.cache-hit != 'true' + if: steps.cache-audio.outputs.cache-hit != 'true' run: | pushd audio if [ ! -d build ]; then @@ -131,6 +154,13 @@ jobs: cmake --build build popd + - name: Build Resvg (if enabled and not cached) + if: ${{ steps.use-resvg.outputs.value }} && (steps.cache-resvg.outputs.cache-hit != 'true') + run: | + pushd resvg/c-api + cargo build --release + popd + - name: Build libopenshot run: | if [ "_${{ runner.os }}" == "_macOS" ]; then @@ -157,7 +187,8 @@ jobs: -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install" \ -DOpenShotAudio_ROOT="./audio/build" \ ${CMAKE_EXTRA} \ - "${{ steps.coverage.outputs.value }}" + "${{ steps.coverage.outputs.value }}" \ + "${{ steps.use-resvg.outputs.value }}" cmake --build build -- VERBOSE=1 - name: Test libopenshot From af15649fe81a9e49ef7563b30959faa03d974a56 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 8 Oct 2021 12:04:50 -0400 Subject: [PATCH 3/4] Add support for Resvg 0.11.0+ --- src/QtImageReader.cpp | 76 ++++++++++++++++++++++++------------------- src/QtImageReader.h | 34 +++++++++++++++---- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 1800a0f2..3cf2ea88 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -40,16 +40,15 @@ #include #include -#if USE_RESVG == 1 - // If defined and found in CMake, utilize the libresvg for parsing - // SVG files and rasterizing them to QImages. - #include "ResvgQt.h" -#endif - using namespace openshot; QtImageReader::QtImageReader(std::string path, bool inspect_reader) : path{QString::fromStdString(path)}, is_open(false) { + +#if RESVG_VERSION_MIN(0, 11) + // Initialize the Resvg options + resvg_options.loadSystemFonts(); +#endif // Open and Close the reader, to populate its attributes (such as height, width, etc...) if (inspect_reader) { Open(); @@ -171,8 +170,8 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) // Calculate max image size QSize current_max_size = calculate_max_size(); - // Scale image smaller (or use a previous scaled image) - if (!cached_image || (max_size.width() != current_max_size.width() || max_size.height() != current_max_size.height())) { + // Scale image smaller (or use a previous scaled image) + if (!cached_image || max_size != current_max_size) { // Check for SVG files and rasterize them to QImages if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) { load_svg_path(path); @@ -181,21 +180,22 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) // We need to resize the original image to a smaller image (for performance reasons) // Only do this once, to prevent tons of unneeded scaling operations cached_image = std::make_shared(image->scaled( - current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + current_max_size, + Qt::KeepAspectRatio, Qt::SmoothTransformation)); - // Set max size (to later determine if max_size is changed) - max_size.setWidth(current_max_size.width()); - max_size.setHeight(current_max_size.height()); - } + // Set max size (to later determine if max_size is changed) + max_size = current_max_size; + } - // Create or get frame object - auto image_frame = std::make_shared( - requested_frame, cached_image->width(), cached_image->height(), "#000000", - Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), - info.channels); + auto sample_count = Frame::GetSamplesPerFrame( + requested_frame, info.fps, info.sample_rate, info.channels); + auto sz = cached_image->size(); - // Add Image data to frame - image_frame->AddImage(cached_image); + // Create frame object + auto image_frame = std::make_shared( + requested_frame, sz.width(), sz.height(), "#000000", + sample_count, info.channels); + image_frame->AddImage(cached_image); // return frame object return image_frame; @@ -270,27 +270,34 @@ QSize QtImageReader::load_svg_path(QString) { // Calculate max image size QSize current_max_size = calculate_max_size(); -#if USE_RESVG == 1 - // Use libresvg for parsing/rasterizing SVG +// Try to use libresvg for parsing/rasterizing SVG, if available +#if RESVG_VERSION_MIN(0, 11) + ResvgRenderer renderer(path, resvg_options); + if (renderer.isValid()) { + default_size = renderer.defaultSize(); + // Scale SVG size to keep aspect ratio, and fill max_size as much as possible + QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio); + auto qimage = renderer.renderToImage(svg_size); + image = std::make_shared( + qimage.convertToFormat(QImage::Format_RGBA8888_Premultiplied)); + loaded = true; + } +#elif RESVG_VERSION_MIN(0, 0) ResvgRenderer renderer(path); if (renderer.isValid()) { - // Set default SVG size - default_size.setWidth(renderer.defaultSize().width()); - default_size.setHeight(renderer.defaultSize().height()); - - // Scale SVG size to keep aspect ratio, and fill the max_size as best as possible - QSize svg_size(default_size.width(), default_size.height()); - svg_size.scale(current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio); - + default_size = renderer.defaultSize(); + // Scale SVG size to keep aspect ratio, and fill max_size as much as possible + QSize svg_size = default_size.scaled(current_max_size, Qt::KeepAspectRatio); // Load SVG at max size - image = std::make_shared(svg_size, QImage::Format_RGBA8888_Premultiplied); + image = std::make_shared(svg_size, + QImage::Format_RGBA8888_Premultiplied); image->fill(Qt::transparent); QPainter p(image.get()); renderer.render(&p); p.end(); loaded = true; } -#endif +#endif // Resvg if (!loaded) { // Use Qt for parsing/rasterizing SVG @@ -304,7 +311,8 @@ QSize QtImageReader::load_svg_path(QString) { if (image->width() < current_max_size.width() || image->height() < current_max_size.height()) { // Load SVG into larger/project size (so image is not blurry) - QSize svg_size = image->size().scaled(current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio); + QSize svg_size = image->size().scaled( + current_max_size, Qt::KeepAspectRatio); if (QCoreApplication::instance()) { // Requires QApplication to be running (for QPixmap support) // Re-rasterize SVG image to max size @@ -312,7 +320,7 @@ QSize QtImageReader::load_svg_path(QString) { } else { // Scale image without re-rasterizing it (due to lack of QApplication) image = std::make_shared(image->scaled( - svg_size.width(), svg_size.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + svg_size, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } } } diff --git a/src/QtImageReader.h b/src/QtImageReader.h index 687e85e1..da74c093 100644 --- a/src/QtImageReader.h +++ b/src/QtImageReader.h @@ -31,19 +31,35 @@ #ifndef OPENSHOT_QIMAGE_READER_H #define OPENSHOT_QIMAGE_READER_H -#include -#include -#include -#include -#include #include +#include + +#include +#include #include "ReaderBase.h" +#include "Json.h" + +#if USE_RESVG == 1 + // If defined and found in CMake, utilize the libresvg for parsing + // SVG files and rasterizing them to QImages. + #include "ResvgQt.h" + + #define RESVG_VERSION_MIN(a, b) (\ + RESVG_MAJOR_VERSION > a \ + || (RESVG_MAJOR_VERSION == a && RESVG_MINOR_VERSION >= b) \ + ) +#else + #define RESVG_VERSION_MIN(a, b) 0 +#endif + +class QImage; namespace openshot { - // Forward decl - class CacheBase; + // Forward decl + class CacheBase; + class Frame; /** * @brief This class uses the Qt library, to open image files, and return @@ -73,6 +89,10 @@ namespace openshot bool is_open; ///> Is Reader opened QSize max_size; ///> Current max_size as calculated with Clip properties +#if RESVG_VERSION_MIN(0, 11) + ResvgOptions resvg_options; +#endif + /// Load an SVG file with Resvg or fallback with Qt /// /// @returns Success as a boolean From c9cbf3f2b09e026f59e9609e138180d424d42cc3 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 8 Oct 2021 16:28:32 -0400 Subject: [PATCH 4/4] QtImageReader.cpp: Convert tabs to spaces --- .github/workflows/ci.yml | 8 +- src/QtImageReader.cpp | 208 +++++++++++++++++++-------------------- 2 files changed, 109 insertions(+), 107 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55e6b2e0..59f97534 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,9 +157,11 @@ jobs: - name: Build Resvg (if enabled and not cached) if: ${{ steps.use-resvg.outputs.value }} && (steps.cache-resvg.outputs.cache-hit != 'true') run: | - pushd resvg/c-api - cargo build --release - popd + if [ -d "resvg/c-api" ]; then + pushd resvg/c-api + cargo build --release + popd + fi - name: Build libopenshot run: | diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 3cf2ea88..2cf5806b 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -49,11 +49,11 @@ QtImageReader::QtImageReader(std::string path, bool inspect_reader) : path{QStri // Initialize the Resvg options resvg_options.loadSystemFonts(); #endif - // Open and Close the reader, to populate its attributes (such as height, width, etc...) - if (inspect_reader) { - Open(); - Close(); - } + // Open and Close the reader, to populate its attributes (such as height, width, etc...) + if (inspect_reader) { + Open(); + Close(); + } } QtImageReader::~QtImageReader() @@ -63,10 +63,10 @@ QtImageReader::~QtImageReader() // Open image file void QtImageReader::Open() { - // Open reader if not already open - if (!is_open) - { - bool loaded = false; + // Open reader if not already open + if (!is_open) + { + bool loaded = false; QSize default_svg_size; // Check for SVG files and rasterizing them to QImages @@ -77,95 +77,95 @@ void QtImageReader::Open() } } - if (!loaded) { - // Attempt to open file using Qt's build in image processing capabilities - // AutoTransform enables exif data to be parsed and auto transform the image - // to the correct orientation - image = std::make_shared(); + if (!loaded) { + // Attempt to open file using Qt's build in image processing capabilities + // AutoTransform enables exif data to be parsed and auto transform the image + // to the correct orientation + image = std::make_shared(); QImageReader imgReader( path ); imgReader.setAutoTransform( true ); loaded = imgReader.read(image.get()); - } + } - if (!loaded) { - // raise exception - throw InvalidFile("File could not be opened.", path.toStdString()); - } + if (!loaded) { + // raise exception + throw InvalidFile("File could not be opened.", path.toStdString()); + } - // Update image properties - info.has_audio = false; - info.has_video = true; - info.has_single_image = true; - #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - // byteCount() is deprecated from Qt 5.10 - info.file_size = image->sizeInBytes(); - #else - info.file_size = image->byteCount(); - #endif - info.vcodec = "QImage"; - if (!default_svg_size.isEmpty()) { - // Use default SVG size (if detected) + // Update image properties + info.has_audio = false; + info.has_video = true; + info.has_single_image = true; + #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + // byteCount() is deprecated from Qt 5.10 + info.file_size = image->sizeInBytes(); + #else + info.file_size = image->byteCount(); + #endif + info.vcodec = "QImage"; + if (!default_svg_size.isEmpty()) { + // Use default SVG size (if detected) info.width = default_svg_size.width(); info.height = default_svg_size.height(); - } else { - // Use Qt Image size as a fallback + } else { + // Use Qt Image size as a fallback info.width = image->width(); info.height = image->height(); - } - info.pixel_ratio.num = 1; - info.pixel_ratio.den = 1; - info.duration = 60 * 60 * 1; // 1 hour duration - info.fps.num = 30; - info.fps.den = 1; - info.video_timebase.num = 1; - info.video_timebase.den = 30; - info.video_length = round(info.duration * info.fps.ToDouble()); + } + info.pixel_ratio.num = 1; + info.pixel_ratio.den = 1; + info.duration = 60 * 60 * 1; // 1 hour duration + info.fps.num = 30; + info.fps.den = 1; + info.video_timebase.num = 1; + info.video_timebase.den = 30; + info.video_length = round(info.duration * info.fps.ToDouble()); - // Calculate the DAR (display aspect ratio) - Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den); + // Calculate the DAR (display aspect ratio) + Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den); - // Reduce size fraction - size.Reduce(); + // Reduce size fraction + size.Reduce(); - // Set the ratio based on the reduced fraction - info.display_ratio.num = size.num; - info.display_ratio.den = size.den; + // Set the ratio based on the reduced fraction + info.display_ratio.num = size.num; + info.display_ratio.den = size.den; - // Set current max size - max_size.setWidth(info.width); - max_size.setHeight(info.height); + // Set current max size + max_size.setWidth(info.width); + max_size.setHeight(info.height); - // Mark as "open" - is_open = true; - } + // Mark as "open" + is_open = true; + } } // Close image file void QtImageReader::Close() { - // Close all objects, if reader is 'open' - if (is_open) - { - // Mark as "closed" - is_open = false; + // Close all objects, if reader is 'open' + if (is_open) + { + // Mark as "closed" + is_open = false; - // Delete the image - image.reset(); + // Delete the image + image.reset(); - info.vcodec = ""; - info.acodec = ""; - } + info.vcodec = ""; + info.acodec = ""; + } } // Get an openshot::Frame object for a specific frame number of this reader. std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) { - // Check for open reader (or throw exception) - if (!is_open) - throw ReaderClosed("The Image is closed. Call Open() before calling this method.", path.toStdString()); + // Check for open reader (or throw exception) + if (!is_open) + throw ReaderClosed("The Image is closed. Call Open() before calling this method.", path.toStdString()); - // Create a scoped lock, allowing only a single thread to run the following code at one time - const GenericScopedLock lock(getFrameCriticalSection); + // Create a scoped lock, allowing only a single thread to run the following code at one time + const GenericScopedLock lock(getFrameCriticalSection); // Calculate max image size QSize current_max_size = calculate_max_size(); @@ -197,8 +197,8 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) sample_count, info.channels); image_frame->AddImage(cached_image); - // return frame object - return image_frame; + // return frame object + return image_frame; } // Calculate the max_size QSize, based on parent timeline and parent clip settings @@ -332,53 +332,53 @@ QSize QtImageReader::load_svg_path(QString) { // Generate JSON string of this object std::string QtImageReader::Json() const { - // Return formatted string - return JsonValue().toStyledString(); + // Return formatted string + return JsonValue().toStyledString(); } // Generate Json::Value for this object Json::Value QtImageReader::JsonValue() const { - // Create root json object - Json::Value root = ReaderBase::JsonValue(); // get parent properties - root["type"] = "QtImageReader"; - root["path"] = path.toStdString(); + // Create root json object + Json::Value root = ReaderBase::JsonValue(); // get parent properties + root["type"] = "QtImageReader"; + root["path"] = path.toStdString(); - // return JsonValue - return root; + // return JsonValue + return root; } // Load JSON string into this object void QtImageReader::SetJson(const std::string value) { - // Parse JSON string into JSON objects - try - { - const Json::Value root = openshot::stringToJson(value); - // Set all values that match - SetJsonValue(root); - } - catch (const std::exception& e) - { - // Error parsing JSON (or missing keys) - throw InvalidJSON("JSON is invalid (missing keys or invalid data types)"); - } + // Parse JSON string into JSON objects + try + { + const Json::Value root = openshot::stringToJson(value); + // Set all values that match + SetJsonValue(root); + } + catch (const std::exception& e) + { + // Error parsing JSON (or missing keys) + throw InvalidJSON("JSON is invalid (missing keys or invalid data types)"); + } } // Load Json::Value into this object void QtImageReader::SetJsonValue(const Json::Value root) { - // Set parent data - ReaderBase::SetJsonValue(root); + // Set parent data + ReaderBase::SetJsonValue(root); - // Set data from Json (if key is found) - if (!root["path"].isNull()) - path = QString::fromStdString(root["path"].asString()); + // Set data from Json (if key is found) + if (!root["path"].isNull()) + path = QString::fromStdString(root["path"].asString()); - // Re-Open path, and re-init everything (if needed) - if (is_open) - { - Close(); - Open(); - } + // Re-Open path, and re-init everything (if needed) + if (is_open) + { + Close(); + Open(); + } }