diff --git a/CMakeLists.txt b/CMakeLists.txt index 999ec152ca..ee32fa41af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,753 +1,740 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.0) set(MIN_FRAMEWORKS_VERSION 5.18.0) 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 (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.11 -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_definitions(-Werror=delete-incomplete) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.1.0-pre-alpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 1) # 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 set(KRITA_YEAR 2018) # update every year 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 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") 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() -if(NOT DEFINED RELEASE_BUILD) - # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline - string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) - set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") - list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) - if (INDEX EQUAL -1) - set(RELEASE_BUILD FALSE) - else() - set(RELEASE_BUILD TRUE) - endif() -endif() -message(STATUS "Release build: ${RELEASE_BUILD}") - # 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("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(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(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) 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.6 EXACT) find_package(PythonLibs 3.6 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.6) 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 Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) 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 "http://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 "http://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 "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE) 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 "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5KIO ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5KIO_FOUND HAVE_KIO) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE's KIO Framework" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used for recent document handling") 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 "http://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) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_NO_URL_CAST_FROM_STRING ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() 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_KRITADEVS "-O3 -g" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") 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) # 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 set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) ########################### ############################ ## Required dependencies ## ############################ ########################### find_package(PNG REQUIRED) if (APPLE) # this is not added correctly on OSX -- see http://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 "http://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 ) ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://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 "http://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_DIR}) 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.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") 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") find_package(HEIF "1.2.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") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://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) find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://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 "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.18.0") 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) ## ## 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(Exiv2 0.16 REQUIRED) set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image metadata library and tools" URL "http://www.exiv2.org" PURPOSE "Required by Krita") ## ## 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() ## ## 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 "-Wabi -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) 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 "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ############################ ############################# ## 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) add_subdirectory(benchmarks) 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() diff --git a/krita/data/actions/ArtisticTextTool.action b/krita/data/actions/ArtisticTextTool.action deleted file mode 100644 index f372072860..0000000000 --- a/krita/data/actions/ArtisticTextTool.action +++ /dev/null @@ -1,106 +0,0 @@ - - - - Artistic Text Tool - - Subscript - - Subscript - format-text-subscript - - - true - Subscript - - - Detach Path - - Detach Path - artistictext-detach-path - - - false - Detach Path - - - Italic text - - Italic text - format-text-italic - - - true - Italic text - - - Anchor at Middle - - Anchor at Middle - format-justify-center - - - true - Anchor at Middle - - - Anchor at End - - Anchor at End - format-justify-right - - - true - Anchor at End - - - Convert to Path - - Convert to Path - pathshape - - - false - Convert to Path - - - Anchor at Start - - Anchor at Start - format-justify-left - - - true - Anchor at Start - - - Bold text - - Bold text - format-text-bold - - - true - Bold text - - - Deselect - Ctrl+Shift+A - Deselect - - - - false - Dese&lect - - - Superscript - - Superscript - format-text-superscript - - - true - Superscript - - - diff --git a/krita/data/actions/CMakeLists.txt b/krita/data/actions/CMakeLists.txt index 73f290a9cd..18914973b4 100644 --- a/krita/data/actions/CMakeLists.txt +++ b/krita/data/actions/CMakeLists.txt @@ -1,8 +1,6 @@ install( FILES - ArtisticTextTool.action InteractionTool.action PathTool.action - TextTool.action ConnectionTool.action MoveTool.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/krita/data/actions/TextTool.action b/krita/data/actions/TextTool.action deleted file mode 100644 index 16f721677a..0000000000 --- a/krita/data/actions/TextTool.action +++ /dev/null @@ -1,416 +0,0 @@ - - - - Text Tool - - Align Right - Ctrl+Alt+R - Align Right - format-justify-right - - - false - Align Right - - - Decrease Indent - - Decrease Indent - - - - false - Decrease Indent - - - Paragraph - Ctrl+Alt+P - Change paragraph margins, text flow, borders, bullets, numbering etc. - - <p>Change paragraph margins, text flow, borders, bullets, numbering etc.</p><p>Select text in multiple paragraphs to change the formatting of all selected paragraphs.</p><p>If no text is selected, the paragraph where the cursor is located will be changed.</p> - - false - Paragraph... - - - Text Color - - Text Color... - format-text-color - - - false - Text Color - - - Special Character - Alt+Shift+C - Insert one or more symbols or characters not found on the keyboard - character-set - Insert one or more symbols or characters not found on the keyboard. - - false - Special Character... - - - Shrink To Fit - - Shrink To Fit - zoom-fit-best - - - false - Shrink To Fit - - - Styles Debug - Ctrl+Alt+Shift+S - Styles Debug - - - - false - Styles Debug - - - Increase Indent - - Increase Indent - - - - false - Increase Indent - - - Underline - Ctrl+U - Underline - format-text-underline - - - false - Underline - - - Grow To Fit Width - - Grow To Fit Width - zoom-fit-best - - - false - Grow To Fit Width - - - Subscript - Ctrl+Shift+B - Subscript - format-text-subscript - - - false - Subscript - - - Insert Non-Breaking Hyphen - Ctrl+Shift+- - Insert Non-Breaking Hyphen - - - - false - Insert Non-Breaking Hyphen - - - Align Left - - Align Left - format-justify-left - - - false - Align Left - - - Insert Comment - Ctrl+Shift+C - Insert Comment - - - - false - Insert Comment - - - Strikethrough - - Strikethrough - format-text-strikethrough - - - false - Strikethrough - - - Configure current section - - Configure current section - configure - - - false - Configure current section - - - Change text direction - Ctrl+Shift+D - Change writing direction - format-text-direction-rtl - - - false - Change text direction - - - Insert Non-Breaking Space - Ctrl+Space - Insert Non-Breaking Space - - - - false - Insert Non-Breaking Space - - - Insert Soft Hyphen - - Insert Soft Hyphen - - - - false - Insert Soft Hyphen - - - Variable - - Variable - - - - false - Variable - - - Bullet list - - Bullet list - format-list-unordered - - - false - Bullet list - - - Insert Index - Ctrl+T - Insert Index - - - - false - Insert Index - - - Align Center - Ctrl+Alt+C - Align Center - format-justify-center - - - false - Align Center - - - Background - - Background Color... - format-fill-color - - - false - Background - - - Decrease Font Size - Ctrl+< - Decrease Font Size - - - - false - Decrease Font Size - - - Insert new section - - Insert new section - insert-text - - - false - Insert new section - - - Insert Paragraph between sections - - Insert Paragraph between sections - split - - - - - - - Font Size - - Font Size - - - - false - Font Size - - - Italic - Ctrl+I - Italic - format-text-italic - - - false - Italic - - - Bold - Ctrl+B - Bold - format-text-bold - - - false - Bold - - - Increase Font Size - Ctrl+> - Increase Font Size - - - - false - Increase Font Size - - - Superscript - Ctrl+Shift+P - Superscript - format-text-superscript - - - false - Superscript - - - Select All - Ctrl+A - Select All - edit-select-all - - - false - Select &All - - - Numbered list - - Numbered list - format-list-ordered - - - false - Numbered list - - - Style Manager - Ctrl+Alt+S - Change attributes of styles - - <p>Change font and paragraph attributes of styles.</p><p>Multiple styles can be changed using the dialog box.</p> - - false - Style Manager... - - - Align Block - Ctrl+Alt+R - Align Block - format-justify-fill - - - false - Align Block - - - Grow To Fit Height - - Grow To Fit Height - zoom-fit-best - - - false - Grow To Fit Height - - - Paragraph Debug - Ctrl+Alt+Shift+P - Paragraph Debug - - - - false - Paragraph Debug - - - Font Family - - Font Family - - - - false - Font Family - - - Font - Ctrl+Alt+F - Change character size, font, boldface, italics etc. - - Change the attributes of the currently selected characters. - - false - Font... - - - Repaint - - Repaint - view-refresh - - - false - Repaint - - - diff --git a/krita/krita.action b/krita/krita.action index efccab0f4e..288fd81458 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3476 +1,3456 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance... Color Balance Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Lazy Brush Tool Lazy Brush Tool Lazy Brush Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false - - - Text Editing Tool - - Text editing - Text editing - - false - - Outline Selection Tool Outline Selection Tool Outline Selection Tool false - - - Artistic Text Tool - - Artistic text editing - Artistic text editing - - false - - Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Zoom Tool Zoom Tool Zoom Tool false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Assistant Tool Assistant Tool Assistant Tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Show in Timeline true Insert Keyframe Left Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false Insert Keyframe Right Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false Insert Multiple Keyframes Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false Insert Column Left Insert column to the left of selection, moving the tail of animation to the right 100000 0 false Insert Column Right Insert column to the right of selection, moving the tail of animation to the right 100000 0 false Insert Multiple Columns Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group - 100000 + 1000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/main.cc b/krita/main.cc index 0ddd93a601..764bdb3af7 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,403 +1,403 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #if defined Q_OS_WIN #include #include #include #include #include #include #include #elif defined HAVE_X11 #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { qDebug() << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); qDebug() << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif KisLoggingManager::initialize(); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita3" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if QT_VERSION >= 0x050900 QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); #if QT_VERSION >= 0x050600 if (kritarc.value("EnableHiDPI", false).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisOpenGL::setDefaultFormat(enableOpenGLDebug, openGLDebugSynchronous); #ifdef Q_OS_WIN QString preferredOpenGLRenderer = kritarc.value("OpenGLRenderer", "auto").toString(); // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", "d3d11"); // Probe QPA auto OpenGL detection char *fakeArgv[2] = { argv[0], nullptr }; // Prevents QCoreApplication from modifying the real argc/argv KisOpenGL::probeWindowsQpaOpenGL(1, fakeArgv, preferredOpenGLRenderer); // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif qDebug() << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. qDebug() << "Override language:" << language; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); } #ifndef Q_OS_LINUX else { // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { uiLanguage.replace(QChar('-'), QChar('_')); } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('_')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.insert(i + 1, uiLanguage); i++; } } qDebug() << "Setting Krita's language to:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); KLocalizedString::setLanguages(uiLanguages); } } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KLocalizedString::setApplicationDomain("krita"); qDebug() << "Available translations" << KLocalizedString::availableApplicationTranslations(); qDebug() << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); qDebug() << "Qt UI languages" << QLocale::system().uiLanguages() << qgetenv("LANG"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); qDebug() << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance - const bool batchRun = (args.print() || args.exportAs() || args.exportAsPdf()); + const bool batchRun = args.exportAs(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if defined HAVE_X11 app.installNativeEventFilter(KisXi2EventFilter::instance()); #endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN { KisConfig cfg; if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); qDebug() << "Using Win8 Pointer Input for tablet support"; } else { qDebug() << "No Win8 Pointer Input available"; delete penFilter; } } } #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index d70183c754..7b31f8b682 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,242 +1,245 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita + Fondazione Krita Krita Foundation + Fundacja Krity Fundação do Krita + Krita Foundation Krita-stiftelsen Фундація Krita xxKrita Foundationxx foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Pintura dixital, llibertá creativa Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita is de digitale kunststudio vol mogelijkheden.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是功能齐全的数字艺术工作室软件。

Krita 是全功能的數位藝術工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。

它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta 16 dan 32 bit channel titik mengambang.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita - отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是绘制概念美术、漫画、渲染纹理和电影布景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。

Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色版中支援 RGB 和 CMYK 等多種色彩空間。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

Krita 具有功能强大的笔刷引擎、特效惊艳的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。

使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png none none none none none none none none none none none none none none none none none none none none Graphics KDE krita
diff --git a/libs/flake/KoCanvasController.cpp b/libs/flake/KoCanvasController.cpp index 8a1b0f25fc..32a8058d88 100644 --- a/libs/flake/KoCanvasController.cpp +++ b/libs/flake/KoCanvasController.cpp @@ -1,160 +1,132 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasController.h" #include "KoToolManager.h" #include #include class Q_DECL_HIDDEN KoCanvasController::Private { public: Private() - : canvasMode(Centered) - , margin(0) + : margin(0) , preferredCenterFractionX(0.5) , preferredCenterFractionY(0.5) , actionCollection(0) { } - CanvasMode canvasMode; int margin; QSize documentSize; QPoint documentOffset; qreal preferredCenterFractionX; qreal preferredCenterFractionY; KActionCollection* actionCollection; }; KoCanvasController::KoCanvasController(KActionCollection* actionCollection) : d(new Private()) { proxyObject = new KoCanvasControllerProxyObject(this); d->actionCollection = actionCollection; } KoCanvasController::~KoCanvasController() { KoToolManager::instance()->removeCanvasController(this); delete d; delete proxyObject; } -void KoCanvasController::setCanvasMode(CanvasMode mode) -{ - d->canvasMode = mode; - switch (mode) { - case AlignTop: - d->preferredCenterFractionX = 0; - d->preferredCenterFractionY = 0.5; - break; - case Infinite: - case Centered: - d->preferredCenterFractionX = 0.5; - d->preferredCenterFractionY = 0.5; - break; - case Spreadsheet: - d->preferredCenterFractionX = 0; - d->preferredCenterFractionY = 0; - break; - }; -} - void KoCanvasController::setMargin(int margin) { d->margin = margin; } int KoCanvasController::margin() const { return d->margin; } - -KoCanvasController::CanvasMode KoCanvasController::canvasMode() const -{ - return d->canvasMode; -} - KoCanvasBase* KoCanvasController::canvas() const { return 0; } void KoCanvasController::setDocumentSize(const QSize &sz) { d->documentSize = sz; } QSize KoCanvasController::documentSize() const { return d->documentSize; } void KoCanvasController::setPreferredCenterFractionX(qreal x) { d->preferredCenterFractionX = x; } qreal KoCanvasController::preferredCenterFractionX() const { return d->preferredCenterFractionX; } void KoCanvasController::setPreferredCenterFractionY(qreal y) { d->preferredCenterFractionY = y; } qreal KoCanvasController::preferredCenterFractionY() const { return d->preferredCenterFractionY; } void KoCanvasController::setDocumentOffset(QPoint &offset) { d->documentOffset = offset; } QPoint KoCanvasController::documentOffset() const { return d->documentOffset; } KoCanvasControllerProxyObject::KoCanvasControllerProxyObject(KoCanvasController *controller, QObject *parent) : QObject(parent) , m_canvasController(controller) { } void KoCanvasControllerProxyObject::updateDocumentSize(const QSize &newSize, bool recalculateCenter) { m_canvasController->updateDocumentSize(newSize, recalculateCenter); } KActionCollection* KoCanvasController::actionCollection() const { return d->actionCollection; } diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h index b4950ef178..b7dcd2f56d 100644 --- a/libs/flake/KoCanvasController.h +++ b/libs/flake/KoCanvasController.h @@ -1,503 +1,477 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLER_H #define KOCANVASCONTROLLER_H #include "kritaflake_export.h" #include #include #include #include #include class KActionCollection; class QRect; class QRectF; class KoShape; class KoCanvasBase; class KoCanvasControllerProxyObject; /** * KoCanvasController is the base class for wrappers around your canvas * that provides scrolling and zooming for your canvas. * * Flake does not provide a canvas, the application will have to * implement a canvas themselves. You canvas can be QWidget-based * or something we haven't invented yet -- as long the class that holds the canvas * imlements KoCanvasController, tools, scrolling and zooming will work. * * A KoCanvasController implementation acts as a decorator around the canvas widget * and provides a way to scroll the canvas, allows the canvas to be centered * in the viewArea and manages tool activation. * *

The using application can instantiate this class and add its * canvas using the setCanvas() call. Which is designed so it can be * called multiple times if you need to exchange one canvas * widget for another, for instance, switching between a plain QWidget or a QOpenGLWidget. * *

There is _one_ KoCanvasController per canvas in your * application. * *

The canvas widget is at most as big as the viewport of the scroll * area, and when the view on the document is near its edges, smaller. * In your canvas widget code, you can find the right place in your * document in view coordinates (pixels) by adding the documentOffset */ class KRITAFLAKE_EXPORT KoCanvasController { public: - /// An enum to alter the positioning and size of the canvas inside the canvas controller - enum CanvasMode { - AlignTop, ///< canvas is top aligned if smaller than the viewport - Centered, ///< canvas is centered if smaller than the viewport - Infinite, ///< canvas is never smaller than the viewport - Spreadsheet ///< same as Infinite, but supports right-to-left layouts - }; // proxy QObject: use this to connect to slots and signals. QPointer proxyObject; /** * Constructor. * @param actionCollection the action collection for this canvas */ explicit KoCanvasController(KActionCollection* actionCollection); virtual ~KoCanvasController(); public: /** * Returns the current margin that is used to pad the canvas with. * This value is read from the KConfig property "canvasmargin" */ virtual int margin() const; /** * Set the new margin to pad the canvas with. */ virtual void setMargin(int margin); - /** - * Sets the how the canvas behaves if the zoomed document becomes smaller than the viewport. - * @param mode the new canvas mode, CanvasMode::Centered is the default value - */ - virtual void setCanvasMode(KoCanvasController::CanvasMode mode); - - /// Returns the current canvas mode - virtual KoCanvasController::CanvasMode canvasMode() const; - /** * compatibility with QAbstractScrollArea */ virtual void scrollContentsBy(int dx, int dy) = 0; /** * @return the size of the viewport */ virtual QSize viewportSize() const = 0; - /** - * Set the shadow option -- by default the canvas controller draws - * a black shadow around the canvas widget, which you may or may - * not want. - * - * @param drawShadow if true, the shadow is drawn, if false, not - */ - virtual void setDrawShadow(bool drawShadow) = 0; - /** * Set the new canvas to be shown as a child * Calling this will emit canvasRemoved() if there was a canvas before, and will emit * canvasSet() with the new canvas. * @param canvas the new canvas. The KoCanvasBase::canvas() will be called to retrieve the * actual widget which will then be added as child of this one. */ virtual void setCanvas(KoCanvasBase *canvas) = 0; /** * Return the currently set canvas. The default implementation will return Null * @return the currently set canvas */ virtual KoCanvasBase *canvas() const; /** * return the amount of pixels vertically visible of the child canvas. * @return the amount of pixels vertically visible of the child canvas. */ virtual int visibleHeight() const = 0; /** * return the amount of pixels horizontally visible of the child canvas. * @return the amount of pixels horizontally visible of the child canvas. */ virtual int visibleWidth() const = 0; /** * return the amount of pixels that are not visible on the left side of the canvas. * The leftmost pixel that is shown is returned. */ virtual int canvasOffsetX() const = 0; /** * return the amount of pixels that are not visible on the top side of the canvas. * The topmost pixel that is shown is returned. */ virtual int canvasOffsetY() const = 0; /** * @brief Scrolls the content of the canvas so that the given rect is visible. * * The rect is to be specified in view coordinates (pixels). The scrollbar positions * are changed so that the centerpoint of the rectangle is centered if possible. * * @param rect the rectangle to make visible * @param smooth if true the viewport translation will make be just enough to ensure visibility, no more. * @see KoViewConverter::documentToView() */ virtual void ensureVisible(const QRectF &rect, bool smooth = false) = 0; /** * @brief Scrolls the content of the canvas so that the given shape is visible. * * This is just a wrapper function of the above function. * * @param shape the shape to make visible */ virtual void ensureVisible(KoShape *shape) = 0; /** * @brief zooms in around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom in on */ virtual void zoomIn(const QPoint ¢er) = 0; /** * @brief zooms out around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom out around */ virtual void zoomOut(const QPoint ¢er) = 0; /** * @brief zooms around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom around * @param zoom the zoom to apply */ virtual void zoomBy(const QPoint ¢er, qreal zoom) = 0; /** * @brief zoom so that rect is exactly visible (as close as possible) * * The rect must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center of the rect becomes center if possible. * * @param rect the rect in view coordinates (pixels) that should fit the view afterwards */ virtual void zoomTo(const QRect &rect) = 0; /** * @brief repositions the scrollbars so previous center is once again center * * The previous center is cached from when the user uses the scrollbars or zoomTo * are called. zoomTo is mostly used when a zoom tool of sorts have marked an area * to zoom in on * * The success of this method is limited by the size of thing. But we try our best. */ virtual void recenterPreferred() = 0; /** * Sets the preferred center point in view coordinates (pixels). * @param viewPoint the new preferred center */ virtual void setPreferredCenter(const QPointF &viewPoint) = 0; /// Returns the currently set preferred center point in view coordinates (pixels) virtual QPointF preferredCenter() const = 0; /** * Move the canvas over the x and y distance of the parameter distance * @param distance the distance in view coordinates (pixels). A positive distance means moving the canvas up/left. */ virtual void pan(const QPoint &distance) = 0; /** * Move the canvas up. This behaves the same as \sa pan() with a positive y coordinate. */ virtual void panUp() = 0; /** * Move the canvas down. This behaves the same as \sa pan() with a negative y coordinate. */ virtual void panDown() = 0; /** * Move the canvas to the left. This behaves the same as \sa pan() with a positive x coordinate. */ virtual void panLeft() = 0; /** * Move the canvas to the right. This behaves the same as \sa pan() with a negative x coordinate. */ virtual void panRight() = 0; /** * Get the position of the scrollbar */ virtual QPoint scrollBarValue() const = 0; /** * Set the position of the scrollbar * @param value the new values of the scroll bars */ virtual void setScrollBarValue(const QPoint &value) = 0; /** * Called when the size of your document in view coordinates (pixels) changes, for instance when zooming. * * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter) = 0; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ virtual void setZoomWithWheel(bool zoom) = 0; /** * Set scroll area to be bigger than actual document. * It allows the user to move the corner of the document * to e.g. the center of the screen * * @param factor the coefficient, defining how much we can scroll out, * measured in parts of the widget size. Null value means vast * scrolling is disabled. */ virtual void setVastScrolling(qreal factor) = 0; /** * Returns the action collection for the canvas * @returns action collection for this canvas, can be 0 */ virtual KActionCollection* actionCollection() const; QPoint documentOffset() const; /** * @return the current position of the cursor fetched from QCursor::pos() and * converted into document coordinates */ virtual QPointF currentCursorPosition() const = 0; protected: void setDocumentSize(const QSize &sz); QSize documentSize() const; void setPreferredCenterFractionX(qreal); qreal preferredCenterFractionX() const; void setPreferredCenterFractionY(qreal); qreal preferredCenterFractionY() const; void setDocumentOffset( QPoint &offset); private: class Private; Private * const d; }; /** * Workaround class for the problem that Qt does not allow two QObject base classes. * KoCanvasController can be implemented by for instance QWidgets, so it cannot be * a QObject directly. The interface of this class should be considered public interface * for KoCanvasController. */ class KRITAFLAKE_EXPORT KoCanvasControllerProxyObject : public QObject { Q_OBJECT Q_DISABLE_COPY(KoCanvasControllerProxyObject) public: explicit KoCanvasControllerProxyObject(KoCanvasController *canvasController, QObject *parent = 0); public: // Convenience methods to invoke the signals from subclasses void emitCanvasRemoved(KoCanvasController *canvasController) { emit canvasRemoved(canvasController); } void emitCanvasSet(KoCanvasController *canvasController) { emit canvasSet(canvasController); } void emitCanvasOffsetXChanged(int offset) { emit canvasOffsetXChanged(offset); } void emitCanvasOffsetYChanged(int offset) { emit canvasOffsetYChanged(offset); } void emitCanvasMousePositionChanged(const QPoint &position) { emit canvasMousePositionChanged(position); } void emitDocumentMousePositionChanged(const QPointF &position) { emit documentMousePositionChanged(position); } void emitSizeChanged(const QSize &size) { emit sizeChanged(size); } void emitMoveDocumentOffset(const QPoint &point) { emit moveDocumentOffset(point); } void emitZoomRelative(const qreal factor, const QPointF &stillPoint) { emit zoomRelative(factor, stillPoint); } // Convenience method to retrieve the canvas controller for who needs to use QPointer KoCanvasController *canvasController() const { return m_canvasController; } Q_SIGNALS: /** * Emitted when a previously added canvas is about to be removed. * @param canvasController this object */ void canvasRemoved(KoCanvasController *canvasController); /** * Emitted when a canvas is set on this widget * @param canvasController this object */ void canvasSet(KoCanvasController *canvasController); /** * Emitted when canvasOffsetX() changes * @param offset the new canvas offset */ void canvasOffsetXChanged(int offset); /** * Emitted when canvasOffsetY() changes * @param offset the new canvas offset */ void canvasOffsetYChanged(int offset); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in view coordinates (pixels). */ void canvasMousePositionChanged(const QPoint &position); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in document coordinates. * * Use \ref canvasMousePositionChanged to get the position * in view coordinates. */ void documentMousePositionChanged(const QPointF &position); /** * Emitted when the entire controller size changes * @param size the size in widget pixels. */ void sizeChanged(const QSize &size); /** * Emitted whenever the document is scrolled. * * @param point the new top-left point from which the document should * be drawn. */ void moveDocumentOffset(const QPoint &point); /** * Emitted when zoomRelativeToPoint have calculated a factor by which * the zoom should change and the point which should stand still * on screen. * Someone needs to connect to this and take action * * @param factor by how much the zoom needs to change. * @param stillPoint the point which will not change its position * in widget during the zooming. It is measured in * view coordinate system *before* zoom. */ void zoomRelative(const qreal factor, const QPointF &stillPoint); public Q_SLOTS: /** * Call this slot whenever the size of your document in view coordinates (pixels) * changes, for instance when zooming. * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ void updateDocumentSize(const QSize &newSize, bool recalculateCenter = true); private: KoCanvasController *m_canvasController; }; class KRITAFLAKE_EXPORT KoDummyCanvasController : public KoCanvasController { public: explicit KoDummyCanvasController(KActionCollection* actionCollection) : KoCanvasController(actionCollection) {} ~KoDummyCanvasController() override {} void scrollContentsBy(int /*dx*/, int /*dy*/) override {} QSize viewportSize() const override { return QSize(); } - void setDrawShadow(bool /*drawShadow*/) override {} void setCanvas(KoCanvasBase *canvas) override {Q_UNUSED(canvas)} KoCanvasBase *canvas() const override {return 0;} int visibleHeight() const override {return 0;} int visibleWidth() const override {return 0;} int canvasOffsetX() const override {return 0;} int canvasOffsetY() const override {return 0;} void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) override {} void ensureVisible(KoShape *shape) override {Q_UNUSED(shape)} void zoomIn(const QPoint &/*center*/) override {} void zoomOut(const QPoint &/*center*/) override {} void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) override {} void zoomTo(const QRect &/*rect*/) override {} void recenterPreferred() override {} void setPreferredCenter(const QPointF &/*viewPoint*/) override {} QPointF preferredCenter() const override {return QPointF();} void pan(const QPoint &/*distance*/) override {} void panUp() override {} void panDown() override {} void panLeft() override {} void panRight() override {} QPoint scrollBarValue() const override {return QPoint();} void setScrollBarValue(const QPoint &/*value*/) override {} void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) override {} void setZoomWithWheel(bool /*zoom*/) override {} void setVastScrolling(qreal /*factor*/) override {} QPointF currentCursorPosition() const override { return QPointF(); } }; #endif diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp index dea47c94fb..97f2053d67 100644 --- a/libs/flake/KoCanvasControllerWidget.cpp +++ b/libs/flake/KoCanvasControllerWidget.cpp @@ -1,621 +1,612 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2008-2009 Thomas Zander * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006, 2009 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007 C. Boemann * Copyright (C) 2006-2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidget.h" #include "KoCanvasControllerWidget_p.h" #include "KoCanvasControllerWidgetViewport_p.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoCanvasBase.h" #include "KoCanvasObserverBase.h" #include "KoCanvasSupervisor.h" #include "KoToolManager_p.h" #include #include #include #include #include #include #include #include #include #include void KoCanvasControllerWidget::Private::setDocumentOffset() { // The margins scroll the canvas widget inside the viewport, not // the document. The documentOffset is meant to be the value that // the canvas must add to the update rect in its paint event, to // compensate. QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value()); q->proxyObject->emitMoveDocumentOffset(pt); QWidget *canvasWidget = canvas->canvasWidget(); if (canvasWidget) { // If it isn't an OpenGL canvas if (qobject_cast(canvasWidget) == 0) { QPoint diff = q->documentOffset() - pt; - if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) { - canvasWidget->scroll(-diff.x(), diff.y()); - } else { - canvasWidget->scroll(diff.x(), diff.y()); - } + canvasWidget->scroll(diff.x(), diff.y()); } } q->setDocumentOffset(pt); } void KoCanvasControllerWidget::Private::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. int docH = q->documentSize().height() + q->margin(); int docW = q->documentSize().width() + q->margin(); int drawH = viewportWidget->height(); int drawW = viewportWidget->width(); QScrollBar *hScroll = q->horizontalScrollBar(); QScrollBar *vScroll = q->verticalScrollBar(); int horizontalReserve = vastScrollingFactor * drawW; int verticalReserve = vastScrollingFactor * drawH; int xMin = -horizontalReserve; int yMin = -verticalReserve; int xMax = docW - drawW + horizontalReserve; int yMax = docH - drawH + verticalReserve; hScroll->setRange(xMin, xMax); vScroll->setRange(yMin, yMax); int fontheight = QFontMetrics(q->font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontheight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontheight); } void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event) { if (!canvas) return; if (!canvas->viewConverter()) return; QPoint pointerPos; QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { pointerPos = mouseEvent->pos(); } else { QTabletEvent *tabletEvent = dynamic_cast(event); if (tabletEvent) { pointerPos = tabletEvent->pos(); } } QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset(); QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } #include void KoCanvasControllerWidget::Private::activate() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } KoCanvasBase *canvas = q->canvas(); Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observer->setObservedCanvas(canvas); } } } void KoCanvasControllerWidget::Private::unsetCanvas() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { if (observer->observedCanvas() == q->canvas()) { observer->unsetObservedCanvas(); } } } } //////////// KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent) : QAbstractScrollArea(parent) , KoCanvasController(actionCollection) , d(new Private(this)) { // We need to set this as QDeclarativeView sets them a bit different from QAbstractScrollArea setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // And then our own Viewport d->viewportWidget = new Viewport(this); setViewport(d->viewportWidget); d->viewportWidget->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus); setFrameStyle(0); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setAutoFillBackground(false); /* Fixes: apps starting at zero zoom. Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the widget means we no longer get rounding errors in zooming and we no longer end up with zero-zoom. Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible */ setMinimumSize(QSize(50, 50)); setMouseTracking(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY())); connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX())); connect(proxyObject, SIGNAL(moveDocumentOffset(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&))); } KoCanvasControllerWidget::~KoCanvasControllerWidget() { delete d; } void KoCanvasControllerWidget::activate() { d->activate(); } void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy) { Q_UNUSED(dx); Q_UNUSED(dy); d->setDocumentOffset(); } QSize KoCanvasControllerWidget::viewportSize() const { return viewport()->size(); } -void KoCanvasControllerWidget::setDrawShadow(bool drawShadow) -{ - d->viewportWidget->setDrawShadow(drawShadow); -} - void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent) { proxyObject->emitSizeChanged(resizeEvent->size()); // XXX: When resizing, keep the area we're looking at now in the // center of the resized view. d->resetScrollBars(); d->setDocumentOffset(); } void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas) { if (d->canvas) { d->unsetCanvas(); proxyObject->emitCanvasRemoved(this); d->canvas->setCanvasController(0); d->canvas->canvasWidget()->removeEventFilter(this); } d->canvas = canvas; if (d->canvas) { d->canvas->setCanvasController(this); changeCanvasWidget(d->canvas->canvasWidget()); proxyObject->emitCanvasSet(this); QTimer::singleShot(0, this, SLOT(activate())); setPreferredCenterFractionX(0); setPreferredCenterFractionY(0); } } KoCanvasBase* KoCanvasControllerWidget::canvas() const { if (d->canvas.isNull()) return 0; return d->canvas; } void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget) { if (d->viewportWidget->canvas()) { widget->setCursor(d->viewportWidget->canvas()->cursor()); d->viewportWidget->canvas()->removeEventFilter(this); } d->viewportWidget->setCanvas(widget); setFocusProxy(d->canvas->canvasWidget()); } int KoCanvasControllerWidget::visibleHeight() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int height1; if (canvasWidget == 0) height1 = viewport()->height(); else height1 = qMin(viewport()->height(), canvasWidget->height()); int height2 = height(); return qMin(height1, height2); } int KoCanvasControllerWidget::visibleWidth() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int width1; if (canvasWidget == 0) width1 = viewport()->width(); else width1 = qMin(viewport()->width(), canvasWidget->width()); int width2 = width(); return qMin(width1, width2); } int KoCanvasControllerWidget::canvasOffsetX() const { int offset = -horizontalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->x() + frameWidth(); } return offset; } int KoCanvasControllerWidget::canvasOffsetY() const { int offset = -verticalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->y() + frameWidth(); } return offset; } void KoCanvasControllerWidget::updateCanvasOffsetX() { proxyObject->emitCanvasOffsetXChanged(canvasOffsetX()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionX((horizontalScrollBar()->value() + viewport()->width() / 2.0) / documentSize().width()); } void KoCanvasControllerWidget::updateCanvasOffsetY() { proxyObject->emitCanvasOffsetYChanged(canvasOffsetY()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionY((verticalScrollBar()->value() + verticalScrollBar()->pageStep() / 2.0) / documentSize().height()); } void KoCanvasControllerWidget::ensureVisible(KoShape *shape) { Q_ASSERT(shape); ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect())); } void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth) { QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight()); QRect viewRect = rect.toRect(); viewRect.translate(d->canvas->documentOrigin()); if (!viewRect.isValid() || currentVisible.contains(viewRect)) return; // its visible. Nothing to do. // if we move, we move a little more so the amount of times we have to move is less. int jumpWidth = smooth ? 0 : currentVisible.width() / 5; int jumpHeight = smooth ? 0 : currentVisible.height() / 5; if (!smooth && viewRect.width() + jumpWidth > currentVisible.width()) jumpWidth = 0; if (!smooth && viewRect.height() + jumpHeight > currentVisible.height()) jumpHeight = 0; int horizontalMove = 0; if (currentVisible.width() <= viewRect.width()) // center view horizontalMove = viewRect.center().x() - currentVisible.center().x(); else if (currentVisible.x() > viewRect.x()) // move left horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth; else if (currentVisible.right() < viewRect.right()) // move right horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth); int verticalMove = 0; if (currentVisible.height() <= viewRect.height()) // center view verticalMove = viewRect.center().y() - currentVisible.center().y(); if (currentVisible.y() > viewRect.y()) // move up verticalMove = viewRect.y() - currentVisible.y() - jumpHeight; else if (currentVisible.bottom() < viewRect.bottom()) // move down verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight); pan(QPoint(horizontalMove, verticalMove)); } void KoCanvasControllerWidget::recenterPreferred() { const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; QPointF center = preferredCenter(); // convert into a viewport based point center.rx() += d->canvas->canvasWidget()->x() + frameWidth(); center.ry() += d->canvas->canvasWidget()->y() + frameWidth(); // scroll to a new center point QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height()); setScrollBarValue(topLeft.toPoint()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomIn(const QPoint ¢er) { zoomBy(center, sqrt(2.0)); } void KoCanvasControllerWidget::zoomOut(const QPoint ¢er) { zoomBy(center, sqrt(0.5)); } void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom) { setPreferredCenterFractionX(1.0 * center.x() / documentSize().width()); setPreferredCenterFractionY(1.0 * center.y() / documentSize().height()); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoom, preferredCenter()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomTo(const QRect &viewRect) { qreal scale; if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height()) scale = 1.0 * viewport()->height() / viewRect.height(); else scale = 1.0 * viewport()->width() / viewRect.width(); zoomBy(viewRect.center(), scale); } void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter) { // Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates. if (KoCanvasController::documentSize() == sz) return; if (!recalculateCenter) { // assume the distance from the top stays equal and recalculate the center. setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width()); setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height()); } const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; KoCanvasController::setDocumentSize(sz); d->viewportWidget->setDocumentSize(sz); d->resetScrollBars(); // Always emit the new offset. updateCanvasOffsetX(); updateCanvasOffsetY(); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::setZoomWithWheel(bool zoom) { d->zoomWithWheel = zoom; } void KoCanvasControllerWidget::setVastScrolling(qreal factor) { d->vastScrollingFactor = factor; } QPointF KoCanvasControllerWidget::currentCursorPosition() const { QWidget *canvasWidget = d->canvas->canvasWidget(); const KoViewConverter *converter = d->canvas->viewConverter(); return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos()); } void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint = scrollBarValue(); setScrollBarValue(sourcePoint + distance); } void KoCanvasControllerWidget::panUp() { pan(QPoint(0, verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panDown() { pan(QPoint(0, -verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panLeft() { pan(QPoint(horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::panRight() { pan(QPoint(-horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint) { setPreferredCenterFractionX(viewPoint.x() / documentSize().width()); setPreferredCenterFractionY(viewPoint.y() / documentSize().height()); recenterPreferred(); } QPointF KoCanvasControllerWidget::preferredCenter() const { QPointF center; center.setX(preferredCenterFractionX() * documentSize().width()); center.setY(preferredCenterFractionY() * documentSize().height()); return center; } void KoCanvasControllerWidget::paintEvent(QPaintEvent *event) { QPainter gc(viewport()); d->viewportWidget->handlePaintEvent(gc, event); } void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event) { d->viewportWidget->handleDragEnterEvent(event); } void KoCanvasControllerWidget::dropEvent(QDropEvent *event) { d->viewportWidget->handleDropEvent(event); } void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event) { d->viewportWidget->handleDragMoveEvent(event); } void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event) { d->viewportWidget->handleDragLeaveEvent(event); } void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event) { if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) { const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5); zoomRelativeToPoint(event->pos(), zoomCoeff); event->accept(); } else QAbstractScrollArea::wheelEvent(event); } void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff) { const QPoint offset = scrollBarValue(); const QPoint mousePos(widgetPoint + offset); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoomCoeff, mousePos); d->ignoreScrollSignals = oldIgnoreScrollSignals; } bool KoCanvasControllerWidget::focusNextPrevChild(bool) { // we always return false meaning the canvas takes keyboard focus, but never gives it away. return false; } void KoCanvasControllerWidget::setMargin(int margin) { KoCanvasController::setMargin(margin); Q_ASSERT(d->viewportWidget); d->viewportWidget->setMargin(margin); } QPoint KoCanvasControllerWidget::scrollBarValue() const { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); return QPoint(hBar->value(), vBar->value()); } void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value) { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); hBar->setValue(value.x()); vBar->setValue(value.y()); } KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoCanvasControllerWidget.cpp" diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h index 142150db01..0ffb9dd91d 100644 --- a/libs/flake/KoCanvasControllerWidget.h +++ b/libs/flake/KoCanvasControllerWidget.h @@ -1,189 +1,187 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLERWIDGET_H #define KOCANVASCONTROLLERWIDGET_H #include "kritaflake_export.h" #include #include #include "KoCanvasController.h" class KoShape; class KoCanvasBase; /** * KoCanvasController implementation for QWidget based canvases */ class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController { Q_OBJECT public: /** * Constructor. * @param parent the parent this widget will belong to */ explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0); ~KoCanvasControllerWidget() override; /** * Reimplemented from QAbstractScrollArea. */ void scrollContentsBy(int dx, int dy) override; QSize viewportSize() const override; /// Reimplemented from KoCanvasController /** * Activate this canvascontroller */ virtual void activate(); - void setDrawShadow(bool drawShadow) override; - void setCanvas(KoCanvasBase *canvas) override; KoCanvasBase *canvas() const override; /** * Change the actual canvas widget used by the current canvas. This allows the canvas widget * to be changed while keeping the current KoCanvasBase canvas and its associated resources as * they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas. * @param widget the new canvas widget. */ virtual void changeCanvasWidget(QWidget *widget); int visibleHeight() const override; int visibleWidth() const override; int canvasOffsetX() const override; int canvasOffsetY() const override; void ensureVisible(const QRectF &rect, bool smooth = false) override; void ensureVisible(KoShape *shape) override; /** * will cause the toolOptionWidgetsChanged to be emitted and all * listeners to be updated to the new widget. * * FIXME: This doesn't belong her and it does an * inherits("KoView") so it too much tied to komain * * @param widgets the map of widgets */ void setToolOptionWidgets(const QList > &widgets); void zoomIn(const QPoint ¢er) override; void zoomOut(const QPoint ¢er) override; void zoomBy(const QPoint ¢er, qreal zoom) override; void zoomTo(const QRect &rect) override; /** * Zoom document keeping point \p widgetPoint unchanged * \param widgetPoint sticky point in widget pixels */ virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff); void recenterPreferred() override; void setPreferredCenter(const QPointF &viewPoint) override; /// Returns the currently set preferred center point in view coordinates (pixels) QPointF preferredCenter() const override; void pan(const QPoint &distance) override; virtual void panUp() override; virtual void panDown() override; virtual void panLeft() override; virtual void panRight() override; void setMargin(int margin) override; QPoint scrollBarValue() const override; /** * Used by KisCanvasController to correct the scrollbars position * after the rotation. */ void setScrollBarValue(const QPoint &value) override; void updateDocumentSize(const QSize &sz, bool recalculateCenter = true) override; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ void setZoomWithWheel(bool zoom) override; void setVastScrolling(qreal factor) override; QPointF currentCursorPosition() const override; /** * \internal */ class Private; KoCanvasControllerWidget::Private *priv(); private Q_SLOTS: /// Called by the horizontal scrollbar when its value changes void updateCanvasOffsetX(); /// Called by the vertical scrollbar when its value changes void updateCanvasOffsetY(); protected: friend class KisZoomAndPanTest; /// reimplemented from QWidget void paintEvent(QPaintEvent *event) override; /// reimplemented from QWidget void resizeEvent(QResizeEvent *resizeEvent) override; /// reimplemented from QWidget void dragEnterEvent(QDragEnterEvent *event) override; /// reimplemented from QWidget void dropEvent(QDropEvent *event) override; /// reimplemented from QWidget void dragMoveEvent(QDragMoveEvent *event) override; /// reimplemented from QWidget void dragLeaveEvent(QDragLeaveEvent *event) override; /// reimplemented from QWidget void wheelEvent(QWheelEvent *event) override; /// reimplemented from QWidget bool focusNextPrevChild(bool next) override; private: Q_PRIVATE_SLOT(d, void activate()) Private * const d; }; #endif diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index 1c8e6f3d8d..e64484997f 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,421 +1,332 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2009 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidgetViewport_p.h" #include #include #include #include #include #include #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoCanvasBase.h" #include "KoShapeLayer.h" #include "KoShapePaintingContext.h" #include "KoToolProxy.h" #include "KoCanvasControllerWidget.h" #include "KoViewConverter.h" #include "KoSvgPaste.h" // ********** Viewport ********** Viewport::Viewport(KoCanvasControllerWidget *parent) : QWidget(parent) , m_draggedShape(0) - , m_drawShadow(false) , m_canvas(0) , m_documentOffset(QPoint(0, 0)) , m_margin(0) { setAutoFillBackground(true); setAcceptDrops(true); setMouseTracking(true); m_parent = parent; } void Viewport::setCanvas(QWidget *canvas) { if (m_canvas) { m_canvas->hide(); delete m_canvas; } m_canvas = canvas; if (!canvas) return; m_canvas->setParent(this); m_canvas->show(); if (!m_canvas->minimumSize().isNull()) { m_documentSize = m_canvas->minimumSize(); } resetLayout(); } void Viewport::setDocumentSize(const QSize &size) { m_documentSize = size; resetLayout(); } void Viewport::documentOffsetMoved(const QPoint &pt) { m_documentOffset = pt; resetLayout(); } -void Viewport::setDrawShadow(bool drawShadow) -{ - m_drawShadow = drawShadow; -} - void Viewport::handleDragEnterEvent(QDragEnterEvent *event) { // if not a canvas set then ignore this, makes it possible to assume // we have a canvas in all the support methods. if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) { event->ignore(); return; } delete m_draggedShape; m_draggedShape = 0; // only allow dropping when active layer is editable KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); KoShapeLayer *activeLayer = selection->activeLayer(); if (activeLayer && (!activeLayer->isShapeEditable() || activeLayer->isGeometryProtected())) { event->ignore(); return; } const QMimeData *data = event->mimeData(); if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || data->hasFormat(SHAPEID_MIMETYPE) || data->hasFormat("image/svg+xml")) { if (data->hasFormat("image/svg+xml")) { KoCanvasBase *canvas = m_parent->canvas(); QSizeF fragmentSize; QList shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"), canvas->shapeController()->documentRectInPixels(), canvas->shapeController()->pixelsPerInch(), &fragmentSize); if (!shapes.isEmpty()) { m_draggedShape = shapes[0]; } } else { QByteArray itemData; bool isTemplate = true; if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) { itemData = data->data(SHAPETEMPLATE_MIMETYPE); } else if (data->hasFormat(SHAPEID_MIMETYPE)) { isTemplate = false; itemData = data->data(SHAPEID_MIMETYPE); } QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString id; dataStream >> id; QString properties; if (isTemplate) dataStream >> properties; // and finally, there is a point. QPointF offset; dataStream >> offset; // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); if (! factory) { warnFlake << "Application requested a shape that is not registered '" << id << "', Ignoring"; event->ignore(); return; } if (isTemplate) { KoProperties props; props.load(properties); m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); } else { m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); } if (m_draggedShape->shapeId().isEmpty()) { m_draggedShape->setShapeId(factory->id()); } } event->setDropAction(Qt::CopyAction); event->accept(); Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; // calculate maximum existing shape zIndex int pasteZIndex = 0; { QList allShapes = m_parent->canvas()->shapeManager()->topLevelShapes(); if (!allShapes.isEmpty()) { std::sort(allShapes.begin(), allShapes.end(), KoShape::compareShapeZIndex); pasteZIndex = qMin(int(KoShape::maxZIndex), allShapes.last()->zIndex() + 1); } } m_draggedShape->setZIndex(pasteZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } else { event->ignore(); } } void Viewport::handleDropEvent(QDropEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos())); return; } repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc. m_draggedShape->setPosition(QPointF(0, 0)); // always save position. QPointF newPos = correctPosition(event->pos()); m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document. m_draggedShape->setAbsolutePosition(newPos); KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0); if (cmd) { m_parent->canvas()->addCommand(cmd); KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); // repaint selection before selecting newly create shape Q_FOREACH (KoShape * shape, selection->selectedShapes()) { shape->update(); } selection->deselectAll(); selection->select(m_draggedShape); } else { delete m_draggedShape; } m_draggedShape = 0; } QPointF Viewport::correctPosition(const QPoint &point) const { QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y()); correctedPos += m_documentOffset; return m_parent->canvas()->viewToDocument(correctedPos); } void Viewport::handleDragMoveEvent(QDragMoveEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos())); return; } m_draggedShape->update(); repaint(m_draggedShape); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_draggedShape->update(); repaint(m_draggedShape); } void Viewport::repaint(KoShape *shape) { QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x()); rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y()); rect.adjust(-2, -2, 2, 2); // adjust for antialias update(rect); } void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event) { if (m_draggedShape) { repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); delete m_draggedShape; m_draggedShape = 0; } else { m_parent->canvas()->toolProxy()->dragLeaveEvent(event); } } void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event) { Q_UNUSED(event); - // Draw the shadow around the canvas. - if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) { - QWidget *canvas = m_parent->canvas()->canvasWidget(); - painter.setPen(QPen(Qt::black, 0)); - QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height()); - rect.adjust(-1, -1, 0, 0); - painter.drawRect(rect); - painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2); - painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2); - } if (m_draggedShape) { const KoViewConverter *vc = m_parent->canvas()->viewConverter(); painter.save(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. painter.translate(canvasWidget->x() - m_documentOffset.x(), canvasWidget->y() - m_documentOffset.y()); QPointF offset = vc->documentToView(m_draggedShape->position()); painter.setOpacity(0.6); painter.translate(offset.x(), offset.y()); painter.setRenderHint(QPainter::Antialiasing); KoShapePaintingContext paintContext; //FIXME m_draggedShape->paint(painter, *vc, paintContext); painter.restore(); } } void Viewport::resetLayout() { // Determine the area we have to show QRect viewRect(m_documentOffset, size()); const int viewH = viewRect.height(); const int viewW = viewRect.width(); - const int docH = m_documentSize.height(); - const int docW = m_documentSize.width(); - - int moveX = 0; - int moveY = 0; - - int resizeW = viewW; - int resizeH = viewH; - - // debugFlake <<"viewH:" << viewH << endl - // << "docH: " << docH << endl - // << "viewW: " << viewW << endl - // << "docW: " << docW << endl; - - if (viewH == docH && viewW == docW) { - // Do nothing - resizeW = docW; - resizeH = docH; - } else if (viewH > docH && viewW > docW) { - // Show entire canvas centered - moveX = (viewW - docW) / 2; - moveY = (viewH - docH) / 2; - resizeW = docW; - resizeH = docH; - } else if (viewW > docW) { - // Center canvas horizontally - moveX = (viewW - docW) / 2; - resizeW = docW; - - int marginTop = m_margin - m_documentOffset.y(); - int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); - - if (marginTop > 0) moveY = marginTop; - if (marginTop > 0) resizeH = viewH - marginTop; - if (marginBottom > 0) resizeH = viewH - marginBottom; - } else if (viewH > docH) { - // Center canvas vertically - moveY = (viewH - docH) / 2; - resizeH = docH; - - int marginLeft = m_margin - m_documentOffset.x(); - int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); - - if (marginLeft > 0) moveX = marginLeft; - if (marginLeft > 0) resizeW = viewW - marginLeft; - if (marginRight > 0) resizeW = viewW - marginRight; - } else { - // Take care of the margin around the canvas - int marginTop = m_margin - m_documentOffset.y(); - int marginLeft = m_margin - m_documentOffset.x(); - int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); - int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); - - if (marginTop > 0) moveY = marginTop; - if (marginLeft > 0) moveX = marginLeft; - - if (marginTop > 0) resizeH = viewH - marginTop; - if (marginLeft > 0) resizeW = viewW - marginLeft; - if (marginRight > 0) resizeW = viewW - marginRight; - if (marginBottom > 0) resizeH = viewH - marginBottom; - } - if (m_parent->canvasMode() == KoCanvasController::AlignTop) { - // have up to m_margin pixels at top. - moveY = qMin(m_margin, moveY); - } if (m_canvas) { - QRect geom; - if (m_parent->canvasMode() == KoCanvasController::Infinite) - geom = QRect(0, 0, viewW, viewH); - else - geom = QRect(moveX, moveY, resizeW, resizeH); + QRect geom = QRect(0, 0, viewW, viewH); if (m_canvas->geometry() != geom) { m_canvas->setGeometry(geom); m_canvas->update(); } } - if (m_drawShadow) { - update(); - } - emit sizeChanged(); #if 0 debugFlake <<"View port geom:" << geometry(); if (m_canvas) debugFlake <<"Canvas widget geom:" << m_canvas->geometry(); #endif } diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.h b/libs/flake/KoCanvasControllerWidgetViewport_p.h index c430e39e59..5c56595b07 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.h +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.h @@ -1,91 +1,85 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLERWIDGETVIEWPORT_P_H #define KOCANVASCONTROLLERWIDGETVIEWPORT_P_H #include #include #include class KoCanvasControllerWidget; class KoShape; class Viewport : public QWidget { Q_OBJECT public: explicit Viewport(KoCanvasControllerWidget *parent); ~Viewport() override {} void setCanvas(QWidget *canvas); QWidget *canvas() const { return m_canvas; } void setDocumentSize(const QSize &size); - /** - * When true, a shadow is drawn around the canvas widet. - */ - void setDrawShadow(bool drawShadow); - public Q_SLOTS: void documentOffsetMoved(const QPoint &); Q_SIGNALS: void sizeChanged(); public: void handleDragEnterEvent(QDragEnterEvent *event); void handleDropEvent(QDropEvent *event); void handleDragMoveEvent(QDragMoveEvent *event); void handleDragLeaveEvent(QDragLeaveEvent *event); void handlePaintEvent(QPainter &gc, QPaintEvent *event); void setMargin(int margin) { m_margin = margin; resetLayout(); } private: QPointF correctPosition(const QPoint &point) const; void repaint(KoShape *shape); /** Decides whether the containing canvas widget should be as big as the viewport (i.e., no margins are visible) or whether there are margins to be left blank, and then places the canvas widget accordingly. */ void resetLayout(); private: KoCanvasControllerWidget *m_parent; KoShape *m_draggedShape; - bool m_drawShadow; QWidget *m_canvas; QSize m_documentSize; // Size in pixels of the document QPoint m_documentOffset; // Place where the canvas widget should int m_margin; // The viewport margin around the document }; #endif diff --git a/libs/flake/commands/KoSubpathJoinCommand.cpp b/libs/flake/commands/KoSubpathJoinCommand.cpp index 4f15ec4b2c..2d4bfa4e7e 100644 --- a/libs/flake/commands/KoSubpathJoinCommand.cpp +++ b/libs/flake/commands/KoSubpathJoinCommand.cpp @@ -1,173 +1,173 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSubpathJoinCommand.h" #include #include "kis_assert.h" #include "KoPathMergeUtils.h" KoSubpathJoinCommand::KoSubpathJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData1(pointData1) , m_pointData2(pointData2) , m_splitIndex(KoPathPointIndex(-1, -1)) , m_oldProperties1(KoPathPoint::Normal) , m_oldProperties2(KoPathPoint::Normal) , m_reverse(0) { KIS_ASSERT(m_pointData1.pathShape == m_pointData2.pathShape); KoPathShape * pathShape = m_pointData1.pathShape; KIS_ASSERT(!pathShape->isClosedSubpath(m_pointData1.pointIndex.first)); KIS_ASSERT(m_pointData1.pointIndex.second == 0 || m_pointData1.pointIndex.second == pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1); KIS_ASSERT(!pathShape->isClosedSubpath(m_pointData2.pointIndex.first)); KIS_ASSERT(m_pointData2.pointIndex.second == 0 || m_pointData2.pointIndex.second == pathShape->subpathPointCount(m_pointData2.pointIndex.first) - 1); //TODO check that points are not the same if (m_pointData2 < m_pointData1) { std::swap(m_pointData1, m_pointData2); } if (!closeSubpathMode()) { if (m_pointData1.pointIndex.second == 0 && pathShape->subpathPointCount(m_pointData1.pointIndex.first) > 1) m_reverse |= ReverseFirst; if (m_pointData2.pointIndex.second != 0) m_reverse |= ReverseSecond; setText(kundo2_i18n("Join subpaths")); } else { setText(kundo2_i18n("Close subpath")); } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); - m_savedControlPoint1 = KritaUtils::fetchControlPoint(point1, m_reverse && ReverseFirst); - m_savedControlPoint2 = KritaUtils::fetchControlPoint(point2, !(m_reverse && ReverseSecond)); + m_savedControlPoint1 = KritaUtils::fetchControlPoint(point1, m_reverse & ReverseFirst); + m_savedControlPoint2 = KritaUtils::fetchControlPoint(point2, !(m_reverse & ReverseSecond)); m_oldProperties1 = point1->properties(); m_oldProperties2 = point2->properties(); } KoSubpathJoinCommand::~KoSubpathJoinCommand() { } void KoSubpathJoinCommand::redo() { KUndo2Command::redo(); KoPathShape * pathShape = m_pointData1.pathShape; KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); KIS_SAFE_ASSERT_RECOVER_RETURN(point1); KIS_SAFE_ASSERT_RECOVER_RETURN(point2); // if the endpoint has a control point create a control point for the new segment to be // at the symmetric position to the exiting one if (closeSubpathMode()) { KritaUtils::makeSymmetric(point1, false); KritaUtils::makeSymmetric(point2, true); } else { KritaUtils::makeSymmetric(point1, !(m_reverse & ReverseFirst)); KritaUtils::makeSymmetric(point2, m_reverse & ReverseSecond); } if (closeSubpathMode()) { pathShape->closeSubpath(m_pointData1.pointIndex); } else { if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } pathShape->moveSubpath(m_pointData2.pointIndex.first, m_pointData1.pointIndex.first + 1); m_splitIndex = m_pointData1.pointIndex; m_splitIndex.second = pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1; pathShape->join(m_pointData1.pointIndex.first); } QList pointIndexes; pointIndexes << pathShape->pathPointIndex(point1); pointIndexes << pathShape->pathPointIndex(point2); pathShape->recommendPointSelectionChange(pointIndexes); pathShape->normalize(); pathShape->update(); } void KoSubpathJoinCommand::undo() { KUndo2Command::undo(); KoPathShape * pathShape = m_pointData1.pathShape; pathShape->update(); if (closeSubpathMode()) { pathShape->openSubpath(m_pointData1.pointIndex); } else { pathShape->breakAfter(m_splitIndex); pathShape->moveSubpath(m_pointData1.pointIndex.first + 1, m_pointData2.pointIndex.first); if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); KIS_SAFE_ASSERT_RECOVER_RETURN(point1); KIS_SAFE_ASSERT_RECOVER_RETURN(point2); // restore the old end points if (closeSubpathMode()) { KritaUtils::restoreControlPoint(point1, true, m_savedControlPoint1); KritaUtils::restoreControlPoint(point2, false, m_savedControlPoint2); } else { KritaUtils::restoreControlPoint(point1, m_reverse & ReverseFirst, m_savedControlPoint1); KritaUtils::restoreControlPoint(point2, !(m_reverse & ReverseSecond), m_savedControlPoint2); } point1->setProperties(m_oldProperties1); point2->setProperties(m_oldProperties2); QList pointIndexes; pointIndexes << pathShape->pathPointIndex(point1); pointIndexes << pathShape->pathPointIndex(point2); pathShape->recommendPointSelectionChange(pointIndexes); pathShape->normalize(); pathShape->update(); } bool KoSubpathJoinCommand::closeSubpathMode() const { return m_pointData1.pointIndex.first == m_pointData2.pointIndex.first; } diff --git a/libs/global/kis_global.h b/libs/global/kis_global.h index 84cb472528..37777ba1f5 100644 --- a/libs/global/kis_global.h +++ b/libs/global/kis_global.h @@ -1,273 +1,272 @@ /* * Copyright (c) 2000 Matthias Elter * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KISGLOBAL_H_ #define KISGLOBAL_H_ #include #include #include "kis_assert.h" #include #include const quint8 quint8_MAX = UCHAR_MAX; const quint16 quint16_MAX = 65535; const qint32 qint32_MAX = (2147483647); const qint32 qint32_MIN = (-2147483647 - 1); const quint8 MAX_SELECTED = UCHAR_MAX; const quint8 MIN_SELECTED = 0; const quint8 SELECTION_THRESHOLD = 1; enum OutlineStyle { OUTLINE_NONE = 0, OUTLINE_CIRCLE, OUTLINE_FULL, OUTLINE_TILT, - OUTLINE_COLOR, N_OUTLINE_STYLE_SIZE }; enum CursorStyle { CURSOR_STYLE_NO_CURSOR = 0, CURSOR_STYLE_TOOLICON, CURSOR_STYLE_POINTER, CURSOR_STYLE_SMALL_ROUND, CURSOR_STYLE_CROSSHAIR, CURSOR_STYLE_TRIANGLE_RIGHTHANDED, CURSOR_STYLE_TRIANGLE_LEFTHANDED, CURSOR_STYLE_BLACK_PIXEL, CURSOR_STYLE_WHITE_PIXEL, N_CURSOR_STYLE_SIZE }; enum OldCursorStyle { OLD_CURSOR_STYLE_TOOLICON = 0, OLD_CURSOR_STYLE_CROSSHAIR = 1, OLD_CURSOR_STYLE_POINTER = 2, OLD_CURSOR_STYLE_OUTLINE = 3, OLD_CURSOR_STYLE_NO_CURSOR = 4, OLD_CURSOR_STYLE_SMALL_ROUND = 5, OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT = 6, OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS = 7, OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED = 8, OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED = 9, OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED = 10, OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED = 11 }; /* * Most wacom pads have 512 levels of pressure; Qt only supports 256, and even * this is downscaled to 127 levels because the line would be too jittery, and * the amount of masks take too much memory otherwise. */ const qint32 PRESSURE_LEVELS = 127; const double PRESSURE_MIN = 0.0; const double PRESSURE_MAX = 1.0; const double PRESSURE_DEFAULT = PRESSURE_MAX; const double PRESSURE_THRESHOLD = 5.0 / 255.0; // copy of lcms.h #define INTENT_PERCEPTUAL 0 #define INTENT_RELATIVE_COLORIMETRIC 1 #define INTENT_SATURATION 2 #define INTENT_ABSOLUTE_COLORIMETRIC 3 #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // converts \p a to [0, 2 * M_PI) range inline qreal normalizeAngle(qreal a) { if (a < 0.0) { a = 2 * M_PI + fmod(a, 2 * M_PI); } return a >= 2 * M_PI ? fmod(a, 2 * M_PI) : a; } // converts \p a to [0, 360.0) range inline qreal normalizeAngleDegrees(qreal a) { if (a < 0.0) { a = 360.0 + fmod(a, 360.0); } return a >= 360.0 ? fmod(a, 360.0) : a; } inline qreal shortestAngularDistance(qreal a, qreal b) { qreal dist = fmod(qAbs(a - b), 2 * M_PI); if (dist > M_PI) dist = 2 * M_PI - dist; return dist; } inline qreal incrementInDirection(qreal a, qreal inc, qreal direction) { qreal b1 = a + inc; qreal b2 = a - inc; qreal d1 = shortestAngularDistance(b1, direction); qreal d2 = shortestAngularDistance(b2, direction); return d1 < d2 ? b1 : b2; } inline qreal bisectorAngle(qreal a, qreal b) { const qreal diff = shortestAngularDistance(a, b); return incrementInDirection(a, 0.5 * diff, b); } template inline PointType snapToClosestAxis(PointType P) { if (qAbs(P.x()) < qAbs(P.y())) { P.setX(0); } else { P.setY(0); } return P; } template inline T pow2(const T& x) { return x * x; } template inline T kisDegreesToRadians(T degrees) { return degrees * M_PI / 180.0; } template inline T kisRadiansToDegrees(T radians) { return radians * 180.0 / M_PI; } template inline T kisGrowRect(const T &rect, U offset) { return rect.adjusted(-offset, -offset, offset, offset); } inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) { return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y())); } inline qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2) { return pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y()); } #include inline qreal kisDistanceToLine(const QPointF &m, const QLineF &line) { const QPointF &p1 = line.p1(); const QPointF &p2 = line.p2(); qreal distance = 0; if (qFuzzyCompare(p1.x(), p2.x())) { distance = qAbs(m.x() - p2.x()); } else if (qFuzzyCompare(p1.y(), p2.y())) { distance = qAbs(m.y() - p2.y()); } else { qreal A = 1; qreal B = - (p1.x() - p2.x()) / (p1.y() - p2.y()); qreal C = - p1.x() - B * p1.y(); distance = qAbs(A * m.x() + B * m.y() + C) / std::sqrt(pow2(A) + pow2(B)); } return distance; } inline QPointF kisProjectOnVector(const QPointF &base, const QPointF &v) { const qreal prod = base.x() * v.x() + base.y() * v.y(); const qreal lengthSq = pow2(base.x()) + pow2(base.y()); qreal coeff = prod / lengthSq; return coeff * base; } #include inline QRect kisEnsureInRect(QRect rc, const QRect &bounds) { if(rc.right() > bounds.right()) { rc.translate(bounds.right() - rc.right(), 0); } if(rc.left() < bounds.left()) { rc.translate(bounds.left() - rc.left(), 0); } if(rc.bottom() > bounds.bottom()) { rc.translate(0, bounds.bottom() - rc.bottom()); } if(rc.top() < bounds.top()) { rc.translate(0, bounds.top() - rc.top()); } return rc; } #include "kis_pointer_utils.h" /** * A special wrapper object that converts Qt-style mutexes and locks * into an object that supports Std's (and Boost's) "Lockable" * concept. Basically, it converts tryLock() into try_lock() to comply * with the syntax. */ template struct StdLockableWrapper { StdLockableWrapper(T *lock) : m_lock(lock) {} void lock() { m_lock->lock(); } bool try_lock() { return m_lock->tryLock(); } void unlock() { m_lock->unlock(); } private: T *m_lock; }; #endif // KISGLOBAL_H_ diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp index ce465f7f8c..dd515f806f 100644 --- a/libs/image/brushengine/kis_paintop_settings.cpp +++ b/libs/image/brushengine/kis_paintop_settings.cpp @@ -1,524 +1,524 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Lukáš Tvrdý * Copyright (c) 2014 Mohit Goyal * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 #include #include #include #include #include #include #include #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paintop_registry.h" #include "kis_timing_information.h" #include #include "kis_paintop_config_widget.h" #include #include "kis_paintop_settings_update_proxy.h" #include #include #include #include #include #include "KisPaintopSettingsIds.h" struct Q_DECL_HIDDEN KisPaintOpSettings::Private { Private() : disableDirtyNotifications(false) {} QPointer settingsWidget; QString modelName; KisPaintOpPresetWSP preset; QList uniformProperties; bool disableDirtyNotifications; class DirtyNotificationsLocker { public: DirtyNotificationsLocker(KisPaintOpSettings::Private *d) : m_d(d), m_oldNotificationsState(d->disableDirtyNotifications) { m_d->disableDirtyNotifications = true; } ~DirtyNotificationsLocker() { m_d->disableDirtyNotifications = m_oldNotificationsState; } private: KisPaintOpSettings::Private *m_d; bool m_oldNotificationsState; Q_DISABLE_COPY(DirtyNotificationsLocker) }; KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxyNoCreate() : 0; } KisPaintopSettingsUpdateProxy* updateProxyCreate() const { auto presetSP = preset.toStrongRef(); return presetSP ? presetSP->updateProxy() : 0; } }; KisPaintOpSettings::KisPaintOpSettings() : d(new Private) { d->preset = 0; } KisPaintOpSettings::~KisPaintOpSettings() { } KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs) : KisPropertiesConfiguration(rhs) , d(new Private) { d->settingsWidget = 0; d->preset = rhs.preset(); d->modelName = rhs.modelName(); } void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget) { d->settingsWidget = widget; } void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset) { d->preset = preset; } KisPaintOpPresetWSP KisPaintOpSettings::preset() const { return d->preset; } bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { Q_UNUSED(modifiers); Q_UNUSED(currentNode); setRandomOffset(paintInformation); return true; // ignore the event by default } void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation) { if (getBool("Texture/Pattern/Enabled")) { if (getBool("Texture/Pattern/isRandomOffsetX")) { setProperty("Texture/Pattern/OffsetX", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX"))); } if (getBool("Texture/Pattern/isRandomOffsetY")) { setProperty("Texture/Pattern/OffsetY", paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY"))); } } } bool KisPaintOpSettings::hasMaskingSettings() const { return getBool(KisPaintOpUtils::MaskingBrushEnabledTag, false); } KisPaintOpSettingsSP KisPaintOpSettings::createMaskingSettings() const { if (!hasMaskingSettings()) return KisPaintOpSettingsSP(); const KoID pixelBrushId(KisPaintOpUtils::MaskingBrushPaintOpId, QString()); KisPaintOpSettingsSP maskingSettings = KisPaintOpRegistry::instance()->settings(pixelBrushId); this->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSettings); const bool useMasterSize = this->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true); if (useMasterSize) { const qreal masterSizeCoeff = getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0); maskingSettings->setPaintOpSize(masterSizeCoeff * paintOpSize()); } return maskingSettings; } QString KisPaintOpSettings::maskingBrushCompositeOp() const { return getString(KisPaintOpUtils::MaskingBrushCompositeOpTag, COMPOSITE_MULT); } KisPaintOpSettingsSP KisPaintOpSettings::clone() const { QString paintopID = getString("paintop"); if (paintopID.isEmpty()) return 0; KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID, "")); QMapIterator i(getProperties()); while (i.hasNext()) { i.next(); settings->setProperty(i.key(), QVariant(i.value())); } settings->setPreset(this->preset()); return settings; } void KisPaintOpSettings::resetSettings(const QStringList &preserveProperties) { QStringList allKeys = preserveProperties; allKeys << "paintop"; QHash preserved; Q_FOREACH (const QString &key, allKeys) { if (hasProperty(key)) { preserved[key] = getProperty(key); } } clearProperties(); for (auto it = preserved.constBegin(); it != preserved.constEnd(); ++it) { setProperty(it.key(), it.value()); } } void KisPaintOpSettings::activate() { } void KisPaintOpSettings::setPaintOpOpacity(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("OpacityValue", value); } void KisPaintOpSettings::setPaintOpFlow(qreal value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("FlowValue", value); } void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("CompositeOp", value); } qreal KisPaintOpSettings::paintOpOpacity() { KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this); return proxy->getDouble("OpacityValue", 1.0); } qreal KisPaintOpSettings::paintOpFlow() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getDouble("FlowValue", 1.0); } QString KisPaintOpSettings::paintOpCompositeOp() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getString("CompositeOp", COMPOSITE_OVER); } void KisPaintOpSettings::setEraserMode(bool value) { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); proxy->setProperty("EraserMode", value); } bool KisPaintOpSettings::eraserMode() { KisLockedPropertiesProxySP proxy( KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this)); return proxy->getBool("EraserMode", false); } QString KisPaintOpSettings::effectivePaintOpCompositeOp() { return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE; } qreal KisPaintOpSettings::savedEraserSize() const { return getDouble("SavedEraserSize", 0.0); } void KisPaintOpSettings::setSavedEraserSize(qreal value) { setProperty("SavedEraserSize", value); setPropertyNotSaved("SavedEraserSize"); } qreal KisPaintOpSettings::savedBrushSize() const { return getDouble("SavedBrushSize", 0.0); } void KisPaintOpSettings::setSavedBrushSize(qreal value) { setProperty("SavedBrushSize", value); setPropertyNotSaved("SavedBrushSize"); } qreal KisPaintOpSettings::savedEraserOpacity() const { return getDouble("SavedEraserOpacity", 0.0); } void KisPaintOpSettings::setSavedEraserOpacity(qreal value) { setProperty("SavedEraserOpacity", value); setPropertyNotSaved("SavedEraserOpacity"); } qreal KisPaintOpSettings::savedBrushOpacity() const { return getDouble("SavedBrushOpacity", 0.0); } void KisPaintOpSettings::setSavedBrushOpacity(qreal value) { setProperty("SavedBrushOpacity", value); setPropertyNotSaved("SavedBrushOpacity"); } QString KisPaintOpSettings::modelName() const { return d->modelName; } void KisPaintOpSettings::setModelName(const QString & modelName) { d->modelName = modelName; } KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const { if (d->settingsWidget.isNull()) return 0; return d->settingsWidget.data(); } bool KisPaintOpSettings::isValid() const { return true; } bool KisPaintOpSettings::isLoadable() { return isValid(); } QString KisPaintOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_ALPHA_DARKEN; } bool KisPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED, false); } qreal KisPaintOpSettings::airbrushInterval() const { qreal rate = getDouble(AIRBRUSH_RATE, 1.0); if (rate == 0.0) { return LONG_TIME; } else { return 1000.0 / rate; } } bool KisPaintOpSettings::useSpacingUpdates() const { return getBool(SPACING_USE_UPDATES, false); } bool KisPaintOpSettings::needsAsynchronousUpdates() const { return false; } -QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) +QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; - if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { + if (mode.isVisible) { path = ellipseOutline(10, 10, 1.0, 0); - if (mode == CursorTiltOutline) { + if (mode.showTiltDecoration) { path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0)); } path.translate(info.pos()); } return path; } QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) { QPainterPath path; QRectF ellipse(0, 0, width * scale, height * scale); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); QTransform m; m.reset(); m.rotate(rotation); path = m.map(path); return path; } QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal maxLength, qreal angle) { if (maxLength == 0.0) maxLength = 50.0; maxLength = qMax(maxLength, 50.0); qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true)); qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0); QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle); guideLine.translate(start); QPainterPath ret; ret.moveTo(guideLine.p1()); ret.lineTo(guideLine.p2()); guideLine.setAngle(baseAngle - angle); ret.lineTo(guideLine.p2()); ret.lineTo(guideLine.p1()); return ret; } void KisPaintOpSettings::setCanvasRotation(qreal angle) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasRotation", angle); setPropertyNotSaved("runtimeCanvasRotation"); } void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasMirroredX", xAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredX"); setProperty("runtimeCanvasMirroredY", yAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredY"); } void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value) { if (value != KisPropertiesConfiguration::getProperty(name) && !d->disableDirtyNotifications) { KisPaintOpPresetSP presetSP = preset().toStrongRef(); if (presetSP) { presetSP->setPresetDirty(true); } } KisPropertiesConfiguration::setProperty(name, value); onPropertyChanged(); } void KisPaintOpSettings::onPropertyChanged() { KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate(); if (proxy) { proxy->notifySettingsChanged(); } } bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config) { return config->getBool("lodUserAllowed", true); } void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value) { config->setProperty("lodUserAllowed", value); } bool KisPaintOpSettings::lodSizeThresholdSupported() const { return true; } qreal KisPaintOpSettings::lodSizeThreshold() const { return getDouble("lodSizeThreshold", 100.0); } void KisPaintOpSettings::setLodSizeThreshold(qreal value) { setProperty("lodSizeThreshold", value); } #include "kis_standard_uniform_properties_factory.h" QList KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(d->uniformProperties); if (props.isEmpty()) { using namespace KisStandardUniformPropertiesFactory; props.append(createProperty(opacity, settings, d->updateProxyCreate())); props.append(createProperty(size, settings, d->updateProxyCreate())); props.append(createProperty(flow, settings, d->updateProxyCreate())); d->uniformProperties = listStrongToWeak(props); } return props; } diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h index 6efd7fe4f8..d820f5bcce 100644 --- a/libs/image/brushengine/kis_paintop_settings.h +++ b/libs/image/brushengine/kis_paintop_settings.h @@ -1,352 +1,351 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_PAINTOP_SETTINGS_H_ #define KIS_PAINTOP_SETTINGS_H_ #include "kis_types.h" #include "kritaimage_export.h" #include #include #include "kis_properties_configuration.h" #include #include class KisPaintOpConfigWidget; class KisPaintopSettingsUpdateProxy; /** * Configuration property used to control whether airbrushing is enabled. */ const QString AIRBRUSH_ENABLED = "PaintOpSettings/isAirbrushing"; /** * Configuration property used to control airbrushing rate. The value should be in dabs per second. */ const QString AIRBRUSH_RATE = "PaintOpSettings/rate"; /** * Configuration property used to control whether airbrushing is configured to ignore distance-based * spacing. */ const QString AIRBRUSH_IGNORE_SPACING = "PaintOpSettings/ignoreSpacing"; /** * Configuration property used to control whether the spacing settings can be updated between * painted dabs. */ const QString SPACING_USE_UPDATES = "PaintOpSettings/updateSpacingBetweenDabs"; /** * This class is used to cache the settings for a paintop * between two creations. There is one KisPaintOpSettings per input device (mouse, tablet, * etc...). * * The settings may be stored in a preset or a recorded brush stroke. Note that if your * paintop's settings subclass has data that is not stored as a property, that data is not * saved and restored. * * The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset * property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is * set to dirty. */ class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration { public: KisPaintOpSettings(); ~KisPaintOpSettings() override; KisPaintOpSettings(const KisPaintOpSettings &rhs); /** * */ void setOptionsWidget(KisPaintOpConfigWidget* widget); /** * This function is called by a tool when the mouse is pressed. It's useful if * the paintop needs mouse interaction for instance in the case of the clone op. * If the tool is supposed to ignore the event, the paint op should return false * and if the tool is supposed to use the event, return true. */ virtual bool mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode); /** * Clone the current settings object. Override this if your settings instance doesn't * store everything as properties. */ virtual KisPaintOpSettingsSP clone() const; /** * Removes all the settings from the object while keeping the paintop id, * which is loaded to the object by the factory */ void resetSettings(const QStringList &preserveProperties = QStringList()); /** * @return the node the paintop is working on. */ KisNodeSP node() const; /** * Call this function when the paint op is selected or the tool is activated */ virtual void activate(); /** * XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will * be completely handled in the paintop, not in the tool. This is a filthy hack to move * the option to the right place, at least. * @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops * do not support non-incremental. */ virtual bool paintIncremental() { return true; } /** * @return the composite op it to which the indirect painting device * should be initialized to. This is used by clone op to reset * the composite op to COMPOSITE_COPY */ virtual QString indirectPaintingCompositeOp() const; /** * Whether this paintop wants to deposit paint even when not moving, i.e. the tool needs to * activate its timer. If this is true, painting updates need to be generated at regular * intervals even in the absence of input device events, e.g. when the cursor is not moving. * * The default implementation checks the property AIRBRUSH_ENABLED, defaulting to false if the * property is not found. This should be suitable for most paintops. */ virtual bool isAirbrushing() const; /** * Indicates the minimum time interval that might be needed between airbrush dabs, in * milliseconds. A lower value means painting updates need to happen more frequently. This value * should be ignored if isAirbrushing() is false. * * The default implementation uses the property AIRBRUSH_RATE, defaulting to an interval of * one second if the property is not found. This should be suitable for most paintops. */ virtual qreal airbrushInterval() const; /** * Indicates whether this configuration allows spacing information to be updated between painted * dabs during a stroke. */ virtual bool useSpacingUpdates() const; /** * Indicates if the tool should call paintOp->doAsynchronousUpdate() inbetween * paintAt() calls to do the asynchronous rendering */ virtual bool needsAsynchronousUpdates() const; /** - * This enum defines the current mode for painting an outline. + * This structure defines the current mode for painting an outline. */ - enum OutlineMode { - CursorIsOutline = 1, ///< When this mode is set, an outline is painted around the cursor - CursorIsCircleOutline, - CursorNoOutline, - CursorTiltOutline, - CursorColorOutline + struct OutlineMode { + bool isVisible = false; + bool forceCircle = false; + bool showTiltDecoration = false; + bool forceFullSize = false; }; /** * Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates. * Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline * always like clone paintop indicating the duplicate position */ - virtual QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode); + virtual QPainterPath brushOutline(const KisPaintInformation &info, const OutlineMode &mode); /** * Helpers for drawing the brush outline */ static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation); /** * Helper for drawing a triangle representing the tilt of the stylus. * * @param start is the offset from the brush's outline's bounding box * @param lengthScale is used for deciding the size of the triangle. * Brush diameter or width are common choices for this. * @param angle is the angle between the two sides of the triangle. */ static QPainterPath makeTiltIndicator(KisPaintInformation const& info, QPointF const& start, qreal lengthScale, qreal angle); /** * Set paintop opacity directly in the properties */ void setPaintOpOpacity(qreal value); /** * Set paintop flow directly in the properties */ void setPaintOpFlow(qreal value); /** * Set paintop composite mode directly in the properties */ void setPaintOpCompositeOp(const QString &value); /** * @return opacity saved in the properties */ qreal paintOpOpacity(); /** * @return flow saved in the properties */ qreal paintOpFlow(); /** * @return composite mode saved in the properties */ QString paintOpCompositeOp(); /** * Set paintop size directly in the properties */ virtual void setPaintOpSize(qreal value) = 0; /** * @return size saved in the properties */ virtual qreal paintOpSize() const = 0; void setEraserMode(bool value); bool eraserMode(); qreal savedEraserSize() const; void setSavedEraserSize(qreal value); qreal savedBrushSize() const; void setSavedBrushSize(qreal value); qreal savedEraserOpacity() const; void setSavedEraserOpacity(qreal value); qreal savedBrushOpacity() const; void setSavedBrushOpacity(qreal value); QString effectivePaintOpCompositeOp(); void setPreset(KisPaintOpPresetWSP preset); KisPaintOpPresetWSP preset() const; /** * @return filename of the 3D brush model, empty if no brush is set */ virtual QString modelName() const; /** * Set filename of 3D brush model. By default no brush is set */ void setModelName(const QString & modelName); /// Check if the settings are valid, setting might be invalid through missing brushes etc /// Overwrite if the settings of a paintop can be invalid /// @return state of the settings, default implementation is true virtual bool isValid() const; /// Check if the settings are loadable, that might the case if we can fallback to something /// Overwrite if the settings can do some kind of fallback /// @return loadable state of the settings, by default implementation return the same as isValid() virtual bool isLoadable(); /** * These methods are populating properties with runtime * information about canvas rotation/mirroring. This information * is set directly by KisToolFreehand. Later the data is accessed * by the pressure options to make a final decision. */ void setCanvasRotation(qreal angle); void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored); /** * Overrides the method in KisPropertiesCofiguration to allow * onPropertyChanged() callback */ void setProperty(const QString & name, const QVariant & value) override; virtual QList uniformProperties(KisPaintOpSettingsSP settings); static bool isLodUserAllowed(const KisPropertiesConfigurationSP config); static void setLodUserAllowed(KisPropertiesConfigurationSP config, bool value); virtual bool lodSizeThresholdSupported() const; qreal lodSizeThreshold() const; void setLodSizeThreshold(qreal value); /** * @return the option widget of the paintop (can be 0 is no option widgets is set) */ KisPaintOpConfigWidget* optionsWidget() const; /** * This function is called to set random offsets to the brush whenever the mouse is clicked. It is * specific to when the pattern option is set. * */ virtual void setRandomOffset(const KisPaintInformation &paintInformation); /** * @return true if this preset demands a secondary masked brush running * alongside it */ bool hasMaskingSettings() const; /** * @return a newly created settings object representing a preset of the masking * brush that should be run alongside the current brush */ KisPaintOpSettingsSP createMaskingSettings() const; /** * @return a composite op id of the masked brush rendering algorithm. * * Please take into account that the brush itself always paints in alpha- * darken mode, but the final result is combined with this composite op. */ QString maskingBrushCompositeOp() const; protected: /** * The callback is called every time when a property changes */ virtual void onPropertyChanged(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/generator/kis_generator_layer.cpp b/libs/image/generator/kis_generator_layer.cpp index bd8a3bc2cd..a83515c885 100644 --- a/libs/image/generator/kis_generator_layer.cpp +++ b/libs/image/generator/kis_generator_layer.cpp @@ -1,167 +1,190 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_generator_layer.h" #include #include "kis_debug.h" #include #include #include "kis_selection.h" #include "filter/kis_filter_configuration.h" #include "kis_processing_information.h" #include "generator/kis_generator_registry.h" #include "generator/kis_generator.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_thread_safe_signal_compressor.h" #include "kis_recalculate_generator_layer_job.h" #define UPDATE_DELAY 100 /*ms */ struct Q_DECL_HIDDEN KisGeneratorLayer::Private { Private() : updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::FIRST_INACTIVE) { } KisThreadSafeSignalCompressor updateSignalCompressor; + QRect preparedRect; + KisFilterConfigurationSP preparedForFilter; }; KisGeneratorLayer::KisGeneratorLayer(KisImageWSP image, const QString &name, KisFilterConfigurationSP kfc, KisSelectionSP selection) : KisSelectionBasedLayer(image, name, selection, kfc, true), m_d(new Private) { connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate())); update(); } KisGeneratorLayer::KisGeneratorLayer(const KisGeneratorLayer& rhs) : KisSelectionBasedLayer(rhs), m_d(new Private) { connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate())); } KisGeneratorLayer::~KisGeneratorLayer() { } void KisGeneratorLayer::setFilter(KisFilterConfigurationSP filterConfig) { KisSelectionBasedLayer::setFilter(filterConfig); + m_d->preparedRect = QRect(); update(); } void KisGeneratorLayer::slotDelayedStaticUpdate() { /** * The mask might have been deleted from the layers stack in the * meanwhile. Just ignore the updates in the case. */ KisLayerSP parentLayer(qobject_cast(parent().data())); if (!parentLayer) return; KisImageSP image = parentLayer->image(); if (image) { image->addSpontaneousJob(new KisRecalculateGeneratorLayerJob(KisGeneratorLayerSP(this))); } } void KisGeneratorLayer::update() { + KisImageSP image = this->image().toStrongRef(); + const QRect updateRect = extent() | image->bounds(); + KisFilterConfigurationSP filterConfig = filter(); + KIS_SAFE_ASSERT_RECOVER_RETURN(filterConfig); - if (!filterConfig) { - warnImage << "BUG: No Filter configuration in KisGeneratorLayer"; - return; + if (filterConfig != m_d->preparedForFilter) { + resetCache(); } + const QRegion processRegion(QRegion(updateRect) - m_d->preparedRect); + if (processRegion.isEmpty()) return; + KisGeneratorSP f = KisGeneratorRegistry::instance()->value(filterConfig->name()); - if (!f) return; + KIS_SAFE_ASSERT_RECOVER_RETURN(f); - QRect processRect = exactBounds(); + KisPaintDeviceSP originalDevice = original(); - resetCache(); + QVector dirtyRegion; - KisPaintDeviceSP originalDevice = original(); + Q_FOREACH (const QRect &rc, processRegion) { + KisProcessingInformation dstCfg(originalDevice, + rc.topLeft(), + KisSelectionSP()); + + f->generate(dstCfg, rc.size(), filterConfig.data()); - KisProcessingInformation dstCfg(originalDevice, - processRect.topLeft(), - KisSelectionSP()); + dirtyRegion << rc; - f->generate(dstCfg, processRect.size(), filterConfig.data()); + } + m_d->preparedRect = updateRect; + m_d->preparedForFilter = filterConfig; // HACK ALERT!!! // this avoids cyclic loop with KisRecalculateGeneratorLayerJob::run() - KisSelectionBasedLayer::setDirty(QVector() << extent()); + KisSelectionBasedLayer::setDirty(dirtyRegion); } bool KisGeneratorLayer::accept(KisNodeVisitor & v) { return v.visit(this); } void KisGeneratorLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } QIcon KisGeneratorLayer::icon() const { return KisIconUtils::loadIcon("fillLayer"); } KisBaseNode::PropertyList KisGeneratorLayer::sectionModelProperties() const { KisFilterConfigurationSP filterConfig = filter(); KisBaseNode::PropertyList l = KisLayer::sectionModelProperties(); l << KisBaseNode::Property(KoID("generator", i18n("Generator")), KisGeneratorRegistry::instance()->value(filterConfig->name())->name()); return l; } void KisGeneratorLayer::setX(qint32 x) { KisSelectionBasedLayer::setX(x); + m_d->preparedRect = QRect(); m_d->updateSignalCompressor.start(); } void KisGeneratorLayer::setY(qint32 y) { KisSelectionBasedLayer::setY(y); + m_d->preparedRect = QRect(); + m_d->updateSignalCompressor.start(); +} + +void KisGeneratorLayer::resetCache() +{ + KisSelectionBasedLayer::resetCache(); + m_d->preparedRect = QRect(); m_d->updateSignalCompressor.start(); } void KisGeneratorLayer::setDirty(const QVector &rects) { KisSelectionBasedLayer::setDirty(rects); m_d->updateSignalCompressor.start(); } diff --git a/libs/image/generator/kis_generator_layer.h b/libs/image/generator/kis_generator_layer.h index 757b464d52..a0ed0545d8 100644 --- a/libs/image/generator/kis_generator_layer.h +++ b/libs/image/generator/kis_generator_layer.h @@ -1,90 +1,92 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_GENERATOR_LAYER_H_ #define KIS_GENERATOR_LAYER_H_ #include "kis_selection_based_layer.h" #include #include class KisFilterConfiguration; /** * A generator layer is a special kind of layer that can be prefilled * with some pixel pattern generated by a KisGenerator plugin. * A KisGenerator is similar to a filter, but doesn't take * input pixel data and creates new pixel data. * * It is not possible to destructively paint on a generator layer. * * XXX: what about threadedness? */ class KRITAIMAGE_EXPORT KisGeneratorLayer : public KisSelectionBasedLayer { Q_OBJECT public: /** * Create a new Generator layer with the given configuration * and selection. Note that the selection will be _copied_ * (using COW, though). */ KisGeneratorLayer(KisImageWSP image, const QString &name, KisFilterConfigurationSP kfc, KisSelectionSP selection); KisGeneratorLayer(const KisGeneratorLayer& rhs); ~KisGeneratorLayer() override; KisNodeSP clone() const override { return KisNodeSP(new KisGeneratorLayer(*this)); } void setFilter(KisFilterConfigurationSP filterConfig) override; bool accept(KisNodeVisitor &) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; QIcon icon() const override; KisBaseNode::PropertyList sectionModelProperties() const override; /** * re-run the generator. This happens over the bounds * of the associated selection. */ void update(); using KisSelectionBasedLayer::setDirty; void setDirty(const QVector &rects) override; void setX(qint32 x) override; void setY(qint32 y) override; + void resetCache() override; + private Q_SLOTS: void slotDelayedStaticUpdate(); public: // KisIndirectPaintingSupport KisLayer* layer() { return this; } private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/kis_cached_paint_device.h index e4c3b39c60..1cb038d39b 100644 --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/kis_cached_paint_device.h @@ -1,71 +1,73 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 __KIS_CACHED_PAINT_DEVICE_H #define __KIS_CACHED_PAINT_DEVICE_H #include "tiles3/kis_lockless_stack.h" +#include "kis_paint_device.h" +#include "kis_selection.h" class KisCachedPaintDevice { public: KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { KisPaintDeviceSP device; if(!m_stack.pop(device)) { device = new KisPaintDevice(prototype->colorSpace()); } device->prepareClone(prototype); return device; } void putDevice(KisPaintDeviceSP device) { device->clear(); device->setDefaultBounds(new KisDefaultBounds()); m_stack.push(device); } private: KisLocklessStack m_stack; }; class KisCachedSelection { public: KisSelectionSP getSelection() { KisSelectionSP selection; if(!m_stack.pop(selection)) { selection = new KisSelection(); } return selection; } void putSelection(KisSelectionSP selection) { selection->clear(); selection->setDefaultBounds(new KisDefaultBounds()); m_stack.push(selection); } private: KisLocklessStack m_stack; }; #endif /* __KIS_CACHED_PAINT_DEVICE_H */ diff --git a/libs/image/kis_fill_painter.cc b/libs/image/kis_fill_painter.cc index 05372cb713..b38058d5cf 100644 --- a/libs/image/kis_fill_painter.cc +++ b/libs/image/kis_fill_painter.cc @@ -1,317 +1,324 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_fill_painter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generator/kis_generator.h" #include "filter/kis_filter_configuration.h" #include "generator/kis_generator_registry.h" #include "kis_processing_information.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include #include "KoColorSpace.h" #include "kis_transaction.h" #include "kis_pixel_selection.h" #include #include #include "kis_selection_filters.h" KisFillPainter::KisFillPainter() : KisPainter() { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : KisPainter(device) { initFillPainter(); } KisFillPainter::KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection) : KisPainter(device, selection) { initFillPainter(); } void KisFillPainter::initFillPainter() { m_width = m_height = -1; m_careForSelection = false; m_sizemod = 0; m_feather = 0; m_useCompositioning = false; m_threshold = 0; } void KisFillPainter::fillSelection(const QRect &rc, const KoColor &color) { KisPaintDeviceSP fillDevice = new KisPaintDevice(device()->colorSpace()); fillDevice->setDefaultPixel(color); bitBlt(rc.topLeft(), fillDevice, rc); } // 'regular' filling // XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind, // this is more eraseToColor. void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoColor& kc, quint8 opacity) { if (w > 0 && h > 0) { // Make sure we're in the right colorspace KoColor kc2(kc); // get rid of const kc2.convertTo(device()->colorSpace()); quint8 * data = kc2.data(); device()->colorSpace()->setOpacity(data, opacity, 1); device()->fill(x1, y1, w, h, data); addDirtyRect(QRect(x1, y1, w, h)); } } -void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern) +void KisFillPainter::fillRect(const QRect &rc, const KoPattern *pattern, const QPoint &offset) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern, offset); +} + +void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern, const QPoint &offset) { if (!pattern) return; if (!pattern->valid()) return; if (!device()) return; if (w < 1) return; if (h < 1) return; KisPaintDeviceSP patternLayer = new KisPaintDevice(device()->compositionSourceColorSpace(), pattern->name()); patternLayer->convertFromQImage(pattern->pattern(), 0); - fillRect(x1, y1, w, h, patternLayer, QRect(0, 0, pattern->width(), pattern->height())); + if (!offset.isNull()) { + patternLayer->moveTo(offset); + } + + fillRect(x1, y1, w, h, patternLayer, QRect(offset.x(), offset.y(), pattern->width(), pattern->height())); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect) { - Q_ASSERT(deviceRect.x() == 0); // the case x,y != 0,0 is not yet implemented - Q_ASSERT(deviceRect.y() == 0); - int sx, sy, sw, sh; + const QRect &patternRect = deviceRect; + const QRect fillRect(x1, y1, w, h); - int y = y1; + auto toPatternLocal = [](int value, int offset, int width) { + const int normalizedValue = value - offset; + return offset + (normalizedValue >= 0 ? + normalizedValue % width : + width - (-normalizedValue - 1) % width - 1); + }; - if (y >= 0) { - sy = y % deviceRect.height(); - } else { - sy = deviceRect.height() - (((-y - 1) % deviceRect.height()) + 1); - } + int dstY = fillRect.y(); + while (dstY <= fillRect.bottom()) { + const int dstRowsRemaining = fillRect.bottom() - dstY + 1; - while (y < y1 + h) { - sh = qMin((y1 + h) - y, deviceRect.height() - sy); + const int srcY = toPatternLocal(dstY, patternRect.y(), patternRect.height()); + const int height = qMin(patternRect.height() - srcY + patternRect.y(), dstRowsRemaining); - int x = x1; + int dstX = fillRect.x(); + while (dstX <= fillRect.right()) { + const int dstColumnsRemaining = fillRect.right() - dstX + 1; - if (x >= 0) { - sx = x % deviceRect.width(); - } else { - sx = deviceRect.width() - (((-x - 1) % deviceRect.width()) + 1); - } + const int srcX = toPatternLocal(dstX, patternRect.x(), patternRect.width()); + const int width = qMin(patternRect.width() - srcX + patternRect.x(), dstColumnsRemaining); - while (x < x1 + w) { - sw = qMin((x1 + w) - x, deviceRect.width() - sx); + bitBlt(dstX, dstY, device, srcX, srcY, width, height); - bitBlt(x, y, device, sx, sy, sw, sh); - x += sw; sx = 0; + dstX += width; } - - y += sh; sy = 0; + dstY += height; } addDirtyRect(QRect(x1, y1, w, h)); } void KisFillPainter::fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator) { if (!generator) return; KisGeneratorSP g = KisGeneratorRegistry::instance()->value(generator->name()); if (!device()) return; if (w < 1) return; if (h < 1) return; QRect tmpRc(x1, y1, w, h); KisProcessingInformation dstCfg(device(), tmpRc.topLeft(), 0); g->generate(dstCfg, tmpRc.size(), generator); addDirtyRect(tmpRc); } // flood filling void KisFillPainter::fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice) { if (!m_useCompositioning) { if (m_sizemod || m_feather || compositeOp()->id() != COMPOSITE_OVER || opacity() != MAX_SELECTED || sourceDevice != device()) { warnKrita << "WARNING: Fast Flood Fill (no compositioning mode)" << "does not support compositeOps, opacity, " << "selection enhancements and separate source " << "devices"; } QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); if (!fillBoundsRect.contains(startPoint)) return; KisScanlineFill gc(device(), startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillColor(paintColor()); } else { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(0, 0, m_width, m_height, paintColor()); painter.end(); genericFillEnd(filled); } } void KisFillPainter::fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice) { genericFillStart(startX, startY, sourceDevice); // Now create a layer and fill it KisPaintDeviceSP filled = device()->createCompositionSourceDevice(); Q_CHECK_PTR(filled); KisFillPainter painter(filled); painter.fillRect(0, 0, m_width, m_height, pattern()); painter.end(); genericFillEnd(filled); } void KisFillPainter::genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice) { Q_ASSERT(m_width > 0); Q_ASSERT(m_height > 0); // Create a selection from the surrounding area m_fillSelection = createFloodSelection(startX, startY, sourceDevice); } void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) { if (progressUpdater() && progressUpdater()->interrupted()) { m_width = m_height = -1; return; } // TODO: filling using the correct bound of the selection would be better, *but* // the selection is limited to the exact bound of a layer, while in reality, we don't // want that, since we want a transparent layer to be completely filled // QRect rc = m_fillSelection->selectedExactRect(); /** * Apply the real selection to a filled one */ KisSelectionSP realSelection = selection(); if (realSelection) { m_fillSelection->pixelSelection()->applySelection( realSelection->projection(), SELECTION_INTERSECT); } setSelection(m_fillSelection); bitBlt(0, 0, filled, 0, 0, m_width, m_height); setSelection(realSelection); if (progressUpdater()) progressUpdater()->setProgress(100); m_width = m_height = -1; } KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice) { if (m_width < 0 || m_height < 0) { if (selection() && m_careForSelection) { QRect rc = selection()->selectedExactRect(); m_width = rc.width() - (startX - rc.x()); m_height = rc.height() - (startY - rc.y()); } } dbgImage << "Width: " << m_width << " Height: " << m_height; // Otherwise the width and height should have been set Q_ASSERT(m_width > 0 && m_height > 0); QRect fillBoundsRect(0, 0, m_width, m_height); QPoint startPoint(startX, startY); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(device())); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!fillBoundsRect.contains(startPoint)) { return selection; } KisScanlineFill gc(sourceDevice, startPoint, fillBoundsRect); gc.setThreshold(m_threshold); gc.fillSelection(pixelSelection); if (m_sizemod > 0) { KisGrowSelectionFilter biggy(m_sizemod, m_sizemod); biggy.process(pixelSelection, selection->selectedRect().adjusted(-m_sizemod, -m_sizemod, m_sizemod, m_sizemod)); } else if (m_sizemod < 0) { KisShrinkSelectionFilter tiny(-m_sizemod, -m_sizemod, false); tiny.process(pixelSelection, selection->selectedRect()); } if (m_feather > 0) { KisFeatherSelectionFilter feathery(m_feather); feathery.process(pixelSelection, selection->selectedRect().adjusted(-m_feather, -m_feather, m_feather, m_feather)); } return selection; } diff --git a/libs/image/kis_fill_painter.h b/libs/image/kis_fill_painter.h index b6132d6a9c..4f1648a60e 100644 --- a/libs/image/kis_fill_painter.h +++ b/libs/image/kis_fill_painter.h @@ -1,285 +1,281 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_FILL_PAINTER_H_ #define KIS_FILL_PAINTER_H_ #include #include "KoColor.h" #include "KoColorSpaceRegistry.h" #include "kis_painter.h" #include "kis_types.h" #include "kis_selection.h" #include class KoPattern; class KisFilterConfiguration; // XXX: Filling should set dirty rect. /** * This painter can be used to fill paint devices in different ways. This can also be used * for flood filling related operations. */ class KRITAIMAGE_EXPORT KisFillPainter : public KisPainter { public: /** * Construct an empty painter. Use the begin(KisPaintDeviceSP) method to attach * to a paint device */ KisFillPainter(); /** * Start painting on the specified paint device */ KisFillPainter(KisPaintDeviceSP device); KisFillPainter(KisPaintDeviceSP device, KisSelectionSP selection); private: void initFillPainter(); public: /** * Fill a rectangle with black transparent pixels (0, 0, 0, 0 for RGBA). */ void eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h); /** * Overloaded version of the above function. */ void eraseRect(const QRect& rc); /** * Fill current selection of KisPainter with a specified \p color. * * The filling rect is limited by \p rc to allow multithreaded * filling/processing. */ void fillSelection(const QRect &rc, const KoColor &color); /** * Fill a rectangle with a certain color. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c); /** * Fill a rectangle with a certain color and opacity. */ void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c, quint8 opacity); /** * Overloaded version of the above function. */ void fillRect(const QRect& rc, const KoColor& c, quint8 opacity); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. */ - void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern); + void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KoPattern * pattern, const QPoint &offset = QPoint()); /** * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the * entire rectangle. */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisPaintDeviceSP device, const QRect& deviceRect); /** * Overloaded version of the above function. */ - void fillRect(const QRect& rc, const KoPattern * pattern); + void fillRect(const QRect& rc, const KoPattern * pattern, const QPoint &offset = QPoint()); /** * Fill the specified area with the output of the generator plugin that is configured * in the generator parameter */ void fillRect(qint32 x1, qint32 y1, qint32 w, qint32 h, const KisFilterConfigurationSP generator); /** * Fills the enclosed area around the point with the set color. If * there is a selection, the whole selection is filled. Note that * you must have set the width and height on the painter if you * don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ void fillColor(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Fills the enclosed area around the point with the set pattern. * If there is a selection, the whole selection is filled. Note * that you must have set the width and height on the painter if * you don't have a selection. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ void fillPattern(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Returns a selection mask for the floodfill starting at the specified position. * * @param startX the X position where the floodfill starts * @param startY the Y position where the floodfill starts * @param sourceDevice the sourceDevice that determines the area that * is floodfilled if sampleMerged is on */ KisSelectionSP createFloodSelection(int startX, int startY, KisPaintDeviceSP sourceDevice); /** * Set the threshold for floodfill. The range is 0-255: 0 means the fill will only * fill parts that are the exact same color, 255 means anything will be filled */ void setFillThreshold(int threshold); /** Returns the fill threshold, see setFillThreshold for details */ int fillThreshold() const { return m_threshold; } bool useCompositioning() const { return m_useCompositioning; } void setUseCompositioning(bool useCompositioning) { m_useCompositioning = useCompositioning; } /** Sets the width of the paint device */ void setWidth(int w) { m_width = w; } /** Sets the height of the paint device */ void setHeight(int h) { m_height = h; } /** If true, floodfill doesn't fill outside the selected area of a layer */ bool careForSelection() const { return m_careForSelection; } /** Set caring for selection. See careForSelection for details */ void setCareForSelection(bool set) { m_careForSelection = set; } /** Sets the auto growth/shrinking radius */ void setSizemod(int sizemod) { m_sizemod = sizemod; } /** Sets how much to auto-grow or shrink (if @param sizemod is negative) the selection flood before painting, this affects every fill operation except fillRect */ int sizemod() { return m_sizemod; } /** Sets feathering radius */ void setFeather(int feather) { m_feather = feather; } /** defines the feathering radius for selection flood operations, this affects every fill operation except fillRect */ uint feather() { return m_feather; } private: // for floodfill void genericFillStart(int startX, int startY, KisPaintDeviceSP sourceDevice); void genericFillEnd(KisPaintDeviceSP filled); KisSelectionSP m_fillSelection; int m_feather; int m_sizemod; int m_threshold; int m_width, m_height; QRect m_rect; bool m_careForSelection; bool m_useCompositioning; }; inline void KisFillPainter::fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor& c) { fillRect(x, y, w, h, c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_OPAQUE_U8); } inline void KisFillPainter::eraseRect(qint32 x1, qint32 y1, qint32 w, qint32 h) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(x1, y1, w, h, c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::eraseRect(const QRect& rc) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor c(Qt::black, cs); fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_TRANSPARENT_U8); } inline void KisFillPainter::fillRect(const QRect& rc, const KoColor& c, quint8 opacity) { fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, opacity); } -inline -void KisFillPainter::fillRect(const QRect& rc, const KoPattern* pattern) -{ - fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern); -} + inline void KisFillPainter::setFillThreshold(int threshold) { m_threshold = threshold; } #endif //KIS_FILL_PAINTER_H_ diff --git a/libs/image/kis_gaussian_kernel.cpp b/libs/image/kis_gaussian_kernel.cpp index f8955904af..0788240921 100644 --- a/libs/image/kis_gaussian_kernel.cpp +++ b/libs/image/kis_gaussian_kernel.cpp @@ -1,233 +1,318 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_gaussian_kernel.h" #include "kis_global.h" #include "kis_convolution_kernel.h" #include #include qreal KisGaussianKernel::sigmaFromRadius(qreal radius) { return 0.3 * radius + 0.3; } int KisGaussianKernel::kernelSizeFromRadius(qreal radius) { return 6 * ceil(sigmaFromRadius(radius)) + 1; } Eigen::Matrix KisGaussianKernel::createHorizontalMatrix(qreal radius) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(1, kernelSize); const qreal sigma = sigmaFromRadius(radius); const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma)); const qreal exponentMultiplicand = 1 / (2 * sigma * sigma); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int x = 0; x < kernelSize; x++) { qreal xDistance = center - x; matrix(0, x) = multiplicand * exp( -xDistance * xDistance * exponentMultiplicand ); } return matrix; } Eigen::Matrix KisGaussianKernel::createVerticalMatrix(qreal radius) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, 1); const qreal sigma = sigmaFromRadius(radius); const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma)); const qreal exponentMultiplicand = 1 / (2 * sigma * sigma); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { qreal yDistance = center - y; matrix(y, 0) = multiplicand * exp( -yDistance * yDistance * exponentMultiplicand ); } return matrix; } KisConvolutionKernelSP KisGaussianKernel::createHorizontalKernel(qreal radius) { Eigen::Matrix matrix = createHorizontalMatrix(radius); return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } KisConvolutionKernelSP KisGaussianKernel::createVerticalKernel(qreal radius) { Eigen::Matrix matrix = createVerticalMatrix(radius); return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } void KisGaussianKernel::applyGaussian(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); if (xRadius > 0.0 && yRadius > 0.0) { KisPaintDeviceSP interm = new KisPaintDevice(device->colorSpace()); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius); qreal verticalCenter = qreal(kernelVertical->height()) / 2.0; KisConvolutionPainter horizPainter(interm); horizPainter.setChannelFlags(channelFlags); horizPainter.setProgress(progressUpdater); horizPainter.applyMatrix(kernelHoriz, device, srcTopLeft - QPoint(0, ceil(verticalCenter)), srcTopLeft - QPoint(0, ceil(verticalCenter)), rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); KisConvolutionPainter verticalPainter(device); verticalPainter.setChannelFlags(channelFlags); verticalPainter.setProgress(progressUpdater); verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } else if (xRadius > 0.0) { KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius); painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } else if (yRadius > 0.0) { KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius); painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } } Eigen::Matrix KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff) { int kernelSize = 4 * std::ceil(radius) + 1; Eigen::Matrix matrix(kernelSize, kernelSize); const qreal sigma = radius/* / sqrt(2)*/; const qreal multiplicand = -1.0 / (M_PI * pow2(pow2(sigma))); const qreal exponentMultiplicand = 1 / (2 * pow2(sigma)); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { const qreal yDistance = center - y; for (int x = 0; x < kernelSize; x++) { const qreal xDistance = center - x; const qreal distance = pow2(xDistance) + pow2(yDistance); const qreal normalizedDistance = exponentMultiplicand * distance; matrix(x, y) = multiplicand * (1.0 - normalizedDistance) * exp(-normalizedDistance); } } qreal lateral = matrix.sum() - matrix(center, center); matrix(center, center) = -lateral; qreal positiveSum = 0; qreal sideSum = 0; qreal quarterSum = 0; for (int y = 0; y < kernelSize; y++) { for (int x = 0; x < kernelSize; x++) { const qreal value = matrix(x, y); if (value > 0) { positiveSum += value; } if (x > center) { sideSum += value; } if (x > center && y > center) { quarterSum += value; } } } const qreal scale = coeff * 2.0 / positiveSum; matrix *= scale; positiveSum *= scale; sideSum *= scale; quarterSum *= scale; //qDebug() << ppVar(positiveSum) << ppVar(sideSum) << ppVar(quarterSum); return matrix; } void KisGaussianKernel::applyLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); Eigen::Matrix matrix = createLoGMatrix(radius, coeff); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(matrix, 0, 0); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } + +Eigen::Matrix KisGaussianKernel::createDilateMatrix(qreal radius) +{ + int kernelSize = 2 * std::ceil(radius) + 1; + Eigen::Matrix matrix(kernelSize, kernelSize); + + + struct UnionFade { + quint8 value(qreal) const { + return 255; + } + }; + + const qreal fadeStart = radius - 1.0; + + /** + * The kernel size should always be odd, then the position of the + * central pixel can be easily calculated + */ + KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); + const int center = kernelSize / 2; + + for (int y = 0; y < kernelSize; y++) { + const qreal yDistance = center - y; + for (int x = 0; x < kernelSize; x++) { + const qreal xDistance = center - x; + + const qreal distance = std::sqrt(pow2(xDistance) + pow2(yDistance)); + + qreal value = 1.0; + + if (distance >= radius) { + value = 0.0; + } else if (distance > fadeStart) { + value = radius - distance; + } + + matrix(x, y) = value; + } + } + + return matrix; +} + +void KisGaussianKernel::applyDilate(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater) +{ + QPoint srcTopLeft = rect.topLeft(); + + KisConvolutionPainter painter(device); + painter.setChannelFlags(channelFlags); + painter.setProgress(progressUpdater); + + Eigen::Matrix matrix = createDilateMatrix(radius); + KisConvolutionKernelSP kernel = + KisConvolutionKernel::fromMatrix(matrix, + 0, + 0); + + painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); +} + +#include "kis_sequential_iterator.h" + +void KisGaussianKernel::applyErodeU8(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1); + + { + KisSequentialIterator dstIt(device, rect); + while (dstIt.nextPixel()) { + quint8 *dstPtr = dstIt.rawData(); + *dstPtr = 255 - *dstPtr; + } + } + + applyDilate(device, rect, radius, channelFlags, progressUpdater); + + { + KisSequentialIterator dstIt(device, rect); + while (dstIt.nextPixel()) { + quint8 *dstPtr = dstIt.rawData(); + *dstPtr = 255 - *dstPtr; + } + } +} diff --git a/libs/image/kis_gaussian_kernel.h b/libs/image/kis_gaussian_kernel.h index e25005d162..ef145a032f 100644 --- a/libs/image/kis_gaussian_kernel.h +++ b/libs/image/kis_gaussian_kernel.h @@ -1,63 +1,77 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 __KIS_GAUSSIAN_KERNEL_H #define __KIS_GAUSSIAN_KERNEL_H #include "kritaimage_export.h" #include "kis_types.h" #include class QRect; class KRITAIMAGE_EXPORT KisGaussianKernel { public: static Eigen::Matrix createHorizontalMatrix(qreal radius); static Eigen::Matrix createVerticalMatrix(qreal radius); static KisConvolutionKernelSP createHorizontalKernel(qreal radius); static KisConvolutionKernelSP createVerticalKernel(qreal radius); static qreal sigmaFromRadius(qreal radius); static int kernelSizeFromRadius(qreal radius); static void applyGaussian(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *updater); static Eigen::Matrix createLoGMatrix(qreal radius, qreal coeff = 1.0); static void applyLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater); + + static Eigen::Matrix createDilateMatrix(qreal radius); + + static void applyDilate(KisPaintDeviceSP device, + const QRect& rect, + qreal radius, + const QBitArray &channelFlags, + KoUpdater *progressUpdater); + + static void applyErodeU8(KisPaintDeviceSP device, + const QRect& rect, + qreal radius, + const QBitArray &channelFlags, + KoUpdater *progressUpdater); }; #endif /* __KIS_GAUSSIAN_KERNEL_H */ diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index 6663aade57..014aef52ab 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,521 +1,588 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #ifdef Q_OS_OSX #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config( KSharedConfig::openConfig()->group(QString())), m_readOnly(readOnly) { #ifdef Q_OS_OSX // clear /var/folders/ swap path set by old broken Krita swap implementation in order to use new default swap dir. QString swap = m_config.readEntry("swaplocation", ""); if (swap.startsWith("/var/folders/")) { m_config.deleteEntry("swaplocation"); } #endif } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryPoolLimitPercent", 0.0) : 0.0; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } -QString KisImageConfig::swapDir(bool requestDefault) +QString KisImageConfig::safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const { #ifdef Q_OS_OSX // On OSX, QDir::tempPath() gives us a folder we cannot reply upon (usually // something like /var/folders/.../...) and that will have vanished when we // try to create the tmp file in KisMemoryWindow::KisMemoryWindow using // swapFileTemplate. thus, we just pick the home folder if swapDir does not // tell us otherwise. // the other option here would be to use a "garbled name" temp file (i.e. no name // KRITA_SWAP_FILE_XXXXXX) in an obscure /var/folders place, which is not // nice to the user. having a clearly named swap file in the home folder is // much nicer to Krita's users. // furthermore, this is just a default and swapDir can always be configured // to another location. - QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/swap"; + QString swap = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QDir::separator() + suffix; #else + Q_UNUSED(suffix); QString swap = QDir::tempPath(); #endif if (requestDefault) { return swap; } - QString configuredSwap = m_config.readEntry("swaplocation", swap); + const QString configuredSwap = m_config.readEntry(configKey, swap); if (!configuredSwap.isEmpty()) { swap = configuredSwap; } return swap; } + +QString KisImageConfig::swapDir(bool requestDefault) +{ + return safelyGetWritableTempLocation("swap", "swaplocation", requestDefault); +} + void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } void KisImageConfig::setLazyFrameCreationEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_OSX #include #include #endif #include int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_OSX int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } QColor def(Qt::green); m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } int KisImageConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_config.readEntry("maxNumberOfThreads", QThread::idealThreadCount())); } void KisImageConfig::setMaxNumberOfThreads(int value) { if (value == QThread::idealThreadCount()) { m_config.deleteEntry("maxNumberOfThreads"); } else { m_config.writeEntry("maxNumberOfThreads", value); } } int KisImageConfig::frameRenderingClones(bool defaultValue) const { const int defaultClonesCount = qMax(1, maxNumberOfThreads(defaultValue) / 2); return defaultValue ? defaultClonesCount : m_config.readEntry("frameRenderingClones", defaultClonesCount); } void KisImageConfig::setFrameRenderingClones(int value) { m_config.writeEntry("frameRenderingClones", value); } int KisImageConfig::fpsLimit(bool defaultValue) const { return defaultValue ? 100 : m_config.readEntry("fpsLimit", 100); } void KisImageConfig::setFpsLimit(int value) { m_config.writeEntry("fpsLimit", value); } + +bool KisImageConfig::useOnDiskAnimationCacheSwapping(bool defaultValue) const +{ + return defaultValue ? true : m_config.readEntry("useOnDiskAnimationCacheSwapping", true); +} + +void KisImageConfig::setUseOnDiskAnimationCacheSwapping(bool value) +{ + m_config.writeEntry("useOnDiskAnimationCacheSwapping", value); +} + +QString KisImageConfig::animationCacheDir(bool defaultValue) const +{ + return safelyGetWritableTempLocation("animation_cache", "animationCacheDir", defaultValue); +} + +void KisImageConfig::setAnimationCacheDir(const QString &value) +{ + m_config.writeEntry("animationCacheDir", value); +} + +bool KisImageConfig::useAnimationCacheFrameSizeLimit(bool defaultValue) const +{ + return defaultValue ? true : m_config.readEntry("useAnimationCacheFrameSizeLimit", true); +} + +void KisImageConfig::setUseAnimationCacheFrameSizeLimit(bool value) +{ + m_config.writeEntry("useAnimationCacheFrameSizeLimit", value); +} + +int KisImageConfig::animationCacheFrameSizeLimit(bool defaultValue) const +{ + return defaultValue ? 2500 : m_config.readEntry("animationCacheFrameSizeLimit", 2500); +} + +void KisImageConfig::setAnimationCacheFrameSizeLimit(int value) +{ + m_config.writeEntry("animationCacheFrameSizeLimit", value); +} + +bool KisImageConfig::useAnimationCacheRegionOfInterest(bool defaultValue) const +{ + return defaultValue ? true : m_config.readEntry("useAnimationCacheRegionOfInterest", true); +} + +void KisImageConfig::setUseAnimationCacheRegionOfInterest(bool value) +{ + m_config.writeEntry("useAnimationCacheRegionOfInterest", value); +} + +qreal KisImageConfig::animationCacheRegionOfInterestMargin(bool defaultValue) const +{ + return defaultValue ? 0.25 : m_config.readEntry("animationCacheRegionOfInterestMargin", 0.25); +} + +void KisImageConfig::setAnimationCacheRegionOfInterestMargin(qreal value) +{ + m_config.writeEntry("animationCacheRegionOfInterestMargin", value); +} diff --git a/libs/image/kis_image_config.h b/libs/image/kis_image_config.h index b701c1d2ab..78ba9f02b5 100644 --- a/libs/image/kis_image_config.h +++ b/libs/image/kis_image_config.h @@ -1,133 +1,153 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_IMAGE_CONFIG_H_ #define KIS_IMAGE_CONFIG_H_ #include #include "kritaimage_export.h" #include "KisProofingConfiguration.h" #include "kis_types.h" class KRITAIMAGE_EXPORT KisImageConfig { public: KisImageConfig(bool readOnly = false); ~KisImageConfig(); bool enableProgressReporting(bool requestDefault = false) const; void setEnableProgressReporting(bool value); bool enablePerfLog(bool requestDefault = false) const; void setEnablePerfLog(bool value); qreal transformMaskOffBoundsReadArea() const; int updatePatchHeight() const; void setUpdatePatchHeight(int value); int updatePatchWidth() const; void setUpdatePatchWidth(int value); qreal maxCollectAlpha() const; qreal maxMergeAlpha() const; qreal maxMergeCollectAlpha() const; qreal schedulerBalancingRatio() const; void setSchedulerBalancingRatio(qreal value); int maxSwapSize(bool requestDefault = false) const; void setMaxSwapSize(int value); int swapSlabSize() const; void setSwapSlabSize(int value); int swapWindowSize() const; void setSwapWindowSize(int value); int tilesHardLimit() const; // MiB int tilesSoftLimit() const; // MiB int poolLimit() const; // MiB qreal memoryHardLimitPercent(bool requestDefault = false) const; // % of total RAM qreal memorySoftLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() * (1 - 0.01 * memoryPoolLimitPercent()) qreal memoryPoolLimitPercent(bool requestDefault = false) const; // % of memoryHardLimitPercent() void setMemoryHardLimitPercent(qreal value); void setMemorySoftLimitPercent(qreal value); void setMemoryPoolLimitPercent(qreal value); static int totalRAM(); // MiB /** * @return a specific directory for the swapfile, if set. If not set, return an * empty QString and use the default KDE directory. */ QString swapDir(bool requestDefault = false); void setSwapDir(const QString &swapDir); int numberOfOnionSkins() const; void setNumberOfOnionSkins(int value); int onionSkinTintFactor() const; void setOnionSkinTintFactor(int value); int onionSkinOpacity(int offset) const; void setOnionSkinOpacity(int offset, int value); bool onionSkinState(int offset) const; void setOnionSkinState(int offset, bool value); QColor onionSkinTintColorBackward() const; void setOnionSkinTintColorBackward(const QColor &value); QColor onionSkinTintColorForward() const; void setOnionSkinTintColorForward(const QColor &value); bool lazyFrameCreationEnabled(bool requestDefault = false) const; void setLazyFrameCreationEnabled(bool value); bool showAdditionalOnionSkinsSettings(bool requestDefault = false) const; void setShowAdditionalOnionSkinsSettings(bool value); int defaultFrameColorLabel() const; void setDefaultFrameColorLabel(int label); KisProofingConfigurationSP defaultProofingconfiguration(); void setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState); bool useLodForColorizeMask(bool requestDefault = false) const; void setUseLodForColorizeMask(bool value); int maxNumberOfThreads(bool defaultValue = false) const; void setMaxNumberOfThreads(int value); int frameRenderingClones(bool defaultValue = false) const; void setFrameRenderingClones(int value); int fpsLimit(bool defaultValue = false) const; void setFpsLimit(int value); + bool useOnDiskAnimationCacheSwapping(bool defaultValue = false) const; + void setUseOnDiskAnimationCacheSwapping(bool value); + + QString animationCacheDir(bool defaultValue = false) const; + void setAnimationCacheDir(const QString &value); + + bool useAnimationCacheFrameSizeLimit(bool defaultValue = false) const; + void setUseAnimationCacheFrameSizeLimit(bool value); + + int animationCacheFrameSizeLimit(bool defaultValue = false) const; + void setAnimationCacheFrameSizeLimit(int value); + + bool useAnimationCacheRegionOfInterest(bool defaultValue = false) const; + void setUseAnimationCacheRegionOfInterest(bool value); + + qreal animationCacheRegionOfInterestMargin(bool defaultValue = false) const; + void setAnimationCacheRegionOfInterestMargin(qreal value); + private: Q_DISABLE_COPY(KisImageConfig) + QString safelyGetWritableTempLocation(const QString &suffix, const QString &configKey, bool requestDefault) const; + private: KConfigGroup m_config; bool m_readOnly; }; #endif /* KIS_IMAGE_CONFIG_H_ */ diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index 4ae1b4cf26..11fc3432fa 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1471 +1,1470 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_layer_utils.h" #include #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "commands/kis_image_change_visibility_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include "commands/kis_node_compositeop_command.h" #include #include "krita_utils.h" namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = qobject_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; bool useInTimeline = false; bool enableOnionSkins = false; virtual KisNodeList allSrcNodes() = 0; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(currLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); paintLayer = qobject_cast(prevLayer.data()); if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled(); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); useInTimeline |= node->useInTimeline(); const KisPaintLayer *paintLayer = qobject_cast(node.data()); if (paintLayer) { enableOnionSkins |= paintLayer->onionSkinEnabled(); } } } KisNodeList mergedNodes; bool nodesCompositingVaries = false; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand { DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { /** * We disable extra compositing only in case all the layers have * the same compositing properties, therefore, we can just sum them using * Normal blend mode */ if (m_info->nodesCompositingVaries) return; // we should disable dirty requests on **redo only**, otherwise // the state of the layers will not be recovered on undo m_info->image->disableDirtyRequests(); Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (node->compositeOpId() != COMPOSITE_OVER) { addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER)); } if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::inheritAlpha, false); addCommand(new KisNodePropertyListCommand(node, props)); } } m_info->image->enableDirtyRequests(); } private: MergeMultipleInfoSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { forceAllDelayedNodesUpdate(node); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } m_info->dstNode->setUseInTimeline(m_info->useInTimeline); KisPaintLayer *dstPaintLayer = qobject_cast(m_info->dstNode.data()); if (dstPaintLayer) { dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); m_info->dstNode = dstPaintLayer; if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } auto channelFlagsLazy = [](KisNodeSP node) { KisLayer *layer = dynamic_cast(node.data()); return layer ? layer->channelFlags() : QBitArray(); }; QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; bool isFirstCycle = true; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (isFirstCycle) { compositeOpId = node->compositeOpId(); channelFlags = channelFlagsLazy(node); isFirstCycle = false; } else if (compositeOpId != node->compositeOpId() || channelFlags != channelFlagsLazy(node)) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } m_info->nodesCompositingVaries = compositionVaries; m_info->dstNode->setUseInTimeline(m_info->useInTimeline); dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins); } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized descendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::end() { KisImageSignalType type; if (isFinalizing()) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } KisLayerSP constructDefaultLayer(KisImageSP image) { return new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, false, true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = constructDefaultLayer(image); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node -- checking // whether the parent is going to be deleted parent = putAfter->parent(); while (parent && nodesToDelete.contains(parent)) { parent = parent->parent(); } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { KisNodeSP oldRoot = m_info->image->root(); KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { if (parent == m_putAfter->parent()) { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, true, false)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, parent->lastChild(), true, false)); } /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } KisNodeList safeNodesToDelete = m_info->allSrcNodes(); for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) { KisNodeSP node = *it; if (node->userLocked() && node->visible()) { addCommand(new KisImageChangeVisibilityCommand(false, node)); } } KritaUtils::filterContainer(safeNodesToDelete, [this](KisNodeSP node) { return !node->userLocked(); }); safeRemoveMultipleNodes(safeNodesToDelete, m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::init() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::end() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd); applyKeyframeColorLabel(keyframe); addCommand(cmd); } void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) { Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) { Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisKeyframeSP keyframe = channel->keyframeAt(m_frame); if (!keyframe.isNull() && keyframe->colorLabel() != 0) { dstKeyframe->setColorLabel(keyframe->colorLabel()); return; } } } dstKeyframe->setColorLabel(0); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // NOTE: shape layer may have emitted spontaneous jobs during layer creation, // wait for them to complete! applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass trhough only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { - - qDebug() << "Skipping node" << ppVar((*it)->name()); + //qDebug() << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible() || node->userLocked()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); applicator.applyCommand(new DisableExtraCompositing(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, 0, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::end() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } void recursiveApplyNodes(KisNodeSP node, std::function func) { func(node); node = node->firstChild(); while (node) { recursiveApplyNodes(node, func); node = node->nextSibling(); } } KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } void forceAllDelayedNodesUpdate(KisNodeSP root) { KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } } diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index 83f0f363e3..a331359180 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2172 +1,2208 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); bool assignProfile(const KoColorProfile * profile); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; + void updateLodDataManager(KisDataManager *srcDataManager, + KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, + const QRect &originalRect, int lod); + + void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); + void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0); Data *srcData = currentNonLodData(); Data *lodData = new Data(srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } -void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) +void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager, + KisDataManager *dstDataManager, + const QPoint &srcOffset, + const QPoint &dstOffset, + const QRect &originalRect, + int lod) { - LodDataStructImpl *dst = dynamic_cast(_dst); - KIS_SAFE_ASSERT_RECOVER_RETURN(dst); - - Data *lodData = dst->lodData.data(); - Data *srcData = currentNonLodData(); - - const int lod = lodData->levelOfDetail(); const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); - const int pixelSize = srcData->dataManager()->pixelSize(); + const int pixelSize = srcDataManager->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } - InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect); - InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect); + InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect); + InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } +void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) +{ + LodDataStructImpl *dst = dynamic_cast(_dst); + KIS_SAFE_ASSERT_RECOVER_RETURN(dst); + + Data *lodData = dst->lodData.data(); + Data *srcData = currentNonLodData(); + + const int lod = lodData->levelOfDetail(); + + updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(), + QPoint(srcData->x(), srcData->y()), + QPoint(lodData->x(), lodData->y()), + originalRect, lod); +} + +void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst)); + + Data *srcData = currentNonLodData(); + updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(), + QPoint(srcData->x(), srcData->y()), + QPoint(dst->x(), dst->y()), + originalRect, lod); +} + void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { class DeviceChangeColorSpaceCommand : public KUndo2Command { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) : m_firstRun(true), m_device(device) { } void emitNotifications() { m_device->emitColorSpaceChanged(); m_device->setDirty(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } private: bool m_firstRun; KisPaintDeviceSP m_device; }; KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q); QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } if (!parentCommand->childCount()) { delete parentCommand; parentCommand = 0; } else { q->emitColorSpaceChanged(); } return parentCommand; } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags); return command; } bool KisPaintDevice::setProfile(const KoColorProfile * profile) { return m_d->assignProfile(profile); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds(); if (r.isValid()) { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { Q_ASSERT(m_d->contentChannel); return m_d->contentChannel.data(); } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } +void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) +{ + m_d->generateLodCloneDevice(dst, originalRect, lod); +} + KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h index 674ee3138d..9a4c833391 100644 --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -1,878 +1,880 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisDataManager; class KisPaintDeviceWriter; class KisKeyframe; class KisRasterKeyframeChannel; class KisPaintDeviceFramesInterface; typedef KisSharedPtr KisDataManagerSP; namespace KritaUtils { enum DeviceCopyMode { CopySnapshot = 0, CopyAllFrames }; } /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x, y position * (it is not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString()); /** * Creates a copy of this device. * * If \p copyMode is CopySnapshot, the newly created device clones the * current frame of \p rhs only (default and efficient * behavior). If \p copyFrames is CopyAllFrames, the new device is a deep * copy of the source with all the frames included. */ KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); ~KisPaintDevice() override; protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel is not completely transparent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (no incremental move) */ void moveTo(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void moveTo(const QPoint& pt); /** * Return an X,Y offset of the device in a convenient form */ QPoint offset() const; /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convenience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *

    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Relaxed version of the exactBounds() that can be used in tight * loops. If the exact bounds value is present in the paint * device cache, returns this value. If the cache is invalidated, * returns extent() and tries to recalculate the exact bounds not * faster than once in 1000 ms. */ QRect exactBoundsAmortized() const; /** * Returns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ QRegion region() const; /** * The slow version of region() that searches for exact bounds of * each rectangle in the region */ QRegion regionExact() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convenience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the image will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. * If frame ID is given, set default pixel for that frame. Otherwise use active frame. */ void setDefaultPixel(const KoColor &defPixel); /** * Get a pointer to the default pixel. * If the frame parameter is given, get the default pixel of * specified frame. Otherwise use currently active frame. */ KoColor defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt opreration. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coinside, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace * * @return a command that can be used to undo the conversion. */ KUndo2Command* convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ bool setProfile(const KoColorProfile * profile); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param srcProfileName name of the RGB profile to interpret the image as. 0 is interpreted as sRGB */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). */ QImage convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * @param oversample: ratio used for antialiasing */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convenience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * transaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; /** * Create a keyframe channel for the content on this device. * @param id identifier for the channel * @param node the parent node for the channel * @return keyframe channel */ KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id); KisRasterKeyframeChannel* keyframeChannel() const; /** * An interface to modify/load/save frames stored inside this device */ KisPaintDeviceFramesInterface* framesInterface(); public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); /** * Add the specified region to the parent layer's dirty region * (if there is a parent layer) */ void setDirty(const QRegion & region); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector rects); /** * Called by KisTransactionData when it thinks current time should * be changed. And the requests is forwarded to the image if * needed. */ void requestTimeSwitch(int time); /** * \return a sequence number corresponding to the current paint * device state. Every time the paint device is changed, * the sequence number is increased */ int sequenceNumber() const; void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const; public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y); KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param rc indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param rc indicates the rectangle that trully contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. * @param selection an up-to-date selection that has the same origin as the paint device */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject() override; }; static MemoryReleaseObject* createMemoryReleaseObject(); public: struct LodDataStruct { virtual ~LodDataStruct(); }; QRegion regionForLodSyncing() const; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); + void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); + void setProjectionDevice(bool value); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; void emitColorSpaceChanged(); void emitProfileChanged(); private: friend class KisPaintDeviceFramesInterface; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/libs/image/kis_painter_blt_multi_fixed.cpp b/libs/image/kis_painter_blt_multi_fixed.cpp index 5db6f4d43a..d1cc9f71a4 100644 --- a/libs/image/kis_painter_blt_multi_fixed.cpp +++ b/libs/image/kis_painter_blt_multi_fixed.cpp @@ -1,206 +1,206 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_painter.h" #include "kis_painter_p.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_random_accessor_ng.h" #include "KisRenderedDab.h" void KisPainter::Private::applyDevice(const QRect &applyRect, const KisRenderedDab &dab, KisRandomAccessorSP dstIt, const KoColorSpace *srcColorSpace, KoCompositeOp::ParameterInfo &localParamInfo) { const QRect dabRect = dab.realBounds(); const QRect rc = applyRect & dabRect; const int srcPixelSize = srcColorSpace->pixelSize(); const int dabRowStride = srcPixelSize * dabRect.width(); qint32 dstY = rc.y(); qint32 rowsRemaining = rc.height(); while (rowsRemaining > 0) { qint32 dstX = rc.x(); qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(rowsRemaining, numContiguousDstRows); qint32 columnsRemaining = rc.width(); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); localParamInfo.dstRowStart = dstIt->rawData(); localParamInfo.dstRowStride = dstRowStride; localParamInfo.maskRowStart = 0; localParamInfo.maskRowStride = 0; localParamInfo.rows = rows; localParamInfo.cols = columns; const int dabX = dstX - dabRect.x(); const int dabY = dstY - dabRect.y(); - localParamInfo.srcRowStart = dab.device->constData() + dabX * pixelSize + dabY * dabRowStride; + localParamInfo.srcRowStart = dab.device->constData() + dabX * srcPixelSize + dabY * dabRowStride; localParamInfo.srcRowStride = dabRowStride; localParamInfo.setOpacityAndAverage(dab.opacity, dab.averageOpacity); localParamInfo.flow = dab.flow; colorSpace->bitBlt(srcColorSpace, localParamInfo, compositeOp, renderingIntent, conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } void KisPainter::Private::applyDeviceWithSelection(const QRect &applyRect, const KisRenderedDab &dab, KisRandomAccessorSP dstIt, KisRandomConstAccessorSP maskIt, const KoColorSpace *srcColorSpace, KoCompositeOp::ParameterInfo &localParamInfo) { const QRect dabRect = dab.realBounds(); const QRect rc = applyRect & dabRect; const int srcPixelSize = srcColorSpace->pixelSize(); const int dabRowStride = srcPixelSize * dabRect.width(); qint32 dstY = rc.y(); qint32 rowsRemaining = rc.height(); while (rowsRemaining > 0) { qint32 dstX = rc.x(); qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousMaskRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(rowsRemaining, qMin(numContiguousDstRows, numContiguousMaskRows)); qint32 columnsRemaining = rc.width(); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousMaskColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(columnsRemaining, qMin(numContiguousDstColumns, numContiguousMaskColumns)); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); maskIt->moveTo(dstX, dstY); localParamInfo.dstRowStart = dstIt->rawData(); localParamInfo.dstRowStride = dstRowStride; localParamInfo.maskRowStart = maskIt->rawDataConst(); localParamInfo.maskRowStride = maskRowStride; localParamInfo.rows = rows; localParamInfo.cols = columns; const int dabX = dstX - dabRect.x(); const int dabY = dstY - dabRect.y(); - localParamInfo.srcRowStart = dab.device->constData() + dabX * pixelSize + dabY * dabRowStride; + localParamInfo.srcRowStart = dab.device->constData() + dabX * srcPixelSize + dabY * dabRowStride; localParamInfo.srcRowStride = dabRowStride; localParamInfo.setOpacityAndAverage(dab.opacity, dab.averageOpacity); localParamInfo.flow = dab.flow; colorSpace->bitBlt(srcColorSpace, localParamInfo, compositeOp, renderingIntent, conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } void KisPainter::bltFixed(const QRect &applyRect, const QList allSrcDevices) { const KoColorSpace *srcColorSpace = 0; QList devices; QRect rc = applyRect; if (d->selection) { rc &= d->selection->selectedRect(); } QRect totalDevicesRect; Q_FOREACH (const KisRenderedDab &dab, allSrcDevices) { if (rc.intersects(dab.realBounds())) { devices.append(dab); totalDevicesRect |= dab.realBounds(); } if (!srcColorSpace) { srcColorSpace = dab.device->colorSpace(); } else { KIS_SAFE_ASSERT_RECOVER_RETURN(*srcColorSpace == *dab.device->colorSpace()); } } rc &= totalDevicesRect; if (devices.isEmpty() || rc.isEmpty()) return; KoCompositeOp::ParameterInfo localParamInfo = d->paramInfo; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(rc.left(), rc.top()); KisRandomConstAccessorSP maskIt = d->selection ? d->selection->projection()->createRandomConstAccessorNG(rc.left(), rc.top()) : 0; if (maskIt) { Q_FOREACH (const KisRenderedDab &dab, devices) { d->applyDeviceWithSelection(rc, dab, dstIt, maskIt, srcColorSpace, localParamInfo); } } else { Q_FOREACH (const KisRenderedDab &dab, devices) { d->applyDevice(rc, dab, dstIt, srcColorSpace, localParamInfo); } } #if 0 // the code above does basically the same thing as this one, // but more efficiently :) Q_FOREACH (KisFixedPaintDeviceSP dev, devices) { const QRect copyRect = dev->bounds() & rc; if (copyRect.isEmpty()) continue; bltFixed(copyRect.topLeft(), dev, copyRect); } #endif } diff --git a/libs/image/kis_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp index ef7f243d6c..ea1eb6c2a0 100644 --- a/libs/image/kis_selection_based_layer.cpp +++ b/libs/image/kis_selection_based_layer.cpp @@ -1,330 +1,353 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_selection_based_layer.h" #include #include "kis_debug.h" #include #include "kis_image.h" #include "kis_painter.h" #include "kis_default_bounds.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" #include "kis_raster_keyframe_channel.h" struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private { public: Private() : useSelectionInProjection(true) {} Private(const Private &rhs) : useSelectionInProjection(rhs.useSelectionInProjection) {} KisSelectionSP selection; KisPaintDeviceSP paintDevice; bool useSelectionInProjection; }; KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig, bool useGeneratorRegistry) : KisLayer(image.data(), name, OPACITY_OPAQUE_U8), KisNodeFilterInterface(filterConfig, useGeneratorRegistry), m_d(new Private()) { if (!selection) { initSelection(); } else { setInternalSelection(selection); } KisImageSP imageSP = image.toStrongRef(); if (!imageSP) { return; } m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(this, imageSP->colorSpace(), KisDefaultBoundsSP(new KisDefaultBounds(image)))); connect(imageSP.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs) : KisLayer(rhs) , KisIndirectPaintingSupport() , KisNodeFilterInterface(rhs) , m_d(new Private(*rhs.m_d)) { setInternalSelection(rhs.m_d->selection); m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data()); } KisSelectionBasedLayer::~KisSelectionBasedLayer() { delete m_d; } void KisSelectionBasedLayer::initSelection() { m_d->selection = KisSelectionSP(new KisSelection(KisDefaultBoundsSP(new KisDefaultBounds(image())))); m_d->selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, m_d->selection->pixelSelection()->colorSpace())); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); } void KisSelectionBasedLayer::slotImageSizeChanged() { if (m_d->selection) { /** * Make sure exactBounds() of the selection got recalculated after * the image changed */ m_d->selection->pixelSelection()->setDirty(); setDirty(); } } void KisSelectionBasedLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); KisLayer::setImage(image); connect(image.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisSelectionBasedLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const { return m_d->selection->pixelSelection(); } bool KisSelectionBasedLayer::needProjection() const { return m_d->selection; } void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const { m_d->useSelectionInProjection = value; } KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const { if (!m_d->selection) return KisSelectionSP(); m_d->selection->updateProjection(rect); KisSelectionSP tempSelection = m_d->selection; KisIndirectPaintingSupport::ReadLocker l(this); if (hasTemporaryTarget()) { /** * Cloning a selection with COW * FIXME: check whether it's faster than usual bitBlt'ing */ tempSelection = new KisSelection(*tempSelection); KisPainter gc2(tempSelection->pixelSelection()); setupTemporaryPainter(&gc2); gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect); } return tempSelection; } void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisSelectionSP tempSelection; if (m_d->useSelectionInProjection) { tempSelection = fetchComposedInternalSelection(rect); /** * When we paint with a selection, the deselected areas will *not* be * overwritten by copyAreaOptimized(), so we need to clear them beforehand */ projection->clear(rect); } KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection); } QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const { return m_d->selection ? rect & m_d->selection->selectedRect() : rect; } QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } void KisSelectionBasedLayer::resetCache() { KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } if (!m_d->paintDevice || *m_d->paintDevice->colorSpace() != *imageSP->colorSpace()) { m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(KisNodeWSP(this), imageSP->colorSpace(), new KisDefaultBounds(image()))); } else { m_d->paintDevice->clear(); } } KisSelectionSP KisSelectionBasedLayer::internalSelection() const { return m_d->selection; } void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection) { if (selection) { m_d->selection = new KisSelection(*selection.data()); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } } else { m_d->selection = 0; } KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } if (selection->pixelSelection()->defaultBounds()->bounds() != imageSP->bounds()) { qWarning() << "WARNING: KisSelectionBasedLayer::setInternalSelection" << "New selection has suspicious default bounds"; qWarning() << "WARNING:" << ppVar(selection->pixelSelection()->defaultBounds()->bounds()); qWarning() << "WARNING:" << ppVar(imageSP->bounds()); } } qint32 KisSelectionBasedLayer::x() const { return m_d->selection ? m_d->selection->x() : 0; } qint32 KisSelectionBasedLayer::y() const { return m_d->selection ? m_d->selection->y() : 0; } void KisSelectionBasedLayer::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } } void KisSelectionBasedLayer::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } } KisKeyframeChannel *KisSelectionBasedLayer::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisRasterKeyframeChannel *contentChannel = m_d->selection->pixelSelection()->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } return KisLayer::requestKeyframeChannel(id); } void KisSelectionBasedLayer::setDirty() { Q_ASSERT(image()); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } setDirty(imageSP->bounds()); } QRect KisSelectionBasedLayer::extent() const { - Q_ASSERT(image()); - KisImageSP imageSP = image().toStrongRef(); - if (!imageSP) { - return QRect(); + QRect resultRect; + + if (m_d->selection) { + resultRect = m_d->selection->selectedRect(); + + // copy for thread safety! + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + + if (temporaryTarget) { + resultRect |= temporaryTarget->extent(); + } + + } else { + KisImageSP image = this->image().toStrongRef(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); + resultRect = image->bounds(); } - return m_d->selection ? - m_d->selection->selectedRect() : imageSP->bounds(); + + return resultRect; } QRect KisSelectionBasedLayer::exactBounds() const { - Q_ASSERT(image()); - KisImageSP imageSP = image().toStrongRef(); - if (!imageSP) { - return QRect(); + QRect resultRect; + + if (m_d->selection) { + resultRect = m_d->selection->selectedExactRect(); + + // copy for thread safety! + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + + if (temporaryTarget) { + resultRect |= temporaryTarget->exactBounds(); + } + + } else { + KisImageSP image = this->image().toStrongRef(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); + resultRect = image->bounds(); } - return m_d->selection ? - m_d->selection->selectedExactRect() : imageSP->bounds(); + return resultRect; } QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h) { KisSelectionSP originalSelection = internalSelection(); KisPaintDeviceSP originalDevice = original(); return originalDevice && originalSelection ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/libs/image/kis_selection_based_layer.h b/libs/image/kis_selection_based_layer.h index aa8a9448d9..cc57a75074 100644 --- a/libs/image/kis_selection_based_layer.h +++ b/libs/image/kis_selection_based_layer.h @@ -1,210 +1,210 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_SELECTION_BASED_LAYER_H_ #define KIS_SELECTION_BASED_LAYER_H_ #include #include "kis_types.h" #include "kis_layer.h" #include "kis_indirect_painting_support.h" #include #include "kis_node_filter_interface.h" class KisFilterConfiguration; /** * @class KisSelectionBasedLayer describes base behaviour for * selection base classes like KisAdjustmentLayer and KisGeneratorLayer. * These classes should have a persistent selection that controls * the area where filter/generators are applied. The area outside * this selection is not affected by the layer */ class KRITAIMAGE_EXPORT KisSelectionBasedLayer : public KisLayer, public KisIndirectPaintingSupport, public KisNodeFilterInterface { Q_OBJECT public: /** * creates a new layer with the given selection. * Note that the selection will be _copied_ (with COW, though). * @param image the image to set this layer to * @param name name of the layer * @param selection is a mask used by the layer to know * where to apply the filter/generator. */ KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig, bool useGeneratorRegistry = false); KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs); ~KisSelectionBasedLayer() override; /** * tells whether the @node can be a child of this layer * @param node to be connected node * @return tells if to be connected is a child of KisMask */ bool allowAsChild(KisNodeSP node) const override; void setImage(KisImageWSP image) override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; bool needProjection() const override; /** * resets cached projection of lower layer to a new device * @return void */ - void resetCache(); + virtual void resetCache(); /** * for KisLayer::setDirty(const QRegion&) */ using KisLayer::setDirty; /** * Mark a layer as dirty. We can't use KisLayer's one * as our extent() function doesn't fit for this */ void setDirty() override; public: /** * Returns the selection of the layer * * Do not mix it with selection() which returns * the currently active selection of the image */ KisSelectionSP internalSelection() const; /** * sets the selection of this layer to a copy of * selection * @param selection the selection to set * @return void */ void setInternalSelection(KisSelectionSP selection); /** * When painted in indirect painting mode, the internal selection * might not contain actual selection, because a part of it is * stored on an indirect painting device. This method returns the * merged copy of the real selection. The area in \p rect only is * guaranteed to be prepared. The content of the rest of the * selection is undefined. */ KisSelectionSP fetchComposedInternalSelection(const QRect &rect) const; /** * gets this layer's x coordinate, taking selection into account * @return x-coordinate value */ qint32 x() const override; /** * gets this layer's y coordinate, taking selection into account * @return y-coordinate value */ qint32 y() const override; /** * sets this layer's y coordinate, taking selection into account * @param x x coordinate */ void setX(qint32 x) override; /** * sets this layer's y coordinate, taking selection into account * @param y y coordinate */ void setY(qint32 y) override; public: /** * gets an approximation of where the bounds on actual data * are in this layer, taking selection into account */ QRect extent() const override; /** * returns the exact bounds of where the actual data resides * in this layer, taking selection into account */ QRect exactBounds() const override; /** * copies the image and reformats it to thumbnail size * and returns the new thumbnail image. * @param w width of the thumbnail to create * @param h height of the thumbnail to create * @return the thumbnail image created. */ QImage createThumbnail(qint32 w, qint32 h) override; protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const override; // override from KisNode QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; protected: void initSelection(); QRect cropChangeRectBySelection(const QRect &rect) const; /** * Sets if the selection should be used in * copyOriginalToProjection() method. * * Default value is 'true'. The descendants should override it to * get desired behaviour. * * Must be called only once in the child's constructor */ void setUseSelectionInProjection(bool value) const; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; public Q_SLOTS: void slotImageSizeChanged(); /** * gets this layer. Overriddes function in * KisIndirectPaintingSupport * @return this AdjustmentLayer */ KisLayer* layer() { return this; } private: struct Private; Private * const m_d; }; #endif /* KIS_SELECTION_BASED_LAYER_H_ */ diff --git a/libs/image/layerstyles/kis_layer_style_filter_environment.cpp b/libs/image/layerstyles/kis_layer_style_filter_environment.cpp index 4ba2d48597..f1e7438a56 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_environment.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_environment.cpp @@ -1,152 +1,134 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_layer_style_filter_environment.h" #include #include "kis_layer.h" #include "kis_ls_utils.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "kis_painter.h" #include "kis_image.h" #include "krita_utils.h" #include #include "kis_random_accessor_ng.h" #include "kis_iterator_ng.h" #include "kis_pixel_selection.h" struct Q_DECL_HIDDEN KisLayerStyleFilterEnvironment::Private { KisLayer *sourceLayer; KisPixelSelectionSP cachedRandomSelection; static KisPixelSelectionSP generateRandomSelection(const QRect &rc); }; KisPixelSelectionSP KisLayerStyleFilterEnvironment::Private:: generateRandomSelection(const QRect &rc) { KisPixelSelectionSP selection = new KisPixelSelection(); KisSequentialIterator dstIt(selection, rc); boost::mt11213b uniformSource; if (uniformSource.max() >= 0x00FFFFFF) { while (dstIt.nextPixel()) { int randValue = uniformSource(); *dstIt.rawData() = (quint8) randValue; if (!dstIt.nextPixel()) break; randValue >>= 8; *dstIt.rawData() = (quint8) randValue; if (!dstIt.nextPixel()) break; randValue >>= 8; *dstIt.rawData() = (quint8) randValue; } } else { while (dstIt.nextPixel()) { *dstIt.rawData() = (quint8) uniformSource(); } } return selection; } KisLayerStyleFilterEnvironment::KisLayerStyleFilterEnvironment(KisLayer *sourceLayer) : m_d(new Private) { Q_ASSERT(sourceLayer); m_d->sourceLayer = sourceLayer; } KisLayerStyleFilterEnvironment::~KisLayerStyleFilterEnvironment() { } QRect KisLayerStyleFilterEnvironment::layerBounds() const { return m_d->sourceLayer ? m_d->sourceLayer->projection()->exactBounds() : QRect(); } QRect KisLayerStyleFilterEnvironment::defaultBounds() const { return m_d->sourceLayer ? m_d->sourceLayer->original()->defaultBounds()->bounds() : QRect(); } int KisLayerStyleFilterEnvironment::currentLevelOfDetail() const { return m_d->sourceLayer ? m_d->sourceLayer->original()->defaultBounds()->currentLevelOfDetail() : 0; } -QPainterPath KisLayerStyleFilterEnvironment::layerOutlineCache() const -{ - // TODO: make it really cachable! - Q_ASSERT(m_d->sourceLayer); - KisPaintDeviceSP srcDevice = m_d->sourceLayer->projection(); - QRect srcRect = srcDevice->exactBounds(); - if (srcRect.isEmpty()) return QPainterPath(); - - KisSelectionSP baseSelection = - KisLsUtils::selectionFromAlphaChannel(srcDevice, srcRect); - KisPixelSelectionSP selection = baseSelection->pixelSelection(); - - // needs no 'invalidate' call - selection->recalculateOutlineCache(); - - return selection->outlineCache(); -} - void KisLayerStyleFilterEnvironment::setupFinalPainter(KisPainter *gc, quint8 opacity, const QBitArray &channelFlags) const { Q_ASSERT(m_d->sourceLayer); gc->setOpacity(KritaUtils::mergeOpacity(opacity, m_d->sourceLayer->opacity())); gc->setChannelFlags(KritaUtils::mergeChannelFlags(channelFlags, m_d->sourceLayer->channelFlags())); } KisPixelSelectionSP KisLayerStyleFilterEnvironment::cachedRandomSelection(const QRect &requestedRect) const { KisPixelSelectionSP selection = m_d->cachedRandomSelection; QRect existingRect; if (selection) { existingRect = selection->selectedExactRect(); } if (!existingRect.contains(requestedRect)) { m_d->cachedRandomSelection = Private::generateRandomSelection(requestedRect | existingRect); } return m_d->cachedRandomSelection; } diff --git a/libs/image/layerstyles/kis_layer_style_filter_environment.h b/libs/image/layerstyles/kis_layer_style_filter_environment.h index 940f1ecdb8..a54c9475c6 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_environment.h +++ b/libs/image/layerstyles/kis_layer_style_filter_environment.h @@ -1,57 +1,55 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 __KIS_LAYER_STYLE_FILTER_ENVIRONMENT_H #define __KIS_LAYER_STYLE_FILTER_ENVIRONMENT_H #include #include #include #include "kis_types.h" class KisPainter; class KisLayer; class QPainterPath; class QBitArray; class KRITAIMAGE_EXPORT KisLayerStyleFilterEnvironment { public: KisLayerStyleFilterEnvironment(KisLayer *sourceLayer); ~KisLayerStyleFilterEnvironment(); QRect layerBounds() const; QRect defaultBounds() const; int currentLevelOfDetail() const; - QPainterPath layerOutlineCache() const; - void setupFinalPainter(KisPainter *gc, quint8 opacity, const QBitArray &channelFlags) const; KisPixelSelectionSP cachedRandomSelection(const QRect &requestedRect) const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_LAYER_STYLE_FILTER_ENVIRONMENT_H */ diff --git a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp index fea07ce239..c8ff470e5e 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp @@ -1,117 +1,117 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_layer_style_filter_projection_plane.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_layer_style_filter.h" #include "kis_layer_style_filter_environment.h" #include "kis_psd_layer_style.h" #include "kis_painter.h" #include "kis_multiple_projection.h" struct KisLayerStyleFilterProjectionPlane::Private { KisLayer *sourceLayer; QScopedPointer filter; KisPSDLayerStyleSP style; QScopedPointer environment; KisMultipleProjection projection; }; KisLayerStyleFilterProjectionPlane:: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer) : m_d(new Private) { Q_ASSERT(sourceLayer); m_d->sourceLayer = sourceLayer; m_d->environment.reset(new KisLayerStyleFilterEnvironment(sourceLayer)); } KisLayerStyleFilterProjectionPlane::~KisLayerStyleFilterProjectionPlane() { } void KisLayerStyleFilterProjectionPlane::setStyle(KisLayerStyleFilter *filter, KisPSDLayerStyleSP style) { m_d->filter.reset(filter); m_d->style = style; } QRect KisLayerStyleFilterProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::recalculate(): [BUG] is not initialized"; return QRect(); } m_d->projection.clear(rect); m_d->filter->processDirectly(m_d->sourceLayer->projection(), &m_d->projection, rect, m_d->style, m_d->environment.data()); return rect; } void KisLayerStyleFilterProjectionPlane::apply(KisPainter *painter, const QRect &rect) { - m_d->projection.apply(painter->device(), rect); + m_d->projection.apply(painter->device(), rect, m_d->environment.data()); } KisPaintDeviceList KisLayerStyleFilterProjectionPlane::getLodCapableDevices() const { return m_d->projection.getLodCapableDevices(); } QRect KisLayerStyleFilterProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::needRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->neededRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::changeRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->changedRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return needRect(rect, pos); } diff --git a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp index 08432cd975..a04f8166b2 100644 --- a/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp +++ b/libs/image/layerstyles/kis_ls_bevel_emboss_filter.cpp @@ -1,493 +1,492 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * See LayerFX plugin for Gimp as a reference implementation of this style: * http://registry.gimp.org/node/186 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_ls_bevel_emboss_filter.h" #include #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "gimp_bump_map.h" #include "kis_transaction.h" #include "kis_multiple_projection.h" KisLsBevelEmbossFilter::KisLsBevelEmbossFilter() : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { } void paintBevelSelection(KisPixelSelectionSP srcSelection, KisPixelSelectionSP dstSelection, const QRect &applyRect, int size, int initialSize, bool invert) { KisSelectionSP tmpBaseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); KisPixelSelectionSP tmpSelection = tmpBaseSelection->pixelSelection(); // NOTE: we are not using createCompositionSourceDevice() intentionally, // because the source device doesn't have alpha channel KisPixelSelectionSP fillDevice = new KisPixelSelection(); KisPainter gc(dstSelection); gc.setCompositeOp(COMPOSITE_COPY); for (int i = 0; i < size; i++) { const int growSize = initialSize - i - 1; quint8 selectedness = invert ? qRound(qreal(size - i - 1) / size * 255.0) : qRound(qreal(i + 1) / size * 255.0); fillDevice->setDefaultPixel(KoColor(&selectedness, fillDevice->colorSpace())); tmpSelection->makeCloneFromRough(srcSelection, srcSelection->selectedRect()); QRect changeRect = KisLsUtils::growSelectionUniform(tmpSelection, growSize, applyRect); gc.setSelection(tmpBaseSelection); gc.bitBlt(changeRect.topLeft(), fillDevice, changeRect); } } struct ContrastOp { static const bool supportsCaching = false; ContrastOp(qreal contrast) : m_contrast(contrast) { } int operator() (int iValue) { qreal value = qreal(iValue - 127) / 127.0; qreal slant = std::tan ((m_contrast + 1) * M_PI_4); value = (value - 0.5) * slant + 0.5; return qRound(value * 255.0); } private: qreal m_contrast; }; struct HighlightsFetchOp { static const bool supportsCaching = true; int operator() (int value) { return qRound(qMax(0, value - 127) * (255.0 / (255 - 127))); } }; struct ShadowsFetchOp { static const bool supportsCaching = true; int operator() (int value) { return 255 - qRound(qMin(value, 127) * (255.0 / 127.0)); } }; template void mapPixelValues(KisPixelSelectionSP srcSelection, KisPixelSelectionSP dstSelection, MapOp mapOp, const QRect &applyRect) { static quint8 mapTable[256]; static bool mapInitialized = false; if (!MapOp::supportsCaching || !mapInitialized) { mapInitialized = true; for (int i = 0; i < 256; i++) { mapTable[i] = mapOp(i); } } KisSequentialConstIterator srcIt(srcSelection, applyRect); KisSequentialIterator dstIt(dstSelection, applyRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *dstPtr = dstIt.rawData(); *dstPtr = mapTable[*srcPtr]; } } template void mapPixelValues(KisPixelSelectionSP dstSelection, MapOp mapOp, const QRect &applyRect) { static quint8 mapTable[256]; static bool mapInitialized = false; if (!MapOp::supportsCaching || !mapInitialized) { mapInitialized = true; for (int i = 0; i < 256; i++) { mapTable[i] = mapOp(i); } } KisSequentialIterator dstIt(dstSelection, applyRect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = mapTable[*dstPtr]; } } struct BevelEmbossRectCalculator { BevelEmbossRectCalculator(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { shadowHighlightsFinalRect = applyRect; applyGaussianRect = shadowHighlightsFinalRect; applyGlossContourRect = KisLsUtils::growRectFromRadius(applyGaussianRect, config->soften()); applyBumpmapRect = applyGlossContourRect; applyContourRect = applyBumpmapRect; applyTextureRect = applyContourRect; applyBevelRect = calcBevelNeedRect(applyTextureRect, config); initialFetchRect = kisGrowRect(applyBevelRect, 1); } QRect totalChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { QRect changeRect = calcBevelChangeRect(applyRect, config); changeRect = kisGrowRect(changeRect, 1); // bumpmap method changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften()); return changeRect; } QRect totalNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { QRect changeRect = applyRect; changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften()); changeRect = kisGrowRect(changeRect, 1); // bumpmap method changeRect = calcBevelNeedRect(applyRect, config); return changeRect; } QRect initialFetchRect; QRect applyBevelRect; QRect applyTextureRect; QRect applyContourRect; QRect applyBumpmapRect; QRect applyGlossContourRect; QRect applyGaussianRect; QRect shadowHighlightsFinalRect; private: QRect calcBevelChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { const int size = config->size(); int limitingGrowSize = 0; switch (config->style()) { case psd_bevel_outer_bevel: limitingGrowSize = size; break; case psd_bevel_inner_bevel: limitingGrowSize = 0; break; case psd_bevel_emboss: { const int initialSize = std::ceil(qreal(size) / 2.0); limitingGrowSize = initialSize; break; } case psd_bevel_pillow_emboss: { const int halfSizeC = std::ceil(qreal(size) / 2.0); limitingGrowSize = halfSizeC; break; } case psd_bevel_stroke_emboss: warnKrita << "WARNING: Stroke Emboss style is not implemented yet!"; return applyRect; } return kisGrowRect(applyRect, limitingGrowSize); } QRect calcBevelNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) { const int size = config->size(); int limitingGrowSize = size; return kisGrowRect(applyRect, limitingGrowSize); } }; void KisLsBevelEmbossFilter::applyBevelEmboss(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_bevel_emboss *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; BevelEmbossRectCalculator d(applyRect, config); KisSelectionSP baseSelection = KisLsUtils::selectionFromAlphaChannel(srcDevice, d.initialFetchRect); KisPixelSelectionSP selection = baseSelection->pixelSelection(); //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); const int size = config->size(); int limitingGrowSize = 0; KisPixelSelectionSP bumpmapSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); switch (config->style()) { case psd_bevel_outer_bevel: paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, size, false); limitingGrowSize = size; break; case psd_bevel_inner_bevel: paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, 0, false); limitingGrowSize = 0; break; case psd_bevel_emboss: { const int initialSize = std::ceil(qreal(size) / 2.0); paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, initialSize, false); limitingGrowSize = initialSize; break; } case psd_bevel_pillow_emboss: { const int halfSizeF = std::floor(qreal(size) / 2.0); const int halfSizeC = std::ceil(qreal(size) / 2.0); // TODO: probably not correct! paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeC, halfSizeC, false); paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeF, 0, true); limitingGrowSize = halfSizeC; break; } case psd_bevel_stroke_emboss: warnKrita << "WARNING: Stroke Emboss style is not implemented yet!"; return; } KisPixelSelectionSP limitingSelection = new KisPixelSelection(*selection); { QRect changeRectUnused = KisLsUtils::growSelectionUniform(limitingSelection, limitingGrowSize, d.applyBevelRect); Q_UNUSED(changeRectUnused); } //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_xconv.png"); if (config->textureEnabled()) { KisPixelSelectionSP textureSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); KisLsUtils::fillPattern(textureSelection, d.applyTextureRect, env, config->textureScale(), config->texturePattern(), config->textureHorizontalPhase(), config->textureVerticalPhase(), config->textureAlignWithLayer()); int contrastadj = 0; { using namespace std; int tex_depth = config->textureDepth(); if (tex_depth >= 0.0) { if (tex_depth <= 100.0) { contrastadj = int(qRound((1-(tex_depth/100.0)) * -127)); } else { contrastadj = int(qRound(((tex_depth-100.0)/900.0) * 127)); } } else { textureSelection->invert(); if (tex_depth >= -100.0) { contrastadj = int(qRound((1-(abs(tex_depth)/100.0)) * -127)); } else { contrastadj = int(qRound(((abs(tex_depth)-100.0)/900.0) * 127)); } } } qreal contrast = qBound(-1.0, qreal(contrastadj) / 127.0, 1.0); mapPixelValues(textureSelection, ContrastOp(contrast), d.applyTextureRect); { KisPainter gc(bumpmapSelection); gc.setCompositeOp(COMPOSITE_MULT); gc.bitBlt(d.applyTextureRect.topLeft(), textureSelection, d.applyTextureRect); gc.end(); } } //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("15_selection_texture.png"); if (config->contourEnabled()) { if (config->range() != KisLsUtils::FULL_PERCENT_RANGE) { KisLsUtils::adjustRange(bumpmapSelection, d.applyContourRect, config->range()); } KisLsUtils::applyContourCorrection(bumpmapSelection, d.applyContourRect, config->contourLookupTable(), config->antiAliased(), true); } bumpmap_vals_t bmvals; bmvals.azimuth = config->angle(); bmvals.elevation = config->altitude(); bmvals.depth = config->depth(); bmvals.ambient = 0; bmvals.compensate = true; bmvals.invert = config->direction() == psd_direction_down; bmvals.type = LINEAR; bumpmap(bumpmapSelection, d.applyBumpmapRect, bmvals); //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_bumpmap.png"); { // TODO: optimize! KisLsUtils::applyContourCorrection(bumpmapSelection, d.applyGlossContourRect, config->glossContourLookupTable(), config->glossAntiAliased(), true); } if (config->soften()) { KisLsUtils::applyGaussian(bumpmapSelection, d.applyGaussianRect, config->soften()); } if (config->textureEnabled() && config->textureInvert()) { bumpmapSelection->invert(); } selection->clear(); mapPixelValues(bumpmapSelection, selection, ShadowsFetchOp(), d.shadowHighlightsFinalRect); selection->applySelection(limitingSelection, SELECTION_INTERSECT); //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("4_dst_before_apply.png"); //selection->convertToQImage(0, QRect(0,0,300,300)).save("4_shadows_sel.png"); { - KisPaintDeviceSP dstDevice = dst->getProjection("00_bevel_shadow", config->shadowBlendMode(), srcDevice); + KisPaintDeviceSP dstDevice = dst->getProjection("00_bevel_shadow", + config->shadowBlendMode(), + config->shadowOpacity(), + QBitArray(), + srcDevice); const KoColor fillColor(config->shadowColor(), dstDevice->colorSpace()); const QRect &fillRect = d.shadowHighlightsFinalRect; KisPaintDeviceSP fillDevice = new KisPaintDevice(dstDevice->colorSpace()); fillDevice->setDefaultPixel(fillColor); - KisPainter gc(dstDevice); - gc.setSelection(baseSelection); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, config->shadowOpacity(), QBitArray()); - gc.bitBlt(fillRect.topLeft(), fillDevice, fillRect); - gc.end(); + KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection); } selection->clear(); mapPixelValues(bumpmapSelection, selection, HighlightsFetchOp(), d.shadowHighlightsFinalRect); selection->applySelection(limitingSelection, SELECTION_INTERSECT); //selection->convertToQImage(0, QRect(0,0,300,300)).save("5_highlights_sel.png"); { - KisPaintDeviceSP dstDevice = dst->getProjection("01_bevel_highlight", config->highlightBlendMode(), srcDevice); + KisPaintDeviceSP dstDevice = dst->getProjection("01_bevel_highlight", + config->highlightBlendMode(), + config->highlightOpacity(), + QBitArray(), + srcDevice); const KoColor fillColor(config->highlightColor(), dstDevice->colorSpace()); const QRect &fillRect = d.shadowHighlightsFinalRect; KisPaintDeviceSP fillDevice = new KisPaintDevice(dstDevice->colorSpace()); fillDevice->setDefaultPixel(fillColor); - KisPainter gc(dstDevice); - gc.setSelection(baseSelection); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, config->highlightOpacity(), QBitArray()); - gc.bitBlt(fillRect.topLeft(), fillDevice, fillRect); - gc.end(); + + KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection); } } void KisLsBevelEmbossFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyBevelEmboss(src, dst, applyRect, w.config, env); } QRect KisLsBevelEmbossFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); BevelEmbossRectCalculator d(rect, w.config); return d.totalNeedRect(rect, w.config); } QRect KisLsBevelEmbossFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); BevelEmbossRectCalculator d(rect, w.config); return d.totalChangeRect(rect, w.config); } diff --git a/libs/image/layerstyles/kis_ls_overlay_filter.cpp b/libs/image/layerstyles/kis_ls_overlay_filter.cpp index baa1494793..d709b2992b 100644 --- a/libs/image/layerstyles/kis_ls_overlay_filter.cpp +++ b/libs/image/layerstyles/kis_ls_overlay_filter.cpp @@ -1,143 +1,133 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_ls_overlay_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" KisLsOverlayFilter::KisLsOverlayFilter(Mode mode) : KisLayerStyleFilter(KoID("lsoverlay", i18n("Overlay (style)"))), m_mode(mode) { } void KisLsOverlayFilter::applyOverlay(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; - KisPaintDeviceSP tempDevice = new KisPaintDevice(srcDevice->colorSpace()); + KisPaintDeviceSP overlayDevice = m_cachedDevices.getDevice(srcDevice); + KisLsUtils::fillOverlayDevice(overlayDevice, applyRect, config, env); - { - // Create overlay device + const QString compositeOp = config->blendMode(); + const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP fillDevice = new KisPaintDevice(srcDevice->colorSpace()); - KisLsUtils::fillOverlayDevice(fillDevice, applyRect, config, env); + KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), + compositeOp, + opacityU8, + QBitArray(), + srcDevice); - KisPainter gc(tempDevice); - gc.setCompositeOp(COMPOSITE_OVER); - gc.bitBlt(applyRect.topLeft(), srcDevice, applyRect); + KisPainter::copyAreaOptimized(applyRect.topLeft(), srcDevice, dstDevice, applyRect); - QBitArray channelFlags = srcDevice->colorSpace()->channelFlags(true, false); - gc.setChannelFlags(channelFlags); - gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); - gc.end(); - } - - { - // Paint over destination - - const QString compositeOp = config->blendMode(); - const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, srcDevice); + KisPainter gc(dstDevice); + gc.setCompositeOp(COMPOSITE_OVER); - KisPainter gc(dstDevice); + const QBitArray channelFlags = srcDevice->colorSpace()->channelFlags(true, false); + gc.setChannelFlags(channelFlags); + gc.bitBlt(applyRect.topLeft(), overlayDevice, applyRect); + gc.end(); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); - - gc.bitBlt(applyRect.topLeft(), tempDevice, applyRect); - gc.end(); - } + m_cachedDevices.putDevice(overlayDevice); } const psd_layer_effects_overlay_base* KisLsOverlayFilter::getOverlayStruct(KisPSDLayerStyleSP style) const { const psd_layer_effects_overlay_base *config = 0; if (m_mode == Color) { config = style->colorOverlay(); } else if (m_mode == Gradient) { config = style->gradientOverlay(); } else if (m_mode == Pattern) { config = style->patternOverlay(); } return config; } void KisLsOverlayFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_overlay_base *config = getOverlayStruct(style); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; applyOverlay(src, dst, applyRect, config, env); } QRect KisLsOverlayFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(style); Q_UNUSED(env); return rect; } QRect KisLsOverlayFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(style); Q_UNUSED(env); return rect; } diff --git a/libs/image/layerstyles/kis_ls_overlay_filter.h b/libs/image/layerstyles/kis_ls_overlay_filter.h index 57f965e5d8..2c5d79f88b 100644 --- a/libs/image/layerstyles/kis_ls_overlay_filter.h +++ b/libs/image/layerstyles/kis_ls_overlay_filter.h @@ -1,64 +1,66 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_LS_OVERLAY_FILTER_H #define KIS_LS_OVERLAY_FILTER_H #include #include "kis_layer_style_filter.h" #include +#include "kis_cached_paint_device.h" struct psd_layer_effects_overlay_base; class KRITAIMAGE_EXPORT KisLsOverlayFilter : public KisLayerStyleFilter { public: enum Mode { Color, Gradient, Pattern }; public: KisLsOverlayFilter(Mode mode); void processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect neededRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; QRect changedRect(const QRect & rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const override; private: const psd_layer_effects_overlay_base* getOverlayStruct(KisPSDLayerStyleSP style) const; void applyOverlay(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) const; private: Mode m_mode; + mutable KisCachedPaintDevice m_cachedDevices; }; #endif diff --git a/libs/image/layerstyles/kis_ls_stroke_filter.cpp b/libs/image/layerstyles/kis_ls_stroke_filter.cpp index 2843abfc64..e6236c5a62 100644 --- a/libs/image/layerstyles/kis_ls_stroke_filter.cpp +++ b/libs/image/layerstyles/kis_ls_stroke_filter.cpp @@ -1,156 +1,151 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_ls_stroke_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" +namespace { -KisLsStrokeFilter::KisLsStrokeFilter() - : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) +int borderSize(psd_stroke_position position, int size) { + int border = 0; + + switch (position) { + case psd_stroke_outside: + border = 2 * size + 1; + break; + case psd_stroke_center: + border = size + 1; + break; + case psd_stroke_inside: + border = 1; + break; + } + + return border; } -void paintPathOnSelection(KisPixelSelectionSP selection, - const QPainterPath &path, - const QRect &applyRect, - int size) +} + + +KisLsStrokeFilter::KisLsStrokeFilter() + : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { - QPen pen(Qt::white, size); - KisPainter gc(selection); - gc.setPaintColor(KoColor(Qt::white, selection->colorSpace())); - gc.drawPainterPath(path, pen, applyRect); - gc.end(); } void KisLsStrokeFilter::applyStroke(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &applyRect, const psd_layer_effects_stroke *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; - KisSelectionSP baseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); - KisPixelSelectionSP selection = baseSelection->pixelSelection(); - - //selection->convertToQImage(0, QRect(0,0,300,300)).save("0_selection_initial.png"); - - QPainterPath strokePath = env->layerOutlineCache(); - if (strokePath.isEmpty()) return; - - if (config->position() == psd_stroke_center) { - paintPathOnSelection(selection, strokePath, - applyRect, config->size()); - } else if (config->position() == psd_stroke_outside || - config->position() == psd_stroke_inside) { + const QRect needRect = kisGrowRect(applyRect, borderSize(config->position(), config->size())); - paintPathOnSelection(selection, strokePath, - applyRect, 2 * config->size()); - - KisSelectionSP knockOutSelection = - KisLsUtils::selectionFromAlphaChannel(srcDevice, applyRect); - - // disabled intentionally, because it creates artifacts on smooth lines - // KisLsUtils::findEdge(knockOutSelection->pixelSelection(), applyRect, true); + KisSelectionSP baseSelection = KisLsUtils::selectionFromAlphaChannel(srcDevice, needRect); + KisPixelSelectionSP selection = baseSelection->pixelSelection(); - if (config->position() == psd_stroke_inside) { - knockOutSelection->pixelSelection()->invert(); + { + KisPixelSelectionSP knockOutSelection = new KisPixelSelection(new KisSelectionEmptyBounds(0)); + knockOutSelection->makeCloneFromRough(selection, needRect); + + if (config->position() == psd_stroke_outside) { + KisGaussianKernel::applyDilate(selection, needRect, 2 * config->size(), QBitArray(), 0); + } else if (config->position() == psd_stroke_inside) { + KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, 2 * config->size(), QBitArray(), 0); + } else if (config->position() == psd_stroke_center) { + KisGaussianKernel::applyDilate(selection, needRect, config->size(), QBitArray(), 0); + KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, config->size(), QBitArray(), 0); } KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); - gc.bitBlt(applyRect.topLeft(), knockOutSelection->pixelSelection(), applyRect); + gc.bitBlt(needRect.topLeft(), knockOutSelection, needRect); gc.end(); } - //selection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_stroke.png"); - KisPaintDeviceSP fillDevice = new KisPaintDevice(srcDevice->colorSpace()); KisLsUtils::fillOverlayDevice(fillDevice, applyRect, config, env); const QString compositeOp = config->blendMode(); const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, srcDevice); - dstDevice->clear(applyRect); - - KisPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); - gc.setSelection(baseSelection); + KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), + compositeOp, + opacityU8, + QBitArray(), + srcDevice); - gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); + KisPainter::copyAreaOptimized(applyRect.topLeft(), fillDevice, dstDevice, applyRect, baseSelection); } void KisLsStrokeFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_stroke *config = style->stroke(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyStroke(src, dst, applyRect, w.config, env); } -QRect KisLsStrokeFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment */*env*/) const -{ - Q_UNUSED(style); - return rect; -} - -QRect KisLsStrokeFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const +QRect KisLsStrokeFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_stroke *config = style->stroke(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); + return kisGrowRect(rect, borderSize(w.config->position(), w.config->size())); +} - const int borderSize = w.config->size() + 1; - return kisGrowRect(rect, borderSize); +QRect KisLsStrokeFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const +{ + return neededRect(rect, style, env); } diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp index 0e0d0ca3a0..e5583ba0be 100644 --- a/libs/image/layerstyles/kis_ls_utils.cpp +++ b/libs/image/layerstyles/kis_ls_utils.cpp @@ -1,599 +1,586 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_ls_utils.h" #include #include #include #include "psd.h" #include "kis_default_bounds.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_iterator_ng.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_layer_style_filter_environment.h" #include "kis_selection_filters.h" #include "kis_multiple_projection.h" namespace KisLsUtils { QRect growSelectionUniform(KisPixelSelectionSP selection, int growSize, const QRect &applyRect) { QRect changeRect = applyRect; if (growSize > 0) { KisGrowSelectionFilter filter(growSize, growSize); changeRect = filter.changeRect(applyRect); filter.process(selection, applyRect); } else if (growSize < 0) { KisShrinkSelectionFilter filter(qAbs(growSize), qAbs(growSize), false); changeRect = filter.changeRect(applyRect); filter.process(selection, applyRect); } return changeRect; } KisSelectionSP selectionFromAlphaChannel(KisPaintDeviceSP device, const QRect &srcRect) { const KoColorSpace *cs = device->colorSpace(); KisSelectionSP baseSelection = new KisSelection(new KisSelectionEmptyBounds(0)); KisPixelSelectionSP selection = baseSelection->pixelSelection(); KisSequentialConstIterator srcIt(device, srcRect); KisSequentialIterator dstIt(selection, srcRect); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); const quint8* srcPtr = srcIt.rawDataConst(); *dstPtr = cs->opacityU8(srcPtr); } return baseSelection; } void findEdge(KisPixelSelectionSP selection, const QRect &applyRect, const bool edgeHidden) { KisSequentialIterator dstIt(selection, applyRect); if (edgeHidden) { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = (*pixelPtr < 24) ? *pixelPtr * 10 : 0xFF; } } else { while(dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = 0xFF; } } } QRect growRectFromRadius(const QRect &rc, int radius) { int halfSize = KisGaussianKernel::kernelSizeFromRadius(radius) / 2; return rc.adjusted(-halfSize, -halfSize, halfSize, halfSize); } void applyGaussian(KisPixelSelectionSP selection, const QRect &applyRect, qreal radius) { KisGaussianKernel::applyGaussian(selection, applyRect, radius, radius, QBitArray(), 0); } namespace Private { void getGradientTable(const KoAbstractGradient *gradient, QVector *table, const KoColorSpace *colorSpace) { KIS_ASSERT_RECOVER_RETURN(table->size() == 256); for (int i = 0; i < 256; i++) { gradient->colorAt(((*table)[i]), qreal(i) / 255.0); (*table)[i].convertTo(colorSpace); } } struct LinearGradientIndex { int popOneIndex(int selectionAlpha) { return 255 - selectionAlpha; } bool nextPixel() { return true; } }; struct JitterGradientIndex { JitterGradientIndex(const QRect &applyRect, int jitter, const KisLayerStyleFilterEnvironment *env) : randomSelection(env->cachedRandomSelection(applyRect)), noiseIt(randomSelection, applyRect), m_jitterCoeff(jitter * 255 / 100) { } int popOneIndex(int selectionAlpha) { int gradientIndex = 255 - selectionAlpha; gradientIndex += m_jitterCoeff * *noiseIt.rawDataConst() >> 8; gradientIndex &= 0xFF; return gradientIndex; } bool nextPixel() { return noiseIt.nextPixel(); } private: KisPixelSelectionSP randomSelection; KisSequentialConstIterator noiseIt; int m_jitterCoeff; }; template void applyGradientImpl(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, IndexFetcher &indexFetcher) { KIS_ASSERT_RECOVER_RETURN( *table.first().colorSpace() == *device->colorSpace()); const KoColorSpace *cs = device->colorSpace(); const int pixelSize = cs->pixelSize(); KisSequentialConstIterator selIt(selection, applyRect); KisSequentialIterator dstIt(device, applyRect); if (edgeHidden) { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { quint8 selAlpha = *selIt.rawDataConst(); int gradientIndex = indexFetcher.popOneIndex(selAlpha); const KoColor &color = table[gradientIndex]; quint8 tableAlpha = color.opacityU8(); memcpy(dstIt.rawData(), color.data(), pixelSize); if (selAlpha < 24 && tableAlpha == 255) { tableAlpha = int(selAlpha) * 10 * tableAlpha >> 8; cs->setOpacity(dstIt.rawData(), tableAlpha, 1); } } } else { while (selIt.nextPixel() && dstIt.nextPixel() && indexFetcher.nextPixel()) { int gradientIndex = indexFetcher.popOneIndex(*selIt.rawDataConst()); const KoColor &color = table[gradientIndex]; memcpy(dstIt.rawData(), color.data(), pixelSize); } } } void applyGradient(KisPaintDeviceSP device, KisPixelSelectionSP selection, const QRect &applyRect, const QVector &table, bool edgeHidden, int jitter, const KisLayerStyleFilterEnvironment *env) { if (!jitter) { LinearGradientIndex fetcher; applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } else { JitterGradientIndex fetcher(applyRect, jitter, env); applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher); } } } const int noiseNeedBorder = 8; void applyNoise(KisPixelSelectionSP selection, const QRect &applyRect, int noise, const psd_layer_effects_context *context, const KisLayerStyleFilterEnvironment *env) { Q_UNUSED(context); const QRect overlayRect = kisGrowRect(applyRect, noiseNeedBorder); KisPixelSelectionSP randomSelection = env->cachedRandomSelection(overlayRect); KisPixelSelectionSP randomOverlay = new KisPixelSelection(); KisSequentialConstIterator noiseIt(randomSelection, overlayRect); KisSequentialConstIterator srcIt(selection, overlayRect); KisRandomAccessorSP dstIt = randomOverlay->createRandomAccessorNG(overlayRect.x(), overlayRect.y()); while (noiseIt.nextPixel() && srcIt.nextPixel()) { int itX = noiseIt.x(); int itY = noiseIt.y(); int x = itX + (*noiseIt.rawDataConst() >> 4) - 8; int y = itY + (*noiseIt.rawDataConst() & 0x0F) - 8; x = (x + itX) >> 1; y = (y + itY) >> 1; dstIt->moveTo(x, y); quint8 dstAlpha = *dstIt->rawData(); quint8 srcAlpha = *srcIt.rawDataConst(); int value = qMin(255, dstAlpha + srcAlpha); *dstIt->rawData() = value; } noise = noise * 255 / 100; KisPainter gc(selection); gc.setOpacity(noise); gc.setCompositeOp(COMPOSITE_COPY); gc.bitBlt(applyRect.topLeft(), randomOverlay, applyRect); } //const int FULL_PERCENT_RANGE = 100; void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range) { KIS_ASSERT_RECOVER_RETURN(range >= 1 && range <= 100); quint8 rangeTable[256]; for(int i = 0; i < 256; i ++) { quint8 value = i * 100 / range; rangeTable[i] = qMin(value, quint8(255)); } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = rangeTable[*pixelPtr]; } } void applyContourCorrection(KisPixelSelectionSP selection, const QRect &applyRect, const quint8 *lookup_table, bool antiAliased, bool edgeHidden) { quint8 contour[PSD_LOOKUP_TABLE_SIZE] = { 0x00, 0x0b, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d, 0x58, 0x63, 0x6e, 0x79, 0x84, 0x8f, 0x9a, 0xa5, 0xb0, 0xbb, 0xc6, 0xd1, 0xdc, 0xf2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; if (edgeHidden) { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[i] >> 8; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)] >> 8; } } } else { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[i]; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)]; } } } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = contour[*pixelPtr]; } } void knockOutSelection(KisPixelSelectionSP selection, KisPixelSelectionSP knockOutSelection, const QRect &srcRect, const QRect &dstRect, const QRect &totalNeedRect, const bool knockOutInverted) { KIS_ASSERT_RECOVER_RETURN(knockOutSelection); QRect knockOutRect = !knockOutInverted ? srcRect : totalNeedRect; knockOutRect &= dstRect; KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect); } void fillPattern(KisPaintDeviceSP fillDevice, const QRect &applyRect, KisLayerStyleFilterEnvironment *env, int scale, KoPattern *pattern, int horizontalPhase, int verticalPhase, bool alignWithLayer) { if (scale != 100) { warnKrita << "KisLsOverlayFilter::applyOverlay(): Pattern scaling is NOT implemented!"; } QSize psize(pattern->width(), pattern->height()); QPoint patternOffset(qreal(psize.width()) * horizontalPhase / 100, qreal(psize.height()) * verticalPhase / 100); const QRect boundsRect = alignWithLayer ? env->layerBounds() : env->defaultBounds(); patternOffset += boundsRect.topLeft(); patternOffset.rx() %= psize.width(); patternOffset.ry() %= psize.height(); QRect fillRect = applyRect | applyRect.translated(patternOffset); KisFillPainter gc(fillDevice); gc.fillRect(fillRect.x(), fillRect.y(), - fillRect.width(), fillRect.height(), pattern); + fillRect.width(), fillRect.height(), pattern, -patternOffset); gc.end(); - - fillDevice->setX(-patternOffset.x()); - fillDevice->setY(-patternOffset.y()); } void fillOverlayDevice(KisPaintDeviceSP fillDevice, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) { if (config->fillType() == psd_fill_solid_color) { KoColor color(config->color(), fillDevice->colorSpace()); fillDevice->setDefaultPixel(color); } else if (config->fillType() == psd_fill_pattern) { fillPattern(fillDevice, applyRect, env, config->scale(), config->pattern(), config->horizontalPhase(), config->verticalPhase(), config->alignWithLayer()); } else if (config->fillType() == psd_fill_gradient) { const QRect boundsRect = config->alignWithLayer() ? env->layerBounds() : env->defaultBounds(); QPoint center = boundsRect.center(); center += QPoint(boundsRect.width() * config->gradientXOffset() / 100, boundsRect.height() * config->gradientYOffset() / 100); int width = (boundsRect.width() * config->scale() + 100) / 200; int height = (boundsRect.height() * config->scale() + 100) / 200; /* copy paste from libpsd */ int angle = config->angle(); int corner_angle = (int)(atan((qreal)boundsRect.height() / boundsRect.width()) * 180 / M_PI + 0.5); int sign_x = 1; int sign_y = 1; if(angle < 0) { angle += 360; } if (angle >= 90 && angle < 180) { angle = 180 - angle; sign_x = -1; } else if (angle >= 180 && angle < 270) { angle = angle - 180; sign_x = -1; sign_y = -1; } else if (angle >= 270 && angle <= 360) { angle = 360 - angle; sign_y = -1; } int radius_x = 0; int radius_y = 0; if (angle <= corner_angle) { radius_x = width; radius_y = (int)(radius_x * tan(kisDegreesToRadians(qreal(angle))) + 0.5); } else { radius_y = height; radius_x = (int)(radius_y / tan(kisDegreesToRadians(qreal(angle))) + 0.5); } int radius_corner = (int)(std::sqrt((qreal)(radius_x * radius_x + radius_y * radius_y)) + 0.5); /* end of copy paste from libpsd */ KisGradientPainter gc(fillDevice); gc.setGradient(config->gradient().data()); QPointF gradStart; QPointF gradEnd; KisGradientPainter::enumGradientRepeat repeat = KisGradientPainter::GradientRepeatNone; QPoint rectangularOffset(sign_x * radius_x, -sign_y * radius_y); switch(config->style()) { case psd_gradient_style_linear: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; case psd_gradient_style_radial: gc.setGradientShape(KisGradientPainter::GradientShapeRadial); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + QPointF(radius_corner, 0); break; case psd_gradient_style_angle: gc.setGradientShape(KisGradientPainter::GradientShapeConical); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + rectangularOffset; break; case psd_gradient_style_reflected: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatAlternate; gradStart = center - rectangularOffset; gradEnd = center; break; case psd_gradient_style_diamond: gc.setGradientShape(KisGradientPainter::GradientShapeBiLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; default: qFatal("Gradient Overlay: unknown switch case!"); break; } gc.paintGradient(gradStart, gradEnd, repeat, 0.0, config->reverse(), applyRect); } } void applyFinalSelection(const QString &projectionId, KisSelectionSP baseSelection, KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &/*srcRect*/, const QRect &dstRect, const psd_layer_effects_context */*context*/, const psd_layer_effects_shadow_base *config, const KisLayerStyleFilterEnvironment *env) { const KoColor effectColor(config->color(), srcDevice->colorSpace()); const QRect effectRect(dstRect); const QString compositeOp = config->blendMode(); const quint8 opacityU8 = 255.0 / 100.0 * config->opacity(); - KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, srcDevice); + KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, opacityU8, QBitArray(), srcDevice); if (config->fillType() == psd_fill_solid_color) { KisFillPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); + gc.setCompositeOp(COMPOSITE_COPY); gc.setSelection(baseSelection); gc.fillSelection(effectRect, effectColor); gc.end(); } else if (config->fillType() == psd_fill_gradient) { if (!config->gradient()) { warnKrita << "KisLsUtils::applyFinalSelection: Gradient object is null! Skipping..."; return; } - KisPaintDeviceSP overlayDevice = - new KisPaintDevice(dstDevice->colorSpace()); - QVector table(256); Private::getGradientTable(config->gradient().data(), &table, dstDevice->colorSpace()); - Private::applyGradient(overlayDevice, baseSelection->pixelSelection(), + Private::applyGradient(dstDevice, baseSelection->pixelSelection(), effectRect, table, true, config->jitter(), env); - - KisPainter gc(dstDevice); - gc.setCompositeOp(COMPOSITE_OVER); - env->setupFinalPainter(&gc, opacityU8, QBitArray()); - gc.bitBlt(effectRect.topLeft(), overlayDevice, effectRect); - gc.end(); } //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("6_device_shadow.png"); } bool checkEffectEnabled(const psd_layer_effects_shadow_base *config, KisMultipleProjection *dst) { bool result = config->effectEnabled(); if (!result) { dst->freeAllProjections(); } return result; } } diff --git a/libs/image/layerstyles/kis_multiple_projection.cpp b/libs/image/layerstyles/kis_multiple_projection.cpp index b3e4ae9637..ffc3e3c3af 100644 --- a/libs/image/layerstyles/kis_multiple_projection.cpp +++ b/libs/image/layerstyles/kis_multiple_projection.cpp @@ -1,147 +1,161 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_multiple_projection.h" #include #include #include #include "kis_painter.h" #include "kis_paint_device.h" +#include "kis_layer_style_filter_environment.h" struct ProjectionStruct { KisPaintDeviceSP device; QString compositeOpId; + quint8 opacity = OPACITY_OPAQUE_U8; + QBitArray channelFlags; }; typedef QMap PlanesMap; struct KisMultipleProjection::Private { QReadWriteLock lock; PlanesMap planes; }; KisMultipleProjection::KisMultipleProjection() : m_d(new Private) { } KisMultipleProjection::~KisMultipleProjection() { } QString KisMultipleProjection::defaultProjectionId() { return "00_default"; } -KisPaintDeviceSP KisMultipleProjection::getProjection(const QString &id, const QString &compositeOpId, KisPaintDeviceSP prototype) +KisPaintDeviceSP KisMultipleProjection::getProjection(const QString &id, + const QString &compositeOpId, + quint8 opacity, + const QBitArray &channelFlags, + KisPaintDeviceSP prototype) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator constIt = m_d->planes.constFind(id); if (constIt == m_d->planes.constEnd() || constIt->compositeOpId != compositeOpId || + constIt->opacity != opacity || + constIt->channelFlags != channelFlags || *constIt->device->colorSpace() != *prototype->colorSpace()) { readLocker.unlock(); { QWriteLocker writeLocker(&m_d->lock); PlanesMap::iterator writeIt = m_d->planes.find(id); if (writeIt == m_d->planes.end()) { ProjectionStruct plane; plane.device = new KisPaintDevice(prototype->colorSpace()); plane.device->prepareClone(prototype); plane.compositeOpId = compositeOpId; + plane.opacity = opacity; + plane.channelFlags = channelFlags; writeIt = m_d->planes.insert(id, plane); } else if (writeIt->compositeOpId != compositeOpId || *writeIt->device->colorSpace() != *prototype->colorSpace()) { writeIt->device->prepareClone(prototype); writeIt->compositeOpId = compositeOpId; + writeIt->opacity = opacity; + writeIt->channelFlags = channelFlags; } return writeIt->device; } } return constIt->device; } void KisMultipleProjection::freeProjection(const QString &id) { QWriteLocker writeLocker(&m_d->lock); m_d->planes.remove(id); } void KisMultipleProjection::freeAllProjections() { QWriteLocker writeLocker(&m_d->lock); m_d->planes.clear(); } void KisMultipleProjection::clear(const QRect &rc) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); for (; it != end; ++it) { const_cast(it->device.data())->clear(rc); } } -void KisMultipleProjection::apply(KisPaintDeviceSP dstDevice, const QRect &rect) +void KisMultipleProjection::apply(KisPaintDeviceSP dstDevice, const QRect &rect, KisLayerStyleFilterEnvironment *env) { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); for (; it != end; ++it) { KisPainter gc(dstDevice); gc.setCompositeOp(it->compositeOpId); + env->setupFinalPainter(&gc, it->opacity, it->channelFlags); gc.bitBlt(rect.topLeft(), it->device, rect); } } KisPaintDeviceList KisMultipleProjection::getLodCapableDevices() const { QReadLocker readLocker(&m_d->lock); PlanesMap::const_iterator it = m_d->planes.constBegin(); PlanesMap::const_iterator end = m_d->planes.constEnd(); KisPaintDeviceList list; for (; it != end; ++it) { list << it->device; } return list; } diff --git a/libs/image/layerstyles/kis_multiple_projection.h b/libs/image/layerstyles/kis_multiple_projection.h index a6f9038a6b..3055d01600 100644 --- a/libs/image/layerstyles/kis_multiple_projection.h +++ b/libs/image/layerstyles/kis_multiple_projection.h @@ -1,50 +1,51 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 __KIS_MULTIPLE_PROJECTION_H #define __KIS_MULTIPLE_PROJECTION_H #include #include "kis_types.h" #include "kritaimage_export.h" +class KisLayerStyleFilterEnvironment; class KRITAIMAGE_EXPORT KisMultipleProjection { public: KisMultipleProjection(); ~KisMultipleProjection(); static QString defaultProjectionId(); - KisPaintDeviceSP getProjection(const QString &id, const QString &compositeOpId, KisPaintDeviceSP prototype); + KisPaintDeviceSP getProjection(const QString &id, const QString &compositeOpId, quint8 opacity, const QBitArray &channelFlags, KisPaintDeviceSP prototype); void freeProjection(const QString &id); void freeAllProjections(); void clear(const QRect &rc); - void apply(KisPaintDeviceSP dstDevice, const QRect &rect); + void apply(KisPaintDeviceSP dstDevice, const QRect &rect, KisLayerStyleFilterEnvironment *env); KisPaintDeviceList getLodCapableDevices() const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_MULTIPLE_PROJECTION_H */ diff --git a/libs/image/tests/data/convolution_painter_test/dilate_dilate10.png b/libs/image/tests/data/convolution_painter_test/dilate_dilate10.png new file mode 100644 index 0000000000..63e41542b6 Binary files /dev/null and b/libs/image/tests/data/convolution_painter_test/dilate_dilate10.png differ diff --git a/libs/image/tests/data/convolution_painter_test/dilate_erode5.png b/libs/image/tests/data/convolution_painter_test/dilate_erode5.png new file mode 100644 index 0000000000..55fdd4fc8b Binary files /dev/null and b/libs/image/tests/data/convolution_painter_test/dilate_erode5.png differ diff --git a/libs/image/tests/data/convolution_painter_test/dilate_initial.png b/libs/image/tests/data/convolution_painter_test/dilate_initial.png new file mode 100644 index 0000000000..54e7d48dd2 Binary files /dev/null and b/libs/image/tests/data/convolution_painter_test/dilate_initial.png differ diff --git a/libs/image/tests/data/fill_painter/patterns_fill__custom_origin.png b/libs/image/tests/data/fill_painter/patterns_fill__custom_origin.png new file mode 100644 index 0000000000..5d19d0955f Binary files /dev/null and b/libs/image/tests/data/fill_painter/patterns_fill__custom_origin.png differ diff --git a/libs/image/tests/data/fill_painter/patterns_fill__null_origin.png b/libs/image/tests/data/fill_painter/patterns_fill__null_origin.png new file mode 100644 index 0000000000..b0877caa3e Binary files /dev/null and b/libs/image/tests/data/fill_painter/patterns_fill__null_origin.png differ diff --git a/libs/image/tests/kis_convolution_painter_test.cpp b/libs/image/tests/kis_convolution_painter_test.cpp index efcd181116..0bcfade595 100644 --- a/libs/image/tests/kis_convolution_painter_test.cpp +++ b/libs/image/tests/kis_convolution_painter_test.cpp @@ -1,440 +1,474 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 //MSVC requires that Vc come first #include "kis_convolution_painter_test.h" #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include #include #include "testutil.h" KisPaintDeviceSP initAsymTestDevice(QRect &imageRect, int &pixelSize, QByteArray &initialData) { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); pixelSize = dev->pixelSize(); imageRect = QRect(0,0,5,5); initialData.resize(25 * pixelSize); quint8 *ptr = (quint8*) initialData.data(); for(int i = 0; i < 25; i++) { KoColor pixel(QColor(i,i,i,255), dev->colorSpace()); memcpy(ptr, pixel.data(), pixelSize); ptr += pixelSize; } dev->writeBytes((const quint8*)initialData.constData(), imageRect); return dev; } Eigen::Matrix initSymmFilter(qreal &offset, qreal &factor) { Eigen::Matrix filter; filter(0,0) = 1.0 / 21; filter(0,1) = 3.0 / 21; filter(0,2) = 1.0 / 21; filter(1,0) = 3.0 / 21; filter(1,1) = 5.0 / 21; filter(1,2) = 3.0 / 21; filter(2,0) = 1.0 / 21; filter(2,1) = 3.0 / 21; filter(2,2) = 1.0 / 21; offset = 0.0; factor = 1.0; return filter; } Eigen::Matrix initAsymmFilter(qreal &offset, qreal &factor) { Eigen::Matrix filter; filter(0,0) = 1.0; filter(1,0) = 2.0; filter(2,0) = 1.0; filter(0,1) = 0.0; filter(1,1) = 1.0; filter(2,1) = 0.0; filter(0,2) =-1.0; filter(1,2) =-2.0; filter(2,2) =-1.0; offset = 0.0; factor = 1.0; return filter; } void printPixel(QString prefix, int pixelSize, quint8 *data) { QString str = prefix; for(int i = 0; i < pixelSize; i++) { str += ' '; str += QString::number(data[i]); } dbgKrita << str; } void KisConvolutionPainterTest::testIdentityConvolution() { QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->convertFromQImage(qimage, 0, 0, 0); KisConvolutionKernelSP kernel = new KisConvolutionKernel(3, 3, 0, 0); kernel->data()(0) = 0; kernel->data()(1) = 0; kernel->data()(2) = 0; kernel->data()(3) = 0; kernel->data()(4) = 1; kernel->data()(5) = 0; kernel->data()(6) = 0; kernel->data()(7) = 0; kernel->data()(8) = 0; KisConvolutionPainter gc(dev); gc.beginTransaction(); gc.applyMatrix(kernel, dev, QPoint(0, 0), QPoint(0, 0), QSize(qimage.width(), qimage.height())); gc.deleteTransaction(); QImage resultImage = dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, qimage, resultImage)) { resultImage.save("identity_convolution.png"); QFAIL(QString("Identity kernel did change image, first different pixel: %1,%2 ").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisConvolutionPainterTest::testSymmConvolution() { qreal offset = 0.0; qreal factor = 1.0; Eigen::Matrix filter = initSymmFilter(offset, factor); QRect imageRect; int pixelSize = 0; QByteArray initialData; KisPaintDeviceSP dev = initAsymTestDevice(imageRect, pixelSize, initialData); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(filter, offset, factor); KisConvolutionPainter gc(dev); gc.beginTransaction(); QRect filterRect = imageRect.adjusted(1,1,-1,-1); gc.applyMatrix(kernel, dev, filterRect.topLeft(), filterRect.topLeft(), filterRect.size()); gc.deleteTransaction(); QByteArray resultData(initialData.size(), 0); dev->readBytes((quint8*)resultData.data(), imageRect); QCOMPARE(resultData, initialData); } void KisConvolutionPainterTest::testAsymmConvolutionImp(QBitArray channelFlags) { qreal offset = 0.0; qreal factor = 1.0; Eigen::Matrix filter = initAsymmFilter(offset, factor); QRect imageRect; int pixelSize = -1; QByteArray initialData; KisPaintDeviceSP dev = initAsymTestDevice(imageRect, pixelSize, initialData); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(filter, offset, factor); KisConvolutionPainter gc(dev); gc.beginTransaction(); gc.setChannelFlags(channelFlags); QRect filterRect = imageRect.adjusted(1,1,-1,-1); gc.applyMatrix(kernel, dev, filterRect.topLeft(), filterRect.topLeft(), filterRect.size()); gc.deleteTransaction(); QByteArray resultData(initialData.size(), 0); dev->readBytes((quint8*)resultData.data(), imageRect); QRect filteredRect = imageRect.adjusted(1, 1, -1, -1); quint8 *srcPtr = (quint8*) initialData.data(); quint8 *resPtr = (quint8*) resultData.data(); for(int row = 0; row < imageRect.height(); row++) { for(int col = 0; col < imageRect.width(); col++) { bool isFiltered = filteredRect.contains(col, row); int pixelValue = 8 + row * imageRect.width() + col; KoColor filteredPixel(QColor(pixelValue, pixelValue, pixelValue, 255), dev->colorSpace()); KoColor resultPixel(dev->colorSpace()); for(int j = 0; j < pixelSize; j++) { resultPixel.data()[j] = isFiltered && channelFlags[j] ? filteredPixel.data()[j] : srcPtr[j]; } if(memcmp(resPtr, resultPixel.data(), pixelSize)) { printPixel("Actual: ", pixelSize, resPtr); printPixel("Expected:", pixelSize, resultPixel.data()); QFAIL("Failed to filter area"); } srcPtr += pixelSize; resPtr += pixelSize; } } } void KisConvolutionPainterTest::testAsymmAllChannels() { QBitArray channelFlags = KoColorSpaceRegistry::instance()->rgb8()->channelFlags(true, true); testAsymmConvolutionImp(channelFlags); } void KisConvolutionPainterTest::testAsymmSkipRed() { QBitArray channelFlags = KoColorSpaceRegistry::instance()->rgb8()->channelFlags(true, true); channelFlags[2] = false; testAsymmConvolutionImp(channelFlags); } void KisConvolutionPainterTest::testAsymmSkipGreen() { QBitArray channelFlags = KoColorSpaceRegistry::instance()->rgb8()->channelFlags(true, true); channelFlags[1] = false; testAsymmConvolutionImp(channelFlags); } void KisConvolutionPainterTest::testAsymmSkipBlue() { QBitArray channelFlags = KoColorSpaceRegistry::instance()->rgb8()->channelFlags(true, true); channelFlags[0] = false; testAsymmConvolutionImp(channelFlags); } void KisConvolutionPainterTest::testAsymmSkipAlpha() { QBitArray channelFlags = KoColorSpaceRegistry::instance()->rgb8()->channelFlags(true, true); channelFlags[3] = false; testAsymmConvolutionImp(channelFlags); } // #include void KisConvolutionPainterTest::benchmarkConvolution() { QImage referenceImage(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); QRect imageRect(QPoint(), referenceImage.size()); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->convertFromQImage(referenceImage, 0, 0, 0); int diameter = 1; for (int i = 0; i < 4; i++) { KisCircleMaskGenerator* kas = new KisCircleMaskGenerator(diameter, 1.0, 5, 5, 2, false); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMaskGenerator(kas); KisConvolutionPainter gc(dev); QTime timer; timer.start(); // CALLGRIND_START_INSTRUMENTATION; gc.beginTransaction(); gc.applyMatrix(kernel, dev, imageRect.topLeft(), imageRect.topLeft(), imageRect.size()); gc.deleteTransaction(); // CALLGRIND_STOP_INSTRUMENTATION; dbgKrita << "Diameter:" << diameter << "time:" << timer.elapsed(); if(diameter < 4) { diameter += 2; } else { diameter += 8; } } } void KisConvolutionPainterTest::testGaussianBase(KisPaintDeviceSP dev, bool useFftw, const QString &prefix) { QBitArray channelFlags = KoColorSpaceRegistry::instance()->rgb8()->channelFlags(true, true); KisPainter gc(dev); qreal horizontalRadius = 5, verticalRadius = 5; for(int i = 0; i < 3 ; i++, horizontalRadius+=5, verticalRadius+=5) { QTime timer; timer.start(); gc.beginTransaction(); if (( horizontalRadius > 0 ) && ( verticalRadius > 0 )) { KisPaintDeviceSP interm = new KisPaintDevice(dev->colorSpace()); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(horizontalRadius); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(verticalRadius); const QRect applyRect = dev->exactBounds(); KisConvolutionPainter::TestingEnginePreference enginePreference = useFftw ? KisConvolutionPainter::FFTW : KisConvolutionPainter::SPATIAL; KisConvolutionPainter horizPainter(interm, enginePreference); horizPainter.setChannelFlags(channelFlags); horizPainter.applyMatrix(kernelHoriz, dev, applyRect.topLeft() - QPoint(0, verticalRadius), applyRect.topLeft() - QPoint(0, verticalRadius), applyRect.size() + QSize(0, 2 * verticalRadius), BORDER_REPEAT); KisConvolutionPainter verticalPainter(dev, enginePreference); verticalPainter.setChannelFlags(channelFlags); verticalPainter.applyMatrix(kernelVertical, interm, applyRect.topLeft(), applyRect.topLeft(), applyRect.size(), BORDER_REPEAT); QImage result = dev->convertToQImage(0, applyRect.x(), applyRect.y(), applyRect.width(), applyRect.height()); QString engine = useFftw ? "fftw" : "spatial"; QString testCaseName = QString("test_gaussian_%1_%2_%3.png").arg(horizontalRadius).arg(verticalRadius).arg(engine); TestUtil::checkQImage(result, "convolution_painter_test", QString("gaussian_") + prefix, testCaseName); gc.revertTransaction(); } dbgKrita << "Elapsed time:" << timer.elapsed() << "ms"; } } void KisConvolutionPainterTest::testGaussian(bool useFftw) { QImage referenceImage(TestUtil::fetchDataFileLazy("kritaTransparent.png")); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->convertFromQImage(referenceImage, 0, 0, 0); testGaussianBase(dev, useFftw, ""); } void KisConvolutionPainterTest::testGaussianSpatial() { testGaussian(false); } void KisConvolutionPainterTest::testGaussianFFTW() { testGaussian(true); } void KisConvolutionPainterTest::testGaussianSmall(bool useFftw) { KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); KoColor c(Qt::yellow, dev->colorSpace()); for (int i = 0; i < 50; i++) { quint8 baseOpacity = 75; KoColor c(Qt::magenta, dev->colorSpace()); for (int j = 0; j <= 6; j++) { c.setOpacity(static_cast(baseOpacity + 30 * j)); dev->setPixel(i + j, i, c); } } testGaussianBase(dev, useFftw, "reduced"); } void KisConvolutionPainterTest::testGaussianSmallSpatial() { testGaussianSmall(false); } void KisConvolutionPainterTest::testGaussianSmallFFTW() { testGaussianSmall(true); } void KisConvolutionPainterTest::testGaussianDetails(bool useFftw) { QImage referenceImage(TestUtil::fetchDataFileLazy("resolution_test.png")); KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); dev->convertFromQImage(referenceImage, 0, 0, 0); testGaussianBase(dev, useFftw, "details"); } void KisConvolutionPainterTest::testGaussianDetailsSpatial() { testGaussianDetails(false); } void KisConvolutionPainterTest::testGaussianDetailsFFTW() { testGaussianDetails(true); } +#include "kis_transaction.h" + +void KisConvolutionPainterTest::testDilate() +{ + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); + KisPaintDeviceSP dev = new KisPaintDevice(cs); + + const QRect imageRect(0,0,256,256); + dev->fill(QRect(50,50,100,20), KoColor(Qt::white, cs)); + dev->fill(QRect(150,50,20,100), KoColor(Qt::white, cs)); + + TestUtil::checkQImage(dev->convertToQImage(0, imageRect), "convolution_painter_test", "dilate", "initial"); + + KisGaussianKernel::applyDilate(dev, imageRect, 10, QBitArray(), 0); + + TestUtil::checkQImage(dev->convertToQImage(0, imageRect), "convolution_painter_test", "dilate", "dilate10"); +} + +void KisConvolutionPainterTest::testErode() +{ + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); + KisPaintDeviceSP dev = new KisPaintDevice(cs); + + const QRect imageRect(0,0,256,256); + dev->fill(QRect(50,50,100,20), KoColor(Qt::white, cs)); + dev->fill(QRect(150,50,20,100), KoColor(Qt::white, cs)); + + TestUtil::checkQImage(dev->convertToQImage(0, imageRect), "convolution_painter_test", "dilate", "initial"); + + KisGaussianKernel::applyErodeU8(dev, imageRect, 5, QBitArray(), 0); + + TestUtil::checkQImage(dev->convertToQImage(0, imageRect), "convolution_painter_test", "dilate", "erode5"); +} + QTEST_MAIN(KisConvolutionPainterTest) diff --git a/libs/image/tests/kis_convolution_painter_test.h b/libs/image/tests/kis_convolution_painter_test.h index d46e5c1e9b..a6e293036b 100644 --- a/libs/image/tests/kis_convolution_painter_test.h +++ b/libs/image/tests/kis_convolution_painter_test.h @@ -1,61 +1,64 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_CONVOLUTION_PAINTER_TEST_H #define KIS_CONVOLUTION_PAINTER_TEST_H #include #include class QBitArray; class KisConvolutionPainterTest : public QObject { Q_OBJECT private: void testAsymmConvolutionImp(QBitArray channelFlags); void testGaussianBase(KisPaintDeviceSP dev, bool useFftw, const QString &prefix); void testGaussian(bool useFftw); void testGaussianSmall(bool useFftw); void testGaussianDetails(bool useFftw); private Q_SLOTS: void testIdentityConvolution(); void testSymmConvolution(); void testAsymmAllChannels(); void testAsymmSkipRed(); void testAsymmSkipGreen(); void testAsymmSkipBlue(); void testAsymmSkipAlpha(); void benchmarkConvolution(); void testGaussianSpatial(); void testGaussianFFTW(); void testGaussianSmallSpatial(); void testGaussianSmallFFTW(); void testGaussianDetailsSpatial(); void testGaussianDetailsFFTW(); + + void testDilate(); + void testErode(); }; #endif diff --git a/libs/image/tests/kis_fill_painter_test.cpp b/libs/image/tests/kis_fill_painter_test.cpp index 2b08bc16c1..577ba562aa 100644 --- a/libs/image/tests/kis_fill_painter_test.cpp +++ b/libs/image/tests/kis_fill_painter_test.cpp @@ -1,153 +1,208 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_fill_painter_test.h" #include "testutil.h" #include #include "kis_fill_painter.h" #include #define THRESHOLD 10 void KisFillPainterTest::testCreation() { KisFillPainter test; } void KisFillPainterTest::benchmarkFillPainter(const QPoint &startPoint, bool useCompositioning) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage srcImage(TestUtil::fetchDataFileLazy("heavy_labyrinth.png")); QVERIFY(!srcImage.isNull()); QRect imageRect = srcImage.rect(); dev->convertFromQImage(srcImage, 0, 0, 0); QBENCHMARK_ONCE { KisFillPainter gc(dev); gc.setFillThreshold(THRESHOLD); gc.setWidth(imageRect.width()); gc.setHeight(imageRect.height()); gc.setPaintColor(KoColor(Qt::red, dev->colorSpace())); gc.setUseCompositioning(useCompositioning); gc.fillColor(startPoint.x(), startPoint.y(), dev); } QImage resultImage = dev->convertToQImage(0, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height()); QString testName = QString("heavy_labyrinth_%1_%2_%3") .arg(startPoint.x()) .arg(startPoint.y()) .arg(useCompositioning ? "composed" : "direct"); QVERIFY(TestUtil::checkQImage(resultImage, "fill_painter", "general_", testName)); } void KisFillPainterTest::benchmarkFillPainter() { benchmarkFillPainter(QPoint(), false); } void KisFillPainterTest::benchmarkFillPainterOffset() { benchmarkFillPainter(QPoint(5,5), false); } void KisFillPainterTest::benchmarkFillPainterOffsetCompositioning() { benchmarkFillPainter(QPoint(5,5), true); } void KisFillPainterTest::benchmarkFillingScanlineColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage srcImage(TestUtil::fetchDataFileLazy("heavy_labyrinth.png")); QVERIFY(!srcImage.isNull()); QRect imageRect = srcImage.rect(); dev->convertFromQImage(srcImage, 0, 0, 0); QBENCHMARK_ONCE { KisScanlineFill gc(dev, QPoint(), imageRect); gc.setThreshold(THRESHOLD); gc.fillColor(KoColor(Qt::red, dev->colorSpace())); } QImage resultImage = dev->convertToQImage(0, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height()); QVERIFY(TestUtil::checkQImage(resultImage, "fill_painter", "scanline_", "heavy_labyrinth_top_left")); } void KisFillPainterTest::benchmarkFillingScanlineSelection() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage srcImage(TestUtil::fetchDataFileLazy("heavy_labyrinth.png")); QVERIFY(!srcImage.isNull()); QRect imageRect = srcImage.rect(); dev->convertFromQImage(srcImage, 0, 0, 0); KisPixelSelectionSP pixelSelection = new KisPixelSelection(); QBENCHMARK_ONCE { KisScanlineFill gc(dev, QPoint(), imageRect); gc.setThreshold(THRESHOLD); gc.fillSelection(pixelSelection); } QImage resultImage = pixelSelection->convertToQImage(0, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height()); QVERIFY(TestUtil::checkQImage(resultImage, "fill_painter", "scanline_", "heavy_labyrinth_top_left_selection")); } +void KisFillPainterTest::testPatternFill() +{ + const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); + KisPaintDeviceSP dst = new KisPaintDevice(cs); + + KisPaintDeviceSP pattern = new KisPaintDevice(cs); + pattern->fill(QRect(0,0,32,32), KoColor(Qt::red, cs)); + pattern->fill(QRect(32,32,32,32), KoColor(Qt::red, cs)); + pattern->fill(QRect(32,0,32,32), KoColor(Qt::yellow, cs)); + pattern->fill(QRect(0,32,32,32), KoColor(Qt::white, cs)); + + const QRect fillRect(-128,-128,384,384); + KisFillPainter painter(dst); + + + { // fill aligned + const QRect patternRect = pattern->exactBounds(); + painter.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, patternRect); + dst->fill(QRect(0,0,10,10), KoColor(Qt::black, cs)); + + QImage resultImage = + dst->convertToQImage(0, + fillRect.x(), fillRect.y(), + fillRect.width(), fillRect.height()); + + QVERIFY(TestUtil::checkQImage(resultImage, + "fill_painter", + "patterns_fill_", + "null_origin")); + } + + { // fill with offset + dst->clear(); + pattern->setX(7); + pattern->setY(-13); + + + const QRect patternRect = pattern->exactBounds(); + + painter.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, patternRect); + dst->fill(QRect(0,0,10,10), KoColor(Qt::black, cs)); + + QImage resultImage = + dst->convertToQImage(0, + fillRect.x(), fillRect.y(), + fillRect.width(), fillRect.height()); + + QVERIFY(TestUtil::checkQImage(resultImage, + "fill_painter", + "patterns_fill_", + "custom_origin")); + } + +} + QTEST_MAIN(KisFillPainterTest) diff --git a/libs/image/tests/kis_fill_painter_test.h b/libs/image/tests/kis_fill_painter_test.h index 1fcc02a26f..d194d589c7 100644 --- a/libs/image/tests/kis_fill_painter_test.h +++ b/libs/image/tests/kis_fill_painter_test.h @@ -1,41 +1,43 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KIS_FILL_PAINTER_TEST_H #define KIS_FILL_PAINTER_TEST_H #include class KisFillPainterTest : public QObject { Q_OBJECT private: void benchmarkFillPainter(const QPoint &startPoint, bool useCompositioning); private Q_SLOTS: void testCreation(); void benchmarkFillPainter(); void benchmarkFillPainterOffset(); void benchmarkFillPainterOffsetCompositioning(); void benchmarkFillingScanlineColor(); void benchmarkFillingScanlineSelection(); + + void testPatternFill(); }; #endif diff --git a/libs/image/tests/kis_layer_styles_test.cpp b/libs/image/tests/kis_layer_styles_test.cpp index a79d02221e..318984e647 100644 --- a/libs/image/tests/kis_layer_styles_test.cpp +++ b/libs/image/tests/kis_layer_styles_test.cpp @@ -1,279 +1,279 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "kis_layer_styles_test.h" #include #include "kis_transaction.h" #include "testutil.h" #include #include "layerstyles/kis_layer_style_filter.h" #include "layerstyles/kis_layer_style_filter_environment.h" #include "layerstyles/kis_ls_drop_shadow_filter.h" #include "kis_psd_layer_style.h" #include "layerstyles/kis_multiple_projection.h" struct TestConfig { TestConfig() : distance(0), angle(0), spread(0), size(0), noise(0), knocks_out(false), keep_original(false) { } int distance; int angle; int spread; int size; int noise; int knocks_out; int opacity; bool keep_original; void writeProperties(KisPSDLayerStyleSP style) const { style->context()->keep_original = keep_original; style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(distance); style->dropShadow()->setSpread(spread); style->dropShadow()->setSize(size); style->dropShadow()->setNoise(noise); style->dropShadow()->setKnocksOut(knocks_out); style->dropShadow()->setOpacity(opacity); } QString genTestname(const QString &prefix) const { return QString("%1_d_%2_an_%3_sz_%4_spr_%5_nz_%6_ko_%7_keep_%8") .arg(prefix) .arg(distance) .arg(angle) .arg(size) .arg(spread) .arg(noise) .arg(knocks_out) .arg(keep_original); } }; void testDropShadowImpl(const TestConfig &config, const QVector &applyRects, const QString &testName, bool useSeparateDevices) { Q_UNUSED(useSeparateDevices); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QRect srcRect(50, 50, 100, 100); QRect dstRect(0, 0, 200, 200); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(srcRect, KoColor(Qt::red, cs)); KisMultipleProjection projection; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); config.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); Q_FOREACH (const QRect &rc, applyRects) { lsFilter.processDirectly(dev, &projection, rc, style, &env); } KisPaintDeviceSP dst = new KisPaintDevice(cs); - projection.apply(dst, dstRect); + projection.apply(dst, dstRect, &env); QImage resultImage = dst->convertToQImage(0, dstRect); TestUtil::checkQImage(resultImage, "layer_styles_test", "common", config.genTestname(testName)); } void KisLayerStylesTest::testLayerStylesFull() { TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, QVector() << QRect(0,0,200,200), "full", false); } void KisLayerStylesTest::testLayerStylesPartial() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, rects, "partial", true); } void KisLayerStylesTest::testLayerStylesPartialVary() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = true; testDropShadowImpl(c, rects, "partial", true); c.noise = 90; testDropShadowImpl(c, rects, "partial", true); c.noise = 0; testDropShadowImpl(c, rects, "partial", true); c.noise = 10; testDropShadowImpl(c, rects, "partial", true); c.angle = 90; testDropShadowImpl(c, rects, "partial", true); c.angle = 45; testDropShadowImpl(c, rects, "partial", true); c.knocks_out = true; testDropShadowImpl(c, rects, "partial", true); c.spread = 90; testDropShadowImpl(c, rects, "partial", true); } void testDropShadowNeedChangeRects(int distance, int noise, int size, int spread, const QRect &applyRect, const QRect &needRect, const QRect &changeRect) { TestConfig c; c.distance = distance; c.spread = spread; c.size = size; c.noise = noise; c.angle = 90; c.knocks_out = false; c.opacity = 50; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); c.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); QCOMPARE(lsFilter.neededRect(applyRect, style, &env), needRect); QCOMPARE(lsFilter.changedRect(applyRect, style, &env), changeRect); } void KisLayerStylesTest::testLayerStylesRects() { QRect applyRect; QRect needRect; QRect changeRect; applyRect = QRect(10,10,10,10); needRect = applyRect; changeRect = applyRect; testDropShadowNeedChangeRects(0, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(10,0,10,20); changeRect = QRect(10,10,10,20); testDropShadowNeedChangeRects(10, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(2,2,26,26); changeRect = QRect(2,2,26,26); testDropShadowNeedChangeRects(0, 30, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 50, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 75, applyRect, needRect, changeRect); } QTEST_MAIN(KisLayerStylesTest) diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index c388f23b79..38b6e288e8 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,592 +1,599 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/kis_dlg_internal_color_selector.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_config_notifier.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp + opengl/KisOpenGLUpdateInfoBuilder.cpp kis_fps_decoration.cpp recorder/kis_node_query_path_editor.cc recorder/kis_recorded_action_creator.cc recorder/kis_recorded_action_creator_factory.cc recorder/kis_recorded_action_creator_factory_registry.cc recorder/kis_recorded_action_editor_factory.cc recorder/kis_recorded_action_editor_factory_registry.cc recorder/kis_recorded_filter_action_editor.cc recorder/kis_recorded_filter_action_creator.cpp recorder/kis_recorded_paint_action_editor.cc tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_recording_adapter.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_popup_button.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_spinbox_color_selector.cpp widgets/kis_screen_color_picker.cpp widgets/kis_preset_live_preview_view.cpp widgets/KoDualColorButton.cpp widgets/kis_color_input.cpp widgets/kis_color_button.cpp widgets/KisVisualColorSelector.cpp widgets/KisVisualColorSelectorShape.cpp widgets/KisVisualEllipticalSelectorShape.cpp widgets/KisVisualRectangleSelectorShape.cpp widgets/KisVisualTriangleSelectorShape.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisPaletteModel.cpp kis_palette_delegate.cpp kis_palette_view.cpp KisColorsetChooser.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h ) if(WIN32) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp opengl/kis_opengl_win.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp + KisFrameDataSerializer.cpp + KisFrameCacheStore.cpp + KisFrameCacheSwapper.cpp + KisAbstractFrameCacheSwapper.cpp + KisInMemoryFrameCacheSwapper.cpp + input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgpaintactioneditor.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui brushhud/kis_dlg_brush_hud_config.ui forms/wdgdlginternalcolorselector.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/tests/kis_resource_server_provider_test.h b/libs/ui/KisAbstractFrameCacheSwapper.cpp similarity index 72% copy from libs/ui/tests/kis_resource_server_provider_test.h copy to libs/ui/KisAbstractFrameCacheSwapper.cpp index c062d0900a..1540d8f32b 100644 --- a/libs/ui/tests/kis_resource_server_provider_test.h +++ b/libs/ui/KisAbstractFrameCacheSwapper.cpp @@ -1,33 +1,22 @@ /* - * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org + * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "KisAbstractFrameCacheSwapper.h" -#ifndef KIS_AUTOGRADIENT_RESOURCE_TEST_H -#define KIS_AUTOGRADIENT_RESOURCE_TEST_H - -#include - -class KisResourceServerProviderTest : public QObject +KisAbstractFrameCacheSwapper::~KisAbstractFrameCacheSwapper() { - Q_OBJECT -private Q_SLOTS: - - void testFetchResource(); - -}; - -#endif +} diff --git a/libs/ui/KisAbstractFrameCacheSwapper.h b/libs/ui/KisAbstractFrameCacheSwapper.h new file mode 100644 index 0000000000..c01db7953c --- /dev/null +++ b/libs/ui/KisAbstractFrameCacheSwapper.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 KISABSTRACTFRAMECACHESWAPPER_H +#define KISABSTRACTFRAMECACHESWAPPER_H + +#include "kritaui_export.h" + +class QRect; + +template +class KisSharedPtr; + +class KisOpenGLUpdateInfo; +typedef KisSharedPtr KisOpenGLUpdateInfoSP; + + +class KRITAUI_EXPORT KisAbstractFrameCacheSwapper +{ +public: + virtual ~KisAbstractFrameCacheSwapper(); + + // WARNING: after transferring \p info to saveFrame() the object becomes invalid + virtual void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) = 0; + virtual KisOpenGLUpdateInfoSP loadFrame(int frameId) = 0; + + virtual void moveFrame(int srcFrameId, int dstFrameId) = 0; + virtual void forgetFrame(int frameId) = 0; + + virtual bool hasFrame(int frameId) const = 0; + + virtual int frameLevelOfDetail(int frameId) const = 0; + virtual QRect frameDirtyRect(int frameId) const = 0; +}; + +#endif // KISABSTRACTFRAMECACHESWAPPER_H diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index f2c8c87dfc..1d1c1ef300 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,864 +1,840 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include #include #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (m_fileCount > 0 || hideSplash) { m_splash->hide(); } else { m_splash->setWindowFlags(Qt::Dialog); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); m_splash->setParent(0); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } m_splash->show(); m_splash->activateWindow(); } } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { -// qDebug() << "addResourceTypes();"; + // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); // Indicate that it is now safe for users of KoResourcePaths to load resources KoResourcePaths::setReady(); } void KisApplication::loadResources() { -// qDebug() << "loadResources();"; + // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { -// qDebug() << "loadResourceTags()"; + // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { -// qDebug() << "loadPlugins();"; + // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { -// qDebug() << "loadGuiPlugins();"; + // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); -// qDebug() << "loading tools"; + // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); -// qDebug() << "loading dockers"; + // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); -// qDebug() << "loading exiv2"; + // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg; #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); - const bool print = args.print(); const bool exportAs = args.exportAs(); - const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); - d->batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); - // print & exportAsPdf do user interaction ATM + d->batchRun = (exportAs || !exportFileName.isEmpty()); const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed - // TODO: fix print & exportAsPdf to work without mainwindow shown bool showmainWindow = !exportAs; // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); - // Initialize all Krita directories etc. - KoGlobal::initialize(); - KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open - // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) + // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments - short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } - nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } else if (d->mainWindow) { - KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { - if (print) { - d->mainWindow->slotFilePrint(); - nPrinted++; - // TODO: trigger closing of app once printing is done - } - else if (exportAsPdf) { - KisPrintJob *job = d->mainWindow->exportToPdf(exportFileName); - if (job) - connect (job, SIGNAL(destroyed(QObject*)), d->mainWindow, - SLOT(slotFileQuit()), Qt::QueuedConnection); - nPrinted++; - } else { + if (fileName.endsWith(".bundle")) { + d->mainWindow->installBundle(fileName); + } + else { + KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; + + if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } - } else { - // .... if failed - // delete doc; done by openDocument } } } } - - if (d->batchRun) { - return nPrinted > 0; - } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { - //d->splashScreen->loadingLabel->setText(textToLoad); + //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisApplicationArguments.cpp b/libs/ui/KisApplicationArguments.cpp index c0de4c62f4..759636541d 100644 --- a/libs/ui/KisApplicationArguments.cpp +++ b/libs/ui/KisApplicationArguments.cpp @@ -1,361 +1,337 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * 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; either * version 2.1 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 library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplicationArguments.h" #include #include #include #include #include #include #include #include #include #include #include #include struct Q_DECL_HIDDEN KisApplicationArguments::Private { Private() { } QStringList filenames; int dpiX {72}; int dpiY {72}; bool doTemplate {false}; - bool print {false}; bool exportAs {false}; - bool exportAsPdf {false}; QString exportFileName; QString workspace; QString windowLayout; QString session; bool canvasOnly {false}; bool noSplash {false}; bool fullScreen {false}; bool newImage {false}; QString colorModel {"RGBA"}; QString colorDepth {"U8"}; int width {2000}; int height {5000}; }; KisApplicationArguments::KisApplicationArguments() : d(new Private) { } KisApplicationArguments::~KisApplicationArguments() { } KisApplicationArguments::KisApplicationArguments(const QApplication &app) : d(new Private) { QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); - parser.addOption(QCommandLineOption(QStringList() << QLatin1String("print"), i18n("Only print and exit"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("template"), i18n("Open a new document with a template"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("new-image"), i18n("Create a new image on startup.\n" "Possible colorspace values are:\n" " * RGBA\n" " * XYZA\n" " * LABA\n" " * CMYKA\n" " * GRAY\n" " * YCbCrA\n" "Possible channel depth arguments are\n" " * U8 (8 bits integer)\n" " * U16 (16 bits integer)\n" " * F16 (16 bits floating point)\n" " * F32 (32 bits floating point)\n"), QLatin1String("colorspace,depth,width,height"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("workspace"), i18n("The name of the workspace to open Krita with"), QLatin1String("workspace"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("windowlayout"), i18n("The name of the window layout to open Krita with"), QLatin1String("windowlayout"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("load-session"), i18n("The name of the session to open Krita with"), QLatin1String("load-session"))); // NB: the argument "session" is already used by QGuiApplication parser.addOption(QCommandLineOption(QStringList() << QLatin1String("canvasonly"), i18n("Start Krita in canvas-only mode"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("nosplash"), i18n("Do not show the splash screen"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("fullscreen"), i18n("Start Krita in full-screen mode"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("dpi"), i18n("Override display DPI"), QLatin1String("dpiX,dpiY"))); - parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-pdf"), i18n("Only export to PDF and exit"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export"), i18n("Export to the given filename and exit"))); - parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-filename"), i18n("Filename for export/export-pdf"), QLatin1String("filename"))); + parser.addOption(QCommandLineOption(QStringList() << QLatin1String("export-filename"), i18n("Filename for export"), QLatin1String("filename"))); parser.addPositionalArgument(QLatin1String("[file(s)]"), i18n("File(s) or URL(s) to open")); parser.process(app); QString dpiValues = parser.value("dpi"); if (!dpiValues.isEmpty()) { int sep = dpiValues.indexOf(QRegExp("[x, ]")); bool ok = true; if (sep != -1) { d->dpiY = dpiValues.mid(sep + 1).toInt(&ok); dpiValues.truncate(sep); } if (ok) { d->dpiX = dpiValues.toInt(&ok); if (ok) { if (!d->dpiY) d->dpiY = d->dpiX; } } } QString newImageValues = parser.value("new-image"); d->newImage = !newImageValues.isEmpty(); if (d->newImage) { QStringList v = newImageValues.split(","); if (v.size() != 4) { d->newImage = false; qWarning() << "Cannot create a new image: please specify colormodel, depth, width and height."; } d->colorModel = v[0].toUpper(); d->colorDepth = v[1].toUpper(); d->width = v[2].toInt(); d->height = v[3].toInt(); } d->exportFileName = parser.value("export-filename"); d->workspace = parser.value("workspace"); d->windowLayout = parser.value("windowlayout"); d->session = parser.value("load-session"); d->doTemplate = parser.isSet("template"); - d->print = parser.isSet("print"); d->exportAs = parser.isSet("export"); - d->exportAsPdf = parser.isSet("export-pdf"); d->canvasOnly = parser.isSet("canvasonly"); d->noSplash = parser.isSet("nosplash"); d->fullScreen = parser.isSet("fullscreen"); const QDir currentDir = QDir::current(); Q_FOREACH (const QString &filename, parser.positionalArguments()) { d->filenames << currentDir.absoluteFilePath(filename); } } KisApplicationArguments::KisApplicationArguments(const KisApplicationArguments &rhs) : d(new Private) { d->filenames = rhs.filenames(); d->dpiX = rhs.dpiX(); d->dpiY = rhs.dpiY(); d->doTemplate = rhs.doTemplate(); - d->print = rhs.print(); d->exportAs = rhs.exportAs(); - d->exportAsPdf = rhs.exportAsPdf(); d->exportFileName = rhs.exportFileName(); d->canvasOnly = rhs.canvasOnly(); d->workspace = rhs.workspace(); d->windowLayout = rhs.windowLayout(); d->session = rhs.session(); d->noSplash = rhs.noSplash(); d->fullScreen = rhs.fullScreen(); } void KisApplicationArguments::operator=(const KisApplicationArguments &rhs) { d->filenames = rhs.filenames(); d->dpiX = rhs.dpiX(); d->dpiY = rhs.dpiY(); d->doTemplate = rhs.doTemplate(); - d->print = rhs.print(); d->exportAs = rhs.exportAs(); - d->exportAsPdf = rhs.exportAsPdf(); d->exportFileName = rhs.exportFileName(); d->canvasOnly = rhs.canvasOnly(); d->workspace = rhs.workspace(); d->windowLayout = rhs.windowLayout(); d->session = rhs.session(); d->noSplash = rhs.noSplash(); d->fullScreen = rhs.fullScreen(); } QByteArray KisApplicationArguments::serialize() { QByteArray ba; QBuffer buf(&ba); buf.open(QIODevice::WriteOnly); QDataStream ds(&buf); ds.setVersion(QDataStream::Qt_5_0); ds << d->filenames.count(); Q_FOREACH (const QString &filename, d->filenames) { ds << filename; } ds << d->dpiX; ds << d->dpiY; ds << d->doTemplate; - ds << d->print; ds << d->exportAs; - ds << d->exportAsPdf; ds << d->exportFileName; ds << d->workspace; ds << d->windowLayout; ds << d->session; ds << d->canvasOnly; ds << d->noSplash; ds << d->fullScreen; ds << d->newImage; ds << d->height; ds << d->width; ds << d->height; ds << d->colorModel; ds << d->colorDepth; buf.close(); return ba; } KisApplicationArguments KisApplicationArguments::deserialize(QByteArray &serialized) { KisApplicationArguments args; QBuffer buf(&serialized); buf.open(QIODevice::ReadOnly); QDataStream ds(&buf); ds.setVersion(QDataStream::Qt_5_0); int count; ds >> count; for(int i = 0; i < count; ++i) { QString s; ds >> s; args.d->filenames << s; } ds >> args.d->dpiX; ds >> args.d->dpiY; ds >> args.d->doTemplate; - ds >> args.d->print; ds >> args.d->exportAs; - ds >> args.d->exportAsPdf; ds >> args.d->exportFileName; ds >> args.d->workspace; ds >> args.d->windowLayout; ds >> args.d->session; ds >> args.d->canvasOnly; ds >> args.d->noSplash; ds >> args.d->fullScreen; ds >> args.d->newImage; ds >> args.d->height; ds >> args.d->width; ds >> args.d->height; ds >> args.d->colorModel; ds >> args.d->colorDepth; buf.close(); return args; } QStringList KisApplicationArguments::filenames() const { return d->filenames; } int KisApplicationArguments::dpiX() const { return d->dpiX; } int KisApplicationArguments::dpiY() const { return d->dpiY; } bool KisApplicationArguments::doTemplate() const { return d->doTemplate; } -bool KisApplicationArguments::print() const -{ - return d->print; -} - bool KisApplicationArguments::exportAs() const { return d->exportAs; } -bool KisApplicationArguments::exportAsPdf() const -{ - return d->exportAsPdf; -} - QString KisApplicationArguments::exportFileName() const { return d->exportFileName; } QString KisApplicationArguments::workspace() const { return d->workspace; } QString KisApplicationArguments::windowLayout() const { return d->windowLayout; } QString KisApplicationArguments::session() const { return d->session; } bool KisApplicationArguments::canvasOnly() const { return d->canvasOnly; } bool KisApplicationArguments::noSplash() const { return d->noSplash; } bool KisApplicationArguments::fullScreen() const { return d->fullScreen; } bool KisApplicationArguments::doNewImage() const { return d->newImage; } KisDocument *KisApplicationArguments::image() const { KisDocument *doc = KisPart::instance()->createDocument(); qDebug() << d->colorDepth << d->colorDepth; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(d->colorModel, d->colorDepth, ""); if (!cs) { qWarning() << "Could not create the colorspace for the new image. Check the colorspace and depth arguments."; return 0; } doc->newImage(i18n("Unnamed"), d->width, d->height, cs, KoColor(QColor(Qt::white), cs), false, 1, "", 100.0); return doc; } diff --git a/libs/ui/KisApplicationArguments.h b/libs/ui/KisApplicationArguments.h index d6e9d92219..e7129d11ab 100644 --- a/libs/ui/KisApplicationArguments.h +++ b/libs/ui/KisApplicationArguments.h @@ -1,70 +1,67 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * 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; either * version 2.1 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 library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISAPPLICATIONARGUMENTS_H #define KISAPPLICATIONARGUMENTS_H #include class QApplication; class QByteArray; class QStringList; class KisDocument; #include "kritaui_export.h" class KRITAUI_EXPORT KisApplicationArguments { public: KisApplicationArguments(const QApplication &app); KisApplicationArguments(const KisApplicationArguments &rhs); ~KisApplicationArguments(); void operator=(const KisApplicationArguments& rhs); QByteArray serialize(); static KisApplicationArguments deserialize(QByteArray &serialized); QStringList filenames() const; int dpiX() const; int dpiY() const; bool doTemplate() const; - bool print() const; bool exportAs() const; - bool exportAsPdf() const; QString exportFileName() const; QString workspace() const; QString windowLayout() const; QString session() const; bool canvasOnly() const; bool noSplash() const; bool fullScreen() const; bool doNewImage() const; KisDocument *image() const; - private: KisApplicationArguments(); struct Private; const QScopedPointer d; }; #endif // KISAPPLICATIONARGUMENTS_H diff --git a/libs/ui/KisAsyncAnimationCacheRenderer.cpp b/libs/ui/KisAsyncAnimationCacheRenderer.cpp index 1e49f3720a..d41b7dcd02 100644 --- a/libs/ui/KisAsyncAnimationCacheRenderer.cpp +++ b/libs/ui/KisAsyncAnimationCacheRenderer.cpp @@ -1,84 +1,84 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "KisAsyncAnimationCacheRenderer.h" #include "kis_animation_frame_cache.h" #include "kis_update_info.h" struct KisAsyncAnimationCacheRenderer::Private { KisAnimationFrameCacheSP requestedCache; KisOpenGLUpdateInfoSP requestInfo; }; KisAsyncAnimationCacheRenderer::KisAsyncAnimationCacheRenderer() : m_d(new Private) { connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(slotCompleteRegenerationInternal(int))); } KisAsyncAnimationCacheRenderer::~KisAsyncAnimationCacheRenderer() { } void KisAsyncAnimationCacheRenderer::setFrameCache(KisAnimationFrameCacheSP cache) { m_d->requestedCache = cache; } -void KisAsyncAnimationCacheRenderer::frameCompletedCallback(int frame) +void KisAsyncAnimationCacheRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion) { KisAnimationFrameCacheSP cache = m_d->requestedCache; KisImageSP image = requestedImage(); if (!cache || !image) return; - m_d->requestInfo = cache->fetchFrameData(frame, image); + m_d->requestInfo = cache->fetchFrameData(frame, image, requestedRegion); emit sigCompleteRegenerationInternal(frame); } void KisAsyncAnimationCacheRenderer::slotCompleteRegenerationInternal(int frame) { if (!isActive()) return; KIS_SAFE_ASSERT_RECOVER(m_d->requestInfo) { frameCancelledCallback(frame); return; } m_d->requestedCache->addConvertedFrameData(m_d->requestInfo, frame); notifyFrameCompleted(frame); } void KisAsyncAnimationCacheRenderer::frameCancelledCallback(int frame) { notifyFrameCancelled(frame); } void KisAsyncAnimationCacheRenderer::clearFrameRegenerationState(bool isCancelled) { m_d->requestInfo.clear(); m_d->requestedCache.clear(); KisAsyncAnimationRendererBase::clearFrameRegenerationState(isCancelled); } diff --git a/libs/ui/KisAsyncAnimationCacheRenderer.h b/libs/ui/KisAsyncAnimationCacheRenderer.h index 9701203dfc..4d581c6a59 100644 --- a/libs/ui/KisAsyncAnimationCacheRenderer.h +++ b/libs/ui/KisAsyncAnimationCacheRenderer.h @@ -1,50 +1,50 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KISASYNCANIMATIONCACHERENDERER_H #define KISASYNCANIMATIONCACHERENDERER_H #include class KisAsyncAnimationCacheRenderer : public KisAsyncAnimationRendererBase { Q_OBJECT public: KisAsyncAnimationCacheRenderer(); ~KisAsyncAnimationCacheRenderer(); void setFrameCache(KisAnimationFrameCacheSP cache); protected: - void frameCompletedCallback(int frame) override; + void frameCompletedCallback(int frame, const QRegion &requestedRegion) override; void frameCancelledCallback(int frame) override; void clearFrameRegenerationState(bool isCancelled) override; Q_SIGNALS: void sigCompleteRegenerationInternal(int frame); private Q_SLOTS: void slotCompleteRegenerationInternal(int frame); private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONCACHERENDERER_H diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp index 442a9fbc6b..76809a84fd 100644 --- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp +++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp @@ -1,127 +1,132 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "KisAsyncAnimationFramesSavingRenderer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "KisImportExportFilter.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_time_range.h" #include "kis_paint_layer.h" struct KisAsyncAnimationFramesSavingRenderer::Private { Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration) : savingDoc(KisPart::instance()->createDocument()), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), exportConfiguration(_exportConfiguration) { savingDoc->setInfiniteAutoSaveInterval(); savingDoc->setFileBatchMode(true); KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(), image->bounds().width(), image->bounds().height(), image->colorSpace(), QString()); savingImage->setResolution(image->xRes(), image->yRes()); savingDoc->setCurrentImage(savingImage); KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255); savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0)); savingDevice = paintLayer->paintDevice(); } QScopedPointer savingDoc; KisPaintDeviceSP savingDevice; KisTimeRange range; int sequenceNumberingOffset = 0; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image, const QString &fileNamePrefix, const QString &fileNameSuffix, const QByteArray &outputMimeType, const KisTimeRange &range, const int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration) : m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration)) { m_d->filenamePrefix = fileNamePrefix; m_d->filenameSuffix = fileNameSuffix; m_d->outputMimeType = outputMimeType; connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int))); connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int))); } KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer() { } -void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame) +void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion) { KisImageSP image = requestedImage(); if (!image) return; + KIS_SAFE_ASSERT_RECOVER (requestedRegion == image->bounds()) { + emit sigCancelRegenerationInternal(frame); + return; + } + m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds()); KisTimeRange range(frame, 1); KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; for (int i = range.start(); i <= range.end(); i++) { QString frameNumber = QString("%1").arg(i + m_d->sequenceNumberingOffset, 4, 10, QChar('0')); QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix; if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) { status = KisImportExportFilter::InternalError; break; } } if (status == KisImportExportFilter::OK) { emit sigCompleteRegenerationInternal(frame); } else { emit sigCancelRegenerationInternal(frame); } } void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame) { notifyFrameCancelled(frame); } diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.h b/libs/ui/KisAsyncAnimationFramesSavingRenderer.h index 6dffff69a5..efa5fcaf5e 100644 --- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.h +++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.h @@ -1,53 +1,53 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KISASYNCANIMATIONFRAMESSAVINGRENDERER_H #define KISASYNCANIMATIONFRAMESSAVINGRENDERER_H #include class KisDocument; class KisTimeRange; class KisAsyncAnimationFramesSavingRenderer : public KisAsyncAnimationRendererBase { Q_OBJECT public: KisAsyncAnimationFramesSavingRenderer(KisImageSP image, const QString &fileNamePrefix, const QString &fileNameSuffix, const QByteArray &outputMimeType, const KisTimeRange &range, int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration); ~KisAsyncAnimationFramesSavingRenderer(); protected: - void frameCompletedCallback(int frame) override; + void frameCompletedCallback(int frame, const QRegion &requestedRegion) override; void frameCancelledCallback(int frame) override; Q_SIGNALS: void sigCompleteRegenerationInternal(int frame); void sigCancelRegenerationInternal(int frame); private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONFRAMESSAVINGRENDERER_H diff --git a/libs/ui/KisAsyncAnimationRendererBase.cpp b/libs/ui/KisAsyncAnimationRendererBase.cpp index 4216a923b7..1ba2f92c5f 100644 --- a/libs/ui/KisAsyncAnimationRendererBase.cpp +++ b/libs/ui/KisAsyncAnimationRendererBase.cpp @@ -1,160 +1,168 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "KisAsyncAnimationRendererBase.h" #include #include #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_signal_auto_connection.h" -struct KisAsyncAnimationRendererBase::Private +struct KRITAUI_NO_EXPORT KisAsyncAnimationRendererBase::Private { KisSignalAutoConnectionsStore imageRequestConnections; QTimer regenerationTimeout; KisImageSP requestedImage; int requestedFrame = -1; bool isCancelled = false; + QRegion requestedRegion; static const int WAITING_FOR_FRAME_TIMEOUT = 10000; }; KisAsyncAnimationRendererBase::KisAsyncAnimationRendererBase(QObject *parent) : QObject(parent), m_d(new Private()) { connect(&m_d->regenerationTimeout, SIGNAL(timeout()), SLOT(slotFrameRegenerationCancelled())); m_d->regenerationTimeout.setSingleShot(true); m_d->regenerationTimeout.setInterval(Private::WAITING_FOR_FRAME_TIMEOUT); } KisAsyncAnimationRendererBase::~KisAsyncAnimationRendererBase() { } -void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame) +void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame, const QRegion ®ionOfInterest) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread()); m_d->requestedImage = image; m_d->requestedFrame = frame; m_d->isCancelled = false; + m_d->requestedRegion = !regionOfInterest.isEmpty() ? regionOfInterest : image->bounds(); KisImageAnimationInterface *animation = m_d->requestedImage->animationInterface(); m_d->imageRequestConnections.clear(); m_d->imageRequestConnections.addConnection( animation, SIGNAL(sigFrameReady(int)), this, SLOT(slotFrameRegenerationFinished(int)), Qt::DirectConnection); m_d->imageRequestConnections.addConnection( animation, SIGNAL(sigFrameCancelled()), this, SLOT(slotFrameRegenerationCancelled()), Qt::AutoConnection); m_d->regenerationTimeout.start(); - animation->requestFrameRegeneration(m_d->requestedFrame, image->bounds()); + animation->requestFrameRegeneration(m_d->requestedFrame, m_d->requestedRegion); +} + +void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame) +{ + startFrameRegeneration(image, frame, QRegion()); } bool KisAsyncAnimationRendererBase::isActive() const { return m_d->requestedImage; } void KisAsyncAnimationRendererBase::cancelCurrentFrameRendering() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage); frameCancelledCallback(m_d->requestedFrame); } void KisAsyncAnimationRendererBase::slotFrameRegenerationCancelled() { // the timeout can arrive in async way if (!m_d->requestedImage) return; frameCancelledCallback(m_d->requestedFrame); } void KisAsyncAnimationRendererBase::slotFrameRegenerationFinished(int frame) { // We might have already cancelled the regeneration. We don't check // isCancelled flag here because this code runs asynchronously. if (!m_d->requestedImage) return; // WARNING: executed in the context of image worker thread! // probably a bit too strict... KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != this->thread()); - frameCompletedCallback(frame); + frameCompletedCallback(frame, m_d->requestedRegion); } void KisAsyncAnimationRendererBase::notifyFrameCompleted(int frame) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread()); // the image events can come with a delay, even after // the processing was cancelled if (m_d->isCancelled) return; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame); clearFrameRegenerationState(false); emit sigFrameCompleted(frame); } void KisAsyncAnimationRendererBase::notifyFrameCancelled(int frame) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread()); // the image events can come with a delay, even after // the processing was cancelled if (m_d->isCancelled) return; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage); KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame); clearFrameRegenerationState(true); emit sigFrameCancelled(frame); } void KisAsyncAnimationRendererBase::clearFrameRegenerationState(bool isCancelled) { // TODO: for some reason we mark the process as cancelled in any case, and it // seem to be a correct behavior Q_UNUSED(isCancelled); m_d->imageRequestConnections.clear(); m_d->requestedImage = 0; m_d->requestedFrame = -1; m_d->regenerationTimeout.stop(); m_d->isCancelled = true; + m_d->requestedRegion = QRegion(); } KisImageSP KisAsyncAnimationRendererBase::requestedImage() const { return m_d->requestedImage; } diff --git a/libs/ui/KisAsyncAnimationRendererBase.h b/libs/ui/KisAsyncAnimationRendererBase.h index 388d586b61..48074838a5 100644 --- a/libs/ui/KisAsyncAnimationRendererBase.h +++ b/libs/ui/KisAsyncAnimationRendererBase.h @@ -1,136 +1,145 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 KISASYNCANIMATIONRENDERERBASE_H #define KISASYNCANIMATIONRENDERERBASE_H #include #include "kis_types.h" +#include "kritaui_export.h" + /** * KisAsyncAnimationRendererBase is a special class represinting a * single worker thread inside KisAsyncAnimationRenderDialogBase. It connects * the specified image using correct Qt::DirectConnection connections and * reacts on them. On sigFrameReady() signal it calls frameCompletedCallback(), * so the derived class can fetch a frame from the image and process it. On * sigFrameCancelled() it calls frameCancelledCallback(). The derived class * should override these two methods to do the actual work. */ -class KisAsyncAnimationRendererBase : public QObject +class KRITAUI_EXPORT KisAsyncAnimationRendererBase : public QObject { Q_OBJECT public: explicit KisAsyncAnimationRendererBase(QObject *parent = 0); virtual ~KisAsyncAnimationRendererBase(); /** - * Initiates the rendering of the frame \p frame on an image \p image + * Initiates the rendering of the frame \p frame on an image \p image. + * Only \p regionOfInterest is regenerated. If \p regionOfInterest is + * empty, then entire bounds of the image is regenerated. + */ + void startFrameRegeneration(KisImageSP image, int frame, const QRegion ®ionOfInterest); + + /** + * Convenience overload that regenerates the full image */ - virtual void startFrameRegeneration(KisImageSP image, int frame); + void startFrameRegeneration(KisImageSP image, int frame); /** * @return true if the regeneration process is in progress */ bool isActive() const; public Q_SLOTS: /** * @brief cancels current rendering operation * * After calling this slot requestedImage() becomes invalid. * @see requestedImage() */ void cancelCurrentFrameRendering(); Q_SIGNALS: void sigFrameCompleted(int frame); void sigFrameCancelled(int frame); private Q_SLOTS: void slotFrameRegenerationCancelled(); void slotFrameRegenerationFinished(int frame); protected Q_SLOTS: /** * Called by a derived class to continue processing of the frames */ void notifyFrameCompleted(int frame); /** * Called by a derived class to cancel processing of the frames. After calling * this method, the dialog will stop processing the frames and close. */ void notifyFrameCancelled(int frame); protected: /** * @brief frameCompletedCallback is called by the renderer when * a new frame becomes ready * * NOTE1: the callback is called from the context of a image * worker thread! So it is asynchronous from the GUI thread. * NOTE2: in case of successful processing of the frame, the callback * must issue some signal, connected to notifyFrameCompleted() * via auto connection, to continue processing. Please do not * call the method directly, because notifyFame*() slots should * be called from the context of the GUI thread. * NOTE3: In case of failure, notifyFrameCancelled(). The same threading * rules apply. */ - virtual void frameCompletedCallback(int frame) = 0; + virtual void frameCompletedCallback(int frame, const QRegion &requestedRegion) = 0; /** * @brief frameCancelledCallback is called when the rendering of * the frame was cancelled. * * The rendering of the frame can be either cancelled by the image itself or * by receiving a timeout signal (10 seconds). * * NOTE: the slot is called in the GUI thread. Don't forget to call * notifyFrameCancelled() in he end of your call. */ virtual void frameCancelledCallback(int frame) = 0; /** * Called by KisAsyncAnimationRendererBase when the processing has been completed * and the internal state of the populator should be cleared * * @param isCancelled tells if frame regeneration has failed to be regenerated */ virtual void clearFrameRegenerationState(bool isCancelled); protected: /** * @return the image that for which the rendering was requested using * startFrameRegeneration(). Should be used by the derived classes only. * * Please note that requestedImage() will become null as soon as the user * cancels the processing. That happends in the GUI thread so * frameCompletedCallback() should be extremely careful when requesting the * value (check the shared pointer after fetching). */ KisImageSP requestedImage() const; private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONRENDERERBASE_H diff --git a/libs/ui/KisCloneDocumentStroke.cpp b/libs/ui/KisCloneDocumentStroke.cpp index a6290c91c9..17071af1dc 100644 --- a/libs/ui/KisCloneDocumentStroke.cpp +++ b/libs/ui/KisCloneDocumentStroke.cpp @@ -1,59 +1,60 @@ /* * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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 "KisCloneDocumentStroke.h" #include "KisDocument.h" #include "kis_layer_utils.h" +#include + + struct KRITAIMAGE_NO_EXPORT KisCloneDocumentStroke::Private { Private(KisDocument *_document) : document(_document) { } KisDocument *document = 0; }; KisCloneDocumentStroke::KisCloneDocumentStroke(KisDocument *document) : KisSimpleStrokeStrategy("clone-document-stroke", kundo2_i18n("Clone Document")), m_d(new Private(document)) { setClearsRedoOnStart(false); - setRequestsOtherStrokesToEnd(false); // TODO: ??? + setRequestsOtherStrokesToEnd(false); enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } KisCloneDocumentStroke::~KisCloneDocumentStroke() { } void KisCloneDocumentStroke::initStrokeCallback() { KisLayerUtils::forceAllDelayedNodesUpdate(m_d->document->image()->root()); } -#include - void KisCloneDocumentStroke::finishStrokeCallback() { KisDocument *doc = m_d->document->clone(); doc->moveToThread(qApp->thread()); emit sigDocumentCloned(doc); } diff --git a/libs/ui/KisColorsetChooser.cpp b/libs/ui/KisColorsetChooser.cpp index 6a3c9e396d..7cbee899b0 100644 --- a/libs/ui/KisColorsetChooser.cpp +++ b/libs/ui/KisColorsetChooser.cpp @@ -1,166 +1,166 @@ /* This file is part of the KDE project * Copyright (C) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisColorsetChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "KisViewManager.h" #include #include #include #include -#include + #include #include "kis_int_parse_spin_box.h" class ColorSetDelegate : public QAbstractItemDelegate { public: ColorSetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~ColorSetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void ColorSetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); if (! index.isValid()) return; KoResource* resource = static_cast(index.internalPointer()); KoColorSet* colorSet = static_cast(resource); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); painter->setPen(option.palette.highlightedText().color()); } else { painter->setBrush(option.palette.text().color()); } painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, colorSet->name()); int size = 7; for (quint32 i = 0; i < colorSet->nColors() && i*size < (quint32)option.rect.width(); i++) { QRect rect(option.rect.x() + i*size, option.rect.y() + option.rect.height() - size, size, size); painter->fillRect(rect, colorSet->getColorGlobal(i).color().toQColor()); } painter->restore(); } KisColorsetChooser::KisColorsetChooser(QWidget* parent): QWidget(parent) { KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); m_itemChooser = new KoResourceItemChooser(adapter, this); m_itemChooser->setItemDelegate(new ColorSetDelegate(this)); m_itemChooser->showTaggingBar(true); m_itemChooser->setFixedSize(250, 250); m_itemChooser->setRowHeight(30); m_itemChooser->setColumnCount(1); connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*))); KisConfig cfg; m_itemChooser->configureKineticScrolling(cfg.kineticScrollingGesture(), cfg.kineticScrollingSensitivity(), cfg.kineticScrollingScrollbar()); QPushButton* saveButton = new QPushButton(i18n("Save")); connect(saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSave())); m_nameEdit = new QLineEdit(this); m_nameEdit->setPlaceholderText(i18n("Insert name")); m_nameEdit->setClearButtonEnabled(true); m_columnEdit = new KisIntParseSpinBox(this); m_columnEdit->setRange(1, 30); m_columnEdit->setValue(10); QGridLayout* layout = new QGridLayout(this); layout->addWidget(m_itemChooser, 0, 0, 1, 3); layout->setColumnStretch(1, 1); layout->addWidget(saveButton, 2, 2, 1, 1); layout->addWidget(m_nameEdit, 1, 1, 1, 2); layout->addWidget(new QLabel(i18n("Name:"), this), 1, 0, 1, 1); layout->addWidget(m_columnEdit, 2, 1, 1, 1); layout->addWidget(new QLabel(i18n("Columns:"), this), 2, 0, 1, 1); } KisColorsetChooser::~KisColorsetChooser() { } void KisColorsetChooser::resourceSelected(KoResource* resource) { emit paletteSelected(static_cast(resource)); } void KisColorsetChooser::slotSave() { KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorset = new KoColorSet(); colorset->setValid(true); QString saveLocation = rserver->saveLocation(); QString name = m_nameEdit->text(); int columns = m_columnEdit->value(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Palette"); } QFileInfo fileInfo(saveLocation + name + colorset->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + colorset->defaultFileExtension()); i++; } colorset->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Palette %1", i); } colorset->setName(name); colorset->setColumnCount(columns); rserver->addResource(colorset); } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index e3946c0317..b2b8b6cba5 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1791 +1,1797 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg; if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage)); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { d->undoStack->setClean(); } setRecovered(false); removeAutoSaveFiles(); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg; d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified() { d->modified = true; } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles() { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg; d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisConfig cfg; KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } -KisSharedPtr KisDocument::createReferenceImagesLayer(KisImageSP targetImage) +KisSharedPtr KisDocument::getOrCreateReferenceImagesLayer(KisImageSP targetImage) { if (!d->referenceImagesLayer) { if (targetImage.isNull()) targetImage = d->image; d->referenceImagesLayer = new KisReferenceImagesLayer(shapeController(), targetImage); targetImage->addNode(d->referenceImagesLayer, targetImage->root()); } return d->referenceImagesLayer; } +// TODO: change signature to return a shared pointer KisReferenceImagesLayer *KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } +void KisDocument::setReferenceImagesLayer(KisSharedPtr layer) +{ + d->referenceImagesLayer = layer; +} + void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 22130bfb27..71232d2df7 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,644 +1,648 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeBasedDocumentBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); /** @internal */ void setModified(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeBasedDocumentBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); /** * Get existing reference images layer or create new if none exists. + * + * TODO: use setReferenceImagesLayer() combined with undo commands instead */ - KisSharedPtr createReferenceImagesLayer(KisImageSP targetImage = KisImageSP()); + KRITAUI_DEPRECATED KisSharedPtr getOrCreateReferenceImagesLayer(KisImageSP targetImage = KisImageSP()); /** * Get existing reference images layer or null if none exists. */ KisReferenceImagesLayer *referenceImagesLayer() const; + void setReferenceImagesLayer(KisSharedPtr layer); + bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisFrameCacheStore.cpp b/libs/ui/KisFrameCacheStore.cpp new file mode 100644 index 0000000000..14493e6eab --- /dev/null +++ b/libs/ui/KisFrameCacheStore.cpp @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 "KisFrameCacheStore.h" + +#include +#include "kis_update_info.h" +#include "KisFrameDataSerializer.h" +#include "opengl/KisOpenGLUpdateInfoBuilder.h" + +#define SANITY_CHECK + +namespace { +enum FrameType { + FrameFull, + FrameCopy, + FrameDiff +}; + +struct FrameInfo; +typedef QSharedPointer FrameInfoSP; + +struct FrameInfo { + // full frame + FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, const KisFrameDataSerializer::Frame &frame); + // diff frame + FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame, const KisFrameDataSerializer::Frame &frame); + // copy frame + FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame); + + ~FrameInfo(); + + FrameType type() const { + return m_type; + } + + int levelOfDetail() const { + return m_levelOfDetail; + } + + QRect dirtyImageRect() const { + return m_dirtyImageRect; + } + + QRect imageBounds() const { + return m_imageBounds; + } + + int frameDataId() const { + return m_savedFrameDataId; + } + + FrameInfoSP baseFrame() const { + return m_baseFrame; + } + + int m_levelOfDetail = 0; + QRect m_dirtyImageRect; + QRect m_imageBounds; + FrameInfoSP m_baseFrame; + FrameType m_type = FrameFull; + int m_savedFrameDataId = -1; + KisFrameDataSerializer &m_serializer; +}; + +// full frame +FrameInfo::FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, const KisFrameDataSerializer::Frame &frame) + : m_levelOfDetail(levelOfDetail), + m_dirtyImageRect(dirtyImageRect), + m_imageBounds(imageBounds), + m_baseFrame(0), + m_type(FrameFull), + m_serializer(serializer) +{ + m_savedFrameDataId = m_serializer.saveFrame(frame); +} + +// diff frame +FrameInfo::FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame, const KisFrameDataSerializer::Frame &frame) + : m_levelOfDetail(levelOfDetail), + m_dirtyImageRect(dirtyImageRect), + m_imageBounds(imageBounds), + m_baseFrame(baseFrame), + m_type(FrameDiff), + m_serializer(serializer) +{ + m_savedFrameDataId = m_serializer.saveFrame(frame); +} + +// copy frame +FrameInfo::FrameInfo(const QRect &dirtyImageRect, const QRect &imageBounds, int levelOfDetail, KisFrameDataSerializer &serializer, FrameInfoSP baseFrame) + : m_levelOfDetail(levelOfDetail), + m_dirtyImageRect(dirtyImageRect), + m_imageBounds(imageBounds), + m_baseFrame(baseFrame), + m_type(FrameCopy), + m_savedFrameDataId(-1), + m_serializer(serializer) +{ +} + +FrameInfo::~FrameInfo() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedFrameDataId >= 0 || m_type == FrameCopy); + + if (m_savedFrameDataId >= 0) { + m_serializer.forgetFrame(m_savedFrameDataId); + } +} + +} + + +struct KRITAUI_NO_EXPORT KisFrameCacheStore::Private +{ + Private(const QString &frameCachePath) + : serializer(frameCachePath) + { + } + + // the serializer should be killed after *all* the frame info objects + // got destroyed, because they use it in their own destruction + KisFrameDataSerializer serializer; + + KisFrameDataSerializer::Frame lastSavedFullFrame; + int lastSavedFullFrameId = -1; + + KisFrameDataSerializer::Frame lastLoadedBaseFrame; + FrameInfoSP lastLoadedBaseFrameInfo; + + QMap savedFrames; +}; + +KisFrameCacheStore::KisFrameCacheStore() + : KisFrameCacheStore(QString()) +{ +} + +KisFrameCacheStore::KisFrameCacheStore(const QString &frameCachePath) + : m_d(new Private(frameCachePath)) +{ +} + + +KisFrameCacheStore::~KisFrameCacheStore() +{ +} + +void KisFrameCacheStore::saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) +{ + int pixelSize = 0; + + Q_FOREACH (auto tile, info->tileList) { +#ifdef SANITY_CHECK + if (!pixelSize) { + pixelSize = tile->pixelSize(); + } else { + KIS_SAFE_ASSERT_RECOVER_RETURN(pixelSize == tile->pixelSize()); + } +#else + pixelSize = tile->pixelSize(); + break; +#endif + } + + KIS_SAFE_ASSERT_RECOVER_RETURN(pixelSize); + + // TODO: assert that dirty image rect is equal to the full image rect + // TODO: assert tile color space coicides with the destination color space + + KisFrameDataSerializer::Frame frame; + frame.pixelSize = pixelSize; + + for (auto it = info->tileList.begin(); it != info->tileList.end(); ++it) { + KisFrameDataSerializer::FrameTile tile(KisTextureTileInfoPoolSP(0)); // TODO: fix the pool should never be null! + tile.col = (*it)->tileCol(); + tile.row = (*it)->tileRow(); + tile.rect = (*it)->realPatchRect(); + tile.data = std::move((*it)->takePixelData()); + + frame.frameTiles.push_back(std::move(tile)); + } + + FrameInfoSP frameInfo; + + if (m_d->lastSavedFullFrame.isValid()) { + boost::optional uniqueness = KisFrameDataSerializer::estimateFrameUniqueness(m_d->lastSavedFullFrame, frame, 0.01); + + if (uniqueness) { + if (*uniqueness == 0.0) { + FrameInfoSP baseFrameInfo = m_d->savedFrames[m_d->lastSavedFullFrameId]; + frameInfo = toQShared(new FrameInfo(info->dirtyImageRect(), + imageBounds, + info->levelOfDetail(), + m_d->serializer, + baseFrameInfo)); + + } else if (*uniqueness < 0.5) { + FrameInfoSP baseFrameInfo = m_d->savedFrames[m_d->lastSavedFullFrameId]; + + KisFrameDataSerializer::subtractFrames(frame, m_d->lastSavedFullFrame); + frameInfo = toQShared(new FrameInfo(info->dirtyImageRect(), + imageBounds, + info->levelOfDetail(), + m_d->serializer, + baseFrameInfo, + frame)); + } + } + } + + if (!frameInfo) { + frameInfo = toQShared(new FrameInfo(info->dirtyImageRect(), + imageBounds, + info->levelOfDetail(), + m_d->serializer, + frame)); + } + + m_d->savedFrames.insert(frameId, frameInfo); + + if (frameInfo->type() == FrameFull) { + m_d->lastSavedFullFrame = std::move(frame); + m_d->lastSavedFullFrameId = frameId; + } +} + +KisOpenGLUpdateInfoSP KisFrameCacheStore::loadFrame(int frameId, const KisOpenGLUpdateInfoBuilder &builder) +{ + KisOpenGLUpdateInfoSP info = new KisOpenGLUpdateInfo(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->savedFrames.contains(frameId), info); + + FrameInfoSP frameInfo = m_d->savedFrames[frameId]; + + info->assignDirtyImageRect(frameInfo->dirtyImageRect()); + info->assignLevelOfDetail(frameInfo->levelOfDetail()); + + KisFrameDataSerializer::Frame frame; + + switch (frameInfo->type()) { + case FrameFull: + frame = m_d->serializer.loadFrame(frameInfo->frameDataId(), builder.textureInfoPool()); + m_d->lastLoadedBaseFrame = frame.clone(); + m_d->lastLoadedBaseFrameInfo = frameInfo; + break; + case FrameCopy: { + FrameInfoSP baseFrameInfo = frameInfo->baseFrame(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(baseFrameInfo, KisOpenGLUpdateInfoSP()); + + if (baseFrameInfo == m_d->lastLoadedBaseFrameInfo) { + frame = m_d->lastLoadedBaseFrame.clone(); + } else { + frame = m_d->serializer.loadFrame(baseFrameInfo->frameDataId(), builder.textureInfoPool()); + m_d->lastLoadedBaseFrame = frame.clone(); + m_d->lastLoadedBaseFrameInfo = baseFrameInfo; + } + break; + } + case FrameDiff: { + FrameInfoSP baseFrameInfo = frameInfo->baseFrame(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(baseFrameInfo, KisOpenGLUpdateInfoSP()); + + if (baseFrameInfo == m_d->lastLoadedBaseFrameInfo) { + // noop + } else { + m_d->lastLoadedBaseFrame = m_d->serializer.loadFrame(baseFrameInfo->frameDataId(), builder.textureInfoPool()); + m_d->lastLoadedBaseFrameInfo = baseFrameInfo; + + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->lastLoadedBaseFrame.isValid(), KisOpenGLUpdateInfoSP()); + } + + const KisFrameDataSerializer::Frame &baseFrame = m_d->lastLoadedBaseFrame; + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(baseFrame.isValid(), KisOpenGLUpdateInfoSP()); + + frame = m_d->serializer.loadFrame(frameInfo->frameDataId(), builder.textureInfoPool()); + KisFrameDataSerializer::addFrames(frame, baseFrame); + break; + } + } + + for (auto it = frame.frameTiles.begin(); it != frame.frameTiles.end(); ++it) { + KisFrameDataSerializer::FrameTile &tile = *it; + + QRect patchRect = tile.rect; + + if (frameInfo->levelOfDetail()) { + patchRect = KisLodTransform::upscaledRect(patchRect, frameInfo->levelOfDetail()); + } + + const QRect fullSizeTileRect = + builder.calculatePhysicalTileRect(tile.col, tile.row, + frameInfo->imageBounds(), + frameInfo->levelOfDetail()); + + KisTextureTileUpdateInfoSP tileInfo( + new KisTextureTileUpdateInfo(tile.col, tile.row, + fullSizeTileRect, patchRect, + frameInfo->imageBounds(), + frameInfo->levelOfDetail(), + builder.textureInfoPool())); + + tileInfo->putPixelData(std::move(tile.data), builder.destinationColorSpace()); + + info->tileList << tileInfo; + } + + return info; +} + +void KisFrameCacheStore::moveFrame(int srcFrameId, int dstFrameId) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(srcFrameId != dstFrameId); + + KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->savedFrames.contains(srcFrameId)); + + KIS_SAFE_ASSERT_RECOVER(!m_d->savedFrames.contains(dstFrameId)) { + m_d->savedFrames.remove(dstFrameId); + } + + m_d->savedFrames.insert(dstFrameId, m_d->savedFrames[srcFrameId]); + m_d->savedFrames.remove(srcFrameId); + + if (m_d->lastSavedFullFrameId == srcFrameId) { + m_d->lastSavedFullFrameId = dstFrameId; + } +} + +void KisFrameCacheStore::forgetFrame(int frameId) +{ + KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->savedFrames.contains(frameId)); + + if (m_d->lastSavedFullFrameId == frameId) { + m_d->lastSavedFullFrame = KisFrameDataSerializer::Frame(); + m_d->lastSavedFullFrameId = -1; + } + + m_d->savedFrames.remove(frameId); +} + +bool KisFrameCacheStore::hasFrame(int frameId) const +{ + return m_d->savedFrames.contains(frameId); +} + +int KisFrameCacheStore::frameLevelOfDetail(int frameId) const +{ + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->savedFrames.contains(frameId), 0); + return m_d->savedFrames[frameId]->levelOfDetail(); +} + +QRect KisFrameCacheStore::frameDirtyRect(int frameId) const +{ + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->savedFrames.contains(frameId), QRect()); + return m_d->savedFrames[frameId]->dirtyImageRect(); +} diff --git a/libs/ui/KisFrameCacheStore.h b/libs/ui/KisFrameCacheStore.h new file mode 100644 index 0000000000..58db2681ee --- /dev/null +++ b/libs/ui/KisFrameCacheStore.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 KISFRAMECACHESTORE_H +#define KISFRAMECACHESTORE_H + +#include "kritaui_export.h" +#include +#include "kis_types.h" + +#include "opengl/kis_texture_tile_info_pool.h" + +class KisOpenGLUpdateInfoBuilder; + +class KisOpenGLUpdateInfo; +typedef KisSharedPtr KisOpenGLUpdateInfoSP; + +/** + * KisFrameCacheStore is a middle-level class for reading/writing + * animation frames on disk. Its main responsibilities: + * + * 1) Convert frames from KisOpenGLUpdateInfo format into a serializable + * KisFrameDataSerializer::Frame format. + * + * 2) Calculate differences between the frames and decide which + * frame will be a keyframe for other frames. + * + * 3) The keyframes will be used as a base for difference + * calculation and stored in a short in-memory cache to avoid + * fetching them from disk too often. + * + * 4) The in-memory cache of the keyframes is stored in serializable + * KisFrameDataSerializer::Frame format. + */ + +class KRITAUI_EXPORT KisFrameCacheStore +{ +public: + KisFrameCacheStore(); + KisFrameCacheStore(const QString &frameCachePath); + + ~KisFrameCacheStore(); + + // WARNING: after transferring \p info to saveFrame() the object becomes invalid + void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds); + KisOpenGLUpdateInfoSP loadFrame(int frameId, const KisOpenGLUpdateInfoBuilder &builder); + + void moveFrame(int srcFrameId, int dstFrameId); + + void forgetFrame(int frameId); + bool hasFrame(int frameId) const; + + int frameLevelOfDetail(int frameId) const; + QRect frameDirtyRect(int frameId) const; + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif // KISFRAMECACHESTORE_H diff --git a/libs/ui/KisFrameCacheSwapper.cpp b/libs/ui/KisFrameCacheSwapper.cpp new file mode 100644 index 0000000000..15c138fcd1 --- /dev/null +++ b/libs/ui/KisFrameCacheSwapper.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 "KisFrameCacheSwapper.h" + +#include "KisFrameCacheStore.h" + +#include "kis_update_info.h" +#include "opengl/KisOpenGLUpdateInfoBuilder.h" + +struct KisFrameCacheSwapper::Private +{ + Private(const KisOpenGLUpdateInfoBuilder &_builder, const QString &frameCachePath) + : frameStore(frameCachePath), + builder(_builder) + { + } + + KisFrameCacheStore frameStore; + const KisOpenGLUpdateInfoBuilder &builder; +}; + +KisFrameCacheSwapper::KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder) + : KisFrameCacheSwapper(builder, "") +{ +} + +KisFrameCacheSwapper::KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder, const QString &frameCachePath) + : m_d(new Private(builder, frameCachePath)) +{ +} + +KisFrameCacheSwapper::~KisFrameCacheSwapper() +{ +} + +void KisFrameCacheSwapper::saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) +{ + m_d->frameStore.saveFrame(frameId, info, imageBounds); +} + +KisOpenGLUpdateInfoSP KisFrameCacheSwapper::loadFrame(int frameId) +{ + return m_d->frameStore.loadFrame(frameId, m_d->builder); +} + +void KisFrameCacheSwapper::moveFrame(int srcFrameId, int dstFrameId) +{ + m_d->frameStore.moveFrame(srcFrameId, dstFrameId); +} + +void KisFrameCacheSwapper::forgetFrame(int frameId) +{ + m_d->frameStore.forgetFrame(frameId); +} + +bool KisFrameCacheSwapper::hasFrame(int frameId) const +{ + return m_d->frameStore.hasFrame(frameId); +} + +int KisFrameCacheSwapper::frameLevelOfDetail(int frameId) const +{ + return m_d->frameStore.frameLevelOfDetail(frameId); +} + +QRect KisFrameCacheSwapper::frameDirtyRect(int frameId) const +{ + return m_d->frameStore.frameDirtyRect(frameId); +} diff --git a/libs/ui/KisFrameCacheSwapper.h b/libs/ui/KisFrameCacheSwapper.h new file mode 100644 index 0000000000..17cd32390c --- /dev/null +++ b/libs/ui/KisFrameCacheSwapper.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 KISFRAMECACHESWAPPER_H +#define KISFRAMECACHESWAPPER_H + +#include + +#include "KisAbstractFrameCacheSwapper.h" + +class KisOpenGLUpdateInfoBuilder; + + +/** + * KisFrameCacheSwapper is the most highlevel facade of the frame + * swapping infrastructure. The main responsibilities of the class: + * + * 1) Asynchronously predict and prefetch the pending frames from disk + * and maintain a short in-memory cache of these frames (already + * converted into KisOpenGLUpdateInfo) + * + * 2) Pass all the other requests to the lower-level API, + * like KisFrameCacheStore + */ + +class KRITAUI_EXPORT KisFrameCacheSwapper : public KisAbstractFrameCacheSwapper +{ +public: + KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder); + KisFrameCacheSwapper(const KisOpenGLUpdateInfoBuilder &builder, const QString &frameCachePath); + ~KisFrameCacheSwapper(); + + // WARNING: after transferring \p info to saveFrame() the object becomes invalid + void saveFrame(int frameId, KisOpenGLUpdateInfoSP info, const QRect &imageBounds) override; + KisOpenGLUpdateInfoSP loadFrame(int frameId) override; + + void moveFrame(int srcFrameId, int dstFrameId) override; + + void forgetFrame(int frameId) override; + bool hasFrame(int frameId) const override; + + int frameLevelOfDetail(int frameId) const override; + + QRect frameDirtyRect(int frameId) const override; + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif // KISFRAMECACHESWAPPER_H diff --git a/libs/ui/KisFrameDataSerializer.cpp b/libs/ui/KisFrameDataSerializer.cpp new file mode 100644 index 0000000000..67f637e901 --- /dev/null +++ b/libs/ui/KisFrameDataSerializer.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU 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 "KisFrameDataSerializer.h" + +#include +#include +#include "tiles3/swap/kis_lzf_compression.h" + + +struct KRITAUI_NO_EXPORT KisFrameDataSerializer::Private +{ + Private(const QString &frameCachePath) + : framesDir( + (!frameCachePath.isEmpty() ? frameCachePath : QDir::tempPath()) + + QDir::separator() + "KritaFrameCacheXXXXXX") + { + KIS_SAFE_ASSERT_RECOVER_NOOP(framesDir.isValid()); + framesDirObject = QDir(framesDir.path()); + framesDirObject.makeAbsolute(); + } + + QString subfolderNameForFrame(int frameId) + { + const int subfolderIndex = frameId & 0xff00; + return QString::number(subfolderIndex); + } + + QString fileNameForFrame(int frameId) { + return QString("frame_%1").arg(frameId); + } + + QString filePathForFrame(int frameId) + { + return framesDirObject.filePath( + subfolderNameForFrame(frameId) + QDir::separator() + + fileNameForFrame(frameId)); + } + + int generateFrameId() { + // TODO: handle wrapping and range compression + return nextFrameId++; + } + + quint8* getCompressionBuffer(int size) { + if (compressionBuffer.size() < size) { + compressionBuffer.resize(size); + } + return reinterpret_cast(compressionBuffer.data()); + } + + QTemporaryDir framesDir; + QDir framesDirObject; + int nextFrameId = 0; + + QByteArray compressionBuffer; +}; + +KisFrameDataSerializer::KisFrameDataSerializer() + : KisFrameDataSerializer(QString()) +{ +} + +KisFrameDataSerializer::KisFrameDataSerializer(const QString &frameCachePath) + : m_d(new Private(frameCachePath)) +{ +} + +KisFrameDataSerializer::~KisFrameDataSerializer() +{ +} + +int KisFrameDataSerializer::saveFrame(const KisFrameDataSerializer::Frame &frame) +{ + KisLzfCompression compression; + + const int frameId = m_d->generateFrameId(); + + const QString frameSubfolder = m_d->subfolderNameForFrame(frameId); + + if (!m_d->framesDirObject.exists(frameSubfolder)) { + m_d->framesDirObject.mkpath(frameSubfolder); + } + + const QString frameRelativePath = frameSubfolder + QDir::separator() + m_d->fileNameForFrame(frameId); + + if (m_d->framesDirObject.exists(frameRelativePath)) { + qWarning() << "WARNING: overwriting existing frame file!" << frameRelativePath; + forgetFrame(frameId); + } + + const QString frameFilePath = m_d->framesDirObject.filePath(frameRelativePath); + + QFile file(frameFilePath); + file.open(QFile::WriteOnly); + + QDataStream stream(&file); + stream << frameId; + stream << frame.pixelSize; + + stream << int(frame.frameTiles.size()); + + for (int i = 0; i < int(frame.frameTiles.size()); i++) { + const FrameTile &tile = frame.frameTiles[i]; + + stream << tile.col; + stream << tile.row; + stream << tile.rect; + + const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height(); + const int maxBufferSize = compression.outputBufferSize(frameByteSize); + quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize); + + const int compressedSize = + compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize); + + ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize); + + const bool isCompressed = compressedSize < frameByteSize; + stream << isCompressed; + + if (isCompressed) { + stream << compressedSize; + stream.writeRawData((char*)buffer, compressedSize); + } else { + stream << frameByteSize; + stream.writeRawData((char*)tile.data.data(), frameByteSize); + } + } + + file.close(); + + return frameId; +} + +#include "../sdk/tests/testutil.h" +#include + +TestUtil::MeasureAvgPortion CC(10); + +KisFrameDataSerializer::Frame KisFrameDataSerializer::loadFrame(int frameId, KisTextureTileInfoPoolSP pool) +{ + KisLzfCompression compression; + + QElapsedTimer loadingTime; + loadingTime.start(); + + int loadedFrameId = -1; + KisFrameDataSerializer::Frame frame; + + qint64 compressionTime = 0; + + const QString framePath = m_d->filePathForFrame(frameId); + + QFile file(framePath); + KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists()); + if (!file.open(QFile::ReadOnly)) return frame; + + QDataStream stream(&file); + + int numTiles = 0; + + stream >> loadedFrameId; + stream >> frame.pixelSize; + stream >> numTiles; + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(loadedFrameId == frameId, KisFrameDataSerializer::Frame()); + + + + for (int i = 0; i < numTiles; i++) { + FrameTile tile(pool); + stream >> tile.col; + stream >> tile.row; + stream >> tile.rect; + + const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize), + KisFrameDataSerializer::Frame()); + + bool isCompressed = false; + int inputSize = -1; + + stream >> isCompressed; + stream >> inputSize; + + if (isCompressed) { + const int maxBufferSize = compression.outputBufferSize(inputSize); + quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize); + stream.readRawData((char*)buffer, inputSize); + + tile.data.allocate(frame.pixelSize); + + QElapsedTimer compTime; + compTime.start(); + + const int decompressedSize = + compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize); + + compressionTime += compTime.nsecsElapsed(); + + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize, + KisFrameDataSerializer::Frame()); + + } else { + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize, + KisFrameDataSerializer::Frame()); + + tile.data.allocate(frame.pixelSize); + stream.readRawData((char*)tile.data.data(), inputSize); + } + + frame.frameTiles.push_back(std::move(tile)); + } + + file.close(); + + CC.addVal(compressionTime / 1000); + CC.addTotal(loadingTime.nsecsElapsed() / 1000); + + return frame; +} + +void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId) +{ + const QString srcFramePath = m_d->filePathForFrame(srcFrameId); + const QString dstFramePath = m_d->filePathForFrame(dstFrameId); + KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists()); + + KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) { + QFile::remove(dstFramePath); + } + + QFile::rename(srcFramePath, dstFramePath); +} + +bool KisFrameDataSerializer::hasFrame(int frameId) const +{ + const QString framePath = m_d->filePathForFrame(frameId); + return QFileInfo(framePath).exists(); +} + +void KisFrameDataSerializer::forgetFrame(int frameId) +{ + const QString framePath = m_d->filePathForFrame(frameId); + QFile::remove(framePath); +} + +boost::optional KisFrameDataSerializer::estimateFrameUniqueness(const KisFrameDataSerializer::Frame &lhs, const KisFrameDataSerializer::Frame &rhs, qreal portion) +{ + if (lhs.pixelSize != rhs.pixelSize) return boost::none; + if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none; + + const int pixelSize = lhs.pixelSize; + int numSampledPixels = 0; + int numUniquePixels = 0; + const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0; + + for (int i = 0; i < int(lhs.frameTiles.size()); i++) { + const FrameTile &lhsTile = lhs.frameTiles[i]; + const FrameTile &rhsTile = rhs.frameTiles[i]; + + if (lhsTile.col != rhsTile.col || + lhsTile.row != rhsTile.row || + lhsTile.rect != rhsTile.rect) { + + return boost::none; + } + + if (sampleStep > 0) { + const int numPixels = lhsTile.rect.width() * lhsTile.rect.height(); + for (int j = 0; j < numPixels; j += sampleStep) { + quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize; + quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize; + + if (std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) { + numUniquePixels++; + } + numSampledPixels++; + } + } + } + + return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0; +} + +template