diff --git a/CMakeLists.txt b/CMakeLists.txt index ec608257d..be7f79458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,10 @@ add_subdirectory(src) add_subdirectory(examples) add_subdirectory(bindings) +IF (NOT DISABLE_EXTERNAL_EFFECTS) + add_subdirectory(external-effects) +ENDIF (NOT DISABLE_EXTERNAL_EFFECTS) + ### ### Configure Version.h header ### diff --git a/external-effects/CMakeLists.txt b/external-effects/CMakeLists.txt new file mode 100644 index 000000000..3ad34aeb2 --- /dev/null +++ b/external-effects/CMakeLists.txt @@ -0,0 +1,29 @@ +####################### CMakeLists.txt (libopenshot) ######################### +# @brief CMake build file for libopenshot (used to generate makefiles) +# @author Jonathan Thomas +# +# @section 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 . +################################################################################ + +############## PROCESS SUB-DIRECTORIES ############## +add_subdirectory(super-blur) + diff --git a/external-effects/super-blur/CMakeLists.txt b/external-effects/super-blur/CMakeLists.txt new file mode 100644 index 000000000..d95edd88d --- /dev/null +++ b/external-effects/super-blur/CMakeLists.txt @@ -0,0 +1,150 @@ +##################### tests/CMakeLists.txt (libopenshot) ###################### +# @brief CMake build file for libopenshot (used to generate makefiles) +# @author Jonathan Thomas +# +# @section 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 . +################################################################################ + +# Pick up our include directories from the parent context +include_directories(${OPENSHOT_INCLUDE_DIRS}) + +################ IMAGE MAGICK ################## +# Set the Quantum Depth that ImageMagick was built with (default to 16 bits) +IF (MAGICKCORE_QUANTUM_DEPTH) + add_definitions( -DMAGICKCORE_QUANTUM_DEPTH=${MAGICKCORE_QUANTUM_DEPTH} ) +ELSE (MAGICKCORE_QUANTUM_DEPTH) + add_definitions( -DMAGICKCORE_QUANTUM_DEPTH=16 ) +ENDIF (MAGICKCORE_QUANTUM_DEPTH) +IF (MAGICKCORE_HDRI_ENABLE) + add_definitions( -DMAGICKCORE_HDRI_ENABLE=${MAGICKCORE_HDRI_ENABLE} ) +ELSE (MAGICKCORE_HDRI_ENABLE) + add_definitions( -DMAGICKCORE_HDRI_ENABLE=0 ) +ENDIF (MAGICKCORE_HDRI_ENABLE) +IF (OPENSHOT_IMAGEMAGICK_COMPATIBILITY) + add_definitions( -DOPENSHOT_IMAGEMAGICK_COMPATIBILITY=${OPENSHOT_IMAGEMAGICK_COMPATIBILITY} ) +ELSE (OPENSHOT_IMAGEMAGICK_COMPATIBILITY) + add_definitions( -DOPENSHOT_IMAGEMAGICK_COMPATIBILITY=0 ) +ENDIF (OPENSHOT_IMAGEMAGICK_COMPATIBILITY) + +# Find the ImageMagick++ library +FIND_PACKAGE(ImageMagick COMPONENTS Magick++ MagickWand MagickCore) +IF (ImageMagick_FOUND) + # Include ImageMagick++ headers (needed for compile) + include_directories(${ImageMagick_INCLUDE_DIRS}) + + # define a global var (used in the C++) + add_definitions( -DUSE_IMAGEMAGICK=1 ) + SET(CMAKE_SWIG_FLAGS "-DUSE_IMAGEMAGICK=1") + +ENDIF (ImageMagick_FOUND) + +################### FFMPEG ##################### +# Find FFmpeg libraries (used for video encoding / decoding) +FIND_PACKAGE(FFmpeg REQUIRED) + +foreach(ffmpeg_comp AVCODEC AVDEVICE AVFORMAT AVFILTER AVUTIL POSTPROC SWSCALE SWRESAMPLE AVRESAMPLE) + if(${ffmpeg_comp}_FOUND) + # Include FFmpeg headers (needed for compile) + list(APPEND FF_INCLUDES ${${ffmpeg_comp}_INCLUDE_DIRS}) + add_definitions(${${ffmpeg_comp}_DEFINITIONS}) + endif() +endforeach() +list(REMOVE_DUPLICATES FF_INCLUDES) +include_directories(${FF_INCLUDES}) + +################# LIBOPENSHOT-AUDIO ################### +# Find JUCE-based openshot Audio libraries +FIND_PACKAGE(OpenShotAudio 0.1.8 REQUIRED) + +# Include Juce headers (needed for compile) +include_directories(${LIBOPENSHOT_AUDIO_INCLUDE_DIRS}) + +################# QT5 ################### +# Find QT5 libraries +foreach(qt_lib Qt5Widgets Qt5Core Qt5Gui Qt5Multimedia Qt5MultimediaWidgets) + find_package(${qt_lib} REQUIRED) + # Header files + list(APPEND QT_INCLUDES ${${qt_lib}_INCLUDE_DIRS}) + # Compiler definitions + add_definitions(${${qt_lib}_DEFINITIONS}) + # Other CFLAGS + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${${qt_lib}_EXECUTABLE_COMPILE_FLAGS}") +endforeach() +list(REMOVE_DUPLICATES QT_INCLUDES) +include_directories(${QT_INCLUDES}) + +# Manually moc Qt files +qt5_wrap_cpp(MOC_FILES ${QT_HEADER_FILES}) + + +################# BLACKMAGIC DECKLINK ################### +IF (ENABLE_BLACKMAGIC) + # Find BlackMagic DeckLinkAPI libraries + FIND_PACKAGE(BlackMagic) + + IF (BLACKMAGIC_FOUND) + # Include Blackmagic headers (needed for compile) + include_directories(${BLACKMAGIC_INCLUDE_DIR}) + ENDIF (BLACKMAGIC_FOUND) +ENDIF (ENABLE_BLACKMAGIC) + +################### OPENMP ##################### +# Check for OpenMP (used for multi-core processing) +FIND_PACKAGE(OpenMP) + +if (OPENMP_FOUND) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} ") +endif(OPENMP_FOUND) + +################### ZEROMQ ##################### +# Find ZeroMQ library (used for socket communication & logging) +FIND_PACKAGE(ZMQ REQUIRED) + +# Include ZeroMQ headers (needed for compile) +include_directories(${ZMQ_INCLUDE_DIRS}) + +################### RESVG ##################### +# Find resvg library (used for rendering svg files) +FIND_PACKAGE(RESVG) + +# Include resvg headers (optional SVG library) +if (RESVG_FOUND) + include_directories(${RESVG_INCLUDE_DIRS}) +endif(RESVG_FOUND) + +################### JSONCPP ##################### +# Include jsoncpp headers (needed for JSON parsing) +if (USE_SYSTEM_JSONCPP) + find_package(JsonCpp REQUIRED) + include_directories(${JSONCPP_INCLUDE_DIRS}) +else() + include_directories("../../thirdparty/jsoncpp") +endif(USE_SYSTEM_JSONCPP) + +SET ( EFFECT_SOURCE_FILES + SuperBlur.cpp) + +# Create shared openshot library +add_library(effect-superblur SHARED + ${EFFECT_SOURCE_FILES}) + +set(LIB_INSTALL_DIR libeffect${LIB_SUFFIX}) # determine correct lib folder diff --git a/external-effects/super-blur/SuperBlur.cpp b/external-effects/super-blur/SuperBlur.cpp new file mode 100644 index 000000000..f473cd41d --- /dev/null +++ b/external-effects/super-blur/SuperBlur.cpp @@ -0,0 +1,348 @@ +/** + * @file + * @brief Source file for Blur effect class + * @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 "./SuperBlur.h" + +using namespace openshot; + +void * factory(uint16_t openshot_version){ + return (void *)new SuperBlur(); +} + +/// Blank constructor, useful when using Json to load the effect properties +SuperBlur::SuperBlur() : horizontal_radius(6.0), vertical_radius(6.0), sigma(3.0), iterations(3.0) { + // Init effect properties + init_effect_details(); +} + +// Default constructor +SuperBlur::SuperBlur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations) : + horizontal_radius(new_horizontal_radius), vertical_radius(new_vertical_radius), + sigma(new_sigma), iterations(new_iterations) +{ + // Init effect properties + init_effect_details(); +} + +// Init effect settings +void SuperBlur::init_effect_details() +{ + /// Initialize the values of the EffectInfo struct. + InitEffectInfo(); + + /// Set the effect info + info.class_name = "SuperBlur"; + info.name = "SuperBlur"; + info.description = "Adjust the blur of the frame's image."; + info.has_audio = false; + info.has_video = true; +} + +// This method is required for all derived classes of EffectBase, and returns a +// modified openshot::Frame object +std::shared_ptr SuperBlur::GetFrame(std::shared_ptr frame, int64_t frame_number) +{ + // Get the frame's image + std::shared_ptr frame_image = frame->GetImage(); + + // Get the current blur radius + int horizontal_radius_value = horizontal_radius.GetValue(frame_number); + int vertical_radius_value = vertical_radius.GetValue(frame_number); + float sigma_value = sigma.GetValue(frame_number); + int iteration_value = iterations.GetInt(frame_number); + + + // Declare arrays for each color channel + unsigned char *red = new unsigned char[frame_image->width() * frame_image->height()](); + unsigned char *green = new unsigned char[frame_image->width() * frame_image->height()](); + unsigned char *blue = new unsigned char[frame_image->width() * frame_image->height()](); + unsigned char *alpha = new unsigned char[frame_image->width() * frame_image->height()](); + // Create empty target RGBA arrays (for the results of our blur) + unsigned char *blur_red = new unsigned char[frame_image->width() * frame_image->height()](); + unsigned char *blur_green = new unsigned char[frame_image->width() * frame_image->height()](); + unsigned char *blur_blue = new unsigned char[frame_image->width() * frame_image->height()](); + unsigned char *blur_alpha = new unsigned char[frame_image->width() * frame_image->height()](); + + // Loop through pixels and split RGBA channels into separate arrays + unsigned char *pixels = (unsigned char *) frame_image->bits(); + for (int pixel = 0, byte_index=0; pixel < frame_image->width() * frame_image->height(); pixel++, byte_index+=4) + { + // Get the RGBA values from each pixel + unsigned char R = pixels[byte_index]; + unsigned char G = pixels[byte_index + 1]; + unsigned char B = pixels[byte_index + 2]; + unsigned char A = pixels[byte_index + 3]; + + // Split channels into their own arrays + red[pixel] = R; + green[pixel] = G; + blue[pixel] = B; + alpha[pixel] = A; + } + + // Init target RGBA arrays + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) blur_red[i] = red[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) blur_green[i] = green[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) blur_blue[i] = blue[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) blur_alpha[i] = alpha[i]; + + // Loop through each iteration + for (int iteration = 0; iteration < iteration_value; iteration++) + { + // HORIZONTAL BLUR (if any) + if (horizontal_radius_value > 0.0) { + // Init boxes for computing blur + int *bxs = initBoxes(sigma_value, horizontal_radius_value); + + // Apply horizontal blur to target RGBA channels + boxBlurH(red, blur_red, frame_image->width(), frame_image->height(), horizontal_radius_value); + boxBlurH(green, blur_green, frame_image->width(), frame_image->height(), horizontal_radius_value); + boxBlurH(blue, blur_blue, frame_image->width(), frame_image->height(), horizontal_radius_value); + boxBlurH(alpha, blur_alpha, frame_image->width(), frame_image->height(), horizontal_radius_value); + + // Remove boxes + delete[] bxs; + + // Copy blur_ back to for vertical blur or next iteration + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) red[i] = blur_red[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) green[i] = blur_green[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) blue[i] = blur_blue[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) alpha[i] = blur_alpha[i]; + } + + // VERTICAL BLUR (if any) + if (vertical_radius_value > 0.0) { + // Init boxes for computing blur + int *bxs = initBoxes(sigma_value, vertical_radius_value); + + // Apply vertical blur to target RGBA channels + boxBlurT(red, blur_red, frame_image->width(), frame_image->height(), vertical_radius_value); + boxBlurT(green, blur_green, frame_image->width(), frame_image->height(), vertical_radius_value); + boxBlurT(blue, blur_blue, frame_image->width(), frame_image->height(), vertical_radius_value); + boxBlurT(alpha, blur_alpha, frame_image->width(), frame_image->height(), vertical_radius_value); + + // Remove boxes + delete[] bxs; + + // Copy blur_ back to for vertical blur or next iteration + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) red[i] = blur_red[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) green[i] = blur_green[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) blue[i] = blur_blue[i]; + for (int i = 0; i < (frame_image->width() * frame_image->height()); i++) alpha[i] = blur_alpha[i]; + } + } + + // Copy RGBA channels back to original image + for (int pixel = 0, byte_index=0; pixel < frame_image->width() * frame_image->height(); pixel++, byte_index+=4) + { + // Get the RGB values from the pixel + unsigned char R = blur_red[pixel]; + unsigned char G = blur_green[pixel]; + unsigned char B = blur_blue[pixel]; + unsigned char A = blur_alpha[pixel]; + + // Split channels into their own arrays + pixels[byte_index] = R; + pixels[byte_index + 1] = G; + pixels[byte_index + 2] = B; + pixels[byte_index + 3] = A; + } + + // Delete channel arrays + delete[] red; + delete[] green; + delete[] blue; + delete[] alpha; + delete[] blur_red; + delete[] blur_green; + delete[] blur_blue; + delete[] blur_alpha; + + // return the modified frame + return frame; +} + +// Credit: http://blog.ivank.net/fastest-gaussian-blur.html (MIT License) +int* SuperBlur::initBoxes(float sigma, int n) // standard deviation, number of boxes +{ + float wIdeal = sqrt((12.0 * sigma * sigma / n) + 1.0); // Ideal averaging filter width + int wl = floor(wIdeal); + if (wl % 2 == 0) wl--; + int wu = wl + 2; + + float mIdeal = (12.0 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4.0 * wl - 4); + int m = round(mIdeal); + + int *sizes = new int[n](); + for (int i = 0; i < n; i++) sizes[i] = i < m ? wl : wu; + return sizes; +} + +// Credit: http://blog.ivank.net/fastest-gaussian-blur.html (MIT License) +void SuperBlur::boxBlurH(unsigned char *scl, unsigned char *tcl, int w, int h, int r) { + float iarr = 1.0 / (r + r + 1); + for (int i = 0; i < h; i++) { + int ti = i * w, li = ti, ri = ti + r; + int fv = scl[ti], lv = scl[ti + w - 1], val = (r + 1) * fv; + for (int j = 0; j < r; j++) val += scl[ti + j]; + for (int j = 0; j <= r; j++) { + val += scl[ri++] - fv; + tcl[ti++] = round(val * iarr); + } + for (int j = r + 1; j < w - r; j++) { + val += scl[ri++] - scl[li++]; + tcl[ti++] = round(val * iarr); + } + for (int j = w - r; j < w; j++) { + val += lv - scl[li++]; + tcl[ti++] = round(val * iarr); + } + } +} + +void SuperBlur::boxBlurT(unsigned char *scl, unsigned char *tcl, int w, int h, int r) { + float iarr = 1.0 / (r + r + 1); + for (int i = 0; i < w; i++) { + int ti = i, li = ti, ri = ti + r * w; + int fv = scl[ti], lv = scl[ti + w * (h - 1)], val = (r + 1) * fv; + for (int j = 0; j < r; j++) val += scl[ti + j * w]; + for (int j = 0; j <= r; j++) { + val += scl[ri] - fv; + tcl[ti] = round(val * iarr); + ri += w; + ti += w; + } + for (int j = r + 1; j < h - r; j++) { + val += scl[ri] - scl[li]; + tcl[ti] = round(val * iarr); + li += w; + ri += w; + ti += w; + } + for (int j = h - r; j < h; j++) { + val += lv - scl[li]; + tcl[ti] = round(val * iarr); + li += w; + ti += w; + } + } +} + +// Generate JSON string of this object +std::string SuperBlur::Json() { + + // Return formatted string + return JsonValue().toStyledString(); +} + +// Generate Json::JsonValue for this object +Json::Value SuperBlur::JsonValue() { + + // Create root json object + Json::Value root = EffectBase::JsonValue(); // get parent properties + root["type"] = info.class_name; + root["horizontal_radius"] = horizontal_radius.JsonValue(); + root["vertical_radius"] = vertical_radius.JsonValue(); + root["sigma"] = sigma.JsonValue(); + root["iterations"] = iterations.JsonValue(); + + // return JsonValue + return root; +} + +// Load JSON string into this object +void SuperBlur::SetJson(std::string value) { + + // Parse JSON string into JSON objects + Json::Value root; + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + std::string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); + delete reader; + + if (!success) + // Raise exception + throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); + + try + { + // 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::JsonValue into this object +void SuperBlur::SetJsonValue(Json::Value root) { + + // Set parent data + EffectBase::SetJsonValue(root); + + // Set data from Json (if key is found) + if (!root["horizontal_radius"].isNull()) + horizontal_radius.SetJsonValue(root["horizontal_radius"]); + if (!root["vertical_radius"].isNull()) + vertical_radius.SetJsonValue(root["vertical_radius"]); + if (!root["sigma"].isNull()) + sigma.SetJsonValue(root["sigma"]); + if (!root["iterations"].isNull()) + iterations.SetJsonValue(root["iterations"]); +} + +// Get all properties for a specific frame +std::string SuperBlur::PropertiesJSON(int64_t requested_frame) { + + // Generate JSON properties list + Json::Value root; + root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame); + root["position"] = add_property_json("Position", Position(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); + root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame); + root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); + root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); + root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 1000 * 60 * 30, true, requested_frame); + + // Keyframes + root["horizontal_radius"] = add_property_json("Horizontal Radius", horizontal_radius.GetValue(requested_frame), "float", "", &horizontal_radius, 0, 100, false, requested_frame); + root["vertical_radius"] = add_property_json("Vertical Radius", vertical_radius.GetValue(requested_frame), "float", "", &vertical_radius, 0, 100, false, requested_frame); + root["sigma"] = add_property_json("Sigma", sigma.GetValue(requested_frame), "float", "", &sigma, 0, 100, false, requested_frame); + root["iterations"] = add_property_json("Iterations", iterations.GetValue(requested_frame), "float", "", &iterations, 0, 100, false, requested_frame); + + // Return formatted string + return root.toStyledString(); +} + + diff --git a/external-effects/super-blur/SuperBlur.h b/external-effects/super-blur/SuperBlur.h new file mode 100644 index 000000000..1c618cd83 --- /dev/null +++ b/external-effects/super-blur/SuperBlur.h @@ -0,0 +1,129 @@ +/** + * @file + * @brief Header file for SuperBlur effect class + * @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 . + */ + +#ifndef OPENSHOT_EXTERNAL_BLUR_EFFECT_H +#define OPENSHOT_EXTERNAL_BLUR_EFFECT_H + +#include "../../include/EffectBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include "../../include/Color.h" +#include "../../include/Exceptions.h" +#include "../../include/Json.h" +#include "../../include/KeyFrame.h" +#include "../../include/ReaderBase.h" +#include "../../include/FFmpegReader.h" +#include "../../include/QtImageReader.h" +#include "../../include/ChunkReader.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +void * factory(uint16_t); + +#ifdef __cplusplus +} +#endif + + +namespace openshot +{ + + /** + * @brief This class adjusts the blur of an image, and can be animated + * with openshot::Keyframe curves over time. + * + * Adjusting the blur of an image over time can create many different powerful effects. To achieve a + * box blur effect, use identical horizontal and vertical blur values. To achieve a Gaussian blur, + * use 3 iterations, a sigma of 3.0, and a radius between 3 and X (depending on how much blur you want). + */ + class SuperBlur : public EffectBase + { + private: + /// Init effect settings + void init_effect_details(); + + /// Internal blur methods (inspired and credited to http://blog.ivank.net/fastest-gaussian-blur.html) + int* initBoxes(float sigma, int n); + void boxBlurH(unsigned char *scl, unsigned char *tcl, int w, int h, int r); + void boxBlurT(unsigned char *scl, unsigned char *tcl, int w, int h, int r); + + + public: + Keyframe horizontal_radius; ///< Horizontal blur radius keyframe. The size of the horizontal blur operation in pixels. + Keyframe vertical_radius; ///< Vertical blur radius keyframe. The size of the vertical blur operation in pixels. + Keyframe sigma; ///< Sigma keyframe. The amount of spread in the blur operation. Should be larger than radius. + Keyframe iterations; ///< Iterations keyframe. The # of blur iterations per pixel. 3 iterations = Gaussian. + + /// Blank constructor, useful when using Json to load the effect properties + SuperBlur(); + + /// Default constructor, which takes 1 curve. The curve adjusts the blur radius + /// of a frame's image. + /// + /// @param new_horizontal_radius The curve to adjust the horizontal blur radius (between 0 and 100, rounded to int) + /// @param new_vertical_radius The curve to adjust the vertical blur radius (between 0 and 100, rounded to int) + /// @param new_sigma The curve to adjust the sigma amount (the size of the blur brush (between 0 and 100), float values accepted) + /// @param new_iterations The curve to adjust the # of iterations (between 1 and 100) + SuperBlur(Keyframe new_horizontal_radius, Keyframe new_vertical_radius, Keyframe new_sigma, Keyframe new_iterations); + + /// @brief This method is required for all derived classes of EffectBase, and returns a + /// modified openshot::Frame object + /// + /// The frame object is passed into this method, and a frame_number is passed in which + /// tells the effect which settings to use from its keyframes (starting at 1). + /// + /// @returns The modified openshot::Frame object + /// @param frame The frame object that needs the effect applied to it + /// @param frame_number The frame number (starting at 1) of the effect on the timeline. + std::shared_ptr GetFrame(std::shared_ptr frame, int64_t frame_number); + + /// Get and Set JSON methods + std::string Json(); ///< Generate JSON string of this object + void SetJson(std::string value); ///< Load JSON string into this object + Json::Value JsonValue(); ///< Generate Json::JsonValue for this object + void SetJsonValue(Json::Value root); ///< Load Json::JsonValue into this object + + /// Get all properties for a specific frame (perfect for a UI to display the current state + /// of all properties at any time) + std::string PropertiesJSON(int64_t requested_frame); + }; + +} + +#endif diff --git a/src/EffectBase.h b/src/EffectBase.h index 1f967a021..72d48117b 100644 --- a/src/EffectBase.h +++ b/src/EffectBase.h @@ -31,6 +31,8 @@ #ifndef OPENSHOT_EFFECT_BASE_H #define OPENSHOT_EFFECT_BASE_H +#define OPENSHOT_PLUGIN_API_VERSION 0x0001 + #include #include #include diff --git a/src/EffectInfo.cpp b/src/EffectInfo.cpp index 1359238d5..14a1903e2 100644 --- a/src/EffectInfo.cpp +++ b/src/EffectInfo.cpp @@ -32,6 +32,8 @@ using namespace openshot; +static std::map m_loadedDynamicEffects; +static std::vector m_loadedDynamicHandles; // Generate JSON string of this object std::string EffectInfo::Json() { @@ -42,6 +44,11 @@ std::string EffectInfo::Json() { // Create a new effect instance EffectBase* EffectInfo::CreateEffect(std::string effect_type) { + // Try to find dynamically loaded effect + auto effect_factory = m_loadedDynamicEffects.find(effect_type); + if (effect_factory != m_loadedDynamicEffects.end()) + return effect_factory->second(OPENSHOT_PLUGIN_API_VERSION); + // Init the matching effect object if (effect_type == "Bars") return new Bars(); @@ -87,6 +94,62 @@ EffectBase* EffectInfo::CreateEffect(std::string effect_type) { return NULL; } +#if defined(__linux__) +EffectBase* EffectInfo::LoadEffect(std::string location){ + pthread_mutex_lock(&m_mutex); + + void * file = dlopen(location.c_str(), RTLD_NOW); + if (file == nullptr){ + pthread_mutex_unlock(&m_mutex); + throw InvalidFile("Can not open file", location.c_str()); + } + void * factory_ptr = dlsym(file, "factory"); + + if (factory_ptr == nullptr){ + pthread_mutex_unlock(&m_mutex); + dlclose(file); + throw InvalidFile("Can not find requested plugin API in file", location.c_str()); + } + + EffectBase * (*factory)(uint16_t); + factory = (EffectBase* (*)(uint16_t)) factory_ptr; + + EffectBase* instance = factory(OPENSHOT_PLUGIN_API_VERSION); + + if (instance == nullptr){ + pthread_mutex_unlock(&m_mutex); + dlclose(file); + throw InvalidFile("Plugin does not support current version of openshot", location.c_str()); + } + + m_loadedDynamicEffects.insert(make_pair(std::string(instance->info.name), factory)); + m_loadedDynamicHandles.insert(m_loadedDynamicHandles.end(), file); + + pthread_mutex_unlock(&m_mutex); + return instance; +} + +void EffectInfo::UnloadDynamicEffects(){ + pthread_mutex_lock(&m_mutex); + + for (auto & handle : m_loadedDynamicHandles){ + dlclose(handle); + } + + m_loadedDynamicEffects.clear(); + m_loadedDynamicHandles.clear(); + pthread_mutex_unlock(&m_mutex); +} +#else +EffectBase* EffectInfo::LoadEffect(std::string location){ + return NULL; +} + +void EffectInfo::UnloadDynamicEffects(){ + +} +#endif + // Generate Json::Value for this object Json::Value EffectInfo::JsonValue() { @@ -109,6 +172,12 @@ Json::Value EffectInfo::JsonValue() { root.append(Shift().JsonInfo()); root.append(Wave().JsonInfo()); + for (auto & effect : m_loadedDynamicEffects){ + auto instance = effect.second(OPENSHOT_PLUGIN_API_VERSION); + root.append(instance->JsonInfo()); + delete instance; + } + // return JsonValue return root; diff --git a/src/EffectInfo.h b/src/EffectInfo.h index 0e64327bb..de83bb11c 100644 --- a/src/EffectInfo.h +++ b/src/EffectInfo.h @@ -32,7 +32,11 @@ #define OPENSHOT_EFFECT_INFO_H #include "Effects.h" +#include +#if defined(__linux__) +#include +#endif namespace openshot { @@ -49,10 +53,19 @@ namespace openshot // Create an instance of an effect (factory style) EffectBase* CreateEffect(std::string effect_type); + // Load effect built in shared library (factory style) + EffectBase* LoadEffect(std::string location); + + // Unload all effect loaded dynamically + void UnloadDynamicEffects(); + /// JSON methods static std::string Json(); ///< Generate JSON string of this object static Json::Value JsonValue(); ///< Generate Json::Value for this object + private: + pthread_mutex_t m_mutex; + }; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 73a812b5c..a458e67f1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,7 @@ if(ENABLE_BLACKMAGIC) endif() + ############### SET TEST SOURCE FILES ################# set(OPENSHOT_TEST_FILES Cache_Tests.cpp @@ -76,6 +77,13 @@ set(OPENSHOT_TEST_FILES Settings_Tests.cpp Timeline_Tests.cpp ) +if(NOT DISABLE_EXTERNAL_EFFECTS) + list(APPEND OPENSHOT_TEST_FILES DynamicEffects_Tests.cpp) + add_definitions(-DTEST_PLUGIN_NAME="SuperBlur" + -DTEST_PLUGIN="${PROJECT_BINARY_DIR}/external-effects/super-blur/libeffect-superblur.so") +endif() + + ################ TESTER EXECUTABLE ################# # Create unit test executable (openshot-test) message (STATUS "Tests enabled, test executable will be built as tests/openshot-test") @@ -86,6 +94,11 @@ add_executable(openshot-test # Link libraries to the new executable target_link_libraries(openshot-test openshot ${UnitTest++_LIBRARIES}) +if(NOT DISABLE_EXTERNAL_EFFECTS) + add_dependencies(openshot-test effect-superblur) +endif() + + ##### RUNNING TESTS (make os_test / make test) ##### # Hook up the 'make os_test' target to the 'openshot-test' executable add_custom_target(os_test COMMAND openshot-test) diff --git a/tests/DynamicEffects_Tests.cpp b/tests/DynamicEffects_Tests.cpp new file mode 100644 index 000000000..79895c343 --- /dev/null +++ b/tests/DynamicEffects_Tests.cpp @@ -0,0 +1,91 @@ +/** + * @file + * @brief Unit tests for openshot::Timeline + * @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" +#include "../include/OpenShot.h" + +#ifdef TEST_PLUGIN +#if defined(__linux__) + +using namespace openshot; + + +TEST(DynamicEffect_Loader) +{ + auto effect_info = EffectInfo(); + effect_info.UnloadDynamicEffects(); + + auto effect = effect_info.LoadEffect(TEST_PLUGIN); + + CHECK(effect != nullptr); + + delete effect; +} + + +TEST(DynamicEffect_DoubleLoader) +{ + auto effect_info = EffectInfo(); + effect_info.UnloadDynamicEffects(); + + auto effect = effect_info.LoadEffect(TEST_PLUGIN); + + CHECK(effect != nullptr); + + delete effect; + + effect = effect_info.LoadEffect(TEST_PLUGIN); + + CHECK(effect != nullptr); + + delete effect; +} + + +TEST(DynamicEffect_ReachByName) +{ + auto effect_info = EffectInfo(); + effect_info.UnloadDynamicEffects(); + + auto effect = effect_info.LoadEffect(TEST_PLUGIN); + + CHECK(effect != nullptr); + + delete effect; + + effect = effect_info.CreateEffect(TEST_PLUGIN_NAME); + + CHECK(effect != nullptr); + + delete effect; +} + +#endif +#endif \ No newline at end of file