diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a8aa48978..f8d7bf2c99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,934 +1,942 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.12 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_compile_options($<$:-Wno-suggest-override> -Wextra -Wno-class-memaccess) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "5.0.0-prealpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 5) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 0) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 5) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 17") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") option(ENABLE_UPDATERS "Enable updaters/update notifications" ON) configure_file(config-updaters.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-updaters.h) add_feature_info("Enable updaters" ENABLE_UPDATERS "Enable updaters/update notifications.") # Branding. Available options: default, Beta, Plus, Next. Can be set from command line if (NOT DEFINED BRANDING) set(BRANDING "default") endif() message(STATUS "Branding selected: ${BRANDING}") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.8 EXACT) find_package(PythonLibs 3.8 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.8) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent Sql ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "https://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) #if (${Qt5_VERSION} VERSION_GREATER "5.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) if (APPLE) # this is not added correctly on OSX -- see https://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "https://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "https://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "https://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIRS}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.libtiff.org" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "https://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) + macro_bool_to_01(JPEG_FOUND HAVE_JPEG) endif() find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") find_package(OpenJPEG "2.3.0") set_package_properties(OpenJPEG PROPERTIES DESCRIPTION "Library for loading and saving jp2000 files." URL "https://www.openjpeg.org/" TYPE OPTIONAL PURPOSE "Required by the Krita JP2000 filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "https://www.libraw.org/" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "https://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "https://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) +find_package(FFmpeg) +set_package_properties(FFmpeg PROPERTIES + DESCRIPTION "FFmpeg video encoder." + URL "http://ffmpeg.org/" + TYPE OPTIONAL + PURPOSE "Required by the Krita recorder plugin") +macro_bool_to_01(FFMPEG_FOUND HAVE_FFMPEG) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "https://poppler.freedesktop.org/" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/cmake/modules/FindFFmpeg.cmake b/cmake/modules/FindFFmpeg.cmake new file mode 100644 index 0000000000..f97cfbd58f --- /dev/null +++ b/cmake/modules/FindFFmpeg.cmake @@ -0,0 +1,151 @@ +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVFILTER +# - AVUTIL +# - POSTPROC +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindPackageHandleStandardArgs) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(AVFILTER libavfilter avfilter libavfilter/avfilter.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) \ No newline at end of file diff --git a/plugins/dockers/CMakeLists.txt b/plugins/dockers/CMakeLists.txt index db5e216577..846b993cfd 100644 --- a/plugins/dockers/CMakeLists.txt +++ b/plugins/dockers/CMakeLists.txt @@ -1,36 +1,39 @@ add_subdirectory(layerdocker) if(HAVE_OPENEXR) add_subdirectory(smallcolorselector) endif() add_subdirectory(specificcolorselector) add_subdirectory(digitalmixer) add_subdirectory(advancedcolorselector) add_subdirectory(presetdocker) add_subdirectory(historydocker) add_subdirectory(channeldocker) add_subdirectory(artisticcolorselector) add_subdirectory(tasksetdocker) add_subdirectory(compositiondocker) add_subdirectory(patterndocker) add_subdirectory(griddocker) add_subdirectory(arrangedocker) if(HAVE_OCIO) add_subdirectory(lut) endif() add_subdirectory(overview) add_subdirectory(palettedocker) add_subdirectory(animation) add_subdirectory(presethistory) add_subdirectory(svgcollectiondocker) add_subdirectory(histogram) add_subdirectory(gamutmask) +if(HAVE_FFMPEG) + add_subdirectory(recorder) +endif() if(NOT APPLE AND HAVE_QT_QUICK) add_subdirectory(touchdocker) option(ENABLE_CPU_THROTTLE "Build the CPU Throttle Docker" OFF) if (ENABLE_CPU_THROTTLE) add_subdirectory(throttle) endif() endif() add_subdirectory(logdocker) add_subdirectory(snapshotdocker) diff --git a/plugins/dockers/recorder/CMakeLists.txt b/plugins/dockers/recorder/CMakeLists.txt new file mode 100644 index 0000000000..e37883d686 --- /dev/null +++ b/plugins/dockers/recorder/CMakeLists.txt @@ -0,0 +1,12 @@ +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) + +include_directories(SYSTEM + ${AVCODEC_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} ${SWSCALE_INCLUDE_DIRS}) + +set(KRITA_RECORDERDOCKER_SOURCES recorderdocker.cpp recorderdocker_dock.cpp encoder.cpp encoder_queue.cpp) +add_library(kritarecorderdocker MODULE ${KRITA_RECORDERDOCKER_SOURCES}) +target_link_libraries(kritarecorderdocker kritaui ${AVCODEC_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVUTIL_LIBRARIES} ${SWSCALE_LIBRARIES} Threads::Threads) + +install(TARGETS kritarecorderdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/recorder/encoder.cpp b/plugins/dockers/recorder/encoder.cpp new file mode 100644 index 0000000000..a3cda2a8e9 --- /dev/null +++ b/plugins/dockers/recorder/encoder.cpp @@ -0,0 +1,186 @@ +#include "encoder.h" +#include + +void Encoder::init(const std::string& filename, int width, int height) +{ + av_register_all(); + avcodec_register_all(); + + m_filename = filename; + + m_outputFormat = av_guess_format(nullptr, m_filename.c_str(), nullptr); + if (!m_outputFormat) { + errPlugins << "can't create output format"; + return; + } + + int err = avformat_alloc_output_context2(&m_formatContext, m_outputFormat, nullptr, filename.c_str()); + + if (err) { + errPlugins << "can't create output context"; + return; + } + + AVCodec* codec = nullptr; + + codec = avcodec_find_encoder(m_outputFormat->video_codec); + if (!codec) { + errPlugins << "can't create codec"; + return; + } + + m_stream = avformat_new_stream(m_formatContext, codec); + + if (!m_stream) { + errPlugins << "can't find format"; + return; + } + + m_codecContext = avcodec_alloc_context3(codec); + + if (!m_codecContext) { + errPlugins << "can't create codec context"; + return; + } + + m_stream->codecpar->codec_id = m_outputFormat->video_codec; + m_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + m_stream->codecpar->width = width; + m_stream->codecpar->height = height; + m_stream->codecpar->format = AV_PIX_FMT_YUV420P; + m_stream->codecpar->bit_rate = m_bitrate * 1000; + m_stream->avg_frame_rate = (AVRational){m_fps, 1}; + avcodec_parameters_to_context(m_codecContext, m_stream->codecpar); + m_codecContext->time_base = (AVRational){1, 1}; + m_codecContext->max_b_frames = 2; + m_codecContext->gop_size = 12; + m_codecContext->framerate = (AVRational){m_fps, 1}; + + av_opt_set_int(m_codecContext, "lossless", 1, 0); + + avcodec_parameters_from_context(m_stream->codecpar, m_codecContext); + + if ((err = avcodec_open2(m_codecContext, codec, NULL)) < 0) { + errPlugins << "Failed to open codec: " << err; + return; + } + + if (!(m_outputFormat->flags & AVFMT_NOFILE)) { + if ((err = avio_open(&m_formatContext->pb, m_filename.c_str(), AVIO_FLAG_WRITE)) < 0) { + errPlugins << "Failed to open file: " << err; + return; + } + } + + if ((err = avformat_write_header(m_formatContext, NULL)) < 0) { + errPlugins << "Failed to write header" << err; + return; + } + + av_dump_format(m_formatContext, 0, m_filename.c_str(), 1); + m_frameCount = 0; +} + +void Encoder::pushFrame(uint8_t* data) +{ + int err; + if (!m_frame) { + m_frame = av_frame_alloc(); + m_frame->format = AV_PIX_FMT_YUV420P; + m_frame->width = m_codecContext->width; + m_frame->height = m_codecContext->height; + + if ((err = av_frame_get_buffer(m_frame, 32)) < 0) { + errPlugins << "Failed to allocate picture" << err; + return; + } + } + + if (!m_swsContext) { + m_swsContext = + sws_getContext(m_codecContext->width, m_codecContext->height, AV_PIX_FMT_BGRA, m_codecContext->width, + m_codecContext->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, 0, 0, 0); + } + + int inLinesize[1] = {4 * m_codecContext->width}; + + // From RGB to YUV + sws_scale(m_swsContext, (const uint8_t* const*)&data, inLinesize, 0, m_codecContext->height, m_frame->data, + m_frame->linesize); + + m_frame->pts = (m_frameCount++) * m_stream->time_base.den / (m_stream->time_base.num * m_fps); + + if ((err = avcodec_send_frame(m_codecContext, m_frame)) < 0) { + errPlugins << "Failed to send frame" << err; + return; + } + + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + pkt.flags |= AV_PKT_FLAG_KEY; + int ret = 0; + if ((ret = avcodec_receive_packet(m_codecContext, &pkt)) == 0) { + av_interleaved_write_frame(m_formatContext, &pkt); + // av_packet_unref(&pkt); + infoPlugins << "Write frame: " << m_frameCount; + qDebug() << "Write frame: " << m_frameCount; + } + qDebug() << "Write push: " << m_frameCount << ret; + av_packet_unref(&pkt); +} + +void Encoder::finish() +{ + qDebug() << "Encoder finish"; + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + for (;;) { + avcodec_send_frame(m_codecContext, NULL); + if (avcodec_receive_packet(m_codecContext, &pkt) == 0) { + av_interleaved_write_frame(m_formatContext, &pkt); + qDebug() << "final push" ; + } else { + break; + } + } + + av_packet_unref(&pkt); + + av_write_trailer(m_formatContext); + if (!(m_outputFormat->flags & AVFMT_NOFILE)) { + int err = avio_close(m_formatContext->pb); + if (err < 0) { + errPlugins << "Failed to close file" << err; + } + } + infoPlugins << "finished: " << QString::fromStdString(m_filename); + m_frameCount = 0; + + if (m_frame) { + av_frame_free(&m_frame); + m_frame = nullptr; + } + if (m_codecContext) { + avcodec_close(m_codecContext); + avcodec_free_context(&m_codecContext); + m_codecContext = nullptr; + m_stream = nullptr; + } + if (m_formatContext) { + avformat_free_context(m_formatContext); + m_formatContext = nullptr; + } + if (m_swsContext) { + sws_freeContext(m_swsContext); + m_swsContext = nullptr; + } + if (m_outputFormat) { + // no need to free this. + m_outputFormat = nullptr; + } +} diff --git a/plugins/dockers/recorder/encoder.h b/plugins/dockers/recorder/encoder.h new file mode 100644 index 0000000000..57c9867e8e --- /dev/null +++ b/plugins/dockers/recorder/encoder.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Shi Yan + * + * This library 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; version 2 of the License, or + * (at your option) any later version. + * + * This library 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ENCODER_H +#define ENCODER_H + +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +class Encoder +{ + int m_width; + int m_height; + std::string m_filename; + int m_frameCount; + AVFrame* m_frame = nullptr; + AVCodecContext* m_codecContext = nullptr; + SwsContext* m_swsContext = nullptr; + AVFormatContext* m_formatContext = nullptr; + AVOutputFormat* m_outputFormat = nullptr; + AVStream* m_stream = nullptr; + int m_fps = 4; + int m_bitrate = 2000; + +public: + Encoder() + : m_filename() + { + } + + void init(const std::string &filename, int width, int height); + void pushFrame(uint8_t *frame); + void finish(); +}; + +#endif diff --git a/plugins/dockers/recorder/encoder_queue.cpp b/plugins/dockers/recorder/encoder_queue.cpp new file mode 100644 index 0000000000..4c24b279c0 --- /dev/null +++ b/plugins/dockers/recorder/encoder_queue.cpp @@ -0,0 +1,121 @@ +#include "encoder_queue.h" +#include +#include +#include +#include + +void EncoderQueue::setEnable(bool& enabled, const QString& path, const QPointer& m_canvas) +{ + m_isRecording = enabled; + if (m_isRecording) { + m_recordPath = path; + + QUrl fileUrl(m_recordPath); + + QString filename = fileUrl.fileName(); + QString dirPath = fileUrl.adjusted(QUrl::RemoveFilename).path(); + + QDir dir(dirPath); + + if (!dir.exists()) { + if (!dir.mkpath(dirPath)) { + enabled = m_isRecording = false; + return; + } + } + + QFileInfoList images = dir.entryInfoList({filename % "_*.webm"}); + + QRegularExpression namePattern("^" % filename % "_([0-9]{7}).webm$"); + m_recordCounter = -1; + Q_FOREACH (auto info, images) { + QRegularExpressionMatch match = namePattern.match(info.fileName()); + if (match.hasMatch()) { + QString count = match.captured(1); + int numCount = count.toInt(); + + if (m_recordCounter < numCount) { + m_recordCounter = numCount; + } + } + } + + if (m_canvas) { + m_recordingCanvas = m_canvas; + m_shouldStop = false; + m_emptySemaphore = new Semaphore(5); + m_fullSemaphore = new Semaphore(0); + m_head = m_tail = 0; + + for (Payload& p : m_queue) { + p.m_data = new uint8_t[m_canvas->image()->width() * m_canvas->image()->height() * 4]; + } + + m_thread = new std::thread([&]() { + QString finalFileName = QString(m_recordPath % "_%1.webm").arg(++m_recordCounter, 7, 10, QChar('0')); + Encoder* m_encoder = new Encoder(); + m_encoder->init(finalFileName.toStdString().c_str(), m_canvas->image()->width(), + m_canvas->image()->height()); + + while (!m_shouldStop) { + m_fullSemaphore->wait(); + + Payload &p = m_queue[m_tail]; + + if (p.m_command == Payload::Command::Finish) { + break; + } + + m_encoder->pushFrame(p.m_data); + + m_tail = (m_tail + 1) % 5; + + m_emptySemaphore->notify(); + static int counter = 0; + infoPlugins << "Frame pushed" << counter++; + } + + if (m_encoder) { + m_encoder->finish(); + delete m_encoder; + m_encoder = nullptr; + } + infoPlugins << "Thread closed"; + }); + } else { + enabled = m_isRecording = false; + return; + } + } else { + m_shouldStop = true; + m_emptySemaphore->wait(); + Payload &p = m_queue[m_head]; + p.m_command = Payload::Command::Finish; + m_head = (m_head + 1) % 5; + m_fullSemaphore->notify(); + + m_thread->join(); + delete m_fullSemaphore; + delete m_emptySemaphore; + delete m_thread; + for (Payload& p : m_queue) { + delete[] p.m_data; + } + infoPlugins << "File closed"; + } +} + +void EncoderQueue::pushFrame(KisPaintDeviceSP dev, int width, int height) +{ + if (!m_shouldStop) { + m_emptySemaphore->wait(); + + Payload p = m_queue[m_head]; + p.m_command = Payload::Command::Encode; + dev->readBytes((quint8*)p.m_data, 0, 0, width, height); + + m_head = (m_head + 1) % 5; + + m_fullSemaphore->notify(); + } +} diff --git a/plugins/dockers/recorder/encoder_queue.h b/plugins/dockers/recorder/encoder_queue.h new file mode 100644 index 0000000000..43bb142f6f --- /dev/null +++ b/plugins/dockers/recorder/encoder_queue.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Shi Yan + * + * This library 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; version 2 of the License, or + * (at your option) any later version. + * + * This library 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef ENCODER_QUEUE_H +#define ENCODER_QUEUE_H + +#include "encoder.h" +#include +#include +#include +#include +#include +#include + +class Semaphore +{ +public: + Semaphore(int count_ = 0) + : count(count_) + { + } + + inline void notify() + { + std::unique_lock lock(mtx); + count++; + cv.notify_one(); + } + inline void wait() + { + std::unique_lock lock(mtx); + while (count == 0) { + cv.wait(lock); + } + count--; + } + +private: + std::mutex mtx; + std::condition_variable cv; + int count; +}; + +class Payload +{ +public: + enum class Command + { + Encode, + Finish + }; + uint8_t* m_data = nullptr; + Command m_command = Command::Encode; +}; + +class EncoderQueue +{ +private: + bool m_isRecording = false; + QString m_recordPath; + int m_recordCounter = 0; + QPointer m_recordingCanvas; + Semaphore* m_fullSemaphore = nullptr; + Semaphore* m_emptySemaphore = nullptr; + std::array m_queue; + std::thread* m_thread = nullptr; + bool m_shouldStop = false; + int m_tail = 0; + int m_head = 0; + +public: + EncoderQueue() + : m_isRecording(false) + , m_recordPath() + , m_recordCounter(0) + , m_recordingCanvas() + , m_fullSemaphore(nullptr) + , m_emptySemaphore(nullptr) + , m_queue() + , m_thread(nullptr) + , m_shouldStop(false) + , m_tail(0) + , m_head(0) + { + } + + void setEnable(bool& enabled, const QString& path, const QPointer& m_canvas); + bool isRecording() + { + return m_isRecording; + } + QPointer recordingCanvas() + { + return m_recordingCanvas; + } + void pushFrame(KisPaintDeviceSP dev, int width, int height); +}; + +#endif diff --git a/plugins/dockers/recorder/krita_recorderdocker.json b/plugins/dockers/recorder/krita_recorderdocker.json new file mode 100644 index 0000000000..35c3306732 --- /dev/null +++ b/plugins/dockers/recorder/krita_recorderdocker.json @@ -0,0 +1,9 @@ +{ + "Id": "Recorder Docker", + "Type": "Service", + "X-KDE-Library": "kritarecorderdocker", + "X-KDE-ServiceTypes": [ + "Krita/Dock" + ], + "X-Krita-Version": "28" +} diff --git a/plugins/dockers/recorder/recorderdocker.cpp b/plugins/dockers/recorder/recorderdocker.cpp new file mode 100644 index 0000000000..7ffb55856d --- /dev/null +++ b/plugins/dockers/recorder/recorderdocker.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library 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; version 2.1 of the License. + * + * This library 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "recorderdocker.h" + +#include + +#include + +#include +#include +#include + +#include + +#include "KisViewManager.h" +#include "kis_config.h" +#include "kis_cursor.h" +#include "kis_global.h" +#include "kis_types.h" + +#include "recorderdocker_dock.h" +#include + +K_PLUGIN_FACTORY_WITH_JSON(RecorderDockerPluginFactory, "krita_recorderdocker.json", + registerPlugin();) + +class RecorderDockerDockFactory : public KoDockFactoryBase +{ +public: + RecorderDockerDockFactory() + { + } + + QString id() const override + { + return QString("RecorderDocker"); + } + + virtual Qt::DockWidgetArea defaultDockWidgetArea() const + { + return Qt::RightDockWidgetArea; + } + + QDockWidget* createDockWidget() override + { + RecorderDockerDock* dockWidget = new RecorderDockerDock(); + dockWidget->setObjectName(id()); + + return dockWidget; + } + + DockPosition defaultDockPosition() const override + { + return DockMinimized; + } + +private: +}; + +RecorderDockerPlugin::RecorderDockerPlugin(QObject* parent, const QVariantList&) + : QObject(parent) +{ + KoDockRegistry::instance()->add(new RecorderDockerDockFactory()); +} + +RecorderDockerPlugin::~RecorderDockerPlugin() +{ + m_view = nullptr; +} + +#include "recorderdocker.moc" diff --git a/plugins/dockers/recorder/recorderdocker.h b/plugins/dockers/recorder/recorderdocker.h new file mode 100644 index 0000000000..1fb7ab034e --- /dev/null +++ b/plugins/dockers/recorder/recorderdocker.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Shi Yan + * + * This library 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; version 2 of the License, or + * (at your option) any later version. + * + * This library 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _RECORDER_DOCKER_H_ +#define _RECORDER_DOCKER_H_ + +#include +#include + +class KisViewManager; + +/** + * Template of view plugin + */ +class RecorderDockerPlugin : public QObject +{ + Q_OBJECT +public: + RecorderDockerPlugin(QObject* parent, const QVariantList&); + ~RecorderDockerPlugin() override; + +private: + KisViewManager* m_view; +}; + +#endif diff --git a/plugins/dockers/recorder/recorderdocker_dock.cpp b/plugins/dockers/recorder/recorderdocker_dock.cpp new file mode 100644 index 0000000000..8b75aa247c --- /dev/null +++ b/plugins/dockers/recorder/recorderdocker_dock.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2009 Cyrille Berger + * + * This library 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; version 2.1 of the License. + * + * This library 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "recorderdocker_dock.h" + +#include +#include +#include +#include + +#include "encoder_queue.h" +#include "kis_canvas2.h" +#include "kis_image.h" +#include "kis_paint_device.h" +#include "kis_signal_compressor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RecorderDockerDock::RecorderDockerDock() + : QDockWidget(i18n("Recorder")) + , m_canvas(nullptr) + , m_imageIdleWatcher(1000) + , m_encoderQueue(nullptr) +{ + QWidget* page = new QWidget(this); + m_layout = new QGridLayout(page); + m_recordDirectoryLabel = new QLabel(this); + m_recordDirectoryLabel->setText("Directory:"); + + m_layout->addWidget(m_recordDirectoryLabel, 0, 0, 1, 2); + + m_recordDirectoryLineEdit = new QLineEdit(this); + m_recordDirectoryLineEdit->setText(QDir::homePath()); + m_recordDirectoryLineEdit->setReadOnly(true); + m_layout->addWidget(m_recordDirectoryLineEdit, 1, 0); + + m_recordDirectoryPushButton = new QPushButton(this); + m_recordDirectoryPushButton->setIcon(KisIconUtils::loadIcon("folder")); + m_recordDirectoryPushButton->setToolTip(i18n("Record Video")); + + m_layout->addWidget(m_recordDirectoryPushButton, 1, 1); + + m_imageNameLabel = new QLabel(this); + m_imageNameLabel->setText("Video Name:"); + + m_layout->addWidget(m_imageNameLabel, 2, 0, 1, 2); + + m_imageNameLineEdit = new QLineEdit(this); + m_imageNameLineEdit->setText("image"); + + QRegExp rx("[0-9a-zA-z_]+"); + QValidator* validator = new QRegExpValidator(rx, this); + m_imageNameLineEdit->setValidator(validator); + + m_layout->addWidget(m_imageNameLineEdit, 3, 0); + + m_recordToggleButton = new QPushButton(this); + m_recordToggleButton->setCheckable(true); + m_recordToggleButton->setIcon(KisIconUtils::loadIcon("media-record")); + m_recordToggleButton->setToolTip(i18n("Record Video")); + m_layout->addWidget(m_recordToggleButton, 3, 1); + + m_logLabel = new QLabel(this); + m_logLabel->setText("Recent Save:"); + m_layout->addWidget(m_logLabel, 4, 0, 1, 2); + m_logLineEdit = new QLineEdit(this); + m_logLineEdit->setReadOnly(true); + m_layout->addWidget(m_logLineEdit, 5, 0, 1, 2); + + m_spacer = new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding); + m_layout->addItem(m_spacer, 6, 0, 1, 2); + connect(m_recordDirectoryPushButton, SIGNAL(clicked()), this, SLOT(onSelectRecordFolderButtonClicked())); + connect(m_recordToggleButton, SIGNAL(toggled(bool)), this, SLOT(onRecordButtonToggled(bool))); + setWidget(page); +} + +void RecorderDockerDock::setCanvas(KoCanvasBase* canvas) +{ + if (m_canvas == canvas) + return; + + setEnabled(canvas != nullptr); + + if (m_canvas) { + m_canvas->disconnectCanvasObserver(this); + m_canvas->image()->disconnect(this); + } + + m_canvas = dynamic_cast(canvas); + + if (m_canvas) { + m_imageIdleWatcher.setTrackedImage(m_canvas->image()); + + connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, &RecorderDockerDock::generateThumbnail); + connect(m_canvas->image(), SIGNAL(sigSizeChanged(QPointF, QPointF)), SLOT(startUpdateCanvasProjection())); + } +} + +void RecorderDockerDock::startUpdateCanvasProjection() +{ + m_imageIdleWatcher.startCountdown(); +} + +void RecorderDockerDock::unsetCanvas() +{ + setEnabled(false); + m_canvas = nullptr; +} + +void RecorderDockerDock::onRecordButtonToggled(bool enabled) +{ + bool enabled2 = enabled; + enableRecord(enabled2, m_recordDirectoryLineEdit->text() % "/" % m_imageNameLineEdit->text()); + + if (enabled && !enabled2) { + disconnect(m_recordToggleButton, SIGNAL(toggle(bool)), this, SLOT(onRecordButtonToggled(bool))); + m_recordToggleButton->setChecked(false); + + connect(m_recordToggleButton, SIGNAL(toggle(bool)), this, SLOT(onRecordButtonToggled(bool))); + } +} + +void RecorderDockerDock::onSelectRecordFolderButtonClicked() +{ + QFileDialog dialog(this); + dialog.setFileMode(QFileDialog::DirectoryOnly); + QString folder = dialog.getExistingDirectory(this, tr("Select Output Folder"), m_recordDirectoryLineEdit->text(), + QFileDialog::ShowDirsOnly); + m_recordDirectoryLineEdit->setText(folder); +} + +void RecorderDockerDock::enableRecord(bool& enabled, const QString& path) +{ + if (!m_encoderQueue) + { + m_encoderQueue = new EncoderQueue(); + } + + m_encoderQueue->setEnable(enabled,path,m_canvas); + + if (enabled) + { + startUpdateCanvasProjection(); + } + else + { + delete m_encoderQueue; + m_encoderQueue = nullptr; + } +} + +void RecorderDockerDock::generateThumbnail() +{ + if (m_encoderQueue && m_encoderQueue->isRecording()) { + if (m_canvas && (m_encoderQueue->recordingCanvas() == m_canvas)) { + disconnect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, + &RecorderDockerDock::generateThumbnail); + if (m_encoderQueue) { + KisImageSP image = m_canvas->image(); + image->barrierLock(); + KisPaintDeviceSP dev = image->projection(); + m_encoderQueue->pushFrame(dev, image->width(), image->height()); + image->unlock(); + } + + connect(&m_imageIdleWatcher, &KisIdleWatcher::startedIdleMode, this, + &RecorderDockerDock::generateThumbnail); + } + } +} diff --git a/plugins/dockers/recorder/recorderdocker_dock.h b/plugins/dockers/recorder/recorderdocker_dock.h new file mode 100644 index 0000000000..a845e0992b --- /dev/null +++ b/plugins/dockers/recorder/recorderdocker_dock.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Shi Yan + * + * This library 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; version 2 of the License, or + * (at your option) any later version. + * + * This library 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _RECORDER_DOCK_H_ +#define _RECORDER_DOCK_H_ + +#include "kis_idle_watcher.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QVBoxLayout; +class RecorderWidget; +class EncoderQueue; + +class RecorderDockerDock : public QDockWidget, public KoCanvasObserverBase +{ + Q_OBJECT +public: + RecorderDockerDock(); + QString observerName() override + { + return "RecorderDockerDock"; + } + void setCanvas(KoCanvasBase* canvas) override; + void unsetCanvas() override; + +private: + QGridLayout* m_layout; + + QPointer m_canvas; + QLabel* m_recordDirectoryLabel; + QLineEdit* m_recordDirectoryLineEdit; + QPushButton* m_recordDirectoryPushButton; + QLabel* m_imageNameLabel; + QLineEdit* m_imageNameLineEdit; + QPushButton* m_recordToggleButton; + QSpacerItem* m_spacer; + QLabel* m_logLabel; + QLineEdit* m_logLineEdit; + KisIdleWatcher m_imageIdleWatcher; + QMutex m_saveMutex; + QMutex m_eventMutex; + EncoderQueue* m_encoderQueue; + + void enableRecord(bool& enabled, const QString& path); + +private Q_SLOTS: + void onRecordButtonToggled(bool enabled); + void onSelectRecordFolderButtonClicked(); + void startUpdateCanvasProjection(); + void generateThumbnail(); +}; + +#endif