diff --git a/src/AudioReaderSource.cpp b/src/AudioReaderSource.cpp index d12a17fa..8b103ced 100644 --- a/src/AudioReaderSource.cpp +++ b/src/AudioReaderSource.cpp @@ -12,6 +12,10 @@ #include "AudioReaderSource.h" #include "Exceptions.h" +#include "Frame.h" +#include "ZmqLogger.h" + +#include using namespace std; using namespace openshot; diff --git a/src/AudioReaderSource.h b/src/AudioReaderSource.h index b3d1134a..dcca52b5 100644 --- a/src/AudioReaderSource.h +++ b/src/AudioReaderSource.h @@ -13,8 +13,8 @@ #ifndef OPENSHOT_AUDIOREADERSOURCE_H #define OPENSHOT_AUDIOREADERSOURCE_H -#include #include "ReaderBase.h" + #include /// This namespace is the default namespace for all code in the openshot library diff --git a/src/CacheBase.h b/src/CacheBase.h index 8e9426b5..bd3f9115 100644 --- a/src/CacheBase.h +++ b/src/CacheBase.h @@ -14,11 +14,13 @@ #define OPENSHOT_CACHE_BASE_H #include -#include -#include "Frame.h" + #include "Json.h" +#include + namespace openshot { + class Frame; /** * @brief All cache managers in libopenshot are based on this CacheBase class diff --git a/src/CacheDisk.cpp b/src/CacheDisk.cpp index 04c32ae1..5a748f2b 100644 --- a/src/CacheDisk.cpp +++ b/src/CacheDisk.cpp @@ -12,7 +12,9 @@ #include "CacheDisk.h" #include "Exceptions.h" +#include "Frame.h" #include "QtUtilities.h" + #include #include #include diff --git a/src/CacheDisk.h b/src/CacheDisk.h index a9a79e4d..6c0d1b77 100644 --- a/src/CacheDisk.h +++ b/src/CacheDisk.h @@ -16,11 +16,13 @@ #include #include #include + #include "CacheBase.h" -#include "Frame.h" + #include namespace openshot { + class Frame; /** * @brief This class is a disk-based cache manager for Frame objects. diff --git a/src/CacheMemory.cpp b/src/CacheMemory.cpp index 13a0f8d9..8e5bc4f0 100644 --- a/src/CacheMemory.cpp +++ b/src/CacheMemory.cpp @@ -12,6 +12,7 @@ #include "CacheMemory.h" #include "Exceptions.h" +#include "Frame.h" using namespace std; using namespace openshot; diff --git a/src/CacheMemory.h b/src/CacheMemory.h index 81ee7739..9be0fdcf 100644 --- a/src/CacheMemory.h +++ b/src/CacheMemory.h @@ -16,10 +16,11 @@ #include #include #include + #include "CacheBase.h" -#include "Frame.h" namespace openshot { + class Frame; /** * @brief This class is a memory-based cache manager for Frame objects. diff --git a/src/ChunkReader.h b/src/ChunkReader.h index aa3a85dd..9faae27f 100644 --- a/src/ChunkReader.h +++ b/src/ChunkReader.h @@ -13,16 +13,16 @@ #ifndef OPENSHOT_CHUNK_READER_H #define OPENSHOT_CHUNK_READER_H -#include "ReaderBase.h" #include #include -#include "Frame.h" +#include "ReaderBase.h" #include "Json.h" #include "CacheMemory.h" namespace openshot { + class Frame; /** * @brief This struct holds the location of a frame within a chunk. @@ -119,7 +119,7 @@ namespace openshot void SetChunkSize(int64_t new_size) { chunk_size = new_size; }; /// Get the cache object used by this reader (always return NULL for this reader) - openshot::CacheMemory* GetCache() override { return NULL; }; + openshot::CacheMemory* GetCache() override { return nullptr; }; /// @brief Get an openshot::Frame object for a specific frame number of this reader. /// @returns The requested frame (containing the image and audio) diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index 34b39042..6105324a 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -12,6 +12,7 @@ #include "DummyReader.h" #include "Exceptions.h" +#include "Frame.h" using namespace openshot; diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index c5bf81d5..6f6cbe04 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -15,6 +15,7 @@ #include "ImageReader.h" #include "Exceptions.h" +#include "Frame.h" using namespace openshot; diff --git a/src/Qt/VideoCacheThread.h b/src/Qt/VideoCacheThread.h index 88abafbf..5778eeff 100644 --- a/src/Qt/VideoCacheThread.h +++ b/src/Qt/VideoCacheThread.h @@ -16,6 +16,7 @@ #include "../OpenMPUtilities.h" #include "../ReaderBase.h" #include "../RendererBase.h" +#include "../CacheBase.h" namespace openshot { diff --git a/src/QtHtmlReader.cpp b/src/QtHtmlReader.cpp index 3493ef8a..8d07f682 100644 --- a/src/QtHtmlReader.cpp +++ b/src/QtHtmlReader.cpp @@ -14,6 +14,8 @@ #include "QtHtmlReader.h" #include "Exceptions.h" +#include "Frame.h" + #include #include #include diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index ada9f814..ab24e074 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -16,11 +16,12 @@ #include "Clip.h" #include "CacheMemory.h" #include "Timeline.h" -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include using namespace openshot; diff --git a/src/QtImageReader.h b/src/QtImageReader.h index 47a36275..b4eae09f 100644 --- a/src/QtImageReader.h +++ b/src/QtImageReader.h @@ -37,6 +37,10 @@ class QImage; +#include +#include +#include + namespace openshot { // Forward decl diff --git a/src/QtTextReader.cpp b/src/QtTextReader.cpp index 6180a578..b29afa3b 100644 --- a/src/QtTextReader.cpp +++ b/src/QtTextReader.cpp @@ -14,6 +14,8 @@ #include "QtTextReader.h" #include "Exceptions.h" +#include "Frame.h" + #include #include diff --git a/src/QtTextReader.h b/src/QtTextReader.h index 46d6f4da..60e9ae0b 100644 --- a/src/QtTextReader.h +++ b/src/QtTextReader.h @@ -17,15 +17,12 @@ #include "ReaderBase.h" -#include -#include -#include -#include -#include #include + #include "CacheMemory.h" #include "Enums.h" +#include class QImage; diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index 755dbc4c..95267a69 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -15,6 +15,8 @@ #include #include "ReaderBase.h" +#include "ClipBase.h" +#include "Frame.h" #include "Json.h" diff --git a/src/ReaderBase.h b/src/ReaderBase.h index 5388aa78..3279ca33 100644 --- a/src/ReaderBase.h +++ b/src/ReaderBase.h @@ -13,24 +13,23 @@ #ifndef OPENSHOT_READER_BASE_H #define OPENSHOT_READER_BASE_H +#include #include -#include +#include +#include -#include "CacheMemory.h" #include "ChannelLayouts.h" -#include "ClipBase.h" #include "Fraction.h" -#include "Frame.h" #include "Json.h" -#include "ZmqLogger.h" -#include -#include -#include -#include -#include + +#include namespace openshot { + class ClipBase; + class CacheBase; + class Frame; + /** * @brief This struct contains info about a media file, such as height, width, frames per second, etc... * diff --git a/src/TextReader.cpp b/src/TextReader.cpp index 60d07e9b..58b7b14e 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -15,6 +15,7 @@ #include "TextReader.h" #include "Exceptions.h" +#include "Frame.h" using namespace openshot; diff --git a/src/effects/Caption.cpp b/src/effects/Caption.cpp index eda75aeb..e2443747 100644 --- a/src/effects/Caption.cpp +++ b/src/effects/Caption.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include using namespace openshot; diff --git a/src/effects/Crop.cpp b/src/effects/Crop.cpp index 592bc4e0..cddad20f 100644 --- a/src/effects/Crop.cpp +++ b/src/effects/Crop.cpp @@ -12,18 +12,24 @@ #include "Crop.h" #include "Exceptions.h" +#include "KeyFrame.h" + +#include +#include +#include +#include +#include using namespace openshot; -/// Blank constructor, useful when using Json to load the effect properties -Crop::Crop() : left(0.0), top(0.0), right(0.0), bottom(0.0), x(0.0), y(0.0) { - // Init effect properties - init_effect_details(); -} +/// Default constructor, useful when using Json to load the effect properties +Crop::Crop() : Crop::Crop(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) {} -// Default constructor -Crop::Crop(Keyframe left, Keyframe top, Keyframe right, Keyframe bottom) : - left(left), top(top), right(right), bottom(bottom), x(0.0), y(0.0) +Crop::Crop( + Keyframe left, Keyframe top, + Keyframe right, Keyframe bottom, + Keyframe x, Keyframe y) : + left(left), top(top), right(right), bottom(bottom), x(x), y(y) { // Init effect properties init_effect_details(); @@ -50,80 +56,57 @@ std::shared_ptr Crop::GetFrame(std::shared_ptr // Get the frame's image std::shared_ptr frame_image = frame->GetImage(); - // Get transparent color target image (which will become the cropped image) - auto cropped_image = std::make_shared( - frame_image->width(), frame_image->height(), QImage::Format_RGBA8888_Premultiplied); - cropped_image->fill(QColor(QString::fromStdString("transparent"))); - // Get current keyframe values double left_value = left.GetValue(frame_number); double top_value = top.GetValue(frame_number); double right_value = right.GetValue(frame_number); double bottom_value = bottom.GetValue(frame_number); - // Get the current shift amount (if any... to slide the image around in the cropped area) + // Get the current shift amount double x_shift = x.GetValue(frame_number); double y_shift = y.GetValue(frame_number); - // Get pixel array pointers - unsigned char *pixels = (unsigned char *) frame_image->bits(); - unsigned char *cropped_pixels = (unsigned char *) cropped_image->bits(); + QSize sz = frame_image->size(); - // Get pixels sizes of all crop sides - int top_bar_height = top_value * frame_image->height(); - int bottom_bar_height = bottom_value * frame_image->height(); - int left_bar_width = left_value * frame_image->width(); - int right_bar_width = right_value * frame_image->width(); - int column_offset = x_shift * frame_image->width(); - int row_offset = y_shift * frame_image->height(); + // Compute destination rectangle to paint into + QRectF paint_r( + left_value * sz.width(), top_value * sz.height(), + std::max(0.0, 1.0 - left_value - right_value) * sz.width(), + std::max(0.0, 1.0 - top_value - bottom_value) * sz.height()); - // Image copy variables - int image_width = frame_image->width(); - int src_start = left_bar_width; - int dst_start = left_bar_width; - int copy_length = frame_image->width() - right_bar_width - left_bar_width; + // Copy rectangle is destination translated by offsets + QRectF copy_r = paint_r; + copy_r.translate(x_shift * sz.width(), y_shift * sz.height()); - // Adjust for x offset - int copy_offset = 0; - - if (column_offset < 0) { - // dest to the right - src_start += column_offset; - if (src_start < 0) { - int diff = 0 - src_start; // how far under 0 are we? - src_start = 0; - dst_start += diff; - copy_offset = -diff; - } else { - copy_offset = 0; - } - - } else { - // dest to the left - src_start += column_offset; - if (image_width - src_start >= copy_length) { - // We have plenty pixels, use original copy-length - copy_offset = 0; - } else { - // We don't have enough pixels, shorten copy-length - copy_offset = (image_width - src_start) - copy_length; - } + // Constrain offset copy rect to stay within image borders + if (copy_r.left() < 0) { + paint_r.setLeft(paint_r.left() - copy_r.left()); + copy_r.setLeft(0); + } + if (copy_r.right() > sz.width()) { + paint_r.setRight(paint_r.right() - (copy_r.right() - sz.width())); + copy_r.setRight(sz.width()); + } + if (copy_r.top() < 0) { + paint_r.setTop(paint_r.top() - copy_r.top()); + copy_r.setTop(0); + } + if (copy_r.bottom() > sz.height()) { + paint_r.setBottom(paint_r.bottom() - (copy_r.bottom() - sz.height())); + copy_r.setBottom(sz.height()); } - // Loop through rows of pixels - for (int row = 0; row < frame_image->height(); row++) { - int adjusted_row = row - row_offset; - // Is this row visible? - if (adjusted_row >= top_bar_height && adjusted_row < (frame_image->height() - bottom_bar_height) && (copy_length + copy_offset > 0)) { - // Copy image (row by row, with offsets for x and y offset, and src/dst starting points for column filtering) - memcpy(&cropped_pixels[((adjusted_row * frame_image->width()) + dst_start) * 4], - &pixels[((row * frame_image->width()) + src_start) * 4], - sizeof(char) * (copy_length + copy_offset) * 4); - } - } + QImage cropped(sz, QImage::Format_RGBA8888_Premultiplied); + cropped.fill(Qt::transparent); + + const QImage src(*frame_image); + + QPainter p(&cropped); + p.drawImage(paint_r, src, copy_r); + p.end(); // Set frame image - frame->AddImage(cropped_image); + frame->AddImage(std::make_shared(cropped.copy())); // return the modified frame return frame; diff --git a/src/effects/Crop.h b/src/effects/Crop.h index 5961641e..a21391ab 100644 --- a/src/effects/Crop.h +++ b/src/effects/Crop.h @@ -58,7 +58,10 @@ namespace openshot /// @param top The curve to adjust the top bar size (between 0 and 1) /// @param right The curve to adjust the right bar size (between 0 and 1) /// @param bottom The curve to adjust the bottom bar size (between 0 and 1) - Crop(Keyframe left, Keyframe top, Keyframe right, Keyframe bottom); + /// @param x x-offset of original image in output frame (-1.0 - 1.0) + /// @param y y-offset of original image in output frame (-1.0 - 1.0) + Crop(Keyframe left, Keyframe top, Keyframe right, Keyframe bottom, + Keyframe x=0.0, Keyframe y=0.0); /// @brief This method is required for all derived classes of ClipBase, and returns a /// new openshot::Frame object. All Clip keyframes and effects are resolved into @@ -66,7 +69,10 @@ namespace openshot /// /// @returns A new openshot::Frame object /// @param frame_number The frame number (starting at 1) of the clip or effect on the timeline. - std::shared_ptr GetFrame(int64_t frame_number) override { return GetFrame(std::make_shared(), frame_number); } + std::shared_ptr + GetFrame(int64_t frame_number) override { + return GetFrame(std::make_shared(), frame_number); + } /// @brief This method is required for all derived classes of ClipBase, and returns a /// modified openshot::Frame object @@ -77,7 +83,8 @@ namespace openshot /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the clip or effect applied to it /// @param frame_number The frame number (starting at 1) of the clip or effect on the timeline. - std::shared_ptr GetFrame(std::shared_ptr frame, int64_t frame_number) override; + std::shared_ptr + GetFrame(std::shared_ptr frame, int64_t frame_number) override; // Get and Set JSON methods std::string Json() const override; ///< Generate JSON string of this object diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b783ce6f..4619fc16 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,6 +37,8 @@ set(OPENSHOT_TESTS ReaderBase Settings Timeline + # Effects + Crop ) # ImageMagick related test files diff --git a/tests/CacheDisk.cpp b/tests/CacheDisk.cpp index cee9903e..b69c218f 100644 --- a/tests/CacheDisk.cpp +++ b/tests/CacheDisk.cpp @@ -16,6 +16,7 @@ #include #include "CacheDisk.h" +#include "Frame.h" #include "Json.h" using namespace openshot; diff --git a/tests/CacheMemory.cpp b/tests/CacheMemory.cpp index f41c7ab2..dc6917b6 100644 --- a/tests/CacheMemory.cpp +++ b/tests/CacheMemory.cpp @@ -16,6 +16,7 @@ #include #include "CacheMemory.h" +#include "Frame.h" #include "Json.h" using namespace openshot; diff --git a/tests/Crop.cpp b/tests/Crop.cpp new file mode 100644 index 00000000..b06ca49b --- /dev/null +++ b/tests/Crop.cpp @@ -0,0 +1,164 @@ +/** + * @file + * @brief Unit tests for openshot::Crop effect + * @author Jonathan Thomas + * @author FeRD (Frank Dana) + * + * @ref License + */ + +/* LICENSE + * + * Copyright (c) 2008-2021 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 + +#include + +#include "Frame.h" +#include "effects/Crop.h" + +#include +#include +#include +#include + +TEST_CASE( "default constructor", "[libopenshot][effect][crop]" ) +{ + // solid green frame + auto f = std::make_shared(1, 1280, 720, "#00ff00"); + + // Default constructor should have no cropping + openshot::Crop e; + + auto f_out = e.GetFrame(f, 1); + std::shared_ptr i = f_out->GetImage(); + + // Check pixels near edges (should all be green) + std::vector pixels { + i->pixelColor(400, 2), + i->pixelColor(1279, 500), + i->pixelColor(800, 718), + i->pixelColor(1, 200) + }; + + QColor green{Qt::green}; + CHECK(pixels[0] == green); + CHECK(pixels[1] == green); + CHECK(pixels[2] == green); + CHECK(pixels[3] == green); +} + +TEST_CASE( "basic cropping", "[libopenshot][effect][crop]" ) +{ + auto frame = std::make_shared(1, 1280, 720, "#00ff00"); + + // Crop 10% off the input frame on all four sides + openshot::Keyframe left(0.1); + openshot::Keyframe top(0.1); + openshot::Keyframe right(0.1); + openshot::Keyframe bottom(0.1); + openshot::Crop e(left, top, right, bottom); + + auto frame_out = e.GetFrame(frame, 1); + std::shared_ptr i = frame_out->GetImage(); + + QSize sz(1280, 720); + CHECK(i->size() == sz); + + // Green inside the crop region, transparent outside + QColor green{Qt::green}; + QColor trans{Qt::transparent}; + + QColor center_pixel = i->pixelColor(640, 360); + CHECK(center_pixel == green); + + std::vector edge_pixels { + i->pixelColor(50, 200), + i->pixelColor(400, 20), + i->pixelColor(1250, 500), + i->pixelColor(800, 715) + }; + CHECK(edge_pixels[0] == trans); + CHECK(edge_pixels[1] == trans); + CHECK(edge_pixels[2] == trans); + CHECK(edge_pixels[3] == trans); +} + +TEST_CASE( "region collapsing", "[libopenshot][effect][crop]" ) +{ + auto frame = std::make_shared(1, 1920, 1080, "#ff00ff"); + + // Crop 50% off left and right sides (== crop out entire image) + openshot::Keyframe left(0.4); + openshot::Keyframe right(0.6); + openshot::Keyframe none(0.0); + openshot::Crop e(left, none, right, none); + + auto frame_out = e.GetFrame(frame, 1); + auto i = frame_out->GetImage(); + + // Only true if all pixels have been cropped away (as expected) + CHECK(i->allGray()); +} + +TEST_CASE( "x/y offsets", "[libopenshot][effect][crop]" ) +{ + auto frame = std::make_shared(1, 1280, 720, "#ff0000"); + auto frame_img = frame->GetImage(); + QImage img(*frame_img); + + // Make input frame left-half red, right-half blue + QPainter p(&img); + p.fillRect(QRect(640, 0, 640, 720), Qt::blue); + p.end(); + + frame->AddImage(std::make_shared(img)); + + // Crop 20% off all four sides, and shift the source window x +33⅓ % + openshot::Keyframe sides(0.2); + openshot::Keyframe x(0.3); + openshot::Crop e(sides, sides, sides, sides, x); + + auto frame_out = e.GetFrame(frame, 1); + std::shared_ptr i = frame_out->GetImage(); + + // Entire cropped region should be blue (due to x-offset), and will be + // off-center (due to being only 50% wide instead of 60%) + QColor blue{Qt::blue}; + QColor trans{Qt::transparent}; + + std::vector edge_pixels { + i->pixelColor(258, 146), + i->pixelColor(894, 146), + i->pixelColor(894, 574), + i->pixelColor(258, 574) + }; + CHECK(edge_pixels[0] == blue); + CHECK(edge_pixels[1] == blue); + CHECK(edge_pixels[2] == blue); + CHECK(edge_pixels[3] == blue); + + // This pixel would normally be inside the cropping. + // The x-offset moves it outside of the source image area, + // so it becomes a transparent pixel + CHECK(i->pixelColor(900, 360) == trans); +}