diff --git a/examples/1F0CF.svg b/examples/1F0CF.svg
new file mode 100644
index 000000000..4b39d7dad
--- /dev/null
+++ b/examples/1F0CF.svg
@@ -0,0 +1,26 @@
+
diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp
index 90af26a0d..01b023259 100644
--- a/src/QtImageReader.cpp
+++ b/src/QtImageReader.cpp
@@ -33,9 +33,11 @@
#include "Settings.h"
#include "Clip.h"
#include "CacheMemory.h"
+#include "Timeline.h"
#include
#include
#include
+#include
#if USE_RESVG == 1
// If defined and found in CMake, utilize the libresvg for parsing
@@ -64,37 +66,20 @@ void QtImageReader::Open()
// Open reader if not already open
if (!is_open)
{
- bool success = true;
bool loaded = false;
-#if USE_RESVG == 1
- // If defined and found in CMake, utilize the libresvg for parsing
- // SVG files and rasterizing them to QImages.
- // Only use resvg for files ending in '.svg' or '.svgz'
- if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
-
- ResvgRenderer renderer(path);
- if (renderer.isValid()) {
-
- image = std::make_shared(
- renderer.defaultSize(), QImage::Format_RGBA8888_Premultiplied);
- image->fill(Qt::transparent);
-
- QPainter p(image.get());
- renderer.render(&p);
- p.end();
- loaded = true;
- }
- }
-#endif
+ // Check for SVG files and rasterizing them to QImages
+ if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
+ loaded = load_svg_path(path);
+ }
if (!loaded) {
// Attempt to open file using Qt's build in image processing capabilities
image = std::make_shared();
- success = image->load(path);
+ loaded = image->load(path);
}
- if (!success) {
+ if (!loaded) {
// raise exception
throw InvalidFile("File could not be opened.", path.toStdString());
}
@@ -167,94 +152,24 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame)
// Create a scoped lock, allowing only a single thread to run the following code at one time
const GenericScopedLock lock(getFrameCriticalSection);
- // Determine the max size of this source image (based on the timeline's size, the scaling mode,
- // and the scaling keyframes). This is a performance improvement, to keep the images as small as possible,
- // without losing quality. NOTE: We cannot go smaller than the timeline itself, or the add_layer timeline
- // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in
- // the future.
- int max_width = info.width;
- int max_height = info.height;
-
- Clip* parent = (Clip*) ParentClip();
- if (parent) {
- if (parent->ParentTimeline()) {
- // Set max width/height based on parent clip's timeline (if attached to a timeline)
- max_width = parent->ParentTimeline()->preview_width;
- max_height = parent->ParentTimeline()->preview_height;
- }
- if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) {
- // Best fit or Stretch scaling (based on max timeline size * scaling keyframes)
- float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
- float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
- max_width = std::max(float(max_width), max_width * max_scale_x);
- max_height = std::max(float(max_height), max_height * max_scale_y);
-
- } else if (parent->scale == SCALE_CROP) {
- // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes)
- float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
- float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
- QSize width_size(max_width * max_scale_x,
- round(max_width / (float(info.width) / float(info.height))));
- QSize height_size(round(max_height / (float(info.height) / float(info.width))),
- max_height * max_scale_y);
- // respect aspect ratio
- if (width_size.width() >= max_width && width_size.height() >= max_height) {
- max_width = std::max(max_width, width_size.width());
- max_height = std::max(max_height, width_size.height());
- }
- else {
- max_width = std::max(max_width, height_size.width());
- max_height = std::max(max_height, height_size.height());
- }
-
- } else {
- // No scaling, use original image size (slower)
- max_width = info.width;
- max_height = info.height;
- }
- }
+ // 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() != max_width || max_size.height() != max_height)) {
+ if (!cached_image || (max_size.width() != current_max_size.width() || max_size.height() != current_max_size.height())) {
+ // Check for SVG files and rasterize them to QImages
+ if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
+ load_svg_path(path);
+ }
- bool rendered = false;
-#if USE_RESVG == 1
- // If defined and found in CMake, utilize the libresvg for parsing
- // SVG files and rasterizing them to QImages.
- // Only use resvg for files ending in '.svg' or '.svgz'
- if (path.toLower().endsWith(".svg") || path.toLower().endsWith(".svgz")) {
-
- ResvgRenderer renderer(path);
- if (renderer.isValid()) {
- // Scale SVG size to keep aspect ratio, and fill the max_size as best as possible
- QSize svg_size(renderer.defaultSize().width(), renderer.defaultSize().height());
- svg_size.scale(max_width, max_height, Qt::KeepAspectRatio);
-
- // Create empty QImage
- cached_image = std::make_shared(
- QSize(svg_size.width(), svg_size.height()),
- QImage::Format_RGBA8888_Premultiplied);
- cached_image->fill(Qt::transparent);
-
- // Render SVG into QImage
- QPainter p(cached_image.get());
- renderer.render(&p);
- p.end();
- rendered = true;
- }
- }
-#endif
-
- if (!rendered) {
- // 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(
- max_width, max_height, Qt::KeepAspectRatio, Qt::SmoothTransformation));
- }
+ // 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));
// Set max size (to later determine if max_size is changed)
- max_size.setWidth(max_width);
- max_size.setHeight(max_height);
+ max_size.setWidth(current_max_size.width());
+ max_size.setHeight(current_max_size.height());
}
// Create or get frame object
@@ -270,6 +185,103 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame)
return image_frame;
}
+// Calculate the max_size QSize, based on parent timeline and parent clip settings
+QSize QtImageReader::calculate_max_size() {
+ // Get max project size
+ int max_width = info.width;
+ int max_height = info.height;
+ if (max_width == 0 || max_height == 0) {
+ // If no size determined yet, default to 4K
+ max_width = 1920;
+ max_height = 1080;
+ }
+
+ Clip* parent = (Clip*) ParentClip();
+ if (parent) {
+ if (parent->ParentTimeline()) {
+ // Set max width/height based on parent clip's timeline (if attached to a timeline)
+ max_width = parent->ParentTimeline()->preview_width;
+ max_height = parent->ParentTimeline()->preview_height;
+ }
+ if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) {
+ // Best fit or Stretch scaling (based on max timeline size * scaling keyframes)
+ float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
+ float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
+ max_width = std::max(float(max_width), max_width * max_scale_x);
+ max_height = std::max(float(max_height), max_height * max_scale_y);
+
+ } else if (parent->scale == SCALE_CROP) {
+ // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes)
+ float max_scale_x = parent->scale_x.GetMaxPoint().co.Y;
+ float max_scale_y = parent->scale_y.GetMaxPoint().co.Y;
+ QSize width_size(max_width * max_scale_x,
+ round(max_width / (float(info.width) / float(info.height))));
+ QSize height_size(round(max_height / (float(info.height) / float(info.width))),
+ max_height * max_scale_y);
+ // respect aspect ratio
+ if (width_size.width() >= max_width && width_size.height() >= max_height) {
+ max_width = std::max(max_width, width_size.width());
+ max_height = std::max(max_height, width_size.height());
+ }
+ else {
+ max_width = std::max(max_width, height_size.width());
+ max_height = std::max(max_height, height_size.height());
+ }
+ }
+ }
+
+ // Return new QSize of the current max size
+ return QSize(max_width, max_height);
+}
+
+// Load an SVG file with Resvg or fallback with Qt
+bool QtImageReader::load_svg_path(QString) {
+ bool loaded = false;
+
+ // Calculate max image size
+ QSize current_max_size = calculate_max_size();
+
+#if USE_RESVG == 1
+ // Use libresvg for parsing/rasterizing SVG
+ ResvgRenderer renderer(path);
+ if (renderer.isValid()) {
+ // Scale SVG size to keep aspect ratio, and fill the max_size as best as possible
+ QSize svg_size(renderer.defaultSize().width(), renderer.defaultSize().height());
+ svg_size.scale(current_max_size.width(), current_max_size.height(), Qt::KeepAspectRatio);
+
+ // Load SVG at max size
+ 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
+
+ if (!loaded) {
+ // Use Qt for parsing/rasterizing SVG
+ image = std::make_shared();
+ loaded = image->load(path);
+
+ if (loaded && (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);
+ if (QCoreApplication::instance()) {
+ // Requires QApplication to be running (for QPixmap support)
+ // Re-rasterize SVG image to max size
+ image = std::make_shared(QIcon(path).pixmap(svg_size).toImage());
+ } 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));
+ }
+ }
+ }
+
+ return loaded;
+}
+
// Generate JSON string of this object
std::string QtImageReader::Json() const {
diff --git a/src/QtImageReader.h b/src/QtImageReader.h
index 1150b4636..a489168f0 100644
--- a/src/QtImageReader.h
+++ b/src/QtImageReader.h
@@ -73,6 +73,15 @@ namespace openshot
bool is_open; ///> Is Reader opened
QSize max_size; ///> Current max_size as calculated with Clip properties
+ /// Load an SVG file with Resvg or fallback with Qt
+ ///
+ /// @returns Success as a boolean
+ /// @param path The file path of the SVG file
+ bool load_svg_path(QString path);
+
+ /// Calculate the max_size QSize, based on parent timeline and parent clip settings
+ QSize calculate_max_size();
+
public:
/// @brief Constructor for QtImageReader.
///
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b949e8bb7..50a85dc90 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -73,6 +73,7 @@ set(OPENSHOT_TEST_FILES
FrameMapper_Tests.cpp
KeyFrame_Tests.cpp
Point_Tests.cpp
+ QtImageReader_Tests.cpp
Settings_Tests.cpp
Timeline_Tests.cpp )
diff --git a/tests/QtImageReader_Tests.cpp b/tests/QtImageReader_Tests.cpp
new file mode 100644
index 000000000..2fd78d5e4
--- /dev/null
+++ b/tests/QtImageReader_Tests.cpp
@@ -0,0 +1,107 @@
+/**
+ * @file
+ * @brief Unit tests for openshot::QtImageReader
+ * @author Jonathan Thomas
+ *
+ * @ref License
+ */
+
+/* LICENSE
+ *
+ * Copyright (c) 2008-2019 OpenShot Studios, LLC
+ * . This file is part of
+ * OpenShot Library (libopenshot), an open-source project dedicated to
+ * delivering high quality video editing and animation solutions to the
+ * world. For more information visit .
+ *
+ * OpenShot Library (libopenshot) is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * OpenShot Library (libopenshot) is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with OpenShot Library. If not, see .
+ */
+
+#include "UnitTest++.h"
+// Prevent name clashes with juce::UnitTest
+#define DONT_SET_USING_JUCE_NAMESPACE 1
+#include "QGuiApplication"
+#include "OpenShot.h"
+
+using namespace std;
+using namespace openshot;
+
+SUITE(QtImageReader)
+{
+
+TEST(Default_Constructor)
+{
+ // Check invalid path
+ CHECK_THROW(QtImageReader(""), InvalidFile);
+}
+
+TEST(GetFrame_Before_Opening)
+{
+ // Create a reader
+ stringstream path;
+ path << TEST_MEDIA_PATH << "front.png";
+ QtImageReader r(path.str());
+
+ // Check invalid path
+ CHECK_THROW(r.GetFrame(1), ReaderClosed);
+}
+
+TEST(Check_SVG_Loading)
+{
+ // Create a reader
+ stringstream path;
+ path << TEST_MEDIA_PATH << "1F0CF.svg";
+ QtImageReader r(path.str());
+ r.Open();
+
+ // Get frame, with no Timeline or Clip
+ // Default SVG scaling sizes things to 1920x1080
+ std::shared_ptr f = r.GetFrame(1);
+ CHECK_EQUAL(1080, f->GetImage()->width());
+ CHECK_EQUAL(1080, f->GetImage()->height());
+
+ Fraction fps(30000,1000);
+ Timeline t1(640, 480, fps, 44100, 2, LAYOUT_STEREO);
+
+ Clip clip1(path.str());
+ clip1.Layer(1);
+ clip1.Position(0.0); // Delay the overlay by 0.05 seconds
+ clip1.End(10.0); // Make the duration of the overlay 1/2 second
+
+ // Add clips
+ t1.AddClip(&clip1);
+ t1.Open();
+
+ // Get frame, with 640x480 Timeline
+ // Should scale to 480
+ clip1.Reader()->Open();
+ f = clip1.Reader()->GetFrame(2);
+ CHECK_EQUAL(480, f->GetImage()->width());
+ CHECK_EQUAL(480, f->GetImage()->height());
+
+ // Add scale_x and scale_y. Should scale the square SVG
+ // by the largest scale keyframe (i.e. 4)
+ clip1.scale_x.AddPoint(1.0, 2.0, openshot::LINEAR);
+ clip1.scale_y.AddPoint(1.0, 2.0, openshot::LINEAR);
+ f = clip1.Reader()->GetFrame(3);
+ CHECK_EQUAL(480 * 2, f->GetImage()->width());
+ CHECK_EQUAL(480 * 2, f->GetImage()->height());
+
+ // Close reader
+ t1.Close();
+ r.Close();
+}
+
+} // SUITE(QtImageReader)
+