diff --git a/CMakeLists.txt b/CMakeLists.txt index d50f4443f3..bfa7ea66b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,927 +1,927 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.12 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) - add_compile_options($<$:-Wno-suggest-override> -Wextra) + add_compile_options($<$:-Wno-suggest-override> -Wextra -Wno-class-memaccess) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.3.0-prealpha") # 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 3) # 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() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") 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 Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent Sql ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "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 AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "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) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) #if (${Qt5_VERSION} VERSION_GREATER "5.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) if (APPLE) # this is not added correctly on OSX -- see 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 ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "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") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) endif() find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") find_package(OpenJPEG "2.3.0") set_package_properties(OpenJPEG PROPERTIES DESCRIPTION "Library for loading and saving jp2000 files." URL "http://www.openjpeg.org/" TYPE OPTIONAL PURPOSE "Required by the Krita JP2000 filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "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) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() 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.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so ${KF5_LIBRARIES}/libKF5Archive.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/krita/main.cc b/krita/main.cc index a75c3a8f34..e3bde398a8 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,596 +1,598 @@ /* * 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 #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" #include #include #ifdef Q_OS_ANDROID #include #endif #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #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()); } } // namespace #endif #ifdef Q_OS_WIN namespace { 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) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisPart *kisPart = KisPart::instance(); QList> list = kisPart->documents(); for (QPointer &doc: list) { doc->autoSaveOnPause(); } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); mainWindow->viewFullscreen(false); } __attribute__ ((visibility ("default"))) #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 // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + 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); QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif #ifdef Q_OS_ANDROID const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE"; const QStringList permissions = { write_permission }; const QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList(permissions)); if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) { // TODO: show a dialog and graciously exit dbgKrita << "Permission denied by the user"; } else { dbgKrita << "Permission granted"; } #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #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; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); #ifdef Q_OS_WIN const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); #else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); #endif preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); const KisOpenGL::RendererConfig config = KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } 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 dbgKrita << "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. dbgKrita << "Override language:" << language; bool rightToLeft = false; 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); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // 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) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } 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.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KisUsageLogger::writeHeader(); KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #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)); dbgKrita << "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.exportAs() || args.exportSequence(); 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 (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) + app.setAttribute(Qt::AA_DisableWindowContextHelpButton); +#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(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { 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); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // 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))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); KisConfig(true).logImportantSettings(); if (!app.start(args)) { KisUsageLogger::log("Could not start Krita Application"); return 1; } int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 62c332283f..c9863e2563 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,386 +1,387 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/KisDeselectActiveSelectionCommand.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/KisReselectActiveSelectionCommand.cpp commands/kis_set_global_selection_command.cpp commands/KisNodeRenameCommand.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp commands_new/kis_transaction_based_command.cpp commands_new/KisHoldUIUpdatesCommand.cpp commands_new/KisChangeChannelFlagsCommand.cpp commands_new/KisChangeChannelLockFlagsCommand.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_convert_color_space_processing_visitor.cpp processing/kis_assign_profile_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp processing/KisSelectionBasedProcessingHelper.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp KisCroppedOriginalLayerInterface.cpp + KisDecoratedNodeInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc kis_node_uuid_info.cpp kis_clone_layer.cpp kis_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobDataBase.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp KisImageSignals.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp KisSafeNodeProjectionStore.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc KisSelectionUpdateCompressor.cpp kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h KisRecycleProjectionsJob.cpp kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp layerstyles/KisLayerStyleKnockoutBlower.cpp KisProofingConfiguration.cpp kis_node_query_path.cc kis_asl_layer_style_serializer.cpp KisAslStorage.cpp kis_psd_layer_style.cpp ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets kritaglobal kritapsd kritaodf kritapigment kritacommand kritawidgetutils kritametadata Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) target_link_libraries(kritaimage PUBLIC atomic) endif() endif() if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/ui/tests/kis_shape_selection_test.h b/libs/image/KisDecoratedNodeInterface.cpp similarity index 73% copy from libs/ui/tests/kis_shape_selection_test.h copy to libs/image/KisDecoratedNodeInterface.cpp index 898433b9a1..b67179b8d4 100644 --- a/libs/ui/tests/kis_shape_selection_test.h +++ b/libs/image/KisDecoratedNodeInterface.cpp @@ -1,34 +1,28 @@ /* - * Copyright (c) 2008 Sven Langkamp + * Copyright (c) 2019 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_SHAPE_SELETION_TEST_H -#define KIS_SHAPE_SELETION_TEST_H +#include "KisDecoratedNodeInterface.h" -#include - -class KisShapeSelectionTest : public QObject +KisDecoratedNodeInterface::~KisDecoratedNodeInterface() { - Q_OBJECT - -private Q_SLOTS: - - void testAddChild(); -}; - -#endif +} +void KisDecoratedNodeInterface::setDecorationsVisible(bool value) +{ + setDecorationsVisible(value, true); +} diff --git a/libs/image/KisDecoratedNodeInterface.h b/libs/image/KisDecoratedNodeInterface.h new file mode 100644 index 0000000000..c79da00604 --- /dev/null +++ b/libs/image/KisDecoratedNodeInterface.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 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 KISDECORATEDNODEINTERFACE_H +#define KISDECORATEDNODEINTERFACE_H + +#include "kritaimage_export.h" + +/** + * A special interface for layer that have a "decorations", + * that is, a data that is written into layer stack, but is + * not a part of user's image. + */ +class KRITAIMAGE_EXPORT KisDecoratedNodeInterface +{ +public: + virtual ~KisDecoratedNodeInterface(); + + /** + * \return true is the layer is allowed to write + * its decorative information into the stack. The + * value should be "true" by default. + */ + virtual bool decorationsVisible() const = 0; + + /** + * Enable or disable writing decorative information into + * layer stack. + */ + virtual void setDecorationsVisible(bool value, bool update) = 0; + + /** + * Convenience override for setDecorationsVisible() + */ + void setDecorationsVisible(bool value); +}; + +#endif // KISDECORATEDNODEINTERFACE_H diff --git a/libs/image/kis_abstract_projection_plane.cpp b/libs/image/kis_abstract_projection_plane.cpp index 59319a839b..90e227bb53 100644 --- a/libs/image/kis_abstract_projection_plane.cpp +++ b/libs/image/kis_abstract_projection_plane.cpp @@ -1,70 +1,75 @@ /* * 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_abstract_projection_plane.h" KisAbstractProjectionPlane::KisAbstractProjectionPlane() { } KisAbstractProjectionPlane::~KisAbstractProjectionPlane() { } QRect KisDumbProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); return rect; } void KisDumbProjectionPlane::apply(KisPainter *painter, const QRect &rect) { Q_UNUSED(painter); Q_UNUSED(rect); } QRect KisDumbProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisDumbProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisDumbProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisDumbProjectionPlane::needRectForOriginal(const QRect &rect) const { return rect; } +QRect KisDumbProjectionPlane::tightUserVisibleBounds() const +{ + return QRect(); +} + KisPaintDeviceList KisDumbProjectionPlane::getLodCapableDevices() const { // arghm... return KisPaintDeviceList(); } diff --git a/libs/image/kis_abstract_projection_plane.h b/libs/image/kis_abstract_projection_plane.h index dfc8420b2b..932fe1f9be 100644 --- a/libs/image/kis_abstract_projection_plane.h +++ b/libs/image/kis_abstract_projection_plane.h @@ -1,113 +1,120 @@ /* * 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_ABSTRACT_PROJECTION_PLANE_H #define __KIS_ABSTRACT_PROJECTION_PLANE_H #include "kis_types.h" #include "kis_layer.h" class QRect; class KisPainter; /** * An interface of the node to the compositioning * system. Compositioning system KisAsyncMerger knows nothing about * the internals of the layer, it just knows that the layer can: * * 1) recalculate() its internal representation if it is filthy * * 2) apply() itself onto a global projection using, writing all its data * to the projection. * * 3) report parameters of these operations using needRect(), * changeRect() and accessRect() methods, which mean the same as * the corresponding methods of KisNode, but include more * transformations for the layer, e.g. Layer Styles and * Pass-through mode. */ class KRITAIMAGE_EXPORT KisAbstractProjectionPlane { public: KisAbstractProjectionPlane(); virtual ~KisAbstractProjectionPlane(); /** * Is called by the async merger when the node is filthy and * should recalculate its internal representation. For usual * layers it means just calling updateProjection(). */ virtual QRect recalculate(const QRect& rect, KisNodeSP filthyNode) = 0; /** * Writes the data of the projection plane onto a global * projection using \p painter object. */ virtual void apply(KisPainter *painter, const QRect &rect) = 0; /** * Works like KisNode::needRect(), but includes more * transformations of the layer */ virtual QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0; /** * Works like KisNode::changeRect(), but includes more * transformations of the layer */ virtual QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0; /** * Works like KisNode::needRect(), but includes more * transformations of the layer */ virtual QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0; /** * Works like KisLayer::needRectForOriginal(), but includes needed * rects of layer styles */ virtual QRect needRectForOriginal(const QRect &rect) const = 0; + /** + * Return a tight rectange, where the contents of the plane + * is placed from user's point of view. It includes everything + * belonging to the plane (e.g. layer styles). + */ + virtual QRect tightUserVisibleBounds() const = 0; + /** * Returns a list of devices which should synchronize the lod cache on update */ virtual KisPaintDeviceList getLodCapableDevices() const = 0; }; /** * A simple implementation of KisAbstractProjectionPlane interface * that does nothing. */ class KisDumbProjectionPlane : public KisAbstractProjectionPlane { public: QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; - + QRect tightUserVisibleBounds() const override; KisPaintDeviceList getLodCapableDevices() const override; }; #endif /* __KIS_ABSTRACT_PROJECTION_PLANE_H */ diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/kis_cached_paint_device.h index f83eb34480..2cbde7e427 100644 --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/kis_cached_paint_device.h @@ -1,114 +1,122 @@ /* * 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); } + bool isEmpty() const { + return m_stack.isEmpty(); + } + struct Guard { Guard(KisPaintDeviceSP prototype, KisCachedPaintDevice &parent) : m_parent(parent) { m_device = m_parent.getDevice(prototype); } ~Guard() { m_parent.putDevice(m_device); } KisPaintDeviceSP device() const { return m_device; } private: KisCachedPaintDevice &m_parent; KisPaintDeviceSP m_device; }; private: KisLocklessStack m_stack; }; class KisCachedSelection { public: KisSelectionSP getSelection() { KisSelectionSP selection; if(!m_stack.pop(selection)) { selection = new KisSelection(new KisSelectionEmptyBounds(0)); } return selection; } void putSelection(KisSelectionSP selection) { selection->clear(); selection->setDefaultBounds(new KisSelectionEmptyBounds(0)); selection->pixelSelection()->moveTo(QPoint()); m_stack.push(selection); } + bool isEmpty() const { + return m_stack.isEmpty(); + } + struct Guard { Guard(KisCachedSelection &parent) : m_parent(parent) { m_selection = m_parent.getSelection(); } ~Guard() { m_parent.putSelection(m_selection); } KisSelectionSP selection() const { return m_selection; } private: KisCachedSelection &m_parent; KisSelectionSP m_selection; }; private: KisLocklessStack m_stack; }; #endif /* __KIS_CACHED_PAINT_DEVICE_H */ diff --git a/libs/image/kis_default_bounds.cpp b/libs/image/kis_default_bounds.cpp index bf2a3880aa..a84cdb9ee3 100644 --- a/libs/image/kis_default_bounds.cpp +++ b/libs/image/kis_default_bounds.cpp @@ -1,149 +1,159 @@ /* * Copyright (c) 2010 Boudewijn Rempt * 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_global.h" #include "kis_default_bounds.h" #include "kis_paint_device.h" #include "kis_image_animation_interface.h" #include "kis_image.h" const QRect KisDefaultBounds::infiniteRect = QRect(qint32_MIN/2, qint32_MIN/2, qint32_MAX, qint32_MAX); /******************************************************************/ /* KisDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisDefaultBounds::Private { KisImageWSP image; }; KisDefaultBounds::KisDefaultBounds(KisImageWSP image) : m_d(new Private()) { m_d->image = image; } KisDefaultBounds::~KisDefaultBounds() { delete m_d; } QRect KisDefaultBounds::bounds() const { /** * By default return infinite rect to cover everything */ return m_d->image ? m_d->image->effectiveLodBounds() : infiniteRect; } bool KisDefaultBounds::wrapAroundMode() const { return m_d->image ? m_d->image->wrapAroundModeActive() : false; } int KisDefaultBounds::currentLevelOfDetail() const { return m_d->image ? m_d->image->currentLevelOfDetail() : 0; } int KisDefaultBounds::currentTime() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->currentTime() : 0; } bool KisDefaultBounds::externalFrameActive() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->externalFrameActive() : false; } +void *KisDefaultBounds::sourceCookie() const +{ + return m_d->image.data(); +} + /******************************************************************/ /* KisSelectionDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisSelectionDefaultBounds::Private { KisPaintDeviceWSP parentDevice; }; KisSelectionDefaultBounds::KisSelectionDefaultBounds(KisPaintDeviceSP parentDevice) : m_d(new Private()) { m_d->parentDevice = parentDevice; } KisSelectionDefaultBounds::~KisSelectionDefaultBounds() { delete m_d; } QRect KisSelectionDefaultBounds::bounds() const { return m_d->parentDevice ? m_d->parentDevice->extent() | m_d->parentDevice->defaultBounds()->bounds() : QRect(); } bool KisSelectionDefaultBounds::wrapAroundMode() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->wrapAroundMode() : false; } int KisSelectionDefaultBounds::currentLevelOfDetail() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->currentLevelOfDetail() : 0; } int KisSelectionDefaultBounds::currentTime() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->currentTime() : 0; } bool KisSelectionDefaultBounds::externalFrameActive() const { return m_d->parentDevice ? - m_d->parentDevice->defaultBounds()->externalFrameActive() : false; + m_d->parentDevice->defaultBounds()->externalFrameActive() : false; +} + +void *KisSelectionDefaultBounds::sourceCookie() const +{ + return m_d->parentDevice.data(); } /******************************************************************/ /* KisSelectionEmptyBounds */ /******************************************************************/ KisSelectionEmptyBounds::KisSelectionEmptyBounds(KisImageWSP image) : KisDefaultBounds(image) { } KisSelectionEmptyBounds::~KisSelectionEmptyBounds() { } QRect KisSelectionEmptyBounds::bounds() const { return QRect(0, 0, 0, 0); } diff --git a/libs/image/kis_default_bounds.h b/libs/image/kis_default_bounds.h index c3493225ff..c234633a2d 100644 --- a/libs/image/kis_default_bounds.h +++ b/libs/image/kis_default_bounds.h @@ -1,83 +1,85 @@ /* * Copyright (c) 2010 Boudewijn Rempt * 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_DEFAULT_BOUNDS_H #define KIS_DEFAULT_BOUNDS_H #include #include "kis_types.h" #include "kis_default_bounds_base.h" class KisDefaultBounds; class KisSelectionDefaultBounds; class KisSelectionEmptyBounds; typedef KisSharedPtr KisDefaultBoundsSP; typedef KisSharedPtr KisSelectionDefaultBoundsSP; typedef KisSharedPtr KisSelectionEmptyBoundsSP; class KRITAIMAGE_EXPORT KisDefaultBounds : public KisDefaultBoundsBase { public: KisDefaultBounds(KisImageWSP image = 0); ~KisDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; + void * sourceCookie() const override; protected: friend class KisPaintDeviceTest; static const QRect infiniteRect; private: Q_DISABLE_COPY(KisDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionDefaultBounds : public KisDefaultBoundsBase { public: KisSelectionDefaultBounds(KisPaintDeviceSP parentPaintDevice); ~KisSelectionDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; + void * sourceCookie() const override; private: Q_DISABLE_COPY(KisSelectionDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionEmptyBounds : public KisDefaultBounds { public: KisSelectionEmptyBounds(KisImageWSP image); ~KisSelectionEmptyBounds() override; QRect bounds() const override; }; #endif // KIS_DEFAULT_BOUNDS_H diff --git a/libs/image/kis_default_bounds_base.h b/libs/image/kis_default_bounds_base.h index 464b17ea07..b4c1cdfd5f 100644 --- a/libs/image/kis_default_bounds_base.h +++ b/libs/image/kis_default_bounds_base.h @@ -1,43 +1,56 @@ /* * Copyright (c) 2010 Boudewijn Rempt * 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_DEFAULT_BOUNDS_BASE_H #define KIS_DEFAULT_BOUNDS_BASE_H #include #include "kis_shared.h" #include "kis_shared_ptr.h" #include "kritaimage_export.h" class KisDefaultBoundsBase; typedef KisSharedPtr KisDefaultBoundsBaseSP; class KRITAIMAGE_EXPORT KisDefaultBoundsBase : public KisShared { public: virtual ~KisDefaultBoundsBase(); virtual QRect bounds() const = 0; virtual bool wrapAroundMode() const = 0; virtual int currentLevelOfDetail() const = 0; virtual int currentTime() const = 0; virtual bool externalFrameActive() const = 0; + + /** + * Return an abstract pointer to the source object, + * where default bounds takes its data from. It the + * cookie is nullptr, then the default bounds is not + * connected to anything. One can also compare if two + * default bounds are connected to the same source by + * comparing two pointers. + * + * NOTE: It is intended to be used for debugging + * purposes only! + */ + virtual void* sourceCookie() const = 0; }; #endif // KIS_DEFAULT_BOUNDS_BASE_H diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 7a5ed9548f..a283371fbd 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,991 +1,1006 @@ /* * 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_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" #include "kis_projection_leaf.h" #include "KisSafeNodeProjectionStore.h" class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; class KisLayerMasksCache { public: KisLayerMasksCache(KisLayer *parent) : m_parent(parent) { } KisSelectionMaskSP selectionMask() { QReadLocker readLock(&m_lock); if (!m_isSelectionMaskValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isSelectionMaskValid) { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_selectionMask = dynamic_cast(mask.data()); break; } } m_isSelectionMaskValid = true; } // return under write lock return m_selectionMask; } // return under read lock return m_selectionMask; } QList effectMasks() { QReadLocker readLock(&m_lock); if (!m_isEffectMasksValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isEffectMasksValid) { m_effectMasks = m_parent->searchEffectMasks(0); m_isEffectMasksValid = true; } // return under write lock return m_effectMasks; } // return under read lock return m_effectMasks; } void setDirty() { QWriteLocker l(&m_lock); m_isSelectionMaskValid = false; m_isEffectMasksValid = false; m_selectionMask = 0; m_effectMasks.clear(); } private: KisLayer *m_parent; QReadWriteLock m_lock; bool m_isSelectionMaskValid = false; bool m_isEffectMasksValid = false; KisSelectionMaskSP m_selectionMask; QList m_effectMasks; }; struct Q_DECL_HIDDEN KisLayer::Private { Private(KisLayer *q) : masksCache(q) { } QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; KisLayerProjectionPlaneSP projectionPlane; KisSafeNodeProjectionStoreSP safeProjection; KisLayerMasksCache masksCache; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode(image) , m_d(new Private(this)) { setName(name); setOpacity(opacity); m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(); m_d->safeProjection->setImage(image); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private(this)) { if (this != &rhs) { m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(*rhs.m_d->safeProjection); m_d->safeProjection->setImage(image()); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone().dynamicCast(); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = this->image(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } void KisLayer::setImage(KisImageWSP image) { // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } m_d->safeProjection->setImage(image); KisNode::setImage(image); } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } void KisLayer::notifyChildMaskChanged() { m_d->masksCache.setDirty(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->masksCache.selectionMask(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = this->image(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks() const { return m_d->masksCache.effectMasks(); } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf()); KisProjectionLeafSP child = projectionLeaf()->firstChild(); while (child) { if (child->node() == lastNode) break; KIS_SAFE_ASSERT_RECOVER_NOOP(child); KIS_SAFE_ASSERT_RECOVER_NOOP(child->node()); if (child->visible()) { KisEffectMaskSP mask = dynamic_cast(const_cast(child->node().data())); if (mask) { masks.append(mask); } } child = child->nextSibling(); } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->masksCache.effectMasks().isEmpty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || (!visible() && !hasClones()) || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection->releaseDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection->getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : KisAbstractProjectionPlaneSP(m_d->projectionPlane); } KisLayerProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection->getDeviceLazy(originalDevice) : originalDevice; } +QRect KisLayer::tightUserVisibleBounds() const +{ + QRect changeRect = exactBounds(); + + /// we do not use incomingChangeRect() here, because + /// exactBounds() already takes it into account (it + /// was used while preparing original()) + + bool changeRectVaries; + changeRect = outgoingChangeRect(changeRect); + changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); + + return changeRect; +} + QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { notifyChildMaskChanged(); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::needRectForOriginal(const QRect &rect) const { QRect needRect = rect; const QList masks = effectMasks(); if (!masks.isEmpty()) { QStack applyRects; bool needRectVaries; needRect = masksNeedRect(masks, rect, applyRects, needRectVaries); } return needRect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); if (originalDevice ) { KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); if (channel) { KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); KisKeyframeSP keyframe = channel->activeKeyframeAt(time); channel->fetchFrame(keyframe, targetDevice); return targetDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } return createThumbnail(w, h); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_layer.h b/libs/image/kis_layer.h index 380e3375ae..5fc6ef667d 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,419 +1,427 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * 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. */ #ifndef KIS_LAYER_H_ #define KIS_LAYER_H_ #include #include #include #include #include "kritaimage_export.h" #include "kis_base_node.h" #include "kis_types.h" #include "kis_node.h" #include "kis_psd_layer_style.h" template class QStack; class QBitArray; class KisCloneLayer; class KisPSDLayerStyle; class KisAbstractProjectionPlane; class KisLayerProjectionPlane; typedef QSharedPointer KisLayerProjectionPlaneSP; namespace KisMetaData { class Store; } /** * Abstract class that represents the concept of a Layer in Krita. This is not related * to the paint devices: this is merely an abstraction of how layers can be stacked and * rendered differently. * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer * is at the top of the group in the layerlist, using next will iterate to the bottom to last, * whereas previous will go up to first again. * * * TODO: Add a layer mode whereby the projection of the layer is used * as a clipping path? **/ class KRITAIMAGE_EXPORT KisLayer : public KisNode { Q_OBJECT public: /** * @param image is the pointer of the image or null * @param opacity is a value between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 **/ KisLayer(KisImageWSP image, const QString &name, quint8 opacity); KisLayer(const KisLayer& rhs); ~KisLayer() override; /// returns the image's colorSpace or null, if there is no image const KoColorSpace * colorSpace() const override; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const override; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ KisAbstractProjectionPlaneSP projectionPlane() const override; /** * The projection plane representing the layer itself without any * styles or anything else. It is used by the layer styles projection * plane to stack up the planes. */ virtual KisLayerProjectionPlaneSP internalProjectionPlane() const; QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect); void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect); virtual bool needProjection() const; /** * Return the fully rendered representation of this layer: its * data and its effect masks */ KisPaintDeviceSP projection() const override; /** * Return the layer data before the effect masks have had their go * at it. */ KisPaintDeviceSP original() const override = 0; /** * @return the selection associated with this layer, if there is * one. Otherwise, return 0; */ virtual KisSelectionMaskSP selectionMask() const; /** * @return the selection contained in the first KisSelectionMask associated * with this layer or the image, if either exists, otherwise, return 0. */ virtual KisSelectionSP selection() const; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; /** * set/unset the channel flag for the alpha channel of this layer */ void disableAlphaChannel(bool disable); /** * returns true if the channel flag for the alpha channel * of this layer is not set. * returns false otherwise. */ bool alphaChannelDisabled() const; /** * set the channelflags for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ virtual void setChannelFlags(const QBitArray & channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is active or not. If the channelflags bit * array is empty, all channels are active. */ QBitArray & channelFlags() const; /** * Returns true if this layer is temporary: i.e., it should not * appear in the layerbox, even though it is temporarily in the * layer stack and taken into account on recomposition. */ bool temporary() const; /** * Set to true if this layer should not appear in the layerbox, * even though it is temporarily in the layer stack and taken into * account on recomposition. */ void setTemporary(bool t); /** * Set the image this layer belongs to. */ void setImage(KisImageWSP image) override; /** * Create and return a layer that is the result of merging * this with layer. * * This method is designed to be called only within KisImage::mergeLayerDown(). * * Decendands override this to create specific merged types when possible. * The KisLayer one creates a KisPaintLayerSP via a bitBlt, and can work on all layer types. * * Descendants that perform their own version do NOT call KisLayer::createMergedLayer */ virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clones should be informed about updates of the original * layer, so this is a way to register them */ void registerClone(KisCloneLayerWSP clone); /** * Deregisters the clone from the update list * * \see registerClone() */ void unregisterClone(KisCloneLayerWSP clone); /** * Return the list of the clones of this node. Be careful * with the list, because it is not thread safe. */ const QList registeredClones() const; /** * Returns whether we have a clone. * * Be careful with it. It is not thread safe to add/remove * clone while checking hasClones(). So there should be no updates. */ bool hasClones() const; /** * It is calles by the async merger after projection update is done */ void updateClones(const QRect &rect); /** * Informs this layers that its masks might have changed. */ void notifyChildMaskChanged(); public: qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const override; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const override; QImage createThumbnail(qint32 w, qint32 h) override; QImage createThumbnailForFrame(qint32 w, qint32 h, int time) override; + /** + * Return a tight rectange, where the contents of the layer + * is placed from user's point of view. This rectangle includes + * all the masks and effects the layer has (excluding layer + * styles, they report their bounds via projection plane). + */ + QRect tightUserVisibleBounds() const; + public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ QList effectMasks() const; /** * @return the list of effect masks up to a certain node */ QList effectMasks(KisNodeSP lastNode) const; /** * Get the group layer that contains this layer. */ KisLayerSP parentLayer() const; /** * @return the metadata object associated with this object. */ KisMetaData::Store* metaData(); protected: // override from KisNode QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; void childNodeChanged(KisNodeSP changedChildNode) override; protected: /** * Ask the layer to assemble its data & apply all the effect masks * to it. */ QRect updateProjection(const QRect& rect, KisNodeSP filthyNode); /** * Layers can override this method to get some special behavior * when copying data from \p original to \p projection, e.g. blend * in indirect painting device. If you need to modify data * outside \p rect, please also override outgoingChangeRect() * method. */ virtual void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; /** * For KisLayer classes change rect transformation consists of two * parts: incoming and outgoing. * * 1) incomingChangeRect(rect) chande rect transformation * performed by the transformations done basing on global * projection. It is performed in KisAsyncMerger + * KisUpdateOriginalVisitor classes. It happens before data * coming to KisLayer::original() therefore it is * 'incoming'. See KisAdjustmentLayer for example of usage. * * 2) outgoingChangeRect(rect) change rect transformation that * happens in KisLayer::copyOriginalToProjection(). It applies * *only* when the layer is 'filthy', that is was the cause of * the merge process. See KisCloneLayer for example of usage. * * The flow of changed areas can be illustrated in the * following way: * * 1. Current projection of size R1 is stored in KisAsyncMerger::m_currentProjection * | * | <-- KisUpdateOriginalVisitor writes data into layer's original() device. * | The changed area on KisLayer::original() is * | R2 = KisLayer::incomingChangeRect(R1) * | * 2. KisLayer::original() / changed rect: R2 * | * | <-- KisLayer::updateProjection() starts composing a layer * | It calls KisLayer::copyOriginalToProjection() which copies some area * | to a temporaty device. The temporary device now stores * | R3 = KisLayer::outgoingChangeRect(R2) * | * 3. Temporary device / changed rect: R3 * | * | <-- KisLayer::updateProjection() continues composing a layer. It merges a mask. * | R4 = KisMask::changeRect(R3) * | * 4. KisLayer::original() / changed rect: R4 * * So in the end rect R4 will be passed up to the next layers in the stack. */ virtual QRect incomingChangeRect(const QRect &rect) const; /** * \see incomingChangeRect() */ virtual QRect outgoingChangeRect(const QRect &rect) const; /** * Return need rect that should be prepared on original() * device of the layer to get \p rect on its projection. * * This method is used either for layers that can have other * layers as children (yes, KisGroupLayer, I'm looking at you!), * or for layers that depend on the lower nodes (it's you, * KisAdjustmentLayer!). * * These layers may have some filter masks that need a bit * more pixels than requested, therefore child nodes should do * a bit more work to prepare them. */ QRect needRectForOriginal(const QRect &rect) const; /** * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return an area that should be updated because of * the change of @requestedRect of the layer */ QRect masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const; /** * Get needRects for all masks * @param changeRect requested rect to be updated on final * projection. Should be a return value * of @ref masksChangedRect() * @param applyRects (out param) a stack of the rects where filters * should be applied * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return a needRect that should be prepared on the layer's * paintDevice for all masks to succeed */ QRect masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const; QRect applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const; bool canMergeAndKeepBlendOptions(KisLayerSP otherLayer); QList searchEffectMasks(KisNodeSP lastNode) const; private: friend class KisLayerMasksCache; friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: QRect layerExtentImpl(bool exactBounds) const; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_layer_projection_plane.cpp b/libs/image/kis_layer_projection_plane.cpp index 0ee62c63e5..371a05e0da 100644 --- a/libs/image/kis_layer_projection_plane.cpp +++ b/libs/image/kis_layer_projection_plane.cpp @@ -1,158 +1,163 @@ /* * 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_projection_plane.h" #include #include #include #include #include "kis_painter.h" #include "kis_projection_leaf.h" #include "kis_cached_paint_device.h" #include "kis_sequential_iterator.h" struct KisLayerProjectionPlane::Private { KisLayer *layer; KisCachedPaintDevice cachedDevice; }; KisLayerProjectionPlane::KisLayerProjectionPlane(KisLayer *layer) : m_d(new Private) { m_d->layer = layer; } KisLayerProjectionPlane::~KisLayerProjectionPlane() { } QRect KisLayerProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { return m_d->layer->updateProjection(rect, filthyNode); } void KisLayerProjectionPlane::applyImpl(KisPainter *painter, const QRect &rect, bool maxOutAlpha) { KisPaintDeviceSP device = m_d->layer->projection(); if (!device) return; QRect needRect = rect; if (m_d->layer->compositeOpId() != COMPOSITE_COPY && m_d->layer->compositeOpId() != COMPOSITE_DESTINATION_IN && m_d->layer->compositeOpId() != COMPOSITE_DESTINATION_ATOP) { needRect &= device->extent(); } if(needRect.isEmpty()) return; QBitArray channelFlags = m_d->layer->projectionLeaf()->channelFlags(); // if the color spaces don't match we will have a problem with the channel flags // because the channel flags from the source layer doesn't match with the colorspace of the projection device // this leads to the situation that the wrong channels will be enabled/disabled const KoColorSpace* srcCS = device->colorSpace(); const KoColorSpace* dstCS = painter->device()->colorSpace(); if (!channelFlags.isEmpty() && srcCS != dstCS) { bool alphaFlagIsSet = (srcCS->channelFlags(false,true) & channelFlags) == srcCS->channelFlags(false,true); bool allColorFlagsAreSet = (srcCS->channelFlags(true,false) & channelFlags) == srcCS->channelFlags(true,false); bool allColorFlagsAreUnset = (srcCS->channelFlags(true,false) & channelFlags).count(true) == 0; if(allColorFlagsAreSet) { channelFlags = dstCS->channelFlags(true, alphaFlagIsSet); } else if(allColorFlagsAreUnset) { channelFlags = dstCS->channelFlags(false, alphaFlagIsSet); } else { //TODO: convert the cannel flags properly // for now just the alpha channel bit is copied and the other channels are left alone for (quint32 i=0; i < dstCS->channelCount(); ++i) { if (dstCS->channels()[i]->channelType() == KoChannelInfo::ALPHA) { channelFlags.setBit(i, alphaFlagIsSet); break; } } } } if (maxOutAlpha) { KisPaintDeviceSP tmp = m_d->cachedDevice.getDevice(device); tmp->makeCloneFromRough(device, needRect); const KoColorSpace *cs = tmp->colorSpace(); KisSequentialIterator it(tmp, needRect); int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { numConseqPixels = it.nConseqPixels(); cs->setOpacity(it.rawData(), quint8(255), numConseqPixels); } device = tmp; } painter->setChannelFlags(channelFlags); painter->setCompositeOp(m_d->layer->compositeOpId()); painter->setOpacity(m_d->layer->projectionLeaf()->opacity()); painter->bitBlt(needRect.topLeft(), device, needRect); if (maxOutAlpha) { m_d->cachedDevice.putDevice(device); } } void KisLayerProjectionPlane::apply(KisPainter *painter, const QRect &rect) { applyImpl(painter, rect, false); } void KisLayerProjectionPlane::applyMaxOutAlpha(KisPainter *painter, const QRect &rect) { applyImpl(painter, rect, true); } KisPaintDeviceList KisLayerProjectionPlane::getLodCapableDevices() const { return KisPaintDeviceList() << m_d->layer->projection(); } QRect KisLayerProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return m_d->layer->needRect(rect, pos); } QRect KisLayerProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return m_d->layer->changeRect(rect, pos); } QRect KisLayerProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return m_d->layer->accessRect(rect, pos); } QRect KisLayerProjectionPlane::needRectForOriginal(const QRect &rect) const { return m_d->layer->needRectForOriginal(rect); } +QRect KisLayerProjectionPlane::tightUserVisibleBounds() const +{ + return m_d->layer->tightUserVisibleBounds(); +} + diff --git a/libs/image/kis_layer_projection_plane.h b/libs/image/kis_layer_projection_plane.h index 141e4588d2..5f885895e9 100644 --- a/libs/image/kis_layer_projection_plane.h +++ b/libs/image/kis_layer_projection_plane.h @@ -1,60 +1,61 @@ /* * 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_PROJECTION_PLANE_H #define __KIS_LAYER_PROJECTION_PLANE_H #include "kis_abstract_projection_plane.h" #include /** * An implementation of the KisAbstractProjectionPlane interface for a * layer object */ class KisLayerProjectionPlane : public KisAbstractProjectionPlane { public: KisLayerProjectionPlane(KisLayer *layer); ~KisLayerProjectionPlane() override; QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; void applyMaxOutAlpha(KisPainter *painter, const QRect &rect); QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; + QRect tightUserVisibleBounds() const override; KisPaintDeviceList getLodCapableDevices() const override; private: void applyImpl(KisPainter *painter, const QRect &rect, bool maxOutAlpha); private: struct Private; const QScopedPointer m_d; }; typedef QSharedPointer KisLayerProjectionPlaneSP; typedef QWeakPointer KisLayerProjectionPlaneWSP; #endif /* __KIS_LAYER_PROJECTION_PLANE_H */ diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index cf4e363797..272107a12f 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1647 +1,1647 @@ /* * 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 "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 #include "krita_utils.h" #include "kis_image_signal_router.h" namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { Q_FOREACH(KisNodeSP child, node->childNodes(QStringList("KisSelectionMask"), KoProperties())) { KisSelectionMaskSP mask = qobject_cast(child.data()); if (mask) { 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(m_info->image, node, preparedRect); } } 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::partB() { KisImageSignalType type; if (getState() == State::FINALIZING) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } SelectGlobalSelectionMask::SelectGlobalSelectionMask(KisImageSP image) : m_image(image) { } void SelectGlobalSelectionMask::redo() { KisImageSignalType type = ComplexNodeReselectionSignal(m_image->rootLayer()->selectionMask(), KisNodeList()); m_image->signalRouter()->emitNotification(type); } 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); auto isNodeWeird = [] (KisNodeSP node) { const bool normalCompositeMode = node->compositeOpId() == COMPOSITE_OVER; KisLayer *layer = dynamic_cast(node.data()); const bool hasInheritAlpha = layer && layer->alphaChannelDisabled(); return !normalCompositeMode && !hasInheritAlpha; }; while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, !isNodeWeird(node), true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); 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()) && !node->isFakeNode()) { 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 all parents if they are included in nodesToDelete // Not every descendant is included in nodesToDelete even if in fact // they are going to be deleted, so we need to check it. // If we consider the path from root to the putAfter node, // if there are any nodes marked for deletion, any node afterwards // is going to be deleted, too. // example: root . . . . . ! ! . . ! ! ! ! . . . . putAfter // it should be: root . . . . . ! ! ! ! ! ! ! ! ! ! ! ! !putAfter // and here: root . . . . X ! ! . . ! ! ! ! . . . . putAfter // you can see which node is "the perfect ancestor" // (marked X; called "parent" in the function arguments). // and here: root . . . . . O ! . . ! ! ! ! . . . . putAfter // you can see which node is "the topmost deleted ancestor" (marked 'O') KisNodeSP node = putAfter->parent(); bool foundDeletedAncestor = false; KisNodeSP topmostAncestorToDelete = nullptr; while (node) { if (nodesToDelete.contains(node) && !nodesToDelete.contains(node->parent())) { foundDeletedAncestor = true; topmostAncestorToDelete = node; // Here node is to be deleted and its parent is not, // so its parent is the one of the first not deleted (="perfect") ancestors. // We need the one that is closest to the top (root) } node = node->parent(); } if (foundDeletedAncestor) { parent = topmostAncestorToDelete->parent(); putAfter = topmostAncestorToDelete; } else { parent = putAfter->parent(); // putAfter (and none of its ancestors) is to be deleted, so its parent is the first not deleted ancestor } } 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)); // copy all fake nodes into the new image KisLayerUtils::recursiveApplyNodes(oldRoot, [this, oldRoot, newRoot] (KisNodeSP node) { if (node->isFakeNode() && node->parent() == oldRoot) { addCommand(new KisImageLayerAddCommand(m_info->image, node->clone(), newRoot, KisNodeSP(), false, false)); } }); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, 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, [](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::partA() { 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::partB() { 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(); } } /** * \see a comment in mergeMultipleLayersImpl() */ 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 through 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()); 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; } KisNodeList sortAndFilterAnyMergableNodesSafe(const KisNodeList &nodes, KisImageSP image) { KisNodeList filteredNodes = nodes; KisNodeList sortedNodes; KisLayerUtils::filterMergableNodes(filteredNodes, true); bool haveExternalNodes = false; Q_FOREACH (KisNodeSP node, nodes) { if (node->graphListener() != image->root()->graphListener()) { haveExternalNodes = true; break; } } if (!haveExternalNodes) { KisLayerUtils::sortMergableNodes(image->root(), filteredNodes, sortedNodes); } else { sortedNodes = filteredNodes; } return sortedNodes; } 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 filterUnlockedNodes(KisNodeList &nodes) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((*it)->userLocked()) { it = nodes.erase(it); } else { ++it; } } } 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(); } /** * There might be two approaches for merging multiple layers: * * 1) Consider the selected nodes as a distinct "group" and merge them * as if they were isolated from the rest of the image. The key point * of this approach is that the look of the image will change, when * merging "weird" layers, like adjustment layers or layers with * non-normal blending mode. * * 2) Merge layers in a way to keep the look of the image as unchanged as * possible. With this approach one uses a few heuristics: * * * when merging multiple layers with non-normal (but equal) blending * mode, first merge these layers together using Normal blending mode, * then set blending mode of the result to the original blending mode * * * when merging multiple layers with different blending modes or * layer styles, they are first rasterized, and then laid over each * other with their own composite op. The blending mode of the final * layer is set to Normal, so the user could clearly see that he should * choose the correct blending mode. * * Krita uses the second approach: after merge operation, the image should look * as if nothing has happened (if it is technically possible). */ 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() && !mergedNodes.isEmpty()) { /* If the putAfter node is invisible, * we should instead pick one of the nodes * to be merged to avoid a null putAfter. */ if (!putAfter->visible()){ putAfter = mergedNodes.first(); } 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.isEmpty()) { 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, KisNodeSP activeNode) { if (!activeNode) { activeNode = image->root()->lastChild(); } KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, activeNode, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::partB() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } 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(); } }); } bool hasDelayedNodeWithUpdates(KisNodeSP root) { return recursiveFindNode(root, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); return delayedUpdate ? delayedUpdate->hasPendingTimedUpdates() : false; }); } void forceAllHiddenOriginalsUpdate(KisNodeSP root) { KisLayerUtils::recursiveApplyNodes(root, [] (KisNodeSP node) { KisCroppedOriginalLayerInterface *croppedUpdate = dynamic_cast(node.data()); if (croppedUpdate) { croppedUpdate->forceUpdateHiddenAreaOnOriginal(); } }); } KisImageSP findImageByHierarchy(KisNodeSP node) { while (node) { const KisLayer *layer = dynamic_cast(node.data()); if (layer) { return layer->image(); } node = node->parent(); } return 0; } namespace Private { QRect realNodeChangeRect(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeChangeRect(node, currentRect); node = node->nextSibling(); } if (!rootNode->isFakeNode()) { // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); } return currentRect; } } void refreshHiddenAreaAsync(KisImageSP image, KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = Private::realNodeChangeRect(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; Q_FOREACH (const QRect &rc, dirtyRegion.rects()) { image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } - QRect recursiveNodeExactBounds(KisNodeSP rootNode) + QRect recursiveTightNodeVisibleBounds(KisNodeSP rootNode) { QRect exactBounds; recursiveApplyNodes(rootNode, [&exactBounds] (KisNodeSP node) { - exactBounds |= node->exactBounds(); + exactBounds |= node->projectionPlane()->tightUserVisibleBounds(); }); return exactBounds; } } diff --git a/libs/image/kis_layer_utils.h b/libs/image/kis_layer_utils.h index b43696ce3e..7a7f33a41d 100644 --- a/libs/image/kis_layer_utils.h +++ b/libs/image/kis_layer_utils.h @@ -1,239 +1,239 @@ /* * 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_UTILS_H #define __KIS_LAYER_UTILS_H #include #include "kundo2command.h" #include "kis_types.h" #include "kritaimage_export.h" #include "kis_command_utils.h" class KoProperties; class KoColor; class QUuid; namespace KisMetaData { class MergeStrategy; } namespace KisLayerUtils { KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList &inputNodes, QList &outputNodes); KRITAIMAGE_EXPORT KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes); KRITAIMAGE_EXPORT void filterMergableNodes(KisNodeList &nodes, bool allowMasks = false); KRITAIMAGE_EXPORT KisNodeList sortAndFilterAnyMergableNodesSafe(const KisNodeList &nodes, KisImageSP image); KRITAIMAGE_EXPORT bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents); KRITAIMAGE_EXPORT void filterUnlockedNodes(KisNodeList &nodes); KRITAIMAGE_EXPORT void refreshHiddenAreaAsync(KisImageSP image, KisNodeSP rootNode, const QRect &preparedArea); - KRITAIMAGE_EXPORT QRect recursiveNodeExactBounds(KisNodeSP rootNode); + KRITAIMAGE_EXPORT QRect recursiveTightNodeVisibleBounds(KisNodeSP rootNode); /** * Returns true if: * o \p node is a clone of some layer in \p nodes * o \p node is a clone any child layer of any layer in \p nodes * o \p node is a clone of a clone of a ..., that in the end points * to any layer in \p nodes of their children. */ KRITAIMAGE_EXPORT bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes); KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root); KRITAIMAGE_EXPORT bool hasDelayedNodeWithUpdates(KisNodeSP root); KRITAIMAGE_EXPORT void forceAllHiddenOriginalsUpdate(KisNodeSP root); KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false); KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy); KRITAIMAGE_EXPORT QSet fetchLayerFrames(KisNodeSP node); KRITAIMAGE_EXPORT QSet fetchLayerFramesRecursive(KisNodeSP rootNode); KRITAIMAGE_EXPORT void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter); KRITAIMAGE_EXPORT void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter); KRITAIMAGE_EXPORT bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter); KRITAIMAGE_EXPORT void flattenLayer(KisImageSP image, KisLayerSP layer); KRITAIMAGE_EXPORT void flattenImage(KisImageSP image, KisNodeSP activeNode); KRITAIMAGE_EXPORT void addCopyOfNameTag(KisNodeSP node); KRITAIMAGE_EXPORT KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot); KRITAIMAGE_EXPORT void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color); typedef QMap > FrameJobs; void updateFrameJobs(FrameJobs *jobs, KisNodeSP node); void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode); struct SwitchFrameCommand : public KisCommandUtils::FlipFlopCommand { struct SharedStorage { /** * For some reason the absence of a destructor in the SharedStorage * makes Krita crash on exit. Seems like some compiler weirdness... (DK) */ ~SharedStorage(); int value; }; typedef QSharedPointer SharedStorageSP; public: SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage); ~SwitchFrameCommand() override; private: void partA() override; void partB() override; private: KisImageWSP m_image; int m_newTime; SharedStorageSP m_storage; }; /** * A command to keep correct set of selected/active nodes thoroughout * the action. */ class KRITAIMAGE_EXPORT KeepNodesSelectedCommand : public KisCommandUtils::FlipFlopCommand { public: KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent = 0); void partB() override; private: KisNodeList m_selectedBefore; KisNodeList m_selectedAfter; KisNodeSP m_activeBefore; KisNodeSP m_activeAfter; KisImageWSP m_image; }; struct KRITAIMAGE_EXPORT SelectGlobalSelectionMask : public KUndo2Command { SelectGlobalSelectionMask(KisImageSP image); void redo() override; KisImageSP m_image; }; class KRITAIMAGE_EXPORT RemoveNodeHelper { public: virtual ~RemoveNodeHelper(); protected: virtual void addCommandImpl(KUndo2Command *cmd) = 0; void safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image); private: bool checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes); static bool scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove); }; struct SimpleRemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand { SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image); void populateChildCommands() override; protected: void addCommandImpl(KUndo2Command *cmd) override; private: KisNodeList m_nodes; KisImageSP m_image; KisNodeList m_selectedNodes; KisNodeSP m_activeNode; }; class KRITAIMAGE_EXPORT KisSimpleUpdateCommand : public KisCommandUtils::FlipFlopCommand { public: KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent = 0); void partB() override; static void updateNodes(const KisNodeList &nodes); private: KisNodeList m_nodes; }; template bool checkNodesDiffer(KisNodeList nodes, std::function checkerFunc) { bool valueDiffers = false; bool initialized = false; T currentValue = T(); Q_FOREACH (KisNodeSP node, nodes) { if (!initialized) { currentValue = checkerFunc(node); initialized = true; } else if (currentValue != checkerFunc(node)) { valueDiffers = true; break; } } return valueDiffers; } /** * Applies \p func to \p node and all its children recursively */ template void recursiveApplyNodes(NodePointer node, Functor func) { func(node); node = node->firstChild(); while (node) { recursiveApplyNodes(node, func); node = node->nextSibling(); } } /** * Walks through \p node and all its children recursively until * \p func returns true. When \p func returns true, the node is * considered to be found, the search is stopped and the found * node is returned to the caller. */ KisNodeSP KRITAIMAGE_EXPORT recursiveFindNode(KisNodeSP node, std::function func); /** * Recursively searches for a node with specified Uuid */ KisNodeSP KRITAIMAGE_EXPORT findNodeByUuid(KisNodeSP root, const QUuid &uuid); KisImageSP KRITAIMAGE_EXPORT findImageByHierarchy(KisNodeSP node); template T* findNodeByType(KisNodeSP root) { return dynamic_cast(recursiveFindNode(root, [] (KisNodeSP node) { return bool(dynamic_cast(node.data())); }).data()); } } #endif /* __KIS_LAYER_UTILS_H */ diff --git a/libs/image/kis_mask_projection_plane.cpp b/libs/image/kis_mask_projection_plane.cpp index 4856037621..50e37434b0 100644 --- a/libs/image/kis_mask_projection_plane.cpp +++ b/libs/image/kis_mask_projection_plane.cpp @@ -1,86 +1,91 @@ /* * 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_mask_projection_plane.h" #include #include #include #include "kis_painter.h" #include "kis_mask.h" struct KisMaskProjectionPlane::Private { KisMask *mask; }; KisMaskProjectionPlane::KisMaskProjectionPlane(KisMask *mask) : m_d(new Private) { m_d->mask = mask; } KisMaskProjectionPlane::~KisMaskProjectionPlane() { } QRect KisMaskProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); KIS_ASSERT_RECOVER_NOOP(0 && "KisMaskProjectionPlane::recalculate() is not defined!"); return rect; } void KisMaskProjectionPlane::apply(KisPainter *painter, const QRect &rect) { Q_UNUSED(painter); Q_UNUSED(rect); KIS_ASSERT_RECOVER_NOOP(0 && "KisMaskProjectionPlane::apply() is not defined!"); } KisPaintDeviceList KisMaskProjectionPlane::getLodCapableDevices() const { // masks have no projection return KisPaintDeviceList(); } QRect KisMaskProjectionPlane::needRect(const QRect &rect, KisNode::PositionToFilthy pos) const { return m_d->mask->needRect(rect, pos); } QRect KisMaskProjectionPlane::changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const { return m_d->mask->changeRect(rect, pos); } QRect KisMaskProjectionPlane::accessRect(const QRect &rect, KisNode::PositionToFilthy pos) const { return m_d->mask->accessRect(rect, pos); } QRect KisMaskProjectionPlane::needRectForOriginal(const QRect &rect) const { return rect; } +QRect KisMaskProjectionPlane::tightUserVisibleBounds() const +{ + return QRect(); +} + diff --git a/libs/image/kis_mask_projection_plane.h b/libs/image/kis_mask_projection_plane.h index 5963ff2d00..1013b6ca25 100644 --- a/libs/image/kis_mask_projection_plane.h +++ b/libs/image/kis_mask_projection_plane.h @@ -1,55 +1,56 @@ /* * 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_MASK_PROJECTION_PLANE_H #define __KIS_MASK_PROJECTION_PLANE_H #include "kis_abstract_projection_plane.h" #include /** * An implementation of the KisAbstractProjectionPlane interface for a * layer object. * * Please note that recalculate() and apply() methods are not defined * for masks, because the KisLayer code still uses traditional * methods of KisMask directly. */ class KisMaskProjectionPlane : public KisAbstractProjectionPlane { public: KisMaskProjectionPlane(KisMask *mask); ~KisMaskProjectionPlane() override; QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; QRect needRect(const QRect &rect, KisNode::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisNode::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; + QRect tightUserVisibleBounds() const override; KisPaintDeviceList getLodCapableDevices() const override; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_MASK_PROJECTION_PLANE_H */ diff --git a/libs/image/kis_onion_skin_cache.cpp b/libs/image/kis_onion_skin_cache.cpp index 03904ad563..b36da795e5 100644 --- a/libs/image/kis_onion_skin_cache.cpp +++ b/libs/image/kis_onion_skin_cache.cpp @@ -1,132 +1,139 @@ /* * Copyright (c) 2016 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_onion_skin_cache.h" #include #include #include #include "kis_paint_device.h" #include "kis_onion_skin_compositor.h" #include "kis_default_bounds.h" #include "kis_image.h" +#include "KoColorSpace.h" #include "kis_raster_keyframe_channel.h" struct KisOnionSkinCache::Private { KisPaintDeviceSP cachedProjection; int cacheTime = 0; int cacheConfigSeqNo = 0; int framesHash = 0; QReadWriteLock lock; bool checkCacheValid(KisPaintDeviceSP source, KisOnionSkinCompositor *compositor) { const KisRasterKeyframeChannel *keyframes = source->keyframeChannel(); const int time = source->defaultBounds()->currentTime(); const int seqNo = compositor->configSeqNo(); const int hash = keyframes->framesHash(); return time == cacheTime && cacheConfigSeqNo == seqNo && framesHash == hash; } void updateCacheMetrics(KisPaintDeviceSP source, KisOnionSkinCompositor *compositor) { const KisRasterKeyframeChannel *keyframes = source->keyframeChannel(); const int time = source->defaultBounds()->currentTime(); const int seqNo = compositor->configSeqNo(); const int hash = keyframes->framesHash(); cacheTime = time; cacheConfigSeqNo = seqNo; framesHash = hash; } }; KisOnionSkinCache::KisOnionSkinCache() : m_d(new Private) { } KisOnionSkinCache::~KisOnionSkinCache() { } KisPaintDeviceSP KisOnionSkinCache::projection(KisPaintDeviceSP source) { KisOnionSkinCompositor *compositor = KisOnionSkinCompositor::instance(); KisPaintDeviceSP cachedProjection; QReadLocker readLocker(&m_d->lock); cachedProjection = m_d->cachedProjection; if (!cachedProjection || !m_d->checkCacheValid(source, compositor)) { readLocker.unlock(); QWriteLocker writeLocker(&m_d->lock); cachedProjection = m_d->cachedProjection; - if (!cachedProjection || !m_d->checkCacheValid(source, compositor)) { + if (!cachedProjection || + !m_d->checkCacheValid(source, compositor) || + *cachedProjection->colorSpace() != *source->colorSpace()) { if (!cachedProjection) { cachedProjection = new KisPaintDevice(source->colorSpace()); } else { cachedProjection->setDefaultBounds(new KisDefaultBounds()); cachedProjection->clear(); + + if (*cachedProjection->colorSpace() != *source->colorSpace()) { + cachedProjection->convertTo(source->colorSpace()); + } } const QRect extent = compositor->calculateExtent(source); compositor->composite(source, cachedProjection, extent); cachedProjection->setDefaultBounds(source->defaultBounds()); /** * It might happen that the lod planes has already been * generated for all the devices, so we should cold-init them * for the onion skins. */ const int lod = source->defaultBounds()->currentLevelOfDetail(); if (lod > 0) { QScopedPointer data(cachedProjection->createLodDataStruct(lod)); cachedProjection->updateLodDataStruct(data.data(), extent); cachedProjection->uploadLodDataStruct(data.data()); } m_d->updateCacheMetrics(source, compositor); m_d->cachedProjection = cachedProjection; } } return cachedProjection; } void KisOnionSkinCache::reset() { QWriteLocker writeLocker(&m_d->lock); m_d->cachedProjection = 0; } KisPaintDeviceSP KisOnionSkinCache::lodCapableDevice() const { return m_d->cachedProjection; } diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index 9ec10f497a..129aaf8ae7 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2235 +1,2241 @@ /* * 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; class DeviceChangeProfileCommand; class DeviceChangeColorSpaceCommand; 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); void convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand); bool assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); 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(q, 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(q, 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(q, it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(q, rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); - Q_ASSERT(fastBitBltPossible(src)); + KIS_SAFE_ASSERT_RECOVER_NOOP(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(q, 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(q, srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(q, 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(q, 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(q, 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); - + /** + * The result of currentData() depends on the current + * level of detail and animation frame index. So we + * should first connect the device to the new + * default bounds object, and only after that ask + * currentData() to start cloning. + */ q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); + + currentData()->prepareClone(srcData); } 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(q, 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::updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod) { if (originalRect.isEmpty()) return; 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 = 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(), 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(q, 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); } class KisPaintDevice::Private::DeviceChangeProfileCommand : public KUndo2Command { public: DeviceChangeProfileCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) : KUndo2Command(parent), m_firstRun(true), m_device(device) { } virtual void emitNotifications() { m_device->emitProfileChanged(); } void redo() override { if (m_firstRun) { m_firstRun = false; return; } KUndo2Command::redo(); emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } protected: KisPaintDeviceSP m_device; private: bool m_firstRun; }; class KisPaintDevice::Private::DeviceChangeColorSpaceCommand : public DeviceChangeProfileCommand { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) : DeviceChangeProfileCommand(device, parent) { } void emitNotifications() override { m_device->emitColorSpaceChanged(); } }; void KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { QList dataObjects = allDataObjects(); if (dataObjects.isEmpty()) return; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, mainCommand); } q->emitColorSpaceChanged(); } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace, mainCommand); } 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) { makeFullCopyFrom(rhs, copyMode, newParentNode); } } void KisPaintDevice::makeFullCopyFrom(const KisPaintDevice &rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) { // 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()); } void KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } bool KisPaintDevice::setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { return m_d->assignProfile(profile, parentCommand); } 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 (const 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 (const 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(); const QRect r = selection->selectedExactRect(); 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(); } } // purge() must be executed **after** all iterators have been destroyed! 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 { if (m_d->contentChannel) { return m_d->contentChannel.data(); } return 0; } 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_layer.cc b/libs/image/kis_paint_layer.cc index ba3210a97f..5005396950 100644 --- a/libs/image/kis_paint_layer.cc +++ b/libs/image/kis_paint_layer.cc @@ -1,347 +1,368 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2006 Bart Coppens * Copyright (c) 2007 Boudewijn Rempt * 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_paint_layer.h" #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_default_bounds.h" #include "kis_onion_skin_compositor.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_auto_connection.h" #include "kis_layer_properties_icons.h" #include "kis_onion_skin_cache.h" struct Q_DECL_HIDDEN KisPaintLayer::Private { public: Private() : contentChannel(0) {} KisPaintDeviceSP paintDevice; QBitArray paintChannelFlags; // the real pointer is owned by the paint device KisRasterKeyframeChannel *contentChannel; KisSignalAutoConnectionsStore onionSkinConnection; KisOnionSkinCache onionSkinCache; + + bool onionSkinVisibleOverride = true; }; KisPaintLayer::KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, KisPaintDeviceSP dev) : KisLayer(image, name, opacity) , m_d(new Private()) { Q_ASSERT(dev); init(dev); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image)); } KisPaintLayer::KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity) : KisLayer(image, name, opacity) , m_d(new Private()) { Q_ASSERT(image); init(new KisPaintDevice(this, image->colorSpace(), new KisDefaultBounds(image))); } KisPaintLayer::KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, const KoColorSpace * colorSpace) : KisLayer(image, name, opacity) , m_d(new Private()) { if (!colorSpace) { Q_ASSERT(image); colorSpace = image->colorSpace(); } Q_ASSERT(colorSpace); init(new KisPaintDevice(this, colorSpace, new KisDefaultBounds(image))); } KisPaintLayer::KisPaintLayer(const KisPaintLayer& rhs) : KisLayer(rhs) , KisIndirectPaintingSupport() , m_d(new Private) { const bool copyFrames = (rhs.m_d->contentChannel != 0); if (!copyFrames) { init(new KisPaintDevice(*rhs.m_d->paintDevice.data()), rhs.m_d->paintChannelFlags); } else { init(new KisPaintDevice(*rhs.m_d->paintDevice.data(), KritaUtils::CopyAllFrames), rhs.m_d->paintChannelFlags); m_d->contentChannel = m_d->paintDevice->keyframeChannel(); addKeyframeChannel(m_d->contentChannel); m_d->contentChannel->setOnionSkinsEnabled(rhs.onionSkinEnabled()); KisLayer::enableAnimation(); } } void KisPaintLayer::init(KisPaintDeviceSP paintDevice, const QBitArray &paintChannelFlags) { m_d->paintDevice = paintDevice; m_d->paintDevice->setParentNode(this); m_d->paintChannelFlags = paintChannelFlags; } KisPaintLayer::~KisPaintLayer() { delete m_d; } bool KisPaintLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisPaintLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisPaintLayer::paintDevice() const { return m_d->paintDevice; } bool KisPaintLayer::needProjection() const { return hasTemporaryTarget() || (isAnimated() && onionSkinEnabled()); } void KisPaintLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisIndirectPaintingSupport::ReadLocker l(this); KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); if (hasTemporaryTarget()) { KisPainter gc(projection); setupTemporaryPainter(&gc); gc.bitBlt(rect.topLeft(), temporaryTarget(), rect); } if (m_d->contentChannel && m_d->contentChannel->keyframeCount() > 1 && onionSkinEnabled() && + m_d->onionSkinVisibleOverride && !m_d->paintDevice->defaultBounds()->externalFrameActive()) { KisPaintDeviceSP skins = m_d->onionSkinCache.projection(m_d->paintDevice); KisPainter gcDest(projection); gcDest.setCompositeOp(m_d->paintDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND)); gcDest.bitBlt(rect.topLeft(), skins, rect); gcDest.end(); } if (!m_d->contentChannel || (m_d->contentChannel->keyframeCount() <= 1) || !onionSkinEnabled()) { m_d->onionSkinCache.reset(); } } QIcon KisPaintLayer::icon() const { return KisIconUtils::loadIcon("paintLayer"); } void KisPaintLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image)); KisLayer::setImage(image); } KisBaseNode::PropertyList KisPaintLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisLayer::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::alphaLocked, alphaLocked()); if (isAnimated()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, onionSkinEnabled()); } return l; } void KisPaintLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.name == i18n("Alpha Locked")) { setAlphaLocked(property.state.toBool()); } else if (property.name == i18n("Onion Skins")) { setOnionSkinEnabled(property.state.toBool()); } } KisLayer::setSectionModelProperties(properties); } const KoColorSpace * KisPaintLayer::colorSpace() const { return m_d->paintDevice->colorSpace(); } bool KisPaintLayer::accept(KisNodeVisitor &v) { return v.visit(this); } void KisPaintLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } void KisPaintLayer::setChannelLockFlags(const QBitArray& channelFlags) { Q_ASSERT(((quint32)channelFlags.count() == colorSpace()->channelCount() || channelFlags.isEmpty())); m_d->paintChannelFlags = channelFlags; } const QBitArray& KisPaintLayer::channelLockFlags() const { return m_d->paintChannelFlags; } QRect KisPaintLayer::extent() const { KisPaintDeviceSP t = temporaryTarget(); QRect rect = t ? t->extent() : QRect(); - if (onionSkinEnabled()) rect |= KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice); + if (onionSkinEnabled() && m_d->onionSkinVisibleOverride) rect |= KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice); return rect | KisLayer::extent(); } QRect KisPaintLayer::exactBounds() const { KisPaintDeviceSP t = temporaryTarget(); QRect rect = t ? t->extent() : QRect(); - if (onionSkinEnabled()) rect |= KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice); + if (onionSkinEnabled() && m_d->onionSkinVisibleOverride) rect |= KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice); return rect | KisLayer::exactBounds(); } bool KisPaintLayer::alphaLocked() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->paintChannelFlags; return flags.count(true) == 0 && !m_d->paintChannelFlags.isEmpty(); } void KisPaintLayer::setAlphaLocked(bool lock) { if(m_d->paintChannelFlags.isEmpty()) m_d->paintChannelFlags = colorSpace()->channelFlags(true, true); if(lock) m_d->paintChannelFlags &= colorSpace()->channelFlags(true, false); else m_d->paintChannelFlags |= colorSpace()->channelFlags(false, true); baseNodeChangedCallback(); } bool KisPaintLayer::onionSkinEnabled() const { return nodeProperties().boolProperty("onionskin", false); } void KisPaintLayer::setOnionSkinEnabled(bool state) { int oldState = onionSkinEnabled(); if (oldState == state) return; if (state == false && oldState) { // FIXME: change ordering! race condition possible! // Turning off onionskins shrinks our extent. Let's clean up the onion skins first setDirty(KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice)); } if (state) { m_d->onionSkinConnection.addConnection(KisOnionSkinCompositor::instance(), SIGNAL(sigOnionSkinChanged()), this, SLOT(slotExternalUpdateOnionSkins())); } else { m_d->onionSkinConnection.clear(); } if (m_d->contentChannel) { m_d->contentChannel->setOnionSkinsEnabled(state); } setNodeProperty("onionskin", state); } void KisPaintLayer::slotExternalUpdateOnionSkins() { if (!onionSkinEnabled()) return; const QRect dirtyRect = KisOnionSkinCompositor::instance()->calculateFullExtent(m_d->paintDevice); setDirty(dirtyRect); } KisKeyframeChannel *KisPaintLayer::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { m_d->contentChannel = m_d->paintDevice->createKeyframeChannel(KisKeyframeChannel::Content); m_d->contentChannel->setOnionSkinsEnabled(onionSkinEnabled()); enableAnimation(); return m_d->contentChannel; } return KisLayer::requestKeyframeChannel(id); } KisPaintDeviceList KisPaintLayer::getLodCapableDevices() const { KisPaintDeviceList list = KisLayer::getLodCapableDevices(); KisPaintDeviceSP onionSkinsDevice = m_d->onionSkinCache.lodCapableDevice(); if (onionSkinsDevice) { list << onionSkinsDevice; } return list; } + +bool KisPaintLayer::decorationsVisible() const +{ + return m_d->onionSkinVisibleOverride; +} + +void KisPaintLayer::setDecorationsVisible(bool value, bool update) +{ + if (value == decorationsVisible()) return; + + const QRect oldExtent = extent(); + + m_d->onionSkinVisibleOverride = value; + + if (update && onionSkinEnabled()) { + setDirty(oldExtent | extent()); + } +} diff --git a/libs/image/kis_paint_layer.h b/libs/image/kis_paint_layer.h index d35673034c..ef442389e6 100644 --- a/libs/image/kis_paint_layer.h +++ b/libs/image/kis_paint_layer.h @@ -1,177 +1,182 @@ /* * Copyright (c) 2005 C. Boemann * 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_PAINT_LAYER_H_ #define KIS_PAINT_LAYER_H_ #include "kis_types.h" #include "kis_layer.h" #include "kis_indirect_painting_support.h" +#include "KisDecoratedNodeInterface.h" #include class KoColorSpace; /** * This layer is of a type that can be drawn on. A paint layer can * have any number of effect masks, a transparency mask, a local * selection and a protection mask. * * The protection mask can be read/write, read-only or write-only. * The transparency mask has two rendering forms: as a selection mask * and by changing the transparency of the paint layer's pixels. */ -class KRITAIMAGE_EXPORT KisPaintLayer : public KisLayer, public KisIndirectPaintingSupport +class KRITAIMAGE_EXPORT KisPaintLayer : public KisLayer, public KisIndirectPaintingSupport, public KisDecoratedNodeInterface { Q_OBJECT public: /** * Construct a paint layer with the given parameters. The default bounds of the paintdevice are overwritten. * @param image this layer belongs to, or null, if it shouldn't belong to any image * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 * @param dev is the paint device, that should be used */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, KisPaintDeviceSP dev); /** * Construct a paint layer with the given parameters * @param image this layer belongs to. it must not be null and it must have a valid color space. * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity); /** * Construct a paint layer with the given parameters * @param image this layer belongs to, or null, if it shouldn't belong to any image. image must not be null, if colorSpace is null * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 * @param colorSpace is the color space, that should be used to construct the paint device. it can be null, if the image is valid. */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, const KoColorSpace * colorSpace); /** * Copy Constructor */ KisPaintLayer(const KisPaintLayer& rhs); ~KisPaintLayer() override; KisNodeSP clone() const override { return KisNodeSP(new KisPaintLayer(*this)); } bool allowAsChild(KisNodeSP) const override; const KoColorSpace * colorSpace() const override; bool needProjection() const override; QIcon icon() const override; void setImage(KisImageWSP image) override; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; public: QRect extent() const override; QRect exactBounds() const override; bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; /** * set the channelflags for locking certain channels (used by painting tools) * for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ void setChannelLockFlags(const QBitArray& channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is locked or not (used by painting tools). * If the channelflags bit array is empty, all channels are active. */ const QBitArray& channelLockFlags() const; /** * Returns the paintDevice that accompanies this layer */ KisPaintDeviceSP paintDevice() const override; /** * Returns the original pixels before masks have been applied. */ KisPaintDeviceSP original() const override; /** * @returns true when painting should not affect the alpha channel */ bool alphaLocked() const; /** * @param l if true, the alpha channel will be protected from modification */ void setAlphaLocked(bool lock); /** * @return true if onion skins should be rendered on this layer */ bool onionSkinEnabled() const; /** * @param state whether onion skins should be rendered */ void setOnionSkinEnabled(bool state); KisPaintDeviceList getLodCapableDevices() const override; + bool decorationsVisible() const override; + void setDecorationsVisible(bool value, bool update) override; + using KisDecoratedNodeInterface::setDecorationsVisible; + public Q_SLOTS: void slotExternalUpdateOnionSkins(); public: // KisIndirectPaintingSupport KisLayer* layer() { return this; } protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const override; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; private: void init(KisPaintDeviceSP paintDevice, const QBitArray &paintChannelFlags = QBitArray()); struct Private; Private * const m_d; }; typedef KisSharedPtr KisPaintLayerSP; #endif // KIS_PAINT_LAYER_H_ diff --git a/libs/image/kis_selection.cc b/libs/image/kis_selection.cc index f2ca048515..59dc9c3bd8 100644 --- a/libs/image/kis_selection.cc +++ b/libs/image/kis_selection.cc @@ -1,356 +1,361 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * * 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.h" #include "kundo2command.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_node_graph_listener.h" #include "kis_node.h" #include "kis_image.h" #include "kis_default_bounds.h" #include "kis_iterator_ng.h" #include "KisLazyStorage.h" #include "KisSelectionUpdateCompressor.h" struct Q_DECL_HIDDEN KisSelection::Private { Private(KisSelection *q) : isVisible(true), shapeSelection(0), updateCompressor(q) { } // used for forwarding setDirty signals only KisNodeWSP parentNode; bool isVisible; //false is the selection decoration should not be displayed KisDefaultBoundsBaseSP defaultBounds; KisPixelSelectionSP pixelSelection; KisSelectionComponent *shapeSelection; KisLazyStorage updateCompressor; }; KisSelection::KisSelection(KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private(this)) { if (!defaultBounds) { defaultBounds = new KisSelectionEmptyBounds(0); } m_d->defaultBounds = defaultBounds; m_d->pixelSelection = new KisPixelSelection(m_d->defaultBounds, this); m_d->pixelSelection->setParentNode(m_d->parentNode); } KisSelection::KisSelection(const KisSelection& rhs) : KisShared(), m_d(new Private(this)) { copyFrom(rhs); } KisSelection::KisSelection(const KisPaintDeviceSP source, KritaUtils::DeviceCopyMode copyMode, KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private(this)) { if (!defaultBounds) { defaultBounds = new KisSelectionEmptyBounds(0); } m_d->defaultBounds = defaultBounds; m_d->pixelSelection = new KisPixelSelection(source, copyMode); m_d->pixelSelection->setParentSelection(this); m_d->pixelSelection->setParentNode(m_d->parentNode); m_d->pixelSelection->setDefaultBounds(m_d->defaultBounds); } KisSelection &KisSelection::operator=(const KisSelection &rhs) { if (&rhs != this) { copyFrom(rhs); } return *this; } void KisSelection::copyFrom(const KisSelection &rhs) { m_d->isVisible = rhs.m_d->isVisible; m_d->defaultBounds = rhs.m_d->defaultBounds; m_d->parentNode = 0; // not supposed to be shared Q_ASSERT(rhs.m_d->pixelSelection); m_d->pixelSelection = new KisPixelSelection(*rhs.m_d->pixelSelection, KritaUtils::CopyAllFrames); m_d->pixelSelection->setParentSelection(this); if (rhs.m_d->shapeSelection) { m_d->shapeSelection = rhs.m_d->shapeSelection->clone(this); Q_ASSERT(m_d->shapeSelection); Q_ASSERT(m_d->shapeSelection != rhs.m_d->shapeSelection); } else { m_d->shapeSelection = 0; } } KisSelection::~KisSelection() { delete m_d->shapeSelection; delete m_d; } void KisSelection::setParentNode(KisNodeWSP node) { m_d->parentNode = node; m_d->pixelSelection->setParentNode(node); // the updates come through the parent image, so all the updates // that happened in the meantime are considered "stalled" if (node) { m_d->updateCompressor->tryProcessStalledUpdate(); } } // for testing purposes only KisNodeWSP KisSelection::parentNode() const { return m_d->parentNode; } bool KisSelection::outlineCacheValid() const { return hasShapeSelection() || m_d->pixelSelection->outlineCacheValid(); } QPainterPath KisSelection::outlineCache() const { QPainterPath outline; if (hasShapeSelection()) { outline += m_d->shapeSelection->outlineCache(); } else if (m_d->pixelSelection->outlineCacheValid()) { outline += m_d->pixelSelection->outlineCache(); } return outline; } void KisSelection::recalculateOutlineCache() { Q_ASSERT(m_d->pixelSelection); if (hasShapeSelection()) { m_d->shapeSelection->recalculateOutlineCache(); } else if (!m_d->pixelSelection->outlineCacheValid()) { m_d->pixelSelection->recalculateOutlineCache(); } } bool KisSelection::thumbnailImageValid() const { return m_d->pixelSelection->thumbnailImageValid(); } void KisSelection::recalculateThumbnailImage(const QColor &maskColor) { m_d->pixelSelection->recalculateThumbnailImage(maskColor); } QImage KisSelection::thumbnailImage() const { return m_d->pixelSelection->thumbnailImage(); } QTransform KisSelection::thumbnailImageTransform() const { return m_d->pixelSelection->thumbnailImageTransform(); } bool KisSelection::hasPixelSelection() const { return m_d->pixelSelection && !m_d->pixelSelection->isEmpty(); } bool KisSelection::hasShapeSelection() const { return m_d->shapeSelection && !m_d->shapeSelection->isEmpty(); } KisPixelSelectionSP KisSelection::pixelSelection() const { return m_d->pixelSelection; } KisSelectionComponent* KisSelection::shapeSelection() const { return m_d->shapeSelection; } void KisSelection::setShapeSelection(KisSelectionComponent* shapeSelection) { const bool needsNotification = shapeSelection != m_d->shapeSelection; m_d->shapeSelection = shapeSelection; if (needsNotification) { requestCompressedProjectionUpdate(QRect()); } } KisPixelSelectionSP KisSelection::projection() const { return m_d->pixelSelection; } void KisSelection::updateProjection(const QRect &rc) { if(hasShapeSelection()) { m_d->shapeSelection->renderToProjection(m_d->pixelSelection, rc); m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache()); } } void KisSelection::updateProjection() { if(hasShapeSelection()) { m_d->pixelSelection->clear(); m_d->shapeSelection->renderToProjection(m_d->pixelSelection); m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache()); } } void KisSelection::setVisible(bool visible) { bool needsNotification = visible != m_d->isVisible; m_d->isVisible = visible; if (needsNotification) { notifySelectionChanged(); } } bool KisSelection::isVisible() { return m_d->isVisible; } bool KisSelection::isTotallyUnselected(const QRect & r) const { return m_d->pixelSelection->isTotallyUnselected(r); } QRect KisSelection::selectedRect() const { return m_d->pixelSelection->selectedRect(); } QRect KisSelection::selectedExactRect() const { return m_d->pixelSelection->selectedExactRect(); } qint32 KisSelection::x() const { return m_d->pixelSelection->x(); } qint32 KisSelection::y() const { return m_d->pixelSelection->y(); } void KisSelection::setX(qint32 x) { Q_ASSERT(m_d->pixelSelection); qint32 delta = x - m_d->pixelSelection->x(); m_d->pixelSelection->setX(x); if (m_d->shapeSelection) { m_d->shapeSelection->moveX(delta); } } void KisSelection::setY(qint32 y) { Q_ASSERT(m_d->pixelSelection); qint32 delta = y - m_d->pixelSelection->y(); m_d->pixelSelection->setY(y); if (m_d->shapeSelection) { m_d->shapeSelection->moveY(delta); } } void KisSelection::setDefaultBounds(KisDefaultBoundsBaseSP bounds) { m_d->defaultBounds = bounds; m_d->pixelSelection->setDefaultBounds(bounds); } void KisSelection::clear() { // FIXME: check whether this is safe delete m_d->shapeSelection; m_d->shapeSelection = 0; m_d->pixelSelection->clear(); } KUndo2Command* KisSelection::flatten() { KUndo2Command *command = 0; if (hasShapeSelection()) { command = m_d->shapeSelection->resetToEmpty(); } return command; } void KisSelection::notifySelectionChanged() { KisNodeWSP parentNode; if (!(parentNode = this->parentNode())) return; KisNodeGraphListener *listener; if (!(listener = parentNode->graphListener())) return; listener->notifySelectionChanged(); } void KisSelection::requestCompressedProjectionUpdate(const QRect &rc) { m_d->updateCompressor->requestUpdate(rc); } +void KisSelection::notifyShapeSelectionBecameEmpty() +{ + m_d->pixelSelection->clear(); +} + quint8 KisSelection::selected(qint32 x, qint32 y) const { KisHLineConstIteratorSP iter = m_d->pixelSelection->createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->oldRawData(); return *pix; } diff --git a/libs/image/kis_selection.h b/libs/image/kis_selection.h index cfbe1c0b43..d8908185a1 100644 --- a/libs/image/kis_selection.h +++ b/libs/image/kis_selection.h @@ -1,225 +1,227 @@ /* * 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. */ #ifndef KIS_SELECTION_H_ #define KIS_SELECTION_H_ #include #include "kis_types.h" #include "kritaimage_export.h" #include "kis_default_bounds.h" #include "kis_image.h" #include "KisSelectionTags.h" #include "kis_pixel_selection.h" class KisSelectionComponent; class QPainterPath; /** * KisSelection is a composite object. It may contain an instance * of KisPixelSelection and a KisShapeSelection object. Both these * selections are merged into a projection of the KisSelection. * * Every pixel in the paint device can indicate a degree of selectedness, varying * between MIN_SELECTED and MAX_SELECTED. * * The projection() paint device itself is only a projection: you can * read from it, but not write to it. You need to keep track of * the need for updating the projection yourself: there is no * automatic updating after changing the contents of one or more * of the selection components. */ class KRITAIMAGE_EXPORT KisSelection : public KisShared { public: /** * Create a new KisSelection. * * @param defaultBounds defines the bounds of the selection when * Select All is initiated. */ KisSelection(KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP()); /** * Copy the selection. The selection components are copied, too. */ KisSelection(const KisSelection& rhs); KisSelection& operator=(const KisSelection &rhs); /** * Delete the selection. The shape selection component is deleted, the * pixel selection component is contained in a shared pointer, so that * may still be valid. */ virtual ~KisSelection(); /** * Create a new selection using the content of copySource as the mask. */ KisSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode, KisDefaultBoundsBaseSP defaultBounds); /** * The paint device of the pixel selection should report * about it's setDirty events to its parent. The creator * should set the parent manually if it wants to get the * signals */ void setParentNode(KisNodeWSP node); bool hasPixelSelection() const; bool hasShapeSelection() const; bool outlineCacheValid() const; QPainterPath outlineCache() const; void recalculateOutlineCache(); /** * Tells whether the cached thumbnail of the selection is still valid */ bool thumbnailImageValid() const; /** * Recalculates the thumbnail of the selection */ void recalculateThumbnailImage(const QColor &maskColor); /** * Returns the thumbnail of the selection. */ QImage thumbnailImage() const; /** * Returns the transformation which should be applied to the thumbnail before * being painted over the image */ QTransform thumbnailImageTransform() const; /** * return the pixel selection component of this selection. Pixel * selection component is always present in the selection. In case * the user wants a vector selection, pixel selection will store * the pixelated version of it. * * NOTE: use pixelSelection() for changing the selection only. For * reading the selection and passing the data to bitBlt function use * projection(). Although projection() and pixelSelection() currently * point ot the same paint device, this behavior may change in the * future. */ KisPixelSelectionSP pixelSelection() const; /** * return the vector selection component of this selection or zero * if hasShapeSelection() returns false. */ KisSelectionComponent* shapeSelection() const; void setShapeSelection(KisSelectionComponent* shapeSelection); /** * Returns the projection of the selection. It may be the same * as pixel selection. You must read selection data from this * paint device only */ KisPixelSelectionSP projection() const; /** * Updates the projection of the selection. You should call this * method after the every change of the selection components. * There is no automatic updates framework present */ void updateProjection(const QRect& rect); void updateProjection(); void setVisible(bool visible); bool isVisible(); /** * Convenience functions. Just call the corresponding methods * of the underlying projection */ bool isTotallyUnselected(const QRect & r) const; QRect selectedRect() const; /** * @brief Slow, but exact way of determining the rectangle * that encloses the selection. * * Default pixel of the selection device may vary and you would get wrong bounds. * selectedExactRect() handles all these cases. * */ QRect selectedExactRect() const; void setX(qint32 x); void setY(qint32 y); qint32 x() const; qint32 y() const; void setDefaultBounds(KisDefaultBoundsBaseSP bounds); void clear(); /** * @brief flatten creates a new pixel selection component from the shape selection * and throws away the shape selection. This has no effect if there is no * shape selection. */ KUndo2Command* flatten(); void notifySelectionChanged(); /** * Request rerendering of the shape selection component in a * compressed way. Usually, you don't need to call it manually, * because all the work is done by KisShapeSelectionModel. */ void requestCompressedProjectionUpdate(const QRect &rc); + void notifyShapeSelectionBecameEmpty(); + /// XXX: This method was marked KDE_DEPRECATED but without information on what to /// replace it with. Undeprecate, therefore. quint8 selected(qint32 x, qint32 y) const; KisNodeWSP parentNode() const; private: friend class KisSelectionTest; friend class KisMaskTest; friend class KisAdjustmentLayerTest; friend class KisUpdateSelectionJob; friend class KisSelectionUpdateCompressor; friend class KisDeselectActiveSelectionCommand; void copyFrom(const KisSelection &rhs); private: struct Private; Private * const m_d; }; #endif // KIS_SELECTION_H_ diff --git a/libs/image/kis_selection_mask.cpp b/libs/image/kis_selection_mask.cpp index a735460946..4428398528 100644 --- a/libs/image/kis_selection_mask.cpp +++ b/libs/image/kis_selection_mask.cpp @@ -1,327 +1,345 @@ /* * 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. */ #include "kis_selection_mask.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_selection.h" #include #include #include #include "kis_fill_painter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_pixel_selection.h" #include "kis_undo_adapter.h" #include #include #include "kis_thread_safe_signal_compressor.h" #include "kis_layer_properties_icons.h" #include "kis_cached_paint_device.h" #include "kis_image_config.h" #include "KisImageConfigNotifier.h" struct Q_DECL_HIDDEN KisSelectionMask::Private { public: Private(KisSelectionMask *_q) : q(_q) , updatesCompressor(0) , maskColor(Qt::green, KoColorSpaceRegistry::instance()->rgb8()) {} KisSelectionMask *q; KisImageWSP image; KisCachedPaintDevice paintDeviceCache; KisCachedSelection cachedSelection; KisThreadSafeSignalCompressor *updatesCompressor; KoColor maskColor; void slotSelectionChangedCompressed(); void slotConfigChangedImpl(bool blockUpdates); void slotConfigChanged(); }; KisSelectionMask::KisSelectionMask(KisImageWSP image) : KisEffectMask() , m_d(new Private(this)) { setName("selection"); setActive(false); setSupportsLodMoves(false); m_d->image = image; m_d->updatesCompressor = new KisThreadSafeSignalCompressor(50, KisSignalCompressor::FIRST_ACTIVE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(image->thread()); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); m_d->slotConfigChangedImpl(false); } KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs) : KisEffectMask(rhs) , m_d(new Private(this)) { m_d->image = rhs.image(); m_d->updatesCompressor = new KisThreadSafeSignalCompressor(300, KisSignalCompressor::POSTPONE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(m_d->image->thread()); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); m_d->slotConfigChangedImpl(false); } KisSelectionMask::~KisSelectionMask() { m_d->updatesCompressor->deleteLater(); delete m_d; } QIcon KisSelectionMask::icon() const { return KisIconUtils::loadIcon("selectionMask"); } void KisSelectionMask::mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, KisNode::PositionToFilthy maskPos) const { Q_UNUSED(maskPos); Q_UNUSED(preparedNeedRect); if (!effectiveSelection) return; { KisSelectionSP mainMaskSelection = this->selection(); if (mainMaskSelection && (!mainMaskSelection->isVisible() || mainMaskSelection->pixelSelection()->defaultBounds()->externalFrameActive())) { return; } } KisCachedPaintDevice::Guard d1(projection, m_d->paintDeviceCache); KisPaintDeviceSP fillDevice = d1.device(); fillDevice->setDefaultPixel(m_d->maskColor); const QRect selectionExtent = effectiveSelection->selectedRect(); if (selectionExtent.contains(applyRect) || selectionExtent.intersects(applyRect)) { KisCachedSelection::Guard s1(m_d->cachedSelection); KisSelectionSP invertedSelection = s1.selection(); invertedSelection->pixelSelection()->makeCloneFromRough(effectiveSelection->pixelSelection(), applyRect); invertedSelection->pixelSelection()->invert(); KisPainter gc(projection); gc.setSelection(invertedSelection); gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); } else { KisPainter gc(projection); gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); } } bool KisSelectionMask::paintsOutsideSelection() const { return true; } void KisSelectionMask::setSelection(KisSelectionSP selection) { if (selection) { KisEffectMask::setSelection(selection); } else { KisEffectMask::setSelection(new KisSelection()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->alpha8(); KisFillPainter gc(KisPaintDeviceSP(this->selection()->pixelSelection().data())); gc.fillRect(image()->bounds(), KoColor(Qt::white, cs), MAX_SELECTED); gc.end(); } setDirty(); } KisImageWSP KisSelectionMask::image() const { return m_d->image; } bool KisSelectionMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisSelectionMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KisBaseNode::PropertyList KisSelectionMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::selectionActive, active()); return l; } void KisSelectionMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisEffectMask::setSectionModelProperties(properties); setActive(properties.at(2).state.toBool()); } void KisSelectionMask::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); setNodeProperty("visible", visible); if (!isLoading && visible != oldVisible) { if (selection()) selection()->setVisible(visible); } } bool KisSelectionMask::active() const { return nodeProperties().boolProperty("active", true); } void KisSelectionMask::setActive(bool active) { KisImageWSP image = this->image(); KisLayerSP parentLayer = qobject_cast(parent().data()); if (active && parentLayer) { KisSelectionMaskSP activeMask = parentLayer->selectionMask(); if (activeMask && activeMask != this) { activeMask->setActive(false); } } const bool oldActive = this->active(); setNodeProperty("active", active); /** * WARNING: we have a direct link to the image here, but we * must not use it for notification until we are a part of * the nore graph! Notifications should be emitted iff we * have graph listener link set up. */ if (graphListener() && image && oldActive != active) { baseNodeChangedCallback(); image->undoAdapter()->emitSelectionChanged(); } } QRect KisSelectionMask::needRect(const QRect &rect, KisNode::PositionToFilthy pos) const { Q_UNUSED(pos); // selection masks just add an overlay, so the needed rect is simply passed through return rect; } QRect KisSelectionMask::changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const { Q_UNUSED(pos); // selection masks just add an overlay, so the changed rect is simply passed through return rect; } QRect KisSelectionMask::extent() const { // since mask overlay is inverted, the mask paints over // the entire image bounds QRect resultRect; KisSelectionSP selection = this->selection(); if (selection) { resultRect = selection->pixelSelection()->defaultBounds()->bounds(); } else if (KisNodeSP parent = this->parent()) { KisPaintDeviceSP dev = parent->projection(); if (dev) { resultRect = dev->defaultBounds()->bounds(); } } return resultRect; } QRect KisSelectionMask::exactBounds() const { return extent(); } void KisSelectionMask::notifySelectionChangedCompressed() { m_d->updatesCompressor->start(); } +bool KisSelectionMask::decorationsVisible() const +{ + return selection()->isVisible(); +} + +void KisSelectionMask::setDecorationsVisible(bool value, bool update) +{ + if (value == decorationsVisible()) return; + + const QRect oldExtent = extent(); + + selection()->setVisible(value); + + if (update) { + setDirty(oldExtent | extent()); + } +} + void KisSelectionMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const { Q_UNUSED(selection); Q_UNUSED(dirtyRect); } void KisSelectionMask::Private::slotSelectionChangedCompressed() { KisSelectionSP currentSelection = q->selection(); if (!currentSelection) return; currentSelection->notifySelectionChanged(); } void KisSelectionMask::Private::slotConfigChangedImpl(bool doUpdates) { const KoColorSpace *cs = image ? image->colorSpace() : KoColorSpaceRegistry::instance()->rgb8(); KisImageConfig cfg(true); maskColor = KoColor(cfg.selectionOverlayMaskColor(), cs); if (doUpdates && image && image->overlaySelectionMask() == q) { q->setDirty(); } } void KisSelectionMask::Private::slotConfigChanged() { slotConfigChangedImpl(true); } #include "moc_kis_selection_mask.cpp" diff --git a/libs/image/kis_selection_mask.h b/libs/image/kis_selection_mask.h index 9fd6467cac..fc479a21a6 100644 --- a/libs/image/kis_selection_mask.h +++ b/libs/image/kis_selection_mask.h @@ -1,100 +1,105 @@ /* * 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_SELECTION_MASK_ #define _KIS_SELECTION_MASK_ #include #include "kis_base_node.h" #include "kis_types.h" #include "kis_effect_mask.h" +#include "KisDecoratedNodeInterface.h" /** * An selection mask is a single channel mask that applies a * particular selection to the layer the mask belongs to. A selection * can contain both vector and pixel selection components. */ -class KRITAIMAGE_EXPORT KisSelectionMask : public KisEffectMask +class KRITAIMAGE_EXPORT KisSelectionMask : public KisEffectMask, public KisDecoratedNodeInterface { Q_OBJECT public: /** * Create an empty selection mask. There is filter and no layer * associated with this mask. */ KisSelectionMask(KisImageWSP image); ~KisSelectionMask() override; KisSelectionMask(const KisSelectionMask& rhs); QIcon icon() const override; KisNodeSP clone() const override { return KisNodeSP(new KisSelectionMask(*this)); } /// Set the selection of this adjustment layer to a copy of selection. void setSelection(KisSelectionSP selection); bool accept(KisNodeVisitor &v) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; void setVisible(bool visible, bool isLoading = false) override; bool active() const; void setActive(bool active); QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect extent() const override; QRect exactBounds() const override; /** * This method works like the one in KisSelection, but it * compressed the incoming events instead of processing each of * them separately. */ void notifySelectionChangedCompressed(); + bool decorationsVisible() const override; + void setDecorationsVisible(bool value, bool update) override; + using KisDecoratedNodeInterface::setDecorationsVisible; + protected: void flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const override; void mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, KisNode::PositionToFilthy maskPos) const override; bool paintsOutsideSelection() const override; private: Q_PRIVATE_SLOT(m_d, void slotSelectionChangedCompressed()) Q_PRIVATE_SLOT(m_d, void slotConfigChanged()) KisImageWSP image() const; struct Private; Private * const m_d; }; #endif //_KIS_SELECTION_MASK_ 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 edc1f35503..97797d7023 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp @@ -1,155 +1,165 @@ /* * 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" #include "KisLayerStyleKnockoutBlower.h" struct KisLayerStyleFilterProjectionPlane::Private { Private(KisLayer *_sourceLayer) : sourceLayer(_sourceLayer), environment(new KisLayerStyleFilterEnvironment(_sourceLayer)) { KIS_SAFE_ASSERT_RECOVER_NOOP(_sourceLayer); } Private(const Private &rhs, KisLayer *_sourceLayer, KisPSDLayerStyleSP clonedStyle) : sourceLayer(_sourceLayer), filter(rhs.filter ? rhs.filter->clone() : 0), style(clonedStyle), environment(new KisLayerStyleFilterEnvironment(_sourceLayer)), projection(rhs.projection) { KIS_SAFE_ASSERT_RECOVER_NOOP(_sourceLayer); } KisLayer *sourceLayer; QScopedPointer filter; KisPSDLayerStyleSP style; QScopedPointer environment; KisLayerStyleKnockoutBlower knockoutBlower; KisMultipleProjection projection; }; KisLayerStyleFilterProjectionPlane:: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer) : m_d(new Private(sourceLayer)) { } KisLayerStyleFilterProjectionPlane::KisLayerStyleFilterProjectionPlane(const KisLayerStyleFilterProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle) : m_d(new Private(*rhs.m_d, sourceLayer, clonedStyle)) { } 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, &m_d->knockoutBlower, 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->environment.data()); } KisPaintDeviceList KisLayerStyleFilterProjectionPlane::getLodCapableDevices() const { return m_d->projection.getLodCapableDevices(); } bool KisLayerStyleFilterProjectionPlane::isEmpty() const { return m_d->projection.isEmpty(); } KisLayerStyleKnockoutBlower *KisLayerStyleFilterProjectionPlane::knockoutBlower() const { return &m_d->knockoutBlower; } 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); } QRect KisLayerStyleFilterProjectionPlane::needRectForOriginal(const QRect &rect) const { return needRect(rect, KisLayer::N_ABOVE_FILTHY); } +QRect KisLayerStyleFilterProjectionPlane::tightUserVisibleBounds() const +{ + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->filter, QRect()); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->sourceLayer, QRect()); + + return m_d->filter->changedRect(m_d->sourceLayer->exactBounds(), + m_d->style, + m_d->environment.data()); +} + diff --git a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h index 71b6bb2e38..4ef37f3a40 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.h @@ -1,66 +1,67 @@ /* * 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_PROJECTION_PLANE_H #define __KIS_LAYER_STYLE_FILTER_PROJECTION_PLANE_H #include "kis_abstract_projection_plane.h" #include #include "kis_types.h" class KisLayerStyleKnockoutBlower; class KisLayerStyleFilterProjectionPlane : public KisAbstractProjectionPlane { public: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer); KisLayerStyleFilterProjectionPlane(const KisLayerStyleFilterProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle); ~KisLayerStyleFilterProjectionPlane() override; void setStyle(KisLayerStyleFilter *filter, KisPSDLayerStyleSP style); QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; + QRect tightUserVisibleBounds() const override; KisPaintDeviceList getLodCapableDevices() const override; /** * \returns true if a call to apply() will actually paint anything. Basically, * it is a cached version of isEnabled(), though the state may change after calling * to recalculate(). */ bool isEmpty() const; KisLayerStyleKnockoutBlower *knockoutBlower() const; private: struct Private; const QScopedPointer m_d; }; typedef QSharedPointer KisLayerStyleFilterProjectionPlaneSP; typedef QWeakPointer KisLayerStyleFilterProjectionPlaneWSP; #endif /* __KIS_LAYER_STYLE_FILTER_PROJECTION_PLANE_H */ diff --git a/libs/image/layerstyles/kis_layer_style_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_projection_plane.cpp index e951d9f134..48f3857b07 100644 --- a/libs/image/layerstyles/kis_layer_style_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_projection_plane.cpp @@ -1,414 +1,426 @@ /* * 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_projection_plane.h" #include "kis_global.h" #include "kis_layer_style_filter_projection_plane.h" #include "kis_layer_projection_plane.h" #include "kis_psd_layer_style.h" #include "kis_ls_drop_shadow_filter.h" #include "kis_ls_satin_filter.h" #include "kis_ls_overlay_filter.h" #include "kis_ls_stroke_filter.h" #include "kis_ls_bevel_emboss_filter.h" #include "kis_projection_leaf.h" #include "kis_cached_paint_device.h" #include "kis_painter.h" #include "kis_ls_utils.h" #include "KisLayerStyleKnockoutBlower.h" struct Q_DECL_HIDDEN KisLayerStyleProjectionPlane::Private { KisLayerProjectionPlaneWSP sourceProjectionPlane; QVector stylesBefore; QVector stylesAfter; QVector stylesOverlay; KisCachedPaintDevice cachedPaintDevice; KisCachedSelection cachedSelection; KisLayer *sourceLayer = 0; KisPSDLayerStyleSP style; bool canHaveChildNodes = false; bool dependsOnLowerNodes = false; void initSourcePlane(KisLayer *sourceLayer) { KIS_SAFE_ASSERT_RECOVER_RETURN(sourceLayer); sourceProjectionPlane = sourceLayer->internalProjectionPlane(); canHaveChildNodes = sourceLayer->projectionLeaf()->canHaveChildLayers(); dependsOnLowerNodes = sourceLayer->projectionLeaf()->dependsOnLowerNodes(); this->sourceLayer = sourceLayer; } QVector allStyles() const { return stylesBefore + stylesOverlay + stylesAfter; } bool hasOverlayStyles() const { Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesOverlay) { if (!plane->isEmpty()) return true; } return false; } bool hasKnockoutStyles() const { Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesBefore) { if (!plane->knockoutBlower()->isEmpty()) return true; } Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesAfter) { if (!plane->knockoutBlower()->isEmpty()) return true; } return false; } void applyComplexPlane(KisPainter *painter, KisLayerStyleFilterProjectionPlaneSP plane, const QRect &rect, KisPaintDeviceSP originalClone); }; KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(KisLayer *sourceLayer) : m_d(new Private) { KisPSDLayerStyleSP style = sourceLayer->layerStyle(); KIS_ASSERT_RECOVER(style) { style = toQShared(new KisPSDLayerStyle()); } init(sourceLayer, style); } KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(const KisLayerStyleProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle) : m_d(new Private) { m_d->initSourcePlane(sourceLayer); m_d->style = clonedStyle; KIS_SAFE_ASSERT_RECOVER(m_d->style) { m_d->style = toQShared(new KisPSDLayerStyle()); } Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->allStyles()) { m_d->stylesBefore << toQShared(new KisLayerStyleFilterProjectionPlane(*plane, sourceLayer, m_d->style)); } } // for testing purposes only! KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(KisLayer *sourceLayer, KisPSDLayerStyleSP layerStyle) : m_d(new Private) { init(sourceLayer, layerStyle); } void KisLayerStyleProjectionPlane::init(KisLayer *sourceLayer, KisPSDLayerStyleSP style) { KIS_SAFE_ASSERT_RECOVER_RETURN(sourceLayer); m_d->initSourcePlane(sourceLayer); m_d->style = style; { KisLayerStyleFilterProjectionPlane *dropShadow = new KisLayerStyleFilterProjectionPlane(sourceLayer); dropShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::DropShadow), style); m_d->stylesBefore << toQShared(dropShadow); } { KisLayerStyleFilterProjectionPlane *outerGlow = new KisLayerStyleFilterProjectionPlane(sourceLayer); outerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::OuterGlow), style); m_d->stylesAfter << toQShared(outerGlow); } { KisLayerStyleFilterProjectionPlane *stroke = new KisLayerStyleFilterProjectionPlane(sourceLayer); stroke->setStyle(new KisLsStrokeFilter(), style); m_d->stylesAfter << toQShared(stroke); } { KisLayerStyleFilterProjectionPlane *bevelEmboss = new KisLayerStyleFilterProjectionPlane(sourceLayer); bevelEmboss->setStyle(new KisLsBevelEmbossFilter(), style); m_d->stylesAfter << toQShared(bevelEmboss); } { KisLayerStyleFilterProjectionPlane *patternOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); patternOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Pattern), style); m_d->stylesOverlay << toQShared(patternOverlay); } { KisLayerStyleFilterProjectionPlane *gradientOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); gradientOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Gradient), style); m_d->stylesOverlay << toQShared(gradientOverlay); } { KisLayerStyleFilterProjectionPlane *colorOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); colorOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Color), style); m_d->stylesOverlay << toQShared(colorOverlay); } { KisLayerStyleFilterProjectionPlane *satin = new KisLayerStyleFilterProjectionPlane(sourceLayer); satin->setStyle(new KisLsSatinFilter(), style); m_d->stylesOverlay << toQShared(satin); } { KisLayerStyleFilterProjectionPlane *innerGlow = new KisLayerStyleFilterProjectionPlane(sourceLayer); innerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerGlow), style); m_d->stylesOverlay << toQShared(innerGlow); } { KisLayerStyleFilterProjectionPlane *innerShadow = new KisLayerStyleFilterProjectionPlane(sourceLayer); innerShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerShadow), style); m_d->stylesOverlay << toQShared(innerShadow); } } KisLayerStyleProjectionPlane::~KisLayerStyleProjectionPlane() { } KisAbstractProjectionPlaneSP KisLayerStyleProjectionPlane::factoryObject(KisLayer *sourceLayer) { Q_ASSERT(sourceLayer); return toQShared(new KisLayerStyleProjectionPlane(sourceLayer)); } QRect KisLayerStyleProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect result = rect; if (m_d->style->isEnabled()) { result = sourcePlane->recalculate(stylesNeedRect(rect), filthyNode); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { plane->recalculate(rect, filthyNode); } } else { result = sourcePlane->recalculate(rect, filthyNode); } return result; } void KisLayerStyleProjectionPlane::Private::applyComplexPlane(KisPainter *painter, KisLayerStyleFilterProjectionPlaneSP plane, const QRect &rect, KisPaintDeviceSP originalClone) { if (plane->isEmpty()) return; if (!plane->knockoutBlower()->isEmpty()) { KisCachedPaintDevice::Guard d1(originalClone, cachedPaintDevice); KisPaintDeviceSP mergedStyle = d1.device(); mergedStyle->makeCloneFromRough(originalClone, rect); KisPainter overlayPainter(mergedStyle); plane->apply(&overlayPainter, rect); plane->knockoutBlower()->apply(painter, mergedStyle, rect); } else { plane->apply(painter, rect); } } void KisLayerStyleProjectionPlane::apply(KisPainter *painter, const QRect &rect) { KisLayerProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); if (m_d->style->isEnabled()) { if (m_d->hasOverlayStyles() || m_d->hasKnockoutStyles()) { KisCachedPaintDevice::Guard d1(painter->device(), m_d->cachedPaintDevice); KisPaintDeviceSP originalClone = d1.device(); originalClone->makeCloneFromRough(painter->device(), rect); Q_FOREACH (const KisLayerStyleFilterProjectionPlaneSP plane, m_d->stylesBefore) { m_d->applyComplexPlane(painter, plane, rect, originalClone); } if (m_d->hasOverlayStyles()) { KisCachedSelection::Guard s1(m_d->cachedSelection); KisSelectionSP knockoutSelection = s1.selection(); KisLsUtils::selectionFromAlphaChannel(m_d->sourceLayer->projection(), knockoutSelection, rect); KisCachedPaintDevice::Guard d2(painter->device(), m_d->cachedPaintDevice); KisPaintDeviceSP sourceProjection = d2.device(); sourceProjection->makeCloneFromRough(painter->device(), rect); { KisPainter overlayPainter(sourceProjection); sourcePlane->applyMaxOutAlpha(&overlayPainter, rect); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesOverlay) { plane->apply(&overlayPainter, rect); } } KisLayerStyleKnockoutBlower blower; blower.setKnockoutSelection(knockoutSelection); blower.apply(painter, sourceProjection, rect); blower.resetKnockoutSelection(); } else { sourcePlane->apply(painter, rect); } Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, m_d->stylesAfter) { m_d->applyComplexPlane(painter, plane, rect, originalClone); } } else { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { plane->apply(painter, rect); } sourcePlane->apply(painter, rect); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { plane->apply(painter, rect); } } } else { sourcePlane->apply(painter, rect); } } KisPaintDeviceList KisLayerStyleProjectionPlane::getLodCapableDevices() const { KisPaintDeviceList list; KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); if (m_d->style->isEnabled()) { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { list << plane->getLodCapableDevices(); } list << sourcePlane->getLodCapableDevices(); } else { list << sourcePlane->getLodCapableDevices(); } return list; } QRect KisLayerStyleProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { /** * Need rect should also be adjust for the layers that generate their 'original' * based on the contents of the underlying layers like KisAdjustmentLayer * * \see bug 390299 */ QRect needRect = rect; const bool adjustmentAboveDirty = m_d->dependsOnLowerNodes && (pos & KisLayer::N_FILTHY || pos & KisLayer::N_ABOVE_FILTHY); if (m_d->style->isEnabled() && adjustmentAboveDirty) { needRect |= stylesNeedRect(rect); } KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); needRect = sourcePlane->needRect(needRect, pos); return needRect; } QRect KisLayerStyleProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect layerChangeRect = sourcePlane->changeRect(rect, pos); QRect changeRect = layerChangeRect; if (m_d->style->isEnabled()) { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { changeRect |= plane->changeRect(layerChangeRect, KisLayer::N_ABOVE_FILTHY); } } return changeRect; } QRect KisLayerStyleProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect accessRect = sourcePlane->accessRect(rect, pos); if (m_d->style->isEnabled()) { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { accessRect |= plane->accessRect(rect, KisLayer::N_ABOVE_FILTHY); } } return accessRect; } QRect KisLayerStyleProjectionPlane::needRectForOriginal(const QRect &rect) const { /** * Need rect should also be adjust for the layers that generate their 'original' * based on the contents of the child layers like KisGroupLayer * * \see bug 366419 */ QRect needRect = rect; if (m_d->style->isEnabled()) { needRect |= stylesNeedRect(needRect); } KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); needRect = sourcePlane->needRectForOriginal(needRect); return needRect; } +QRect KisLayerStyleProjectionPlane::tightUserVisibleBounds() const +{ + KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); + QRect rect = sourcePlane->tightUserVisibleBounds(); + + Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { + rect |= plane->tightUserVisibleBounds(); + } + + return rect; +} + QRect KisLayerStyleProjectionPlane::stylesNeedRect(const QRect &rect) const { QRect needRect = rect; Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { needRect |= plane->needRect(rect, KisLayer::N_ABOVE_FILTHY); } return needRect; } diff --git a/libs/image/layerstyles/kis_layer_style_projection_plane.h b/libs/image/layerstyles/kis_layer_style_projection_plane.h index 760bb0d802..b4995c39a4 100644 --- a/libs/image/layerstyles/kis_layer_style_projection_plane.h +++ b/libs/image/layerstyles/kis_layer_style_projection_plane.h @@ -1,69 +1,70 @@ /* * 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_PROJECTION_PLANE_H #define __KIS_LAYER_STYLE_PROJECTION_PLANE_H #include "kis_abstract_projection_plane.h" #include #include "kis_types.h" #include class KRITAIMAGE_EXPORT KisLayerStyleProjectionPlane : public KisAbstractProjectionPlane { public: KisLayerStyleProjectionPlane(KisLayer *sourceLayer); KisLayerStyleProjectionPlane(const KisLayerStyleProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle); ~KisLayerStyleProjectionPlane() override; QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; + QRect tightUserVisibleBounds() const override; KisPaintDeviceList getLodCapableDevices() const override; // a method for registering on KisLayerStyleProjectionPlaneFactory static KisAbstractProjectionPlaneSP factoryObject(KisLayer *sourceLayer); private: friend class KisLayerStyleProjectionPlaneTest; KisLayerStyleProjectionPlane(KisLayer *sourceLayer, KisPSDLayerStyleSP style); void init(KisLayer *sourceLayer, KisPSDLayerStyleSP layerStyle); QRect stylesNeedRect(const QRect &rect) const; private: struct Private; const QScopedPointer m_d; }; typedef QSharedPointer KisLayerStyleProjectionPlaneSP; typedef QWeakPointer KisLayerStyleProjectionPlaneWSP; #endif /* __KIS_LAYER_STYLE_PROJECTION_PLANE_H */ diff --git a/libs/image/tests/kis_paint_device_test.cpp b/libs/image/tests/kis_paint_device_test.cpp index 2b5f798bfb..a0ed145643 100644 --- a/libs/image/tests/kis_paint_device_test.cpp +++ b/libs/image/tests/kis_paint_device_test.cpp @@ -1,2370 +1,2376 @@ /* * 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. */ #include "kis_paint_device_test.h" #include #include #include #include #include #include #include "kis_paint_device_writer.h" #include "kis_painter.h" #include "kis_types.h" #include "kis_paint_device.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_datamanager.h" #include "kis_global.h" #include "testutil.h" #include "kis_transaction.h" #include "kis_image.h" #include "config-limit-long-tests.h" #include "kistest.h" class KisFakePaintDeviceWriter : public KisPaintDeviceWriter { public: KisFakePaintDeviceWriter(KoStore *store) : m_store(store) { } bool write(const QByteArray &data) override { return (m_store->write(data) == data.size()); } bool write(const char* data, qint64 length) override { return (m_store->write(data, length) == length); } KoStore *m_store; }; void KisPaintDeviceTest::testCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->objectName().isEmpty()); dev = new KisPaintDevice(cs); QVERIFY(*dev->colorSpace() == *cs); QVERIFY(dev->x() == 0); QVERIFY(dev->y() == 0); QVERIFY(dev->pixelSize() == cs->pixelSize()); QVERIFY(dev->channelCount() == cs->channelCount()); QVERIFY(dev->dataManager() != 0); KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test"); KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125); dev = new KisPaintDevice(layer.data(), cs); QVERIFY(*dev->colorSpace() == *cs); QVERIFY(dev->x() == 0); QVERIFY(dev->y() == 0); QVERIFY(dev->pixelSize() == cs->pixelSize()); QVERIFY(dev->channelCount() == cs->channelCount()); QVERIFY(dev->dataManager() != 0); // Let the layer go out of scope and see what happens { KisPaintLayerSP l2 = new KisPaintLayer(image, "blabla", 250); dev = new KisPaintDevice(l2.data(), cs); } } void KisPaintDeviceTest::testStore() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KoStore * readStore = KoStore::createStore(QString(FILES_DATA_DIR) + QDir::separator() + "store_test.kra", KoStore::Read); readStore->open("built image/layers/layer0"); QVERIFY(dev->read(readStore->device())); readStore->close(); delete readStore; QVERIFY(dev->exactBounds() == QRect(0, 0, 100, 100)); KoStore * writeStore = KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Write); KisFakePaintDeviceWriter fakeWriter(writeStore); writeStore->open("built image/layers/layer0"); QVERIFY(dev->write(fakeWriter)); writeStore->close(); delete writeStore; KisPaintDeviceSP dev2 = new KisPaintDevice(cs); readStore = KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Read); readStore->open("built image/layers/layer0"); QVERIFY(dev2->read(readStore->device())); readStore->close(); delete readStore; QVERIFY(dev2->exactBounds() == QRect(0, 0, 100, 100)); QPoint pt; if (!TestUtil::comparePaintDevices(pt, dev, dev2)) { QFAIL(QString("Loading a saved image is not pixel perfect, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1()); } } void KisPaintDeviceTest::testGeometry() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, pixel); dev->fill(0, 0, 512, 512, pixel); QCOMPARE(dev->exactBounds(), QRect(0, 0, 512, 512)); QCOMPARE(dev->extent(), QRect(0, 0, 512, 512)); dev->moveTo(10, 10); QCOMPARE(dev->exactBounds(), QRect(10, 10, 512, 512)); QCOMPARE(dev->extent(), QRect(10, 10, 512, 512)); dev->crop(50, 50, 50, 50); QCOMPARE(dev->exactBounds(), QRect(50, 50, 50, 50)); QCOMPARE(dev->extent(), QRect(10, 10, 128, 128)); QColor c; dev->clear(QRect(50, 50, 50, 50)); dev->pixel(80, 80, &c); QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8); dev->fill(0, 0, 512, 512, pixel); dev->pixel(80, 80, &c); QVERIFY(c == Qt::white); QVERIFY(c.alpha() == OPACITY_OPAQUE_U8); dev->clear(); dev->pixel(80, 80, &c); QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8); QVERIFY(dev->extent().isEmpty()); QVERIFY(dev->exactBounds().isEmpty()); } void KisPaintDeviceTest::testClear() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); dev->clear(); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); QRect fillRect1(50, 100, 150, 100); dev->fill(fillRect1, KoColor(Qt::red, cs)); QCOMPARE(dev->extent(), QRect(0, 64, 256, 192)); QCOMPARE(dev->exactBounds(), fillRect1); dev->clear(QRect(100, 100, 100, 100)); QCOMPARE(dev->extent(), QRect(0, 64, 256, 192)); QCOMPARE(dev->exactBounds(), QRect(50, 100, 50, 100)); dev->clear(); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); } void KisPaintDeviceTest::testCrop() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, pixel); dev->fill(-14, 8, 433, 512, pixel); QVERIFY(dev->exactBounds() == QRect(-14, 8, 433, 512)); // Crop inside dev->crop(50, 50, 150, 150); QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150)); // Crop outside, pd should not grow dev->crop(0, 0, 1000, 1000); QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150)); } void KisPaintDeviceTest::testRoundtripReadWrite() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); dev->convertFromQImage(image, 0); quint8* bytes = new quint8[cs->pixelSize() * image.width() * image.height()]; memset(bytes, 0, image.width() * image.height() * dev->pixelSize()); dev->readBytes(bytes, image.rect()); KisPaintDeviceSP dev2 = new KisPaintDevice(cs); dev2->writeBytes(bytes, image.rect()); QVERIFY(dev2->exactBounds() == image.rect()); dev2->convertToQImage(0, 0, 0, image.width(), image.height()).save("readwrite.png"); QPoint pt; if (!TestUtil::comparePaintDevices(pt, dev, dev2)) { QFAIL(QString("Failed round trip using readBytes and writeBytes, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1()); } } void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs) { QString profile1("no profile"); QString profile2("no profile"); if (srcCs->profile()) profile1 = srcCs->profile()->name(); if (dstCs->profile()) profile2 = dstCs->profile()->name(); QWARN(QString("Failed %1 %2 -> %3 %4 %5") .arg(srcCs->name()) .arg(profile1) .arg(dstCs->name()) .arg(profile2) .arg(reason) .toLatin1()); } void KisPaintDeviceTest::testColorSpaceConversion() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->lab16(); KisPaintDeviceSP dev = new KisPaintDevice(srcCs); dev->convertFromQImage(image, 0); dev->moveTo(10, 10); // Unalign with tile boundaries KUndo2Command* cmd = new KUndo2Command(); dev->convertTo(dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), cmd); QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height())); QCOMPARE(dev->pixelSize(), dstCs->pixelSize()); QVERIFY(*dev->colorSpace() == *dstCs); cmd->redo(); cmd->undo(); QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height())); QCOMPARE(dev->pixelSize(), srcCs->pixelSize()); QVERIFY(*dev->colorSpace() == *srcCs); delete cmd; } void KisPaintDeviceTest::testRoundtripConversion() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); QImage result = dev->convertToQImage(0, 0, 0, 640, 441); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_paint_device_test_test_roundtrip_qimage.png"); result.save("kis_paint_device_test_test_roundtrip_result.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testFastBitBlt() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dstDev = new KisPaintDevice(cs); KisPaintDeviceSP srcDev = new KisPaintDevice(cs); srcDev->convertFromQImage(image, 0); QRect cloneRect(100,100,200,200); QPoint errpoint; QVERIFY(dstDev->fastBitBltPossible(srcDev)); dstDev->fastBitBlt(srcDev, cloneRect); QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } // Test Rough version dstDev->clear(); dstDev->fastBitBltRough(srcDev, cloneRect); srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } srcDev->moveTo(10,10); QVERIFY(!dstDev->fastBitBltPossible(srcDev)); } void KisPaintDeviceTest::testMakeClone() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP srcDev = new KisPaintDevice(cs); srcDev->convertFromQImage(image, 0); srcDev->moveTo(10,10); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->lab16(); KisPaintDeviceSP dstDev = new KisPaintDevice(weirdCS); dstDev->moveTo(1000,1000); QVERIFY(!dstDev->fastBitBltPossible(srcDev)); QRect cloneRect(100,100,200,200); QPoint errpoint; dstDev->makeCloneFrom(srcDev, cloneRect); QVERIFY(*dstDev->colorSpace() == *srcDev->colorSpace()); QCOMPARE(dstDev->pixelSize(), srcDev->pixelSize()); QCOMPARE(dstDev->x(), srcDev->x()); QCOMPARE(dstDev->y(), srcDev->y()); QCOMPARE(dstDev->exactBounds(), cloneRect); QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, dstImage, srcImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testThumbnail() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); { KisPaintDeviceSP thumb = dev->createThumbnailDevice(50, 50); QRect rc = thumb->exactBounds(); QVERIFY(rc.width() <= 50); QVERIFY(rc.height() <= 50); } { QImage thumb = dev->createThumbnail(50, 50); QVERIFY(!thumb.isNull()); QVERIFY(thumb.width() <= 50); QVERIFY(thumb.height() <= 50); image.save("kis_paint_device_test_test_thumbnail.png"); } } void KisPaintDeviceTest::testThumbnailDeviceWithOffset() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); dev->setX(10); dev->setY(10); QImage thumb = dev->createThumbnail(640,441,QRect(10,10,640,441)); image.save("oring.png"); thumb.save("thumb.png"); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, thumb, image)); } void KisPaintDeviceTest::testCaching() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* whitePixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, whitePixel); quint8* blackPixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::black, blackPixel); dev->fill(0, 0, 512, 512, whitePixel); QImage thumb1 = dev->createThumbnail(50, 50); QRect exactBounds1 = dev->exactBounds(); dev->fill(0, 0, 768, 768, blackPixel); QImage thumb2 = dev->createThumbnail(50, 50); QRect exactBounds2 = dev->exactBounds(); dev->moveTo(10, 10); QImage thumb3 = dev->createThumbnail(50, 50); QRect exactBounds3 = dev->exactBounds(); dev->crop(50, 50, 50, 50); QImage thumb4 = dev->createThumbnail(50, 50); QRect exactBounds4 = dev->exactBounds(); QVERIFY(thumb1 != thumb2); QVERIFY(thumb2 == thumb3); // Cache miss, but image is the same QVERIFY(thumb3 != thumb4); QVERIFY(thumb4 != thumb1); QCOMPARE(exactBounds1, QRect(0,0,512,512)); QCOMPARE(exactBounds2, QRect(0,0,768,768)); QCOMPARE(exactBounds3, QRect(10,10,768,768)); QCOMPARE(exactBounds4, QRect(50,50,50,50)); } void KisPaintDeviceTest::testRegion() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* whitePixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, whitePixel); dev->fill(0, 0, 10, 10, whitePixel); dev->fill(70, 70, 10, 10, whitePixel); dev->fill(129, 0, 10, 10, whitePixel); dev->fill(0, 1030, 10, 10, whitePixel); QRegion referenceRegion; referenceRegion += QRect(0,0,64,64); referenceRegion += QRect(64,64,64,64); referenceRegion += QRect(128,0,64,64); referenceRegion += QRect(0,1024,64,64); QCOMPARE(dev->exactBounds(), QRect(0,0,139,1040)); QCOMPARE(dev->region(), referenceRegion); } void KisPaintDeviceTest::testPixel() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QColor c = Qt::red; quint8 opacity = 125; c.setAlpha(opacity); dev->setPixel(5, 5, c); QColor c2; dev->pixel(5, 5, &c2); QVERIFY(c == c2); QVERIFY(opacity == c2.alpha()); } void KisPaintDeviceTest::testPlanarReadWrite() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(QColor(255, 200, 155, 100), pixel); dev->fill(0, 0, 5000, 5000, pixel); delete[] pixel; QColor c1; dev->pixel(5, 5, &c1); QVector planes = dev->readPlanarBytes(500, 500, 100, 100); QVector swappedPlanes; QCOMPARE((int)planes.size(), (int)dev->channelCount()); for (int i = 0; i < 100*100; i++) { // BGRA encoded QVERIFY(planes.at(2)[i] == 255); QVERIFY(planes.at(1)[i] == 200); QVERIFY(planes.at(0)[i] == 155); QVERIFY(planes.at(3)[i] == 100); } for (uint i = 1; i < dev->channelCount() + 1; ++i) { swappedPlanes.append(planes[dev->channelCount() - i]); } dev->writePlanarBytes(swappedPlanes, 0, 0, 100, 100); dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar.png"); dev->pixel(5, 5, &c1); QVERIFY(c1.red() == 200); QVERIFY(c1.green() == 255); QVERIFY(c1.blue() == 100); QVERIFY(c1.alpha() == 155); dev->pixel(75, 50, &c1); QVERIFY(c1.red() == 200); QVERIFY(c1.green() == 255); QVERIFY(c1.blue() == 100); QVERIFY(c1.alpha() == 155); // check if one of the planes is Null. Q_ASSERT(planes.size() == 4); delete [] planes[2]; planes[2] = 0; dev->writePlanarBytes(planes, 0, 0, 100, 100); dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar_noR.png"); dev->pixel(75, 50, &c1); QCOMPARE(c1.red(), 200); QCOMPARE(c1.green(), 200); QCOMPARE(c1.blue(), 155); QCOMPARE(c1.alpha(), 100); QVector::iterator i; for (i = planes.begin(); i != planes.end(); ++i) { delete [] *i; } swappedPlanes.clear(); } void KisPaintDeviceTest::testBltPerformance() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP fdev = new KisPaintDevice(cs); fdev->convertFromQImage(image, 0); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data()); QTime t; t.start(); int x; #ifdef LIMIT_LONG_TESTS int steps = 10; #else int steps = 1000; #endif for (x = 0; x < steps; ++x) { KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), fdev, image.rect()); } dbgKrita << x << "blits" << " done in " << t.elapsed() << "ms"; } void KisPaintDeviceTest::testDeviceDuplication() { QRect fillRect(0,0,64,64); quint8 fillPixel[4]={255,255,255,255}; QRect clearRect(10,10,20,20); QImage referenceImage; QImage resultImage; const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP device = new KisPaintDevice(cs); // dbgKrita<<"FILLING"; device->fill(fillRect.left(), fillRect.top(), fillRect.width(), fillRect.height(),fillPixel); referenceImage = device->convertToQImage(0); KisTransaction transaction1(device); // dbgKrita<<"CLEARING"; device->clear(clearRect); transaction1.revert(); resultImage = device->convertToQImage(0); QVERIFY(resultImage == referenceImage); KisPaintDeviceSP clone = new KisPaintDevice(*device); KisTransaction transaction(clone); // dbgKrita<<"CLEARING"; clone->clear(clearRect); transaction.revert(); resultImage = clone->convertToQImage(0); QVERIFY(resultImage == referenceImage); } void KisPaintDeviceTest::testTranslate() { QRect fillRect(0,0,64,64); quint8 fillPixel[4]={255,255,255,255}; const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP device = new KisPaintDevice(cs); device->fill(fillRect.left(), fillRect.top(), fillRect.width(), fillRect.height(),fillPixel); device->setX(-10); device->setY(10); QCOMPARE(device->exactBounds(), QRect(-10,10,64,64)); QCOMPARE(device->extent(), QRect(-10,10,64,64)); } void KisPaintDeviceTest::testOpacity() { // blt a semi-transparent image on a white paint device QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP fdev = new KisPaintDevice(cs); fdev->convertFromQImage(image, 0); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data()); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), fdev, image.rect()); QImage result = dev->convertToQImage(0, 0, 0, 640, 441); QImage checkResult(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent_result.png"); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, checkResult, result, 1)) { checkResult.save("kis_paint_device_test_test_blt_fixed_opactiy_expected.png"); result.save("kis_paint_device_test_test_blt_fixed_opacity_result.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testExactBoundsWeirdNullAlphaCase() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); dev->fill(QRect(10,10,10,10), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(10,10,10,10)); const quint8 weirdPixelData[4] = {0,10,0,0}; KoColor weirdColor(weirdPixelData, cs); dev->setPixel(6,6,weirdColor); // such weird pixels should not change our opinion about // device's size QCOMPARE(dev->exactBounds(), QRect(10,10,10,10)); } void KisPaintDeviceTest::benchmarkExactBoundsNullDefaultPixel() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QRect fillRect(60,60, 1930, 1930); dev->fill(fillRect, KoColor(Qt::white, cs)); QRect measuredRect; QBENCHMARK { // invalidate the cache dev->setDirty(); measuredRect = dev->exactBounds(); } QCOMPARE(measuredRect, fillRect); } void KisPaintDeviceTest::testAmortizedExactBounds() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QRect fillRect(60,60, 833, 833); QRect extent(0,0,896,896); dev->fill(fillRect, KoColor(Qt::white, cs)); QEXPECT_FAIL("", "Expecting the extent, we somehow get the fillrect", Continue); QCOMPARE(dev->exactBounds(), extent); QCOMPARE(dev->extent(), extent); QCOMPARE(dev->exactBoundsAmortized(), fillRect); dev->setDirty(); QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue); QCOMPARE(dev->exactBoundsAmortized(), fillRect); dev->setDirty(); QCOMPARE(dev->exactBoundsAmortized(), extent); QTest::qSleep(1100); QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue); QCOMPARE(dev->exactBoundsAmortized(), fillRect); } void KisPaintDeviceTest::testNonDefaultPixelArea() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); KoColor defPixel(Qt::red, cs); dev->setDefaultPixel(defPixel); QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); QRect fillRect(10,11,18,14); dev->fill(fillRect, KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QCOMPARE(dev->nonDefaultPixelArea(), fillRect); // non-default pixel variant should also handle weird pixels const quint8 weirdPixelData[4] = {0,10,0,0}; KoColor weirdColor(weirdPixelData, cs); dev->setPixel(100,100,weirdColor); // such weird pixels should not change our opinion about // device's size QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QCOMPARE(dev->nonDefaultPixelArea(), fillRect | QRect(100,100,1,1)); } void KisPaintDeviceTest::testExactBoundsNonTransparent() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test"); KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125); KisPaintDeviceSP dev = layer->paintDevice(); QVERIFY(dev); QRect imageRect(0,0,1000,1000); KoColor defPixel(Qt::red, cs); dev->setDefaultPixel(defPixel); QCOMPARE(dev->exactBounds(), imageRect); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); KoColor fillPixel(Qt::white, cs); dev->fill(imageRect, KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), imageRect); QCOMPARE(dev->nonDefaultPixelArea(), imageRect); dev->fill(QRect(1000,0, 1, 1000), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1000)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1000)); dev->fill(QRect(0,1000, 1000, 1), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1001)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1001)); dev->fill(QRect(0,-1, 1000, 1), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,-1,1001,1002)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,-1,1001,1002)); dev->fill(QRect(-1,0, 1, 1000), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(-1,-1,1002,1002)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(-1,-1,1002,1002)); } KisPaintDeviceSP createWrapAroundPaintDevice(const KoColorSpace *cs) { struct TestingDefaultBounds : public KisDefaultBoundsBase { QRect bounds() const override { return QRect(0,0,20,20); } bool wrapAroundMode() const override { return true; } int currentLevelOfDetail() const override { return 0; } int currentTime() const override { return 0; } bool externalFrameActive() const override { return false; } + void * sourceCookie() const override { + return 0; + } }; KisPaintDeviceSP dev = new KisPaintDevice(cs); KisDefaultBoundsBaseSP bounds = new TestingDefaultBounds(); dev->setDefaultBounds(bounds); return dev; } void checkReadWriteRoundTrip(KisPaintDeviceSP dev, const QRect &rc) { KisPaintDeviceSP deviceCopy = new KisPaintDevice(*dev.data()); int bufSize = rc.width() * rc.height() * dev->pixelSize(); QScopedArrayPointer buf1(new quint8[bufSize]); deviceCopy->readBytes(buf1.data(), rc); deviceCopy->clear(); QVERIFY(deviceCopy->extent().isEmpty()); QScopedArrayPointer buf2(new quint8[bufSize]); deviceCopy->writeBytes(buf1.data(), rc); deviceCopy->readBytes(buf2.data(), rc); QVERIFY(!memcmp(buf1.data(), buf2.data(), bufSize)); } void KisPaintDeviceTest::testReadBytesWrapAround() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); const int pixelSize = dev->pixelSize(); { QRect readRect(10, 10, 20, 20); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final1.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check weird case when the read rect is larger than wrap rect QRect readRect(10, 10, 30, 30); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final2.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // even more large QRect readRect(10, 10, 40, 40); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final3.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap rect contains the read rect entirely QRect readRect(1, 1, 10, 10); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final4.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap happens only on vertical side of the rect QRect readRect(1, 1, 29, 10); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final5.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (21 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (22 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap happens only on horizontal side of the rect QRect readRect(1, 1, 10, 29); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final6.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 21) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 22) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } } #include "kis_random_accessor_ng.h" void KisPaintDeviceTest::testWrappedRandomAccessor() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); const int pixelSize = dev->pixelSize(); int x; int y; x = 3; y = 3; KisRandomAccessorSP dstIt = dev->createRandomAccessorNG(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 23; y = 23; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 3; y = 23; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 23; y = 3; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = -17; y = 3; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 3; y = -17; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = -17; y = -17; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); } #include "kis_iterator_ng.h" static bool nextRowGeneral(KisHLineIteratorSP it, int y, const QRect &rc) { it->nextRow(); return y < rc.height(); } static bool nextRowGeneral(KisVLineIteratorSP it, int y, const QRect &rc) { it->nextColumn(); return y < rc.width(); } template bool checkXY(const QPoint &pt, const QPoint &realPt) { Q_UNUSED(pt); Q_UNUSED(realPt); return false; } template <> bool checkXY(const QPoint &pt, const QPoint &realPt) { return pt == realPt; } template <> bool checkXY(const QPoint &pt, const QPoint &realPt) { return pt.x() == realPt.y() && pt.y() == realPt.x(); } #include template bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { Q_UNUSED(value); Q_UNUSED(pt); Q_UNUSED(wrappedRect); return false; } template <> bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { int x = KisWrappedRect::xToWrappedX(pt.x(), wrappedRect.wrapRect()); int borderX = wrappedRect.originalRect().x() + wrappedRect.wrapRect().width(); int conseq = x >= borderX ? wrappedRect.wrapRect().right() - x + 1 : borderX - x; conseq = qMin(conseq, wrappedRect.originalRect().right() - pt.x() + 1); return value == conseq; } template <> bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { Q_UNUSED(pt); Q_UNUSED(wrappedRect); return value == 1; } template IteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { Q_UNUSED(dev); Q_UNUSED(rc); return 0; } template <> KisHLineIteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { return dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); } template <> KisVLineIteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { return dev->createVLineIteratorNG(rc.x(), rc.y(), rc.height()); } template void testWrappedLineIterator(QString testName, const QRect &rect) { testName = QString("%1_%2_%3_%4_%5") .arg(testName) .arg(rect.x()) .arg(rect.y()) .arg(rect.width()) .arg(rect.height()); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); // test rect fits the wrap rect in both dimensions IteratorSP it = createIterator(dev, rect); int y = 0; do { int x = 0; do { quint8 *data = it->rawData(); data[0] = 10 * x; data[1] = 10 * y; data[2] = 0; data[3] = 255; x++; } while (it->nextPixel()); } while (nextRowGeneral(it, ++y, rect)); QRect rc = dev->defaultBounds()->bounds() | dev->exactBounds(); QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators", testName)); } template void testWrappedLineIterator(const QString &testName) { testWrappedLineIterator(testName, QRect(10,10,20,20)); testWrappedLineIterator(testName, QRect(10,10,10,20)); testWrappedLineIterator(testName, QRect(10,10,20,10)); testWrappedLineIterator(testName, QRect(10,10,10,10)); testWrappedLineIterator(testName, QRect(0,0,20,20)); } void KisPaintDeviceTest::testWrappedHLineIterator() { testWrappedLineIterator("hline_iterator"); } void KisPaintDeviceTest::testWrappedVLineIterator() { testWrappedLineIterator("vline_iterator"); } template void testWrappedLineIteratorReadMoreThanBounds(QString testName) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KisPaintDeviceSP dst = new KisPaintDevice(cs); // fill device with a gradient QRect bounds = dev->defaultBounds()->bounds(); for (int y = bounds.y(); y < bounds.y() + bounds.height(); y++) { for (int x = bounds.x(); x < bounds.x() + bounds.width(); x++) { QColor c((10 * x) % 255, (10 * y) % 255, 0, 255); dev->setPixel(x, y, c); } } // test rect doesn't fit the wrap rect in both dimensions const QRect &rect(bounds.adjusted(-6,-6,8,8)); KisRandomAccessorSP dstIt = dst->createRandomAccessorNG(rect.x(), rect.y()); IteratorSP it = createIterator(dev, rect); for (int y = rect.y(); y < rect.y() + rect.height(); y++) { for (int x = rect.x(); x < rect.x() + rect.width(); x++) { quint8 *data = it->rawData(); QVERIFY(checkConseqPixels(it->nConseqPixels(), QPoint(x, y), KisWrappedRect(rect, bounds))); dstIt->moveTo(x, y); memcpy(dstIt->rawData(), data, cs->pixelSize()); QVERIFY(checkXY(QPoint(it->x(), it->y()), QPoint(x,y))); bool stepDone = it->nextPixel(); QCOMPARE(stepDone, x < rect.x() + rect.width() - 1); } if (!nextRowGeneral(it, y, rect)) break; } testName = QString("%1_%2_%3_%4_%5") .arg(testName) .arg(rect.x()) .arg(rect.y()) .arg(rect.width()) .arg(rect.height()); QRect rc = rect; QImage result = dst->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QImage ref = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators_huge", testName, 1)); } void KisPaintDeviceTest::testWrappedHLineIteratorReadMoreThanBounds() { testWrappedLineIteratorReadMoreThanBounds("hline_iterator"); } void KisPaintDeviceTest::testWrappedVLineIteratorReadMoreThanBounds() { testWrappedLineIteratorReadMoreThanBounds("vline_iterator"); } void KisPaintDeviceTest::testMoveWrapAround() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); // QRect rc = dev->defaultBounds()->bounds(); //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move0.png"); QCOMPARE(dev->exactBounds(), QRect(3,3,16,16)); dev->moveTo(QPoint(10,10)); QCOMPARE(dev->exactBounds(), QRect(8,8,6,6)); //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move1.png"); } #include "kis_lock_free_cache.h" #define NUM_TYPES 3 // high-concurrency #define NUM_CYCLES 500000 #define NUM_THREADS 4 struct TestingCache : KisLockFreeCache { int calculateNewValue() const override { return m_realValue; } QAtomicInt m_realValue; }; class CacheStressJob : public QRunnable { public: CacheStressJob(TestingCache &cache) : m_cache(cache), m_oldValue(0) { } void run() override { for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = i % NUM_TYPES; switch(type) { case 0: m_cache.m_realValue.ref(); m_oldValue = m_cache.m_realValue; m_cache.invalidate(); break; case 1: { int newValue = m_cache.getValue(); Q_ASSERT(newValue >= m_oldValue); Q_UNUSED(newValue); } break; case 3: QTest::qSleep(3); break; } } } private: TestingCache &m_cache; int m_oldValue; }; void KisPaintDeviceTest::testCacheState() { TestingCache cache; QList jobsList; CacheStressJob *job; for(qint32 i = 0; i < NUM_THREADS; i++) { //job = new CacheStressJob(value, cacheValue, cacheState); job = new CacheStressJob(cache); job->setAutoDelete(true); jobsList.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); Q_FOREACH (job, jobsList) { pool.start(job); } pool.waitForDone(); } struct TestingLodDefaultBounds : public KisDefaultBoundsBase { TestingLodDefaultBounds(const QRect &bounds = QRect(0,0,100,100)) : m_lod(0), m_bounds(bounds) {} QRect bounds() const override { return m_bounds; } bool wrapAroundMode() const override { return false; } int currentLevelOfDetail() const override { return m_lod; } int currentTime() const override { return 0; } bool externalFrameActive() const override { return false; } void testingSetLevelOfDetail(int lod) { m_lod = lod; } + void * sourceCookie() const override { + return 0; + } private: int m_lod; QRect m_bounds; }; void fillGradientDevice(KisPaintDeviceSP dev, const QRect &rect, bool flat = false) { if (flat) { dev->fill(rect, KoColor(Qt::red, dev->colorSpace())); } else { // fill device with a gradient KisSequentialIterator it(dev, rect); while (it.nextPixel()) { QColor c((10 * it.x()) & 0xFF, (10 * it.y()) & 0xFF, 0, 255); KoColor color(c, dev->colorSpace()); memcpy(it.rawData(), color.data(), dev->pixelSize()); } } } #include "kis_lod_transform.h" void KisPaintDeviceTest::testLodTransform() { const int lod = 2; // round to 4 KisLodTransform t(lod); QRect rc1(-16, -16, 8, 8); QRect rc2(-16, -16, 7, 7); QRect rc3(-15, -15, 7, 7); QCOMPARE(t.alignedRect(rc1, lod), rc1); QCOMPARE(t.alignedRect(rc2, lod), rc1); QCOMPARE(t.alignedRect(rc3, lod), rc1); } #include "krita_utils.h" void syncLodCache(KisPaintDeviceSP dev, int levelOfDetail) { KisPaintDevice::LodDataStruct* s = dev->createLodDataStruct(levelOfDetail); QRegion region = dev->regionForLodSyncing(); Q_FOREACH(QRect rect2, KritaUtils::splitRegionIntoPatches(region, KritaUtils::optimalPatchSize())) { dev->updateLodDataStruct(s, rect2); } dev->uploadLodDataStruct(s); } void KisPaintDeviceTest::testLodDevice() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(); dev->setDefaultBounds(bounds); // fill device with a gradient // QRect rect = dev->defaultBounds()->bounds(); // fillGradientDevice(dev, rect); fillGradientDevice(dev, QRect(50,50,30,30)); QCOMPARE(dev->exactBounds(), QRect(50,50,30,30)); QImage result; qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "initial")); bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); QCOMPARE(dev->exactBounds(), QRect(25,25,15,15)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1")); bounds->testingSetLevelOfDetail(2); QCOMPARE(dev->exactBounds(), QRect(25,25,15,15)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1")); syncLodCache(dev, 2); QCOMPARE(dev->exactBounds(), QRect(12,12,8,8)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod2")); bounds->testingSetLevelOfDetail(0); dev->setX(20); dev->setY(10); bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); QCOMPARE(dev->exactBounds(), QRect(35,30,15,15)); qDebug() << ppVar(dev->exactBounds()) << ppVar(dev->x()) << ppVar(dev->y()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1-offset-6-14")); } void KisPaintDeviceTest::benchmarkLod1Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); } } void KisPaintDeviceTest::benchmarkLod2Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(2); syncLodCache(dev, 2); } } void KisPaintDeviceTest::benchmarkLod3Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(3); syncLodCache(dev, 3); } } void KisPaintDeviceTest::benchmarkLod4Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(4); syncLodCache(dev, 4); } } #include "kis_keyframe_channel.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "testing_timed_default_bounds.h" void KisPaintDeviceTest::testFramesLeaking() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data is kept separate o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add keyframe at position 10 channel->addKeyframe(10); // two frames, m_data has a default empty value o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); // add keyframe at position 20 channel->addKeyframe(20); // three frames, m_data is default, current frame is 0 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 3); QVERIFY(o.m_currentData == o.m_frames[0]); // switch to frame 10 bounds->testingSetTime(10); // three frames, m_data is default, current frame is 10 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[1]); QCOMPARE(o.m_frames.size(), 3); // switch to frame 20 bounds->testingSetTime(20); // three frames, m_data is default, current frame is 20 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[2]); QCOMPARE(o.m_frames.size(), 3); // switch to frame 15 bounds->testingSetTime(15); // three frames, m_data is default, current frame is 10 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[1]); QCOMPARE(o.m_frames.size(), 3); KisKeyframeSP key; // deletion of frame 0 is forbidden key = channel->keyframeAt(0); QVERIFY(key); QVERIFY(channel->deleteKeyframe(key)); // delete keyframe at position 11 key = channel->activeKeyframeAt(11); QVERIFY(key); QCOMPARE(key->time(), 10); QVERIFY(channel->deleteKeyframe(key)); // two frames, m_data is default, current frame is 0 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 2); // deletion of frame 0 is forbidden key = channel->activeKeyframeAt(11); QVERIFY(key); QCOMPARE(key->time(), 0); QVERIFY(channel->deleteKeyframe(key)); // nothing changed o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 2); // delete keyframe at position 20 key = channel->activeKeyframeAt(20); QVERIFY(key); QCOMPARE(key->time(), 20); QVERIFY(channel->deleteKeyframe(key)); // one keyframe is left at position 0, m_data is default o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 1); // ensure all the objects in the list of all objects are unique QList allObjects = i->testingGetDataObjectsList(); QSet uniqueObjects; Q_FOREACH (KisPaintDeviceData *obj, allObjects) { if (!obj) continue; QVERIFY(!uniqueObjects.contains(obj)); uniqueObjects.insert(obj); } } void KisPaintDeviceTest::testFramesUndoRedo() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; int frameId = -1; const int time = 1; channel->addKeyframe(time, &cmdAdd); frameId = channel->frameIdAt(time); //int frameId = i->createFrame(false, 0, QPoint(), &cmdAdd); QCOMPARE(frameId, 1); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.undo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.redo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdRemove; KisKeyframeSP keyframe = channel->keyframeAt(time); QVERIFY(keyframe); channel->deleteKeyframe(keyframe, &cmdRemove); //i->deleteFrame(1, &cmdRemove); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.redo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); } void KisPaintDeviceTest::testFramesUndoRedoWithChannel() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.undo(); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.redo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdRemove; channel->deleteKeyframe(frame, &cmdRemove); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.redo(); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdMove; channel->moveKeyframe(frame, 12, &cmdMove); QVERIFY(!channel->keyframeAt(10)); QVERIFY(channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdMove.undo(); QVERIFY(channel->keyframeAt(10)); QVERIFY(!channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdMove.redo(); QVERIFY(!channel->keyframeAt(10)); QVERIFY(channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); } void fillRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds) { KUndo2Command parentCommand; KisRasterKeyframeChannel *channel = dev->keyframeChannel(); KisKeyframeSP frame = channel->addKeyframe(time, &parentCommand); const int oldTime = bounds->currentTime(); bounds->testingSetTime(time); KoColor color(Qt::red, dev->colorSpace()); dev->fill(rc, color); bounds->testingSetTime(oldTime); } bool checkRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds) { const int oldTime = bounds->currentTime(); bounds->testingSetTime(time); bool result = dev->exactBounds() == rc; if (!result) { qDebug() << "Failed to check frame:" << ppVar(time) << ppVar(rc) << ppVar(dev->exactBounds()); } bounds->testingSetTime(oldTime); return result; } void testCrossDeviceFrameCopyImpl(bool useChannel) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev1 = new KisPaintDevice(cs); KisPaintDeviceSP dev2 = new KisPaintDevice(cs); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); KisPaintDeviceSP dev3 = new KisPaintDevice(cs3); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev1->setDefaultBounds(bounds); dev2->setDefaultBounds(bounds); dev3->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel1 = dev1->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i1 = dev1->framesInterface(); QVERIFY(channel1); QVERIFY(i1); KisRasterKeyframeChannel *channel2 = dev2->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i2 = dev2->framesInterface(); QVERIFY(channel2); QVERIFY(i2); KisRasterKeyframeChannel *channel3 = dev3->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i3 = dev3->framesInterface(); QVERIFY(channel3); QVERIFY(i3); fillRect(dev1, 10, QRect(100,100,100,100), bounds); fillRect(dev2, 20, QRect(200,200,100,100), bounds); fillRect(dev3, 30, QRect(300,300,100,100), bounds); QCOMPARE(dev1->exactBounds(), QRect()); const int dstFrameId1 = channel1->frameIdAt(10); const int srcFrameId2 = channel2->frameIdAt(20); const int srcFrameId3 = channel3->frameIdAt(30); KUndo2Command cmd1; if (!useChannel) { dev1->framesInterface()->uploadFrame(srcFrameId2, dstFrameId1, dev2); } else { KisKeyframeSP k = channel1->copyExternalKeyframe(channel2, 20, 10, &cmd1); } QCOMPARE(dev1->exactBounds(), QRect()); QVERIFY(checkRect(dev1, 10, QRect(200,200,100,100), bounds)); if (useChannel) { cmd1.undo(); QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds)); } KUndo2Command cmd2; if (!useChannel) { dev1->framesInterface()->uploadFrame(srcFrameId3, dstFrameId1, dev3); } else { KisKeyframeSP k = channel1->copyExternalKeyframe(channel3, 30, 10, &cmd2); } QCOMPARE(dev1->exactBounds(), QRect()); QVERIFY(checkRect(dev1, 10, QRect(300,300,100,100), bounds)); if (useChannel) { cmd2.undo(); QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds)); } } void KisPaintDeviceTest::testCrossDeviceFrameCopyDirect() { testCrossDeviceFrameCopyImpl(false); } void KisPaintDeviceTest::testCrossDeviceFrameCopyChannel() { testCrossDeviceFrameCopyImpl(true); } #include "kis_surrogate_undo_adapter.h" void KisPaintDeviceTest::testLazyFrameCreation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); bounds->testingSetTime(10); QCOMPARE(i->frames().size(), 1); KisSurrogateUndoAdapter undoAdapter; { KisTransaction transaction1(dev); transaction1.commit(&undoAdapter); } QCOMPARE(i->frames().size(), 2); undoAdapter.undoAll(); QCOMPARE(i->frames().size(), 1); undoAdapter.redoAll(); QCOMPARE(i->frames().size(), 2); } void KisPaintDeviceTest::testCopyPaintDeviceWithFrames() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); //QVERIFY(o.m_currentData == o.m_frames[0]); KisPaintDeviceSP newDev = new KisPaintDevice(*dev, KritaUtils::CopyAllFrames); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(10)); } #include #include #include #include #include #include #include #include "KoCompositeOpRegistry.h" using namespace boost::accumulators; accumulator_set > accum; void KisPaintDeviceTest::testCompositionAssociativity() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); qsrand(500); boost::mt11213b _rnd0(qrand()); boost::mt11213b _rnd1(qrand()); boost::mt11213b _rnd2(qrand()); boost::mt11213b _rnd3(qrand()); boost::uniform_smallint rnd0(0, 255); boost::uniform_smallint rnd1(0, 255); boost::uniform_smallint rnd2(0, 255); boost::uniform_smallint rnd3(0, 255); QList allCompositeOps = cs->compositeOps(); Q_FOREACH (const KoCompositeOp *op, allCompositeOps) { accumulator_set > accum; const int numIterations = 10000; for (int j = 0; j < numIterations; j++) { KoColor c1(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor c2(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor c3(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); //KoColor c4(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); //KoColor c5(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor r1(QColor(Qt::transparent), cs); KoColor r2(QColor(Qt::transparent), cs); KoColor r3(QColor(Qt::transparent), cs); op->composite(r1.data(), 0, c1.data(), 0, 0,0, 1,1, 255); op->composite(r1.data(), 0, c2.data(), 0, 0,0, 1,1, 255); op->composite(r1.data(), 0, c3.data(), 0, 0,0, 1,1, 255); //op->composite(r1.data(), 0, c4.data(), 0, 0,0, 1,1, 255); //op->composite(r1.data(), 0, c5.data(), 0, 0,0, 1,1, 255); op->composite(r3.data(), 0, c2.data(), 0, 0,0, 1,1, 255); op->composite(r3.data(), 0, c3.data(), 0, 0,0, 1,1, 255); //op->composite(r3.data(), 0, c4.data(), 0, 0,0, 1,1, 255); //op->composite(r3.data(), 0, c5.data(), 0, 0,0, 1,1, 255); op->composite(r2.data(), 0, c1.data(), 0, 0,0, 1,1, 255); op->composite(r2.data(), 0, r3.data(), 0, 0,0, 1,1, 255); const quint8 *p1 = r1.data(); const quint8 *p2 = r2.data(); if (memcmp(p1, p2, 4) != 0) { for (int i = 0; i < 4; i++) { accum(qAbs(p1[i] - p2[i])); } } } qDebug("Errors for op %25s err rate %7.2f var %7.2f max %7.2f", op->id().toLatin1().data(), (qreal(count(accum)) / (4 * numIterations)), variance(accum), count(accum) > 0 ? (max)(accum) : 0); } } #include struct FillWorker : public QRunnable { FillWorker(KisPaintDeviceSP dev, const QRect &fillRect, bool clear) : m_dev(dev), m_fillRect(fillRect), m_clear(clear) { setAutoDelete(true); } void run() { if (m_clear) { m_dev->clear(m_fillRect); } else { const KoColor fillColor(Qt::red, m_dev->colorSpace()); const int pixelSize = m_dev->colorSpace()->pixelSize(); KisSequentialIterator it(m_dev, m_fillRect); while (it.nextPixel()) { memcpy(it.rawData(), fillColor.data(), pixelSize); } } } private: KisPaintDeviceSP m_dev; QRect m_fillRect; bool m_clear; }; #ifdef Q_OS_LINUX #include #endif void KisPaintDeviceTest::stressTestMemoryFragmentation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KUndo2Stack undoStack; #ifdef LIMIT_LONG_TESTS const int numCycles = 3; undoStack.setUndoLimit(1); #else const int numCycles = 200; undoStack.setUndoLimit(10); #endif const int numThreads = 16; const int desiredWidth = 10000; const int patchSize = 81; const int numSidePatches = desiredWidth / patchSize; QThreadPool pool; pool.setMaxThreadCount(numThreads); for (int i = 0; i < numCycles; i++) { qDebug() << "iteration"<< i; // KisTransaction t(dev); for (int y = 0; y < numSidePatches; y++) { for (int x = 0; x < numSidePatches; x++) { const QRect workerRect(x * patchSize, y * patchSize, patchSize, patchSize); pool.start(new FillWorker(dev, workerRect, (i + x + y) & 0x1)); } } pool.waitForDone(); // undoStack.push(t.endAndTake()); qDebug() << "Iteration:" << i; #ifdef Q_OS_LINUX struct mallinfo info = mallinfo(); qDebug() << "Allocated on heap:" << (info.arena >> 20) << "MiB"; qDebug() << "Mmaped regions:" << info.hblks << (info.hblkhd >> 20) << "MiB"; qDebug() << "Free fastbin chunks:" << info.smblks << (info.fsmblks >> 10) << "KiB"; qDebug() << "Allocated in ordinary blocks" << (info.uordblks >> 20) << "MiB"; qDebug() << "Free in ordinary blockes" << info.ordblks << (info.fordblks >> 20) << "MiB"; #endif qDebug() << "========================================"; } undoStack.clear(); } KISTEST_MAIN(KisPaintDeviceTest) diff --git a/libs/image/tiles3/kis_lockless_stack.h b/libs/image/tiles3/kis_lockless_stack.h index 264d061460..6145af836a 100644 --- a/libs/image/tiles3/kis_lockless_stack.h +++ b/libs/image/tiles3/kis_lockless_stack.h @@ -1,235 +1,235 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * References: * * Maged M. Michael, Safe memory reclamation for dynamic * lock-free objects using atomic reads and writes, * Proceedings of the twenty-first annual symposium on * Principles of distributed computing, July 21-24, 2002, * Monterey, California * * * Idea of m_deleteBlockers is taken from Andrey Gulin's blog * http://users.livejournal.com/_foreseer/34284.html * * 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_LOCKLESS_STACK_H #define __KIS_LOCKLESS_STACK_H #include template class KisLocklessStack { private: struct Node { Node *next; T data; }; public: KisLocklessStack() { } ~KisLocklessStack() { freeList(m_top.fetchAndStoreOrdered(0)); freeList(m_freeNodes.fetchAndStoreOrdered(0)); } void push(T data) { Node *newNode = new Node(); newNode->data = data; Node *top; do { top = m_top; newNode->next = top; } while (!m_top.testAndSetOrdered(top, newNode)); m_numNodes.ref(); } bool pop(T &value) { bool result = false; m_deleteBlockers.ref(); while(1) { Node *top = (Node*) m_top; if(!top) break; // This is safe as we ref'ed m_deleteBlockers Node *next = top->next; if(m_top.testAndSetOrdered(top, next)) { m_numNodes.deref(); result = true; value = top->data; /** * Test if we are the only delete blocker left * (it means that we are the only owner of 'top') * If there is someone else in "delete-blocked section", * then just add the struct to the list of free nodes. */ if (m_deleteBlockers == 1) { cleanUpNodes(); delete top; } else { releaseNode(top); } break; } } m_deleteBlockers.deref(); return result; } void clear() { // a fast-path without write ops if(!m_top) return; m_deleteBlockers.ref(); Node *top = m_top.fetchAndStoreOrdered(0); int removedChunkSize = 0; Node *tmp = top; while(tmp) { removedChunkSize++; tmp = tmp->next; } m_numNodes.fetchAndAddOrdered(-removedChunkSize); while(top) { Node *next = top->next; if (m_deleteBlockers == 1) { /** * We are the only owner of top contents. * So we can delete it freely. */ cleanUpNodes(); freeList(top); next = 0; } else { releaseNode(top); } top = next; } m_deleteBlockers.deref(); } void mergeFrom(KisLocklessStack &other) { Node *otherTop = other.m_top.fetchAndStoreOrdered(0); if (!otherTop) return; int removedChunkSize = 1; Node *last = otherTop; while(last->next) { removedChunkSize++; last = last->next; } other.m_numNodes.fetchAndAddOrdered(-removedChunkSize); Node *top; do { top = m_top; last->next = top; } while (!m_top.testAndSetOrdered(top, otherTop)); m_numNodes.fetchAndAddOrdered(removedChunkSize); } /** * This is impossible to measure the size of the stack * in highly concurrent environment. So we return approximate * value! Do not rely on this value much! */ - qint32 size() { + qint32 size() const { return m_numNodes; } - bool isEmpty() { + bool isEmpty() const { return !m_numNodes; } private: inline void releaseNode(Node *node) { Node *top; do { top = m_freeNodes; node->next = top; } while (!m_freeNodes.testAndSetOrdered(top, node)); } inline void cleanUpNodes() { Node *cleanChain = m_freeNodes.fetchAndStoreOrdered(0); if (!cleanChain) return; /** * If we are the only users of the objects is cleanChain, * then just free it. Otherwise, push them back into the * recycling list and keep them there till another * chance comes. */ if (m_deleteBlockers == 1) { freeList(cleanChain); } else { Node *last = cleanChain; while (last->next) last = last->next; Node *freeTop; do { freeTop = m_freeNodes; last->next = freeTop; } while (!m_freeNodes.testAndSetOrdered(freeTop, cleanChain)); } } inline void freeList(Node *first) { Node *next; while (first) { next = first->next; delete first; first = next; } } private: Q_DISABLE_COPY(KisLocklessStack) QAtomicPointer m_top; QAtomicPointer m_freeNodes; QAtomicInt m_deleteBlockers; QAtomicInt m_numNodes; }; #endif /* __KIS_LOCKLESS_STACK_H */ diff --git a/libs/ui/KisDecorationsWrapperLayer.cpp b/libs/ui/KisDecorationsWrapperLayer.cpp index 73bc915b49..bd71c180fa 100644 --- a/libs/ui/KisDecorationsWrapperLayer.cpp +++ b/libs/ui/KisDecorationsWrapperLayer.cpp @@ -1,162 +1,170 @@ /* * Copyright (c) 2019 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 "KisDecorationsWrapperLayer.h" #include "KisDocument.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_painting_assistant.h" +#include "kis_default_bounds.h" struct KisDecorationsWrapperLayer::Private { KisDocument *document = 0; KisPaintDeviceSP fakeOriginalDevice; }; KisDecorationsWrapperLayer::KisDecorationsWrapperLayer(KisDocument *document) : KisExternalLayer(document->image(), "decorations-wrapper-layer", OPACITY_OPAQUE_U8), m_d(new Private) { m_d->document = document; m_d->fakeOriginalDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + m_d->fakeOriginalDevice->setDefaultBounds(new KisDefaultBounds(document->image())); } KisDecorationsWrapperLayer::KisDecorationsWrapperLayer(const KisDecorationsWrapperLayer &rhs) : KisExternalLayer(rhs.image(), "decorations-wrapper-layer", OPACITY_OPAQUE_U8), m_d(new Private) { m_d->document = rhs.m_d->document; - m_d->fakeOriginalDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + m_d->fakeOriginalDevice = new KisPaintDevice(*rhs.m_d->fakeOriginalDevice); } KisDecorationsWrapperLayer::~KisDecorationsWrapperLayer() { } void KisDecorationsWrapperLayer::setDocument(KisDocument *document) { m_d->document = document; KIS_SAFE_ASSERT_RECOVER(image() == document->image()) { setImage(document->image()); } } KisDocument *KisDecorationsWrapperLayer::document() const { return m_d->document; } bool KisDecorationsWrapperLayer::allowAsChild(KisNodeSP) const { return false; } bool KisDecorationsWrapperLayer::accept(KisNodeVisitor &visitor) { return visitor.visit(this); } void KisDecorationsWrapperLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { visitor.visit(this, undoAdapter); } KisNodeSP KisDecorationsWrapperLayer::clone() const { return new KisDecorationsWrapperLayer(*this); } KisPaintDeviceSP KisDecorationsWrapperLayer::original() const { return m_d->fakeOriginalDevice; } KisPaintDeviceSP KisDecorationsWrapperLayer::paintDevice() const { return 0; } bool KisDecorationsWrapperLayer::isFakeNode() const { return true; } bool KisDecorationsWrapperLayer::supportsPerspectiveTransform() const { return false; } +void KisDecorationsWrapperLayer::setImage(KisImageWSP image) +{ + m_d->fakeOriginalDevice->setDefaultBounds(new KisDefaultBounds(image)); + KisExternalLayer::setImage(image); +} + KUndo2Command *KisDecorationsWrapperLayer::crop(const QRect &rect) { return transform(QTransform::fromTranslate(-rect.x(), -rect.y())); } KUndo2Command *KisDecorationsWrapperLayer::transform(const QTransform &transform) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->document, 0); struct UndoCommand : public KUndo2Command { UndoCommand(KisDocument *document, const QTransform &transform) : m_document(document), m_transform(transform) {} void undo() override { doTransform(m_transform.inverted()); } void redo() override { doTransform(m_transform); } private: void doTransform(const QTransform &transform) { const QTransform imageToDocument = QTransform::fromScale(1 / m_document->image()->xRes(), 1 / m_document->image()->yRes()); KisGridConfig gridConfig = m_document->gridConfig(); if (gridConfig.showGrid()) { gridConfig.transform(transform); m_document->setGridConfig(gridConfig); } KisGuidesConfig guidesConfig = m_document->guidesConfig(); if (guidesConfig.hasGuides()) { guidesConfig.transform(imageToDocument.inverted() * transform * imageToDocument); m_document->setGuidesConfig(guidesConfig); } QList assistants = m_document->assistants(); Q_FOREACH(KisPaintingAssistantSP assistant, assistants) { assistant->transform(imageToDocument.inverted() * transform * imageToDocument); } m_document->setAssistants(assistants); } private: KisDocument *m_document; QTransform m_transform; }; return new UndoCommand(m_d->document, transform); } diff --git a/libs/ui/KisDecorationsWrapperLayer.h b/libs/ui/KisDecorationsWrapperLayer.h index 542f7e5a41..cf9bfbc996 100644 --- a/libs/ui/KisDecorationsWrapperLayer.h +++ b/libs/ui/KisDecorationsWrapperLayer.h @@ -1,74 +1,76 @@ /* * Copyright (c) 2019 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 KISDECORATIONSWRAPPERLAYER_H #define KISDECORATIONSWRAPPERLAYER_H #include "kis_types.h" #include "kis_external_layer_iface.h" #include class KisDocument; /** * KisDecorationsWrapperLayer is a fake node for connecting Grids, Guides and Assistants * the KisNodeVisitor system. This allows things like crops and transformations to be * applied to these decoration style items. */ class KisDecorationsWrapperLayer : public KisExternalLayer { Q_OBJECT public: KisDecorationsWrapperLayer(KisDocument *document); KisDecorationsWrapperLayer(const KisDecorationsWrapperLayer &rhs); ~KisDecorationsWrapperLayer(); void setDocument(KisDocument *document); KisDocument* document() const; public: // reimplemented from KisLayer bool allowAsChild(KisNodeSP) const override; bool accept(KisNodeVisitor&) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KisNodeSP clone() const override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; bool isFakeNode() const override; KUndo2Command* crop(const QRect & rect) override; KUndo2Command* transform(const QTransform &transform) override; bool supportsPerspectiveTransform() const override; + void setImage(KisImageWSP image) override; + private: struct Private; const QScopedPointer m_d; }; typedef KisSharedPtr KisDecorationsWrapperLayerSP; typedef KisWeakSharedPtr KisDecorationsWrapperLayerWSP; #endif // KISDECORATIONSWRAPPERLAYER_H diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index a2f3e91d76..8e79c40983 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1790 +1,1791 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 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_dlg_preferences.h" #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 "KoID.h" #include #include #include #include #include #include #include #include "KisProofingConfiguration.h" #include "KoColorConversionTransformation.h" #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "kis_action_registry.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_color_manager.h" #include "kis_config.h" #include "kis_cursor.h" #include "kis_image_config.h" #include "kis_preference_set_registry.h" #include "widgets/kis_cmb_idlist.h" #include #include "kis_file_name_requester.h" #include #include #include #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" # ifndef USE_QT_TABLET_WINDOWS # include # endif #include "config-high-dpi-scale-factor-rounding-policy.h" #endif struct BackupSuffixValidator : public QValidator { BackupSuffixValidator(QObject *parent) : QValidator(parent) , invalidCharacters(QStringList() << "0" << "1" << "2" << "3" << "4" << "5" << "6" << "7" << "8" << "9" << "/" << "\\" << ":" << ";" << " ") {} ~BackupSuffixValidator() override {} const QStringList invalidCharacters; State validate(QString &line, int &/*pos*/) const override { Q_FOREACH(const QString invalidChar, invalidCharacters) { if (line.contains(invalidChar)) { return Invalid; } } return Acceptable; } }; GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg(true); // // Cursor Tab // m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); // // Window Tab // m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); QString xml = cfg.getMDIBackgroundColor(); KoColor mdiColor = KoColor::fromXML(xml); m_mdiColor->setColor(mdiColor); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", true).toBool()); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY m_chkHiDPIFractionalScaling->setChecked(kritarc.value("EnableHiDPIFractionalScaling", true).toBool()); #else m_chkHiDPIFractionalScaling->setVisible(false); #endif chkUsageLogging->setChecked(kritarc.value("LogUsage", true).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); cmbFlowMode->setCurrentIndex((int)!cfg.readEntry("useCreamyAlphaDarken", true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); chkEnableTouchRotation->setChecked(!cfg.disableTouchRotation()); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste()); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled()); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Middle-Click Drag")); //m_cmbKineticScrollingGesture->addItem(i18n("On Right Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivitySlider->setRange(0, 100); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars()); // // File handling // int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); chkHideAutosaveFiles->setChecked(cfg.readEntry("autosavefileshidden", true)); m_chkCompressKra->setChecked(cfg.compressKra()); chkZip64->setChecked(cfg.useZip64()); m_backupFileCheckBox->setChecked(cfg.backupFile()); cmbBackupFileLocation->setCurrentIndex(cfg.readEntry("backupfilelocation", 0)); txtBackupFileSuffix->setText(cfg.readEntry("backupfilesuffix", "~")); QValidator *validator = new BackupSuffixValidator(txtBackupFileSuffix); txtBackupFileSuffix->setValidator(validator); intNumBackupFiles->setValue(cfg.readEntry("numberofbackupfiles", 1)); // // Miscellaneous // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); // // Resources // m_urlCacheDbLocation->setMode(KoFileDialog::OpenDirectory); m_urlCacheDbLocation->setConfigurationName("cachedb_location"); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setMode(KoFileDialog::OpenDirectory); m_urlResourceFolder->setConfigurationName("resource_directory"); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } void GeneralTab::setDefault() { KisConfig cfg(true); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); chkHideAutosaveFiles->setChecked(true); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); cmbBackupFileLocation->setCurrentIndex(0); txtBackupFileSuffix->setText("~"); intNumBackupFiles->setValue(1); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromXML(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); chkZip64->setChecked(cfg.useZip64(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY m_chkHiDPIFractionalScaling->setChecked(true); #endif chkUsageLogging->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); cmbFlowMode->setCurrentIndex(0); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); chkEnableTouchRotation->setChecked(!cfg.disableTouchRotation(true)); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); m_urlCacheDbLocation->setFileName(cfg.readEntry(KisResourceCacheDb::dbLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); m_urlResourceFolder->setFileName(cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::useZip64() { return chkZip64->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::kineticScrollingEnabled() { return m_groupBoxKineticScrollingSettings->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivitySlider->value(); } bool GeneralTab::kineticScrollingHiddenScrollbars() { return m_chkKineticScrollingHideScrollbars->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QGuiApplication::screens().count(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; KisSqueezedComboBox *cmb = new KisSqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } // disable if not Linux as KisColorManager is not yet implemented outside Linux #ifndef Q_OS_LINUX m_page->chkUseSystemMonitorProfile->setChecked(false); m_page->chkUseSystemMonitorProfile->setDisabled(true); m_page->chkUseSystemMonitorProfile->setHidden(true); #endif refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::screens().count(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors()); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg(true); refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::screens().count(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { KisConfig cfg(true); if (useSystemProfile) { QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::screens().count()) { for(int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::screens().count(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA")); KisConfig cfg(true); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::screens().count(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); m_page->chkUseRightMiddleClickWorkaround->setChecked( KisConfig(true).useRightMiddleTabletButtonWorkaround(true)); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { KisConfig cfg(true); m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #else m_page->grpTabletApi->setVisible(false); #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); m_page->chkUseRightMiddleClickWorkaround->setChecked( cfg.useRightMiddleTabletButtonWorkaround()); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #ifdef USE_QT_TABLET_WINDOWS connect(m_page->btnResolutionSettings, SIGNAL(clicked()), SLOT(slotResolutionSettings())); connect(m_page->radioWintab, SIGNAL(toggled(bool)), m_page->btnResolutionSettings, SLOT(setEnabled(bool))); m_page->btnResolutionSettings->setEnabled(m_page->radioWintab->isChecked()); #else m_page->btnResolutionSettings->setVisible(false); #endif #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS #include "KisDlgCustomTabletResolution.h" #endif void TabletSettingsTab::slotResolutionSettings() { #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS KisDlgCustomTabletResolution dlg(this); dlg.exec(); #endif } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { return KisImageConfig(true).totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg(true); const double totalRAM = cfg.totalRAM(); lblTotalMemory->setText(KFormat().formatByteSize(totalRAM * 1024 * 1024, 0, KFormat::IECBinaryDialect, KFormat::UnitMegaByte)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); + connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 300); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); intCachedFramesSizeLimit->setRange(1, 10000); intCachedFramesSizeLimit->setSuffix(i18n(" px")); intCachedFramesSizeLimit->setSingleStep(1); intCachedFramesSizeLimit->setPageStep(1000); intRegionOfInterestMargin->setRange(1, 100); intRegionOfInterestMargin->setSuffix(i18n(" %")); intRegionOfInterestMargin->setSingleStep(1); intRegionOfInterestMargin->setPageStep(10); connect(chkCachedFramesSizeLimit, SIGNAL(toggled(bool)), intCachedFramesSizeLimit, SLOT(setEnabled(bool))); connect(chkUseRegionOfInterest, SIGNAL(toggled(bool)), intRegionOfInterestMargin, SLOT(setEnabled(bool))); #ifndef Q_OS_WIN // AVX workaround is needed on Windows+GCC only chkDisableAVXOptimizations->setVisible(false); #endif load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg(true); sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2(true); chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); #ifdef Q_OS_WIN chkDisableAVXOptimizations->setChecked(cfg2.disableAVXOptimizations(requestDefault)); #endif chkBackgroundCacheGeneration->setChecked(cfg2.calculateAnimationCacheInBackground(requestDefault)); } if (cfg.useOnDiskAnimationCacheSwapping(requestDefault)) { optOnDisk->setChecked(true); } else { optInMemory->setChecked(true); } chkCachedFramesSizeLimit->setChecked(cfg.useAnimationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setValue(cfg.animationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setEnabled(chkCachedFramesSizeLimit->isChecked()); chkUseRegionOfInterest->setChecked(cfg.useAnimationCacheRegionOfInterest(requestDefault)); intRegionOfInterestMargin->setValue(cfg.animationCacheRegionOfInterestMargin(requestDefault) * 100.0); intRegionOfInterestMargin->setEnabled(chkUseRegionOfInterest->isChecked()); } void PerformanceTab::save() { KisImageConfig cfg(false); cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2(true); cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); #ifdef Q_OS_WIN cfg2.setDisableAVXOptimizations(chkDisableAVXOptimizations->isChecked()); #endif cfg2.setCalculateAnimationCacheInBackground(chkBackgroundCacheGeneration->isChecked()); } cfg.setUseOnDiskAnimationCacheSwapping(optOnDisk->isChecked()); cfg.setUseAnimationCacheFrameSizeLimit(chkCachedFramesSizeLimit->isChecked()); cfg.setAnimationCacheFrameSizeLimit(intCachedFramesSizeLimit->value()); cfg.setUseAnimationCacheRegionOfInterest(chkUseRegionOfInterest->isChecked()); cfg.setAnimationCacheRegionOfInterestMargin(intRegionOfInterestMargin->value() / 100.0); } void PerformanceTab::selectSwapDir() { KisImageConfig cfg(true); QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" #include "opengl/KisOpenGLModeProber.h" #include "opengl/KisScreenInformationAdapter.h" #include #include QString colorSpaceString(KisSurfaceColorSpace cs, int depth) { const QString csString = #ifdef HAVE_HDR cs == KisSurfaceColorSpace::bt2020PQColorSpace ? "Rec. 2020 PQ" : cs == KisSurfaceColorSpace::scRGBColorSpace ? "Rec. 709 Linear" : #endif cs == KisSurfaceColorSpace::sRGBColorSpace ? "sRGB" : cs == KisSurfaceColorSpace::DefaultColorSpace ? "sRGB" : "Unknown Color Space"; return QString("%1 (%2 bit)").arg(csString).arg(depth); } int formatToIndex(KisConfig::RootSurfaceFormat fmt) { return fmt == KisConfig::BT2020_PQ ? 1 : fmt == KisConfig::BT709_G10 ? 2 : 0; } KisConfig::RootSurfaceFormat indexToFormat(int value) { return value == 1 ? KisConfig::BT2020_PQ : value == 2 ? KisConfig::BT709_G10 : KisConfig::BT709_G22; } DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); const QString rendererSoftwareText = i18nc("canvas renderer", "Software Renderer (very slow)"); #ifdef Q_OS_WIN const QString rendererOpenGLESText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); #else const QString rendererOpenGLESText = i18nc("canvas renderer", "OpenGL ES"); #endif const KisOpenGL::OpenGLRenderer renderer = KisOpenGL::getCurrentOpenGLRenderer(); lblCurrentRenderer->setText(renderer == KisOpenGL::RendererOpenGLES ? rendererOpenGLESText : renderer == KisOpenGL::RendererDesktopGL ? rendererOpenGLText : renderer == KisOpenGL::RendererSoftware ? rendererSoftwareText : i18nc("canvas renderer", "Unknown")); cmbPreferredRenderer->clear(); const KisOpenGL::OpenGLRenderers supportedRenderers = KisOpenGL::getSupportedOpenGLRenderers(); const bool onlyOneRendererSupported = supportedRenderers == KisOpenGL::RendererDesktopGL || supportedRenderers == KisOpenGL::RendererOpenGLES || supportedRenderers == KisOpenGL::RendererSoftware; if (!onlyOneRendererSupported) { QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererOpenGLES) { qtPreferredRendererText = rendererOpenGLESText; } else if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererSoftware) { qtPreferredRendererText = rendererSoftwareText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbPreferredRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbPreferredRenderer->setCurrentIndex(0); } else { cmbPreferredRenderer->setEnabled(false); } if (supportedRenderers & KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #ifdef Q_OS_WIN if (supportedRenderers & KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->addItem(rendererOpenGLESText, KisOpenGL::RendererOpenGLES); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererOpenGLES) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } if (supportedRenderers & KisOpenGL::RendererSoftware) { cmbPreferredRenderer->addItem(rendererSoftwareText, KisOpenGL::RendererSoftware); if (KisOpenGL::getUserPreferredOpenGLRendererConfig() == KisOpenGL::RendererSoftware) { cmbPreferredRenderer->setCurrentIndex(cmbPreferredRenderer->count() - 1); } } #endif if (!(supportedRenderers & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES | KisOpenGL::RendererSoftware))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } lblCurrentDisplayFormat->setText(""); lblCurrentRootSurfaceFormat->setText(""); lblHDRWarning->setText(""); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::sRGBColorSpace, 8)); #ifdef HAVE_HDR cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::bt2020PQColorSpace, 10)); cmbPreferedRootSurfaceFormat->addItem(colorSpaceString(KisSurfaceColorSpace::scRGBColorSpace, 16)); #endif cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); QOpenGLContext *context = QOpenGLContext::currentContext(); if (!context) { context = QOpenGLContext::globalShareContext(); } if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (screen && adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { QStringList toolTip; toolTip << i18n("Display Id: %1", info.screen->name()); toolTip << i18n("Display Name: %1 %2", info.screen->manufacturer(), info.screen->model()); toolTip << i18n("Min Luminance: %1", info.minLuminance); toolTip << i18n("Max Luminance: %1", info.maxLuminance); toolTip << i18n("Max Full Frame Luminance: %1", info.maxFullFrameLuminance); toolTip << i18n("Red Primary: %1, %2", info.redPrimary[0], info.redPrimary[1]); toolTip << i18n("Green Primary: %1, %2", info.greenPrimary[0], info.greenPrimary[1]); toolTip << i18n("Blue Primary: %1, %2", info.bluePrimary[0], info.bluePrimary[1]); toolTip << i18n("White Point: %1, %2", info.whitePoint[0], info.whitePoint[1]); lblCurrentDisplayFormat->setToolTip(toolTip.join('\n')); lblCurrentDisplayFormat->setText(colorSpaceString(info.colorSpace, info.bitsPerColor)); } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); } } else { lblCurrentDisplayFormat->setToolTip(""); lblCurrentDisplayFormat->setText(i18n("Unknown")); qWarning() << "Failed to fetch display info:" << adapter.errorString(); } const QSurfaceFormat currentFormat = KisOpenGLModeProber::instance()->surfaceformatInUse(); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace colorSpace = currentFormat.colorSpace(); #else KisSurfaceColorSpace colorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif lblCurrentRootSurfaceFormat->setText(colorSpaceString(colorSpace, currentFormat.redBufferSize())); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(cfg.rootSurfaceFormat())); connect(cmbPreferedRootSurfaceFormat, SIGNAL(currentIndexChanged(int)), SLOT(slotPreferredSurfaceFormatChanged(int))); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } #ifndef HAVE_HDR grpHDRSettings->setVisible(false); tabWidget->removeTab(tabWidget->indexOf(tabHDR)); #endif const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("
    "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
  • "); text.append(warning.toHtmlEscaped()); text.append("
  • "); } text.append("
"); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg(true); cmbPreferredRenderer->setCurrentIndex(0); if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererOpenGLES))) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor(true)); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor(true).alphaF()); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); cmbPreferedRootSurfaceFormat->setCurrentIndex(formatToIndex(KisConfig::BT709_G22)); slotPreferredSurfaceFormatChanged(cmbPreferedRootSurfaceFormat->currentIndex()); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } void DisplaySettingsTab::slotPreferredSurfaceFormatChanged(int index) { Q_UNUSED(index); QOpenGLContext *context = QOpenGLContext::currentContext(); if (context) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *screen = QGuiApplication::screenAt(rect().center()); #else QScreen *screen = 0; #endif KisScreenInformationAdapter adapter(context); if (adapter.isValid()) { KisScreenInformationAdapter::ScreenInfo info = adapter.infoForScreen(screen); if (info.isValid()) { if (cmbPreferedRootSurfaceFormat->currentIndex() != formatToIndex(KisConfig::BT709_G22) && info.colorSpace == KisSurfaceColorSpace::sRGBColorSpace) { lblHDRWarning->setText(i18n("WARNING: current display doesn't support HDR rendering")); } else { lblHDRWarning->setText(""); } } } } } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); setFaceType(KPageDialog::List); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); m_pages << page; addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); m_pages << page; addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); m_pages << page; // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); m_pages << page; addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); m_pages << page; addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); m_pages << page; addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); m_pages << page; addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); m_pages << page; addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); m_pages << page; QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(i18nc("@action:button", "Restore Defaults")); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); QStringList keys = preferenceSetRegistry->keys(); keys.sort(); Q_FOREACH(const QString &key, keys) { KisAbstractPreferenceSetFactory *preferenceSetFactory = preferenceSetRegistry->value(key); KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); KisConfig cfg(true); QString currentPageName = cfg.readEntry("KisDlgPreferences/CurrentPage"); Q_FOREACH(KPageWidgetItem *page, m_pages) { if (page->objectName() == currentPageName) { setCurrentPage(page); break; } } } KisDlgPreferences::~KisDlgPreferences() { KisConfig cfg(true); cfg.writeEntry("KisDlgPreferences/CurrentPage", currentPage()->objectName()); } void KisDlgPreferences::showEvent(QShowEvent *event){ KPageDialog::showEvent(event); button(QDialogButtonBox::Cancel)->setAutoDefault(false); button(QDialogButtonBox::Ok)->setAutoDefault(false); button(QDialogButtonBox::RestoreDefaults)->setAutoDefault(false); button(QDialogButtonBox::Cancel)->setDefault(false); button(QDialogButtonBox::Ok)->setDefault(false); button(QDialogButtonBox::RestoreDefaults)->setDefault(false); } void KisDlgPreferences::slotButtonClicked(QAbstractButton *button) { if (buttonBox()->buttonRole(button) == QDialogButtonBox::RejectRole) { m_cancelClicked = true; } } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { connect(this->buttonBox(), SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonClicked(QAbstractButton*))); int retval = exec(); Q_UNUSED(retval) if (!m_cancelClicked) { // General settings KisConfig cfg(false); cfg.setNewCursorStyle(m_general->cursorStyle()); cfg.setNewOutlineStyle(m_general->outlineStyle()); cfg.setShowRootLayer(m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(m_general->showOutlineWhilePainting()); cfg.setForceAlwaysFullSizedOutline(!m_general->m_changeBrushOutline->isChecked()); cfg.setSessionOnStartup(m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(m_general->saveSessionOnQuit()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", m_general->mdiMode()); cfg.setMDIBackgroundColor(m_general->m_mdiColor->color().toXML()); cfg.setMDIBackgroundImage(m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(m_general->autoSaveInterval()); cfg.writeEntry("autosavefileshidden", m_general->chkHideAutosaveFiles->isChecked()); cfg.setBackupFile(m_general->m_backupFileCheckBox->isChecked()); cfg.writeEntry("backupfilelocation", m_general->cmbBackupFileLocation->currentIndex()); cfg.writeEntry("backupfilesuffix", m_general->txtBackupFileSuffix->text()); cfg.writeEntry("numberofbackupfiles", m_general->intNumBackupFiles->value()); cfg.setShowCanvasMessages(m_general->showCanvasMessages()); cfg.setCompressKra(m_general->compressKra()); cfg.setUseZip64(m_general->useZip64()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", m_general->m_chkHiDPI->isChecked()); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY kritarc.setValue("EnableHiDPIFractionalScaling", m_general->m_chkHiDPIFractionalScaling->isChecked()); #endif kritarc.setValue("EnableSingleApplication", m_general->m_chkSingleApplication->isChecked()); kritarc.setValue("LogUsage", m_general->chkUsageLogging->isChecked()); cfg.setToolOptionsInDocker(m_general->toolOptionsInDocker()); cfg.writeEntry("useCreamyAlphaDarken", (bool)!m_general->cmbFlowMode->currentIndex()); cfg.setKineticScrollingEnabled(m_general->kineticScrollingEnabled()); cfg.setKineticScrollingGesture(m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingHideScrollbars(m_general->kineticScrollingHiddenScrollbars()); cfg.setSwitchSelectionCtrlAlt(m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!m_general->chkEnableTouch->isChecked()); cfg.setDisableTouchRotation(!m_general->chkEnableTouchRotation->isChecked()); cfg.setActivateTransformToolAfterPaste(m_general->chkEnableTranformToolAfterPaste->isChecked()); cfg.setConvertToImageColorspaceOnImport(m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(m_general->undoStackSize()); cfg.setFavoritePresets(m_general->favoritePresets()); cfg.writeEntry(KisResourceCacheDb::dbLocationKey, m_general->m_urlCacheDbLocation->fileName()); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_general->m_urlResourceFolder->fileName()); // Color settings cfg.setUseSystemMonitorProfile(m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::screens().count(); ++i) { if (m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, m_colorSettings->m_monitorProfileWidgets[i]->currentUnsqueezedText(), m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(false); cfgImage.setDefaultProofingConfig(m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), m_colorSettings->m_page->cmbProofingIntent->currentIndex(), m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), m_colorSettings->m_page->gamutAlarm->color(), (double)m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setForcePaletteColors(m_colorSettings->m_page->chkForcePaletteColor->isChecked()); cfg.setPasteBehaviour(m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( m_tabletSettings->m_page->pressureCurve->curve().toString() ); cfg.setUseRightMiddleTabletButtonWorkaround( m_tabletSettings->m_page->chkUseRightMiddleClickWorkaround->isChecked()); #if defined Q_OS_WIN && (!defined USE_QT_TABLET_WINDOWS || defined QT_HAS_WINTAB_SWITCH) #ifdef USE_QT_TABLET_WINDOWS // ask Qt if WinInk is actually available const bool isWinInkAvailable = true; #else const bool isWinInkAvailable = KisTabletSupportWin8::isAvailable(); #endif if (isWinInkAvailable) { cfg.setUseWin8PointerInput(m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif m_performanceSettings->save(); if (!cfg.useOpenGL() && m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); if (m_displaySettings->grpOpenGL->isChecked()) { KisOpenGL::OpenGLRenderer renderer = static_cast( m_displaySettings->cmbPreferredRenderer->itemData( m_displaySettings->cmbPreferredRenderer->currentIndex()).toInt()); KisOpenGL::setUserPreferredOpenGLRendererConfig(renderer); } else { KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::RendererNone); } cfg.setUseOpenGLTextureBuffer(m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(m_displaySettings->chkDisableVsync->isChecked()); cfg.setRootSurfaceFormat(&kritarc, indexToFormat(m_displaySettings->cmbPreferedRootSurfaceFormat->currentIndex())); cfg.setCheckSize(m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(m_displaySettings->hideScrollbars->isChecked()); KoColor c = m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(m_displaySettings->sldSelectionOverlayOpacity->value()); cfgImage.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); m_authorPage->apply(); cfg.logImportantSettings(); } return !m_cancelClicked; } diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp index 4af0df5092..2654a92bb9 100644 --- a/libs/ui/flake/kis_shape_selection.cpp +++ b/libs/ui/flake/kis_shape_selection.cpp @@ -1,416 +1,416 @@ /* * Copyright (c) 2010 Sven Langkamp * Copyright (c) 2011 Jan Hambrecht * * 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_shape_selection.h" #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 "kis_shape_selection_model.h" #include "kis_shape_selection_canvas.h" #include "kis_take_all_shapes_command.h" #include "kis_image_view_converter.h" #include "kis_shape_layer.h" #include KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this)) , m_image(image) , m_shapeControllerBase(shapeControllerBase) { Q_ASSERT(m_image); setShapeId("KisShapeSelection"); setSelectable(false); m_converter = new KisImageViewConverter(image); m_canvas = new KisShapeSelectionCanvas(shapeControllerBase); m_canvas->shapeManager()->addShape(this); m_model->setObjectName("KisShapeSelectionModel"); m_model->moveToThread(image->thread()); m_canvas->setObjectName("KisShapeSelectionCanvas"); m_canvas->moveToThread(image->thread()); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); } KisShapeSelection::~KisShapeSelection() { m_model->setShapeSelection(0); delete m_canvas; delete m_converter; } KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this)) { m_image = rhs.m_image; m_shapeControllerBase = rhs.m_shapeControllerBase; m_converter = new KisImageViewConverter(m_image); m_canvas = new KisShapeSelectionCanvas(m_shapeControllerBase); // TODO: refactor shape selection to pass signals // via KoShapeManager, not via the model m_canvas->shapeManager()->setUpdatesBlocked(true); m_model->setUpdatesEnabled(false); m_canvas->shapeManager()->addShape(this); Q_FOREACH (KoShape *shape, rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } this->addShape(clonedShape); } m_canvas->shapeManager()->setUpdatesBlocked(false); m_model->setUpdatesEnabled(true); } KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection) { /** * TODO: make cloning of vector selections safe! Right now it crashes * on Windows because of manipulations with timers from non-gui thread. */ KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); return new KisShapeSelection(*this, selection); } bool KisShapeSelection::saveSelection(KoStore * store) const { const QSizeF sizeInPx = m_image->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes()); return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeSelection::loadSelection(KoStore* store) { QSizeF fragmentSize; // unused! // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes())); const qreal resolutionPPI = 72.0 * m_image->xRes(); QList shapes; if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); shapes = KisShapeLayer::createShapesFromSvg(&storeDev, "", m_image->bounds(), resolutionPPI, m_canvas->shapeController()->resourceManager(), &fragmentSize); store->close(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { dbgKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { dbgKrita << "No office:body found!"; //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { dbgKrita << "No office:drawing found!"; //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { dbgKrita << "No office:drawing found!"; //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } else { dbgKrita << "No master page found!"; return false; } KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); KoShapeLoadingContext shapeContext(context, 0); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeSelection::setUpdatesEnabled(bool enabled) { m_model->setUpdatesEnabled(enabled); } bool KisShapeSelection::updatesEnabled() const { return m_model->updatesEnabled(); } KUndo2Command* KisShapeSelection::resetToEmpty() { - return new KisTakeAllShapesCommand(this, true); + return new KisTakeAllShapesCommand(this, true, false); } bool KisShapeSelection::isEmpty() const { return !m_model->count(); } QPainterPath KisShapeSelection::outlineCache() const { return m_outline; } bool KisShapeSelection::outlineCacheValid() const { return true; } void KisShapeSelection::recalculateOutlineCache() { QList shapesList = shapes(); QPainterPath outline; Q_FOREACH (KoShape * shape, shapesList) { QTransform shapeMatrix = shape->absoluteTransformation(0); outline = outline.united(shapeMatrix.map(shape->outline())); } QTransform resolutionMatrix; resolutionMatrix.scale(m_image->xRes(), m_image->yRes()); m_outline = resolutionMatrix.map(outline); } void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection) { Q_ASSERT(projection); Q_ASSERT(m_image); QRectF boundingRect = outlineCache().boundingRect(); renderSelection(projection, boundingRect.toAlignedRect()); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); renderSelection(projection, r); } void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect) { KIS_SAFE_ASSERT_RECOVER_RETURN(projection); KIS_SAFE_ASSERT_RECOVER_RETURN(m_image); const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; const QPainterPath selectionOutline = outlineCache(); if (*projection->defaultPixel().data() == OPACITY_TRANSPARENT_U8) { projection->clear(requestedRect); } else { KoColor transparentColor(Qt::transparent, projection->colorSpace()); projection->fill(requestedRect, transparentColor); } const QRect r = requestedRect & selectionOutline.boundingRect().toAlignedRect(); QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter maskPainter(&polygonMaskImage); maskPainter.setRenderHint(QPainter::Antialiasing, true); // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) { for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) { maskPainter.fillRect(polygonMaskImage.rect(), Qt::black); maskPainter.translate(-x, -y); maskPainter.fillPath(selectionOutline, Qt::white); maskPainter.translate(x, y); qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH); qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT); KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight)); while (it.nextPixel()) { (*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y)); } } } } KoShapeManager* KisShapeSelection::shapeManager() const { return m_canvas->shapeManager(); } KisShapeSelectionFactory::KisShapeSelectionFactory() : KoShapeFactoryBase("KisShapeSelection", "selection shape container") { setHidden(true); } void KisShapeSelection::moveX(qint32 x) { const QPointF diff(x / m_image->xRes(), 0); emit sigMoveShapes(diff); } void KisShapeSelection::moveY(qint32 y) { const QPointF diff(0, y / m_image->yRes()); emit sigMoveShapes(diff); } void KisShapeSelection::slotMoveShapes(const QPointF &diff) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(pos + diff); } } } // TODO same code as in vector layer, refactor! KUndo2Command* KisShapeSelection::transform(const QTransform &transform) { QList shapes = m_canvas->shapeManager()->shapes(); if(shapes.isEmpty()) return 0; QTransform realTransform = m_converter->documentToView() * transform * m_converter->viewToDocument(); QList oldTransformations; QList newTransformations; // this code won't work if there are shapes, that inherit the transformation from the parent container. // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that. Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); if (dynamic_cast(shape)) { newTransformations.append(oldTransform); } else { QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform*oldTransform); } } return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations); } diff --git a/libs/ui/flake/kis_shape_selection_model.cpp b/libs/ui/flake/kis_shape_selection_model.cpp index 2cb7a29d65..8a1e9ab558 100644 --- a/libs/ui/flake/kis_shape_selection_model.cpp +++ b/libs/ui/flake/kis_shape_selection_model.cpp @@ -1,174 +1,178 @@ /* * Copyright (c) 2007 Sven Langkamp * * 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_shape_selection_model.h" #include "kis_debug.h" #include #include #include #include "kis_shape_selection.h" #include "kis_selection.h" #include "kis_image.h" #include "kis_update_selection_job.h" KisShapeSelectionModel::KisShapeSelectionModel(KisImageWSP image, KisSelectionWSP selection, KisShapeSelection* shapeSelection) : m_image(image) , m_parentSelection(selection) , m_shapeSelection(shapeSelection) , m_updatesEnabled(true) { } KisShapeSelectionModel::~KisShapeSelectionModel() { m_image = 0; m_parentSelection = 0; } void KisShapeSelectionModel::requestUpdate(const QRect &updateRect) { m_shapeSelection->recalculateOutlineCache(); if (m_updatesEnabled) { m_parentSelection->requestCompressedProjectionUpdate(updateRect); } } void KisShapeSelectionModel::add(KoShape *child) { if (!m_shapeSelection) return; if (m_shapeMap.contains(child)) return; child->setStroke(KoShapeStrokeModelSP()); child->setBackground( QSharedPointer(0)); m_shapeMap.insert(child, child->boundingRect()); m_shapeSelection->shapeManager()->addShape(child); QRect updateRect = child->boundingRect().toAlignedRect(); if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); updateRect = matrix.mapRect(updateRect); } if (m_shapeMap.count() == 1) { // The shape is the first one, so the shape selection just got created // Pixel selection provides no longer the datamanager of the selection // so update the whole selection requestUpdate(QRect()); } else { requestUpdate(updateRect); } } void KisShapeSelectionModel::remove(KoShape *child) { if (!m_shapeMap.contains(child)) return; QRect updateRect = child->boundingRect().toAlignedRect(); m_shapeMap.remove(child); if (m_shapeSelection) { m_shapeSelection->shapeManager()->remove(child); } if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); updateRect = matrix.mapRect(updateRect); if (m_shapeSelection) { // No m_shapeSelection indicates the selection is being deleted requestUpdate(updateRect); + + if (m_updatesEnabled && m_shapeMap.isEmpty()) { + m_parentSelection->notifyShapeSelectionBecameEmpty(); + } } } } void KisShapeSelectionModel::setUpdatesEnabled(bool enabled) { m_updatesEnabled = enabled; } bool KisShapeSelectionModel::updatesEnabled() const { return m_updatesEnabled; } void KisShapeSelectionModel::setClipped(const KoShape *child, bool clipping) { Q_UNUSED(child); Q_UNUSED(clipping); } bool KisShapeSelectionModel::isClipped(const KoShape *child) const { Q_UNUSED(child); return false; } void KisShapeSelectionModel::setInheritsTransform(const KoShape *shape, bool inherit) { Q_UNUSED(shape); Q_UNUSED(inherit); } bool KisShapeSelectionModel::inheritsTransform(const KoShape *shape) const { Q_UNUSED(shape); return false; } int KisShapeSelectionModel::count() const { return m_shapeMap.count(); } QList KisShapeSelectionModel::shapes() const { return QList(m_shapeMap.keys()); } void KisShapeSelectionModel::containerChanged(KoShapeContainer *, KoShape::ChangeType) { } void KisShapeSelectionModel::childChanged(KoShape * child, KoShape::ChangeType type) { if (!m_shapeSelection) return; // TODO: check if still needed if (type == KoShape::ParentChanged) return; QRectF changedRect = m_shapeMap[child]; changedRect = changedRect.united(child->boundingRect()); m_shapeMap[child] = child->boundingRect(); if (m_image.isValid()) { QTransform matrix; matrix.scale(m_image->xRes(), m_image->yRes()); changedRect = matrix.mapRect(changedRect); } requestUpdate(changedRect.toAlignedRect()); } void KisShapeSelectionModel::setShapeSelection(KisShapeSelection* selection) { m_shapeSelection = selection; } diff --git a/libs/ui/flake/kis_take_all_shapes_command.cpp b/libs/ui/flake/kis_take_all_shapes_command.cpp index b686651801..ddd1ccb50e 100644 --- a/libs/ui/flake/kis_take_all_shapes_command.cpp +++ b/libs/ui/flake/kis_take_all_shapes_command.cpp @@ -1,73 +1,74 @@ /* * Copyright (c) 2013 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_take_all_shapes_command.h" #include #include "kis_shape_selection.h" #include -KisTakeAllShapesCommand::KisTakeAllShapesCommand(KisShapeSelection *shapeSelection, bool takeSilently) +KisTakeAllShapesCommand::KisTakeAllShapesCommand(KisShapeSelection *shapeSelection, bool takeSilently, bool restoreSilently) : KUndo2Command(kundo2_i18n("Clear Vector Selection")), m_shapeSelection(shapeSelection), - m_takeSilently(takeSilently) + m_takeSilently(takeSilently), + m_restoreSilently(restoreSilently) { } KisTakeAllShapesCommand::~KisTakeAllShapesCommand() { Q_FOREACH (KoShape *shape, m_shapes) { delete shape; } } void KisTakeAllShapesCommand::redo() { if (m_takeSilently) { m_shapeSelection->setUpdatesEnabled(false); } m_shapes = m_shapeSelection->shapes(); Q_FOREACH (KoShape *shape, m_shapes) { m_shapeSelection->removeShape(shape); } if (m_takeSilently) { m_shapeSelection->setUpdatesEnabled(true); } } void KisTakeAllShapesCommand::undo() { - if (m_takeSilently) { + if (m_restoreSilently) { m_shapeSelection->setUpdatesEnabled(false); } Q_FOREACH (KoShape *shape, m_shapes) { m_shapeSelection->addShape(shape); } m_shapes.clear(); - if (m_takeSilently) { + if (m_restoreSilently) { m_shapeSelection->setUpdatesEnabled(true); } } diff --git a/libs/ui/flake/kis_take_all_shapes_command.h b/libs/ui/flake/kis_take_all_shapes_command.h index e07f4af8e0..4a002d18e8 100644 --- a/libs/ui/flake/kis_take_all_shapes_command.h +++ b/libs/ui/flake/kis_take_all_shapes_command.h @@ -1,48 +1,51 @@ /* * Copyright (c) 2013 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_TAKE_ALL_SHAPES_COMMAND_H #define __KIS_TAKE_ALL_SHAPES_COMMAND_H #include #include "kundo2command.h" #include "kritaui_export.h" #include "kis_types.h" class KoShape; class KisShapeSelection; class KisTakeAllShapesCommand : public KUndo2Command { public: - KisTakeAllShapesCommand(KisShapeSelection *shapeSelection, bool takeSilently); + KisTakeAllShapesCommand(KisShapeSelection *shapeSelection, + bool takeSilently, + bool restoreSilently); ~KisTakeAllShapesCommand() override; void redo() override; void undo() override; private: KisShapeSelection *m_shapeSelection; QList m_shapes; bool m_takeSilently; + bool m_restoreSilently; }; #endif /* __KIS_TAKE_ALL_SHAPES_COMMAND_H */ diff --git a/libs/ui/kis_file_layer.cpp b/libs/ui/kis_file_layer.cpp index 048c02cba1..843836d904 100644 --- a/libs/ui/kis_file_layer.cpp +++ b/libs/ui/kis_file_layer.cpp @@ -1,252 +1,261 @@ /* * Copyright (c) 2013 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_file_layer.h" #include #include #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "kis_node_progress_proxy.h" #include "kis_node_visitor.h" #include "kis_image.h" #include "kis_types.h" #include "commands_new/kis_node_move_command2.h" #include "kis_default_bounds.h" #include "kis_layer_properties_icons.h" #include #include #include KisFileLayer::KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity) { /** * Set default paint device for a layer. It will be used in case * the file does not exist anymore. Or course, this can happen only * in the failing execution path. */ m_paintDevice = new KisPaintDevice(image->colorSpace()); + m_paintDevice->setDefaultBounds(new KisDefaultBounds(image)); + connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int))); } KisFileLayer::KisFileLayer(KisImageWSP image, const QString &basePath, const QString &filename, ScalingMethod scaleToImageResolution, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity) , m_basePath(basePath) , m_filename(filename) , m_scalingMethod(scaleToImageResolution) { /** * Set default paint device for a layer. It will be used in case * the file does not exist anymore. Or course, this can happen only * in the failing execution path. */ m_paintDevice = new KisPaintDevice(image->colorSpace()); + m_paintDevice->setDefaultBounds(new KisDefaultBounds(image)); connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int))); QFileInfo fi(path()); if (fi.exists()) { m_loader.setPath(path()); m_loader.reloadImage(); } } KisFileLayer::~KisFileLayer() { } KisFileLayer::KisFileLayer(const KisFileLayer &rhs) : KisExternalLayer(rhs) { m_basePath = rhs.m_basePath; m_filename = rhs.m_filename; KIS_SAFE_ASSERT_RECOVER_NOOP(QFile::exists(path())); m_scalingMethod = rhs.m_scalingMethod; m_paintDevice = new KisPaintDevice(*rhs.m_paintDevice); connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int))); m_loader.setPath(path()); } QIcon KisFileLayer::icon() const { return KisIconUtils::loadIcon("fileLayer"); } void KisFileLayer::resetCache() { m_loader.reloadImage(); } const KoColorSpace *KisFileLayer::colorSpace() const { return m_paintDevice->colorSpace(); } KisPaintDeviceSP KisFileLayer::original() const { return m_paintDevice; } KisPaintDeviceSP KisFileLayer::paintDevice() const { return 0; } void KisFileLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id== KisLayerPropertiesIcons::openFileLayerFile.id()) { if (property.state.toBool() == false) { openFile(); } } } } KisBaseNode::PropertyList KisFileLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisLayer::sectionModelProperties(); l << KisBaseNode::Property(KoID("sourcefile", i18n("File")), m_filename); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::openFileLayerFile, true); return l; } void KisFileLayer::setFileName(const QString &basePath, const QString &filename) { m_basePath = basePath; m_filename = filename; QFileInfo fi(path()); if (fi.exists()) { m_loader.setPath(path()); m_loader.reloadImage(); } } QString KisFileLayer::fileName() const { return m_filename; } QString KisFileLayer::path() const { if (m_basePath.isEmpty()) { return m_filename; } else { return QDir(m_basePath).filePath(QDir::cleanPath(m_filename)); } } void KisFileLayer::openFile() const { bool fileAlreadyOpen = false; Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { if (doc->url().toLocalFile()==path()){ fileAlreadyOpen = true; } } if (!fileAlreadyOpen) { KisPart::instance()->openExistingFile(QUrl::fromLocalFile(QFileInfo(path()).absoluteFilePath())); } } KisFileLayer::ScalingMethod KisFileLayer::scalingMethod() const { return m_scalingMethod; } void KisFileLayer::setScalingMethod(ScalingMethod method) { m_scalingMethod = method; } void KisFileLayer::slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes) { qint32 oldX = x(); qint32 oldY = y(); const QRect oldLayerExtent = m_paintDevice->extent(); m_paintDevice->makeCloneFrom(projection, projection->extent()); m_paintDevice->setDefaultBounds(new KisDefaultBounds(image())); QSize size = projection->exactBounds().size(); if (m_scalingMethod == ToImagePPI && (image()->xRes() != xRes || image()->yRes() != yRes)) { qreal xscale = image()->xRes() / xRes; qreal yscale = image()->yRes() / yRes; KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic")); worker.run(); } else if (m_scalingMethod == ToImageSize) { QSize sz = size; sz.scale(image()->size(), Qt::KeepAspectRatio); qreal xscale = (qreal)sz.width() / (qreal)size.width(); qreal yscale = (qreal)sz.height() / (qreal)size.height(); KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic")); worker.run(); } m_paintDevice->setX(oldX); m_paintDevice->setY(oldY); setDirty(m_paintDevice->extent() | oldLayerExtent); } KisNodeSP KisFileLayer::clone() const { return KisNodeSP(new KisFileLayer(*this)); } bool KisFileLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } bool KisFileLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisFileLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KUndo2Command* KisFileLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisFileLayer::transform(const QTransform &/*transform*/) { warnKrita << "WARNING: File Layer does not support transformations!" << name(); return 0; } +void KisFileLayer::setImage(KisImageWSP image) +{ + m_paintDevice->setDefaultBounds(new KisDefaultBounds(image)); + KisExternalLayer::setImage(image); +} + diff --git a/libs/ui/kis_file_layer.h b/libs/ui/kis_file_layer.h index 684943296b..5a31c67eef 100644 --- a/libs/ui/kis_file_layer.h +++ b/libs/ui/kis_file_layer.h @@ -1,99 +1,101 @@ /* * Copyright (c) 2013 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_FILE_LAYER_H #define KIS_FILE_LAYER_H #include "kritaui_export.h" #include "kis_external_layer_iface.h" #include "kis_safe_document_loader.h" /** * @brief The KisFileLayer class loads a particular file as a layer into the layer stack. */ class KRITAUI_EXPORT KisFileLayer : public KisExternalLayer { Q_OBJECT public: enum ScalingMethod { None, ToImageSize, ToImagePPI }; KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity); /** * @brief KisFileLayer create a new file layer with the given file * @param image the image the file layer will belong to * @param basePath the path to the image, if it has been saved before. * @param filename the path to the file, relative to the basePath * @param scalingMethod @see ScalingMethod * @param name the name of the layer * @param opacity the opacity of the layer */ KisFileLayer(KisImageWSP image, const QString& basePath, const QString &filename, ScalingMethod scalingMethod, const QString &name, quint8 opacity); ~KisFileLayer() override; KisFileLayer(const KisFileLayer& rhs); QIcon icon() const override; void resetCache() override; const KoColorSpace *colorSpace() const override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; KisBaseNode::PropertyList sectionModelProperties() const override; /** * @brief setFileName replace the existing file with a new one * @param basePath the path to the image, if it has been saved before. * @param filename the path to the file, relative to the basePath */ void setFileName(const QString &basePath, const QString &filename); QString fileName() const; QString path() const; void openFile() const; ScalingMethod scalingMethod() const; void setScalingMethod(ScalingMethod method); KisNodeSP clone() const override; bool allowAsChild(KisNodeSP) const override; bool accept(KisNodeVisitor&) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; KUndo2Command* crop(const QRect & rect) override; KUndo2Command* transform(const QTransform &transform) override; + void setImage(KisImageWSP image) override; + public Q_SLOTS: void slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes); private: QString m_basePath; QString m_filename; ScalingMethod m_scalingMethod; KisPaintDeviceSP m_paintDevice; KisSafeDocumentLoader m_loader; }; #endif // KIS_FILE_LAYER_H diff --git a/libs/ui/kis_statusbar.cc b/libs/ui/kis_statusbar.cc index d66801e553..c7c33f6595 100644 --- a/libs/ui/kis_statusbar.cc +++ b/libs/ui/kis_statusbar.cc @@ -1,414 +1,415 @@ /* This file is part of KimageShop^WKrayon^WKrita * * 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. */ #include "kis_statusbar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_memory_statistics_server.h" #include "KisView.h" #include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_progress_widget.h" #include "kis_zoom_manager.h" #include "KisMainWindow.h" #include "kis_config.h" #include "widgets/KisMemoryReportButton.h" enum { IMAGE_SIZE_ID, POINTER_POSITION_ID }; KisStatusBar::KisStatusBar(KisViewManager *viewManager) : m_viewManager(viewManager) , m_imageView(0) , m_statusBar(0) { } void KisStatusBar::setup() { m_selectionStatus = new QToolButton(); m_selectionStatus->setObjectName("selection status"); m_selectionStatus->setIconSize(QSize(16,16)); m_selectionStatus->setAutoRaise(true); m_selectionStatus->setEnabled(false); updateSelectionIcon(); m_statusBar = m_viewManager->mainWindow()->statusBar(); connect(m_selectionStatus, SIGNAL(clicked()), m_viewManager->selectionManager(), SLOT(slotToggleSelectionDecoration())); connect(m_viewManager->selectionManager(), SIGNAL(displaySelectionChanged()), SLOT(updateSelectionToolTip())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(updateSelectionIcon())); addStatusBarItem(m_selectionStatus); m_selectionStatus->setVisible(false); - m_resetAngleButton = new QToolButton; - m_resetAngleButton->setObjectName("Reset Rotation"); - m_resetAngleButton->setCheckable(false); - m_resetAngleButton->setToolTip(i18n("Reset Rotation")); - m_resetAngleButton->setAutoRaise(true); - m_resetAngleButton->setIcon(KisIconUtils::loadIcon("rotate-canvas-left")); - addStatusBarItem(m_resetAngleButton); - - connect(m_resetAngleButton, SIGNAL(clicked()), m_viewManager, SLOT(slotResetRotation())); - m_resetAngleButton->setVisible(false); - m_statusBarStatusLabel = new KSqueezedTextLabel(); m_statusBarStatusLabel->setObjectName("statsBarStatusLabel"); m_statusBarStatusLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarStatusLabel->setContentsMargins(5, 5, 5, 5); connect(KoToolManager::instance(), SIGNAL(changedStatusText(QString)), m_statusBarStatusLabel, SLOT(setText(QString))); addStatusBarItem(m_statusBarStatusLabel, 2); m_statusBarStatusLabel->setVisible(false); m_statusBarProfileLabel = new KSqueezedTextLabel(); m_statusBarProfileLabel->setObjectName("statsBarProfileLabel"); m_statusBarProfileLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarProfileLabel->setContentsMargins(5, 5, 5, 5); addStatusBarItem(m_statusBarProfileLabel, 3); m_statusBarProfileLabel->setVisible(false); m_progress = new KisProgressWidget(); m_progress->setObjectName("ProgressBar"); addStatusBarItem(m_progress); m_progress->setVisible(false); connect(m_progress, SIGNAL(sigCancellationRequested()), this, SIGNAL(sigCancellationRequested())); m_progressUpdater.reset(new KisProgressUpdater(m_progress, m_progress->progressProxy())); m_progressUpdater->setAutoNestNames(true); m_memoryReportBox = new KisMemoryReportButton(); m_memoryReportBox->setObjectName("memoryReportBox"); m_memoryReportBox->setFlat(true); m_memoryReportBox->setContentsMargins(5, 5, 5, 5); m_memoryReportBox->setMinimumWidth(120); addStatusBarItem(m_memoryReportBox); m_memoryReportBox->setVisible(false); connect(m_memoryReportBox, SIGNAL(clicked()), SLOT(showMemoryInfoToolTip())); m_pointerPositionLabel = new QLabel(QString()); m_pointerPositionLabel->setObjectName("pointerPositionLabel"); m_pointerPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pointerPositionLabel->setMinimumWidth(100); m_pointerPositionLabel->setContentsMargins(5,5, 5, 5); addStatusBarItem(m_pointerPositionLabel); m_pointerPositionLabel->setVisible(false); connect(KisMemoryStatisticsServer::instance(), SIGNAL(sigUpdateMemoryStatistics()), SLOT(imageSizeChanged())); + + m_resetAngleButton = new QToolButton; + m_resetAngleButton->setObjectName("Reset Rotation"); + m_resetAngleButton->setCheckable(false); + m_resetAngleButton->setToolTip(i18n("Reset Rotation")); + m_resetAngleButton->setAutoRaise(true); + m_resetAngleButton->setIcon(KisIconUtils::loadIcon("rotate-canvas-left")); + addStatusBarItem(m_resetAngleButton); + + connect(m_resetAngleButton, SIGNAL(clicked()), m_viewManager, SLOT(slotResetRotation())); + m_resetAngleButton->setVisible(false); + } KisStatusBar::~KisStatusBar() { } void KisStatusBar::setView(QPointer imageView) { if (m_imageView == imageView) { return; } if (m_imageView) { m_imageView->disconnect(this); removeStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); m_imageView = 0; } if (imageView) { m_imageView = imageView; m_resetAngleButton->setVisible(true); connect(m_imageView, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(imageSizeChanged())); updateStatusBarProfileLabel(); addStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); } else { m_resetAngleButton->setVisible(false); } imageSizeChanged(); } void KisStatusBar::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { StatusBarItem sbItem(widget); if (permanent) { m_statusBar->addPermanentWidget(widget, stretch); } else { m_statusBar->addWidget(widget, stretch); } widget->setVisible(true); m_statusBarItems.append(sbItem); } void KisStatusBar::removeStatusBarItem(QWidget *widget) { int i = 0; Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { if (sbItem.widget() == widget) { break; } i++; } if (i < m_statusBarItems.count()) { m_statusBar->removeWidget(m_statusBarItems[i].widget()); m_statusBarItems.remove(i); } } void KisStatusBar::hideAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.hide(); } } void KisStatusBar::showAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.show(); } } void KisStatusBar::documentMousePositionChanged(const QPointF &pos) { if (!m_imageView) return; QPoint pixelPos = m_imageView->image()->documentToImagePixelFloored(pos); pixelPos.setX(qBound(0, pixelPos.x(), m_viewManager->image()->width() - 1)); pixelPos.setY(qBound(0, pixelPos.y(), m_viewManager->image()->height() - 1)); m_pointerPositionLabel->setText(i18nc("@info mouse position (x, y)", "%1, %2", pixelPos.x(), pixelPos.y())); } void KisStatusBar::imageSizeChanged() { updateMemoryStatus(); QString sizeText; KisImageWSP image = m_imageView ? m_imageView->image() : 0; if (image) { qint32 w = image->width(); qint32 h = image->height(); sizeText = i18nc("@info:status width x height (file size)", "%1 &x %2 (%3)", w, h, m_shortMemoryTag); } else { sizeText = m_shortMemoryTag; } m_memoryReportBox->setIcon(m_memoryStatusIcon); m_memoryReportBox->setText(sizeText); m_memoryReportBox->setToolTip(m_longMemoryTag); } void KisStatusBar::updateSelectionIcon() { QIcon icon; if (!m_viewManager->selectionManager()->displaySelection()) { icon = KisIconUtils::loadIcon("selection-mode_invisible"); } else if (m_viewManager->selectionManager()->showSelectionAsMask()) { icon = KisIconUtils::loadIcon("selection-mode_mask"); } else /* if (!m_view->selectionManager()->showSelectionAsMask()) */ { icon = KisIconUtils::loadIcon("selection-mode_ants"); } m_selectionStatus->setIcon(icon); } void KisStatusBar::updateMemoryStatus() { KisMemoryStatisticsServer::Statistics stats = KisMemoryStatisticsServer::instance() ->fetchMemoryStatistics(m_imageView ? m_imageView->image() : 0); const KFormat format; const QString imageStatsMsg = i18nc("tooltip on statusbar memory reporting button (image stats)", "Image size:\t %1\n" " - layers:\t\t %2\n" " - projections:\t %3\n" " - instant preview:\t %4\n", format.formatByteSize(stats.imageSize), format.formatByteSize(stats.layersSize), format.formatByteSize(stats.projectionsSize), format.formatByteSize(stats.lodSize)); const QString memoryStatsMsg = i18nc("tooltip on statusbar memory reporting button (total stats)", "Memory used:\t %1 / %2\n" " image data:\t %3 / %4\n" " pool:\t\t %5 / %6\n" " undo data:\t %7\n" "\n" "Swap used:\t %8", format.formatByteSize(stats.totalMemorySize), format.formatByteSize(stats.totalMemoryLimit), format.formatByteSize(stats.realMemorySize), format.formatByteSize(stats.tilesHardLimit), format.formatByteSize(stats.poolSize), format.formatByteSize(stats.tilesPoolLimit), format.formatByteSize(stats.historicalMemorySize), format.formatByteSize(stats.swapSize)); QString longStats = imageStatsMsg + "\n" + memoryStatsMsg; QString shortStats = format.formatByteSize(stats.imageSize); QIcon icon; const qint64 warnLevel = stats.tilesHardLimit - stats.tilesHardLimit / 8; if (stats.imageSize > warnLevel || stats.realMemorySize > warnLevel) { if (!m_memoryWarningLogged) { m_memoryWarningLogged = true; KisUsageLogger::log(QString("WARNING: %1 is running out of memory:%2\n").arg(m_imageView->document()->url().toLocalFile()).arg(longStats)); } icon = KisIconUtils::loadIcon("warning"); QString suffix = i18nc("tooltip on statusbar memory reporting button", "\n\nWARNING:\tOut of memory! Swapping has been started.\n" "\t\tPlease configure more RAM for Krita in Settings dialog"); longStats += suffix; } m_shortMemoryTag = shortStats; m_longMemoryTag = longStats; m_memoryStatusIcon = icon; m_memoryReportBox->setMaximumMemory(stats.totalMemoryLimit); m_memoryReportBox->setCurrentMemory(stats.totalMemorySize); m_memoryReportBox->setImageWeight(stats.imageSize); emit memoryStatusUpdated(); } void KisStatusBar::showMemoryInfoToolTip() { QToolTip::showText(QCursor::pos(), m_memoryReportBox->toolTip(), m_memoryReportBox); } void KisStatusBar::updateSelectionToolTip() { updateSelectionIcon(); KisSelectionSP selection = m_viewManager->selection(); if (selection) { m_selectionStatus->setEnabled(true); QRect r = selection->selectedExactRect(); QString displayMode = !m_viewManager->selectionManager()->displaySelection() ? i18n("Hidden") : (m_viewManager->selectionManager()->showSelectionAsMask() ? i18n("Mask") : i18n("Ants")); m_selectionStatus->setToolTip( i18n("Selection: x = %1 y = %2 width = %3 height = %4\n" "Display Mode: %5", r.x(), r.y(), r.width(), r.height(), displayMode)); } else { m_selectionStatus->setEnabled(false); m_selectionStatus->setToolTip(i18n("No Selection")); } } void KisStatusBar::setSelection(KisImageWSP image) { Q_UNUSED(image); updateSelectionToolTip(); } void KisStatusBar::setProfile(KisImageWSP image) { if (m_statusBarProfileLabel == 0) { return; } if (!image) return; if (image->profile() == 0) { m_statusBarProfileLabel->setText(i18n("No profile")); } else { m_statusBarProfileLabel->setText(i18nc(" ", "%1 %2", image->colorSpace()->name(), image->profile()->name())); } } void KisStatusBar::setHelp(const QString &t) { Q_UNUSED(t); } void KisStatusBar::updateStatusBarProfileLabel() { if (!m_imageView) return; setProfile(m_imageView->image()); } KoProgressUpdater *KisStatusBar::progressUpdater() { return m_progressUpdater.data(); } diff --git a/libs/ui/tests/kis_shape_selection_test.cpp b/libs/ui/tests/kis_shape_selection_test.cpp index 880825468b..ffc066b62a 100644 --- a/libs/ui/tests/kis_shape_selection_test.cpp +++ b/libs/ui/tests/kis_shape_selection_test.cpp @@ -1,85 +1,192 @@ /* * Copyright (c) 2008 Sven Langkamp * * 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_shape_selection_test.h" #include #include #include #include #include #include #include "kis_selection.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "kis_image.h" #include "testutil.h" #include "kistest.h" #include #include +#include "kis_transaction.h" void KisShapeSelectionTest::testAddChild() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QScopedPointer doc(KisPart::instance()->createDocument()); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); KisImageSP image = doc->image(); KisSelectionSP selection = new KisSelection(); QVERIFY(selection->hasPixelSelection() == false); QVERIFY(selection->hasShapeSelection() == false); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); QRectF rect(50, 50, 100, 100); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); rect = matrix.mapRect(rect); KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topRight()); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.bottomLeft()); shape->close(); KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); selection->setShapeSelection(shapeSelection); shapeSelection->addShape(shape); QVERIFY(selection->hasShapeSelection()); selection->updateProjection(); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); } +KoPathShape *createRectangularShape(const QRectF &rect) +{ + KoPathShape* shape = new KoPathShape(); + shape->setShapeId(KoPathShapeId); + shape->moveTo(rect.topLeft()); + shape->lineTo(rect.topRight()); + shape->lineTo(rect.bottomRight()); + shape->lineTo(rect.bottomLeft()); + shape->close(); + + return shape; +} + +void KisShapeSelectionTest::testUndoFlattening() +{ + const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); + QScopedPointer doc(KisPart::instance()->createDocument()); + KoColor bgColor(QColor(255, 255, 255, 0), cs); + doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); + KisImageSP image = doc->image(); + + QCOMPARE(image->locked(), false); + + KisSelectionSP selection = new KisSelection(); + QCOMPARE(selection->hasPixelSelection(), false); + QCOMPARE(selection->hasShapeSelection(), false); + + selection->setParentNode(image->root()); + + KisPixelSelectionSP pixelSelection = selection->pixelSelection(); + pixelSelection->select(QRect(0, 0, 100, 100)); + + QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); + QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); + + QTransform matrix; + matrix.scale(1 / image->xRes(), 1 / image->yRes()); + const QRectF srcRect1(50, 50, 100, 100); + const QRectF rect1 = matrix.mapRect(srcRect1); + + KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); + selection->setShapeSelection(shapeSelection); + + KoPathShape *shape1 = createRectangularShape(rect1); + shapeSelection->addShape(shape1); + + QVERIFY(selection->hasShapeSelection()); + + selection->pixelSelection()->clear(); + QCOMPARE(selection->selectedExactRect(), QRectF()); + + selection->updateProjection(); + image->waitForDone(); + + QCOMPARE(selection->selectedExactRect(), srcRect1); + QCOMPARE(selection->outlineCacheValid(), true); + QCOMPARE(selection->outlineCache().boundingRect(), srcRect1); + QCOMPARE(selection->hasShapeSelection(), true); + + KisTransaction t1(selection->pixelSelection()); + selection->pixelSelection()->clear(); + KUndo2Command *cmd1 = t1.endAndTake(); + + QTest::qWait(400); + image->waitForDone(); + + QCOMPARE(selection->selectedExactRect(), QRect()); + QCOMPARE(selection->outlineCacheValid(), true); + QCOMPARE(selection->outlineCache().boundingRect(), QRectF()); + QCOMPARE(selection->hasShapeSelection(), false); + + const QRectF srcRect2(10, 10, 20, 20); + const QRectF rect2 = matrix.mapRect(srcRect2); + KoPathShape *shape2 = createRectangularShape(rect2); + shapeSelection->addShape(shape2); + + QTest::qWait(400); + image->waitForDone(); + + QCOMPARE(selection->selectedExactRect(), srcRect2); + QCOMPARE(selection->outlineCacheValid(), true); + QCOMPARE(selection->outlineCache().boundingRect(), srcRect2); + QCOMPARE(selection->hasShapeSelection(), true); + + shapeSelection->removeShape(shape2); + + QTest::qWait(400); + image->waitForDone(); + + QCOMPARE(selection->selectedExactRect(), QRect()); + QCOMPARE(selection->outlineCacheValid(), true); + QCOMPARE(selection->outlineCache().boundingRect(), QRectF()); + QCOMPARE(selection->hasShapeSelection(), false); + + cmd1->undo(); + + QTest::qWait(400); + image->waitForDone(); + + QCOMPARE(selection->selectedExactRect(), srcRect1); + QCOMPARE(selection->outlineCacheValid(), true); + QCOMPARE(selection->outlineCache().boundingRect(), srcRect1); + QCOMPARE(selection->hasShapeSelection(), true); +} + + KISTEST_MAIN(KisShapeSelectionTest) diff --git a/libs/ui/tests/kis_shape_selection_test.h b/libs/ui/tests/kis_shape_selection_test.h index 898433b9a1..d7340b9692 100644 --- a/libs/ui/tests/kis_shape_selection_test.h +++ b/libs/ui/tests/kis_shape_selection_test.h @@ -1,34 +1,36 @@ /* * Copyright (c) 2008 Sven Langkamp * * 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_SHAPE_SELETION_TEST_H #define KIS_SHAPE_SELETION_TEST_H #include class KisShapeSelectionTest : public QObject { Q_OBJECT private Q_SLOTS: void testAddChild(); + + void testUndoFlattening(); }; #endif diff --git a/libs/ui/tool/strokes/move_stroke_strategy.cpp b/libs/ui/tool/strokes/move_stroke_strategy.cpp index 9d6f7a29de..376e88ded8 100644 --- a/libs/ui/tool/strokes/move_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/move_stroke_strategy.cpp @@ -1,302 +1,302 @@ /* * Copyright (c) 2011 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 "move_stroke_strategy.h" #include #include "kis_image_interfaces.h" #include "kis_node.h" #include "commands_new/kis_update_command.h" #include "commands_new/kis_node_move_command2.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "KisRunnableStrokeJobData.h" #include "KisRunnableStrokeJobUtils.h" #include "KisRunnableStrokeJobsInterface.h" MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade), m_nodes(), m_updatesFacade(updatesFacade), m_updatesEnabled(true) { m_nodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, true); KritaUtils::filterContainer(m_nodes, [this](KisNodeSP node) { return !KisLayerUtils::checkIsCloneOf(node, m_nodes) && node->isEditable(false); }); Q_FOREACH(KisNodeSP subtree, m_nodes) { KisLayerUtils::recursiveApplyNodes( subtree, [this](KisNodeSP node) { if (KisLayerUtils::checkIsCloneOf(node, m_nodes) || !node->isEditable(false)) { m_blacklistedNodes.insert(node); } }); } setSupportsWrapAroundMode(true); enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER); } MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs) : QObject(), KisStrokeStrategyUndoCommandBased(rhs), m_nodes(rhs.m_nodes), m_blacklistedNodes(rhs.m_blacklistedNodes), m_updatesFacade(rhs.m_updatesFacade), m_finalOffset(rhs.m_finalOffset), m_dirtyRect(rhs.m_dirtyRect), m_dirtyRects(rhs.m_dirtyRects), m_updatesEnabled(rhs.m_updatesEnabled) { } void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node) { if (!m_blacklistedNodes.contains(node)) { m_initialNodeOffsets.insert(node, QPoint(node->x(), node->y())); } KisNodeSP child = node->firstChild(); while(child) { saveInitialNodeOffsets(child); child = child->nextSibling(); } } void MoveStrokeStrategy::initStrokeCallback() { QVector jobs; KritaUtils::addJobBarrier(jobs, [this]() { Q_FOREACH(KisNodeSP node, m_nodes) { KisLayerUtils::forceAllHiddenOriginalsUpdate(node); } }); KritaUtils::addJobBarrier(jobs, [this]() { Q_FOREACH(KisNodeSP node, m_nodes) { KisLayerUtils::forceAllDelayedNodesUpdate(node); } }); KritaUtils::addJobBarrier(jobs, [this]() { QRect handlesRect; Q_FOREACH(KisNodeSP node, m_nodes) { saveInitialNodeOffsets(node); - handlesRect |= KisLayerUtils::recursiveNodeExactBounds(node); + handlesRect |= KisLayerUtils::recursiveTightNodeVisibleBounds(node); } KisStrokeStrategyUndoCommandBased::initStrokeCallback(); emit this->sigHandlesRectCalculated(handlesRect); m_updateTimer.start(); }); runnableJobsInterface()->addRunnableJobs(jobs); } void MoveStrokeStrategy::finishStrokeCallback() { Q_FOREACH (KisNodeSP node, m_nodes) { KUndo2Command *updateCommand = new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true); addMoveCommands(node, updateCommand); notifyCommandDone(KUndo2CommandSP(updateCommand), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (!m_updatesEnabled) { Q_FOREACH (KisNodeSP node, m_nodes) { m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]); } } KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void MoveStrokeStrategy::cancelStrokeCallback() { if (!m_nodes.isEmpty()) { m_finalOffset = QPoint(); m_hasPostponedJob = true; tryPostUpdateJob(true); } KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } void MoveStrokeStrategy::tryPostUpdateJob(bool forceUpdate) { if (!m_hasPostponedJob) return; if (forceUpdate || (m_updateTimer.elapsed() > m_updateInterval && !m_updatesFacade->hasUpdatesRunning())) { addMutatedJob(new BarrierUpdateData(forceUpdate)); } } void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { Data *d = dynamic_cast(data); if (!m_nodes.isEmpty() && d) { /** * NOTE: we do not care about threading here, because * all our jobs are declared sequential */ m_finalOffset = d->offset; m_hasPostponedJob = true; tryPostUpdateJob(false); } else if (BarrierUpdateData *barrierData = dynamic_cast(data)) { doCanvasUpdate(barrierData->forceUpdate); } else if (KisAsyncronousStrokeUpdateHelper::UpdateData *updateData = dynamic_cast(data)) { tryPostUpdateJob(updateData->forceUpdate); } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } #include "kis_selection_mask.h" #include "kis_selection.h" void MoveStrokeStrategy::doCanvasUpdate(bool forceUpdate) { if (!forceUpdate && (m_updateTimer.elapsed() < m_updateInterval || m_updatesFacade->hasUpdatesRunning())) { return; } if (!m_hasPostponedJob) return; Q_FOREACH (KisNodeSP node, m_nodes) { QRect dirtyRect = moveNode(node, m_finalOffset); m_dirtyRects[node] |= dirtyRect; if (m_updatesEnabled) { m_updatesFacade->refreshGraphAsync(node, dirtyRect); } if (KisSelectionMask *mask = dynamic_cast(node.data())) { Q_UNUSED(mask); //mask->selection()->notifySelectionChanged(); } } m_hasPostponedJob = false; m_updateTimer.restart(); } QRect MoveStrokeStrategy::moveNode(KisNodeSP node, QPoint offset) { QRect dirtyRect; if (!m_blacklistedNodes.contains(node)) { dirtyRect = node->extent(); QPoint newOffset = m_initialNodeOffsets[node] + offset; /** * Some layers, e.g. clones need an update to change extent(), so * calculate the dirty rect manually */ QPoint currentOffset(node->x(), node->y()); dirtyRect |= dirtyRect.translated(newOffset - currentOffset); node->setX(newOffset.x()); node->setY(newOffset.y()); KisNodeMoveCommand2::tryNotifySelection(node); } KisNodeSP child = node->firstChild(); while(child) { dirtyRect |= moveNode(child, offset); child = child->nextSibling(); } return dirtyRect; } void MoveStrokeStrategy::addMoveCommands(KisNodeSP node, KUndo2Command *parent) { if (!m_blacklistedNodes.contains(node)) { QPoint nodeOffset(node->x(), node->y()); new KisNodeMoveCommand2(node, nodeOffset - m_finalOffset, nodeOffset, parent); } KisNodeSP child = node->firstChild(); while(child) { addMoveCommands(child, parent); child = child->nextSibling(); } } void MoveStrokeStrategy::setUpdatesEnabled(bool value) { m_updatesEnabled = value; } bool checkSupportsLodMoves(KisNodeSP subtree) { return !KisLayerUtils::recursiveFindNode( subtree, [](KisNodeSP node) -> bool { return !node->supportsLodMoves(); }); } KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail) { Q_UNUSED(levelOfDetail); Q_FOREACH (KisNodeSP node, m_nodes) { if (!checkSupportsLodMoves(node)) return 0; } MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this); this->setUpdatesEnabled(false); return clone; } diff --git a/libs/ui/widgets/kis_scratch_pad.cpp b/libs/ui/widgets/kis_scratch_pad.cpp index d106e85b8e..f50dd1d0cf 100644 --- a/libs/ui/widgets/kis_scratch_pad.cpp +++ b/libs/ui/widgets/kis_scratch_pad.cpp @@ -1,518 +1,522 @@ /* This file is part of the KDE project * Copyright 2010 (C) Boudewijn Rempt * Copyright 2011 (C) Dmitry Kazakov * * 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 "kis_scratch_pad.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_image.h" #include "kis_undo_stores.h" #include "kis_update_scheduler.h" #include "kis_post_execution_undo_adapter.h" #include "kis_scratch_pad_event_filter.h" #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "kis_image_patch.h" #include "kis_canvas_widget_base.h" #include "kis_layer_projection_plane.h" #include "kis_node_graph_listener.h" class KisScratchPadNodeListener : public KisNodeGraphListener { public: KisScratchPadNodeListener(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override { KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); QMutexLocker locker(&m_lock); Q_FOREACH (const QRect &rc, rects) { m_scratchPad->imageUpdated(rc); } } private: KisScratchPad *m_scratchPad; QMutex m_lock; }; class KisScratchPadDefaultBounds : public KisDefaultBounds { public: KisScratchPadDefaultBounds(KisScratchPad *scratchPad) : m_scratchPad(scratchPad) { } ~KisScratchPadDefaultBounds() override {} QRect bounds() const override { return m_scratchPad->imageBounds(); } + void * sourceCookie() const override { + return m_scratchPad; + } + private: Q_DISABLE_COPY(KisScratchPadDefaultBounds) KisScratchPad *m_scratchPad; }; KisScratchPad::KisScratchPad(QWidget *parent) : QWidget(parent) , m_toolMode(HOVERING) , m_paintLayer(0) , m_displayProfile(0) , m_resourceProvider(0) { setAutoFillBackground(false); setMouseTracking(true); m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5); setCursor(m_cursor); KisConfig cfg(true); QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize()); m_checkBrush = QBrush(checkImage); // We are not supposed to use updates here, // so just set the listener to null m_updateScheduler = new KisUpdateScheduler(0); m_undoStore = new KisSurrogateUndoStore(); m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler); m_nodeListener = new KisScratchPadNodeListener(this); connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection); // filter will be deleted by the QObject hierarchy m_eventFilter = new KisScratchPadEventFilter(this); m_infoBuilder = new KisPaintingInformationBuilder(); m_helper = new KisToolFreehandHelper(m_infoBuilder); m_scaleBorderWidth = 1; } KisScratchPad::~KisScratchPad() { delete m_helper; delete m_infoBuilder; delete m_undoAdapter; delete m_undoStore; delete m_updateScheduler; delete m_nodeListener; } KisScratchPad::Mode KisScratchPad::modeFromButton(Qt::MouseButton button) const { return button == Qt::NoButton ? HOVERING : button == Qt::MidButton ? PANNING : button == Qt::RightButton ? PICKING : PAINTING; } void KisScratchPad::pointerPress(KoPointerEvent *event) { if (m_toolMode != HOVERING) return; m_toolMode = modeFromButton(event->button()); if (m_toolMode == PAINTING) { beginStroke(event); event->accept(); } else if (m_toolMode == PANNING) { beginPan(event); event->accept(); } else if (m_toolMode == PICKING) { pick(event); event->accept(); } } void KisScratchPad::pointerRelease(KoPointerEvent *event) { if (modeFromButton(event->button()) != m_toolMode) return; if (m_toolMode == PAINTING) { endStroke(event); m_toolMode = HOVERING; event->accept(); } else if (m_toolMode == PANNING) { endPan(event); m_toolMode = HOVERING; event->accept(); } else if (m_toolMode == PICKING) { event->accept(); m_toolMode = HOVERING; } } void KisScratchPad::pointerMove(KoPointerEvent *event) { m_helper->cursorMoved(documentToWidget().map(event->point)); if (m_toolMode == PAINTING) { doStroke(event); event->accept(); } else if (m_toolMode == PANNING) { doPan(event); event->accept(); } else if (m_toolMode == PICKING) { pick(event); event->accept(); } } void KisScratchPad::beginStroke(KoPointerEvent *event) { KoCanvasResourceProvider *resourceManager = m_resourceProvider->resourceManager(); m_helper->initPaint(event, documentToWidget().map(event->point), resourceManager, 0, 0, m_updateScheduler, m_paintLayer, m_paintLayer->paintDevice()->defaultBounds()); } void KisScratchPad::doStroke(KoPointerEvent *event) { m_helper->paintEvent(event); } void KisScratchPad::endStroke(KoPointerEvent *event) { Q_UNUSED(event); m_helper->endPaint(); } void KisScratchPad::beginPan(KoPointerEvent *event) { setCursor(QCursor(Qt::ClosedHandCursor)); m_panDocPoint = event->point; } void KisScratchPad::doPan(KoPointerEvent *event) { QPointF docOffset = event->point - m_panDocPoint; m_translateTransform.translate(-docOffset.x(), -docOffset.y()); updateTransformations(); update(); } void KisScratchPad::endPan(KoPointerEvent *event) { Q_UNUSED(event); setCursor(m_cursor); } void KisScratchPad::pick(KoPointerEvent *event) { KoColor color; if (KisToolUtils::pickColor(color, m_paintLayer->projection(), event->point.toPoint())) { emit colorSelected(color); } } void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY) { m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY)); m_scaleTransform = QTransform::fromScale(scaleX, scaleY); updateTransformations(); update(); } QTransform KisScratchPad::documentToWidget() const { return m_translateTransform.inverted() * m_scaleTransform; } QTransform KisScratchPad::widgetToDocument() const { return m_scaleTransform.inverted() * m_translateTransform; } void KisScratchPad::updateTransformations() { m_eventFilter->setWidgetToDocumentTransform(widgetToDocument()); } QRect KisScratchPad::imageBounds() const { return widgetToDocument().mapRect(rect()); } void KisScratchPad::imageUpdated(const QRect &rect) { emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect()); } void KisScratchPad::slotUpdateCanvas(const QRect &rect) { update(rect); } void KisScratchPad::paintEvent ( QPaintEvent * event ) { if(!m_paintLayer) return; QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect())); QRect alignedImageRect = imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth, m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect(); QPointF offset = alignedImageRect.topLeft(); m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer); KisPaintDeviceSP projection = m_paintLayer->projection(); QImage image = projection->convertToQImage(m_displayProfile, alignedImageRect.x(), alignedImageRect.y(), alignedImageRect.width(), alignedImageRect.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QPainter gc(this); gc.fillRect(event->rect(), m_checkBrush); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset)); QBrush brush(Qt::lightGray); QPen pen(brush, 1, Qt::DotLine); gc.setPen(pen); if (m_cutoutOverlay.isValid()) { gc.drawRect(m_cutoutOverlay); } if(!isEnabled()) { QColor color(Qt::lightGray); color.setAlphaF(0.5); QBrush disabledBrush(color); gc.fillRect(event->rect(), disabledBrush); } gc.end(); } void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider, const QColor &defaultColor) { m_resourceProvider = resourceProvider; KisConfig cfg(true); setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this))); connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)), SLOT(setDisplayProfile(const KoColorProfile*))); connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)), SLOT(setOnScreenResolution(qreal,qreal))); connect(this, SIGNAL(colorSelected(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); m_defaultColor = KoColor(defaultColor, KoColorSpaceRegistry::instance()->rgb8()); KisPaintDeviceSP paintDevice = new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad"); m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice); m_paintLayer->setGraphListener(m_nodeListener); m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this)); fillDefault(); } void KisScratchPad::setCutoutOverlayRect(const QRect& rc) { m_cutoutOverlay = rc; } QImage KisScratchPad::cutoutOverlay() const { if(!m_paintLayer) return QImage(); KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect rc = widgetToDocument().mapRect(m_cutoutOverlay); QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); return scaledImage; } void KisScratchPad::setPresetImage(const QImage& image) { m_presetImage = image; } void KisScratchPad::paintCustomImage(const QImage& loadedImage) { // this is 99% copied from the normal paintPresetImage() // we don't want to save over the preset image, so we don't // want to store it in the m_presetImage if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); QRect imageRect(QPoint(), overlayRect.size()); QImage scaledImage = loadedImage.scaled(overlayRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); device->convertFromQImage(scaledImage, 0); KisPainter painter(paintDevice); painter.bitBlt(overlayRect.topLeft(), device, imageRect); update(); } void KisScratchPad::paintPresetImage() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay); QRect imageRect(QPoint(), overlayRect.size()); QImage scaledImage = m_presetImage.scaled(overlayRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace()); device->convertFromQImage(scaledImage, 0); KisPainter painter(paintDevice); painter.bitBlt(overlayRect.topLeft(), device, imageRect); update(); } void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile) { if (colorProfile) { m_displayProfile = colorProfile; QWidget::update(); } } void KisScratchPad::fillDefault() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); paintDevice->setDefaultPixel(m_defaultColor); paintDevice->clear(); update(); } void KisScratchPad::fillTransparent() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); QColor transQColor(0,0,0,0); KoColor transparentColor(transQColor, KoColorSpaceRegistry::instance()->rgb8()); transparentColor.setOpacity(0.0); paintDevice->setDefaultPixel(transparentColor); paintDevice->clear(); update(); } void KisScratchPad::fillGradient() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KoAbstractGradientSP gradient = m_resourceProvider->currentGradient(); QRect gradientRect = widgetToDocument().mapRect(rect()); paintDevice->clear(); KisGradientPainter painter(paintDevice); painter.setGradient(gradient); painter.setGradientShape(KisGradientPainter::GradientShapeLinear); painter.paintGradient(gradientRect.topLeft(), gradientRect.bottomRight(), KisGradientPainter::GradientRepeatNone, 0.2, false, gradientRect.left(), gradientRect.top(), gradientRect.width(), gradientRect.height()); update(); } void KisScratchPad::fillBackground() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); paintDevice->setDefaultPixel(m_resourceProvider->bgColor()); paintDevice->clear(); update(); } void KisScratchPad::fillLayer() { if(!m_paintLayer) return; KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KisPainter painter(paintDevice); QRect sourceRect(0, 0, paintDevice->exactBounds().width(), paintDevice->exactBounds().height()); painter.bitBlt(QPoint(0, 0), m_resourceProvider->currentImage()->projection(), sourceRect); update(); } diff --git a/packaging/linux/flatpak/org.kde.krita-nightly.yaml b/packaging/linux/flatpak/org.kde.krita-nightly.yaml index f58e14b2b1..e43022e451 100644 --- a/packaging/linux/flatpak/org.kde.krita-nightly.yaml +++ b/packaging/linux/flatpak/org.kde.krita-nightly.yaml @@ -1,280 +1,280 @@ app-id: org.kde.krita-nightly default-branch: master runtime: org.kde.Platform runtime-version: '5.12' sdk: org.kde.Sdk command: krita rename-icon: krita rename-desktop-file: org.kde.krita.desktop desktop-file-name-suffix: nightly finish-args: - --share=ipc - --socket=x11 - --share=network - --device=dri - --socket=pulseaudio - --filesystem=host - --filesystem=xdg-config/kdeglobals:ro - --env=PYTHONPATH=/app/lib/python3/dist-packages - --env=TMPDIR=/var/tmp cleanup: - /include - /lib/pkgconfig - /lib/cmake - /share/aclocal - /share/pkgconfig - /share/info - /share/man - /cmake - '*.a' - '*.la' - '*.cmake' modules: - name: sip buildsystem: simple build-commands: - python3 configure.py --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --incdir=/app/include/python3 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages --sip-module=PyQt5.sip - make -j $FLATPAK_BUILDER_N_JOBS - make install cleanup: - /bin sources: - type: archive url: https://www.riverbankcomputing.com/static/Downloads/sip/4.19.17/sip-4.19.17.tar.gz sha256: 12bcd8f4d5feefc105bc075d12c5090ee783f7380728563c91b8b95d0ec45df3 - name: pyqt buildsystem: simple build-commands: - python3 configure.py --confirm-license --sip-incdir=/app/include/python3 --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --designer-plugindir=/app/lib/plugins/designer --qml-plugindir=/app/lib/plugins/PyQt5 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages/PyQt5 - make -j $FLATPAK_BUILDER_N_JOBS - make install cleanup: - /bin sources: - type: archive url: https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.2/PyQt5_gpl-5.12.2.tar.gz sha256: c565829e77dc9c281aa1a0cdf2eddaead4e0f844cbaf7a4408441967f03f5f0f - name: boost buildsystem: simple build-commands: - ./bootstrap.sh --prefix=/app --with-libraries=system - ./b2 -j $FLATPAK_BUILDER_N_JOBS install sources: - type: archive url: https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2 sha256: 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406 - name: eigen buildsystem: cmake-ninja builddir: true cleanup: - '*' sources: - type: archive url: https://bitbucket.org/eigen/eigen/get/3.3.7.tar.bz2 sha256: 9f13cf90dedbe3e52a19f43000d71fdf72e986beb9a5436dddcd61ff9d77a3ce - name: quazip buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo sources: - type: archive url: https://github.com/stachenov/quazip/archive/0.7.6.tar.gz sha256: 4118a830a375a81211956611cc34b1b5b4ddc108c126287b91b40c2493046b70 - type: shell commands: - sed -i 's|${CMAKE_ROOT}/Modules|share/cmake|' CMakeLists.txt - name: exiv2 buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo cleanup: - /bin sources: - type: archive - url: https://www.exiv2.org/builds/exiv2-0.27.1-Source.tar.gz - sha256: f125286980fd1bcb28e188c02a93946951c61e10784720be2301b661a65b3081 + url: http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz + sha256: c75e3c4a0811bf700d92c82319373b7a825a2331c12b8b37d41eb58e4f18eafb - name: ilmbase config-opts: - --disable-static sources: - type: archive url: https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz sha256: 456978d1a978a5f823c7c675f3f36b0ae14dba36638aeaa3c4b0e784f12a3862 - name: openexr config-opts: - --disable-static cleanup: - /bin - /share/doc sources: - type: archive url: https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz sha256: fd6cb3a87f8c1a233be17b94c74799e6241d50fc5efd4df75c7a4b9cf4e25ea6 - name: libraw config-opts: - --disable-static cleanup: - /bin - /share/doc sources: - type: archive url: https://www.libraw.org/data/LibRaw-0.19.2.tar.gz sha256: 400d47969292291d297873a06fb0535ccce70728117463927ddd9452aa849644 - name: opencolorio buildsystem: cmake # ninja build broken (fixed in 2.0) builddir: true build-options: arch: arm: config-opts: - -DOCIO_USE_SSE=OFF aarch64: config-opts: - -DOCIO_USE_SSE=OFF config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DOCIO_BUILD_STATIC=OFF - -DCMAKE_CXX_FLAGS='-Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-error=cast-function-type' cleanup: - /bin sources: - type: archive url: https://github.com/imageworks/OpenColorIO/archive/v1.1.1.tar.gz sha256: c9b5b9def907e1dafb29e37336b702fff22cc6306d445a13b1621b8a754c14c8 - name: vc skip-arches: - aarch64 - arm buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo cleanup: - '*' sources: - type: archive url: https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz sha256: 08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe - type: shell commands: - sed -i 's/x86|/x86|i686|/' CMakeLists.txt - name: poppler-data buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo sources: - type: archive url: https://poppler.freedesktop.org/poppler-data-0.4.9.tar.gz sha256: 1f9c7e7de9ecd0db6ab287349e31bf815ca108a5a175cf906a90163bdbe32012 - name: poppler buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_GTK_TESTS=OFF - -DBUILD_QT5_TESTS=OFF - -DBUILD_CPP_TESTS=OFF - -DENABLE_UTILS=OFF - -DENABLE_CPP=OFF - -DENABLE_GLIB=OFF - -DENABLE_LIBOPENJPEG=none sources: - type: archive url: https://poppler.freedesktop.org/poppler-0.77.0.tar.xz sha256: 7267eb4cbccd64a58244b8211603c1c1b6bf32c7f6a4ced2642865346102f36b - name: gsl config-opts: - --disable-static cleanup: - /bin sources: - type: archive url: https://ftpmirror.gnu.org/gnu/gsl/gsl-2.5.tar.gz sha256: 0460ad7c2542caaddc6729762952d345374784100223995eb14d614861f2258d - name: gmic-qt buildsystem: cmake-ninja builddir: true subdir: gmic-qt config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DGMIC_QT_HOST=krita - -DGMIC_PATH=../src - -DCMAKE_CXX_FLAGS=-lfftw3_threads sources: - type: archive url: https://gmic.eu/files/source/gmic_2.6.4.tar.gz sha256: 4cd88b2dca6b9b1a330ab4556d36656bafb98e4e9814bf0448545b27ef18dae3 - name: x264 config-opts: - --disable-cli - --enable-shared sources: - type: archive url: https://download.videolan.org/x264/snapshots/x264-snapshot-20190305-2245-stable.tar.bz2 sha256: be52c96ef8bd930fbc1ecff03abac9b94976b444ea7641345e08e20d9e594d16 - name: ffmpeg config-opts: - --enable-rpath - --enable-gpl - --disable-static - --enable-shared - --disable-doc - --disable-ffplay - --disable-ffprobe - --enable-libopus - --enable-libvpx - --enable-libx264 cleanup: - /share/ffmpeg/examples sources: - type: archive url: https://www.ffmpeg.org/releases/ffmpeg-4.1.4.tar.xz sha256: f1f049a82fcfbf156564e73a3935d7e750891fab2abf302e735104fd4050a7e1 - name: krita buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_TESTING=OFF build-options: env: PYTHONPATH: /app/lib/python3/dist-packages post-install: - rm -r /app/share/icons/hicolor/{1024x1024,scalable} sources: - type: git url: https://anongit.kde.org/krita diff --git a/packaging/linux/flatpak/org.kde.krita-stable.yaml b/packaging/linux/flatpak/org.kde.krita-stable.yaml index 574e287dab..9dd5405b22 100644 --- a/packaging/linux/flatpak/org.kde.krita-stable.yaml +++ b/packaging/linux/flatpak/org.kde.krita-stable.yaml @@ -1,280 +1,280 @@ app-id: org.kde.krita-stable runtime: org.kde.Platform runtime-version: '5.12' sdk: org.kde.Sdk command: krita rename-icon: krita rename-desktop-file: org.kde.krita.desktop desktop-file-name-suffix: stable finish-args: - --share=ipc - --socket=x11 - --share=network - --device=dri - --socket=pulseaudio - --filesystem=host - --filesystem=xdg-config/kdeglobals:ro - --env=PYTHONPATH=/app/lib/python3/dist-packages - --env=TMPDIR=/var/tmp cleanup: - /include - /lib/pkgconfig - /lib/cmake - /share/aclocal - /share/pkgconfig - /share/info - /share/man - /cmake - '*.a' - '*.la' - '*.cmake' modules: - name: sip buildsystem: simple build-commands: - python3 configure.py --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --incdir=/app/include/python3 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages --sip-module=PyQt5.sip - make -j $FLATPAK_BUILDER_N_JOBS - make install cleanup: - /bin sources: - type: archive url: https://www.riverbankcomputing.com/static/Downloads/sip/4.19.17/sip-4.19.17.tar.gz sha256: 12bcd8f4d5feefc105bc075d12c5090ee783f7380728563c91b8b95d0ec45df3 - name: pyqt buildsystem: simple build-commands: - python3 configure.py --confirm-license --sip-incdir=/app/include/python3 --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --designer-plugindir=/app/lib/plugins/designer --qml-plugindir=/app/lib/plugins/PyQt5 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages/PyQt5 - make -j $FLATPAK_BUILDER_N_JOBS - make install cleanup: - /bin sources: - type: archive url: https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.2/PyQt5_gpl-5.12.2.tar.gz sha256: c565829e77dc9c281aa1a0cdf2eddaead4e0f844cbaf7a4408441967f03f5f0f - name: boost buildsystem: simple build-commands: - ./bootstrap.sh --prefix=/app --with-libraries=system - ./b2 -j $FLATPAK_BUILDER_N_JOBS install sources: - type: archive url: https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2 sha256: 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406 - name: eigen buildsystem: cmake-ninja builddir: true cleanup: - '*' sources: - type: archive url: https://bitbucket.org/eigen/eigen/get/3.3.7.tar.bz2 sha256: 9f13cf90dedbe3e52a19f43000d71fdf72e986beb9a5436dddcd61ff9d77a3ce - name: quazip buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo sources: - type: archive url: https://github.com/stachenov/quazip/archive/0.7.6.tar.gz sha256: 4118a830a375a81211956611cc34b1b5b4ddc108c126287b91b40c2493046b70 - type: shell commands: - sed -i 's|${CMAKE_ROOT}/Modules|share/cmake|' CMakeLists.txt - name: exiv2 buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo cleanup: - /bin sources: - type: archive - url: https://www.exiv2.org/builds/exiv2-0.27.1-Source.tar.gz - sha256: f125286980fd1bcb28e188c02a93946951c61e10784720be2301b661a65b3081 + url: http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz + sha256: c75e3c4a0811bf700d92c82319373b7a825a2331c12b8b37d41eb58e4f18eafb - name: ilmbase config-opts: - --disable-static sources: - type: archive url: https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz sha256: 456978d1a978a5f823c7c675f3f36b0ae14dba36638aeaa3c4b0e784f12a3862 - name: openexr config-opts: - --disable-static cleanup: - /bin - /share/doc sources: - type: archive url: https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz sha256: fd6cb3a87f8c1a233be17b94c74799e6241d50fc5efd4df75c7a4b9cf4e25ea6 - name: libraw config-opts: - --disable-static cleanup: - /bin - /share/doc sources: - type: archive url: https://www.libraw.org/data/LibRaw-0.19.2.tar.gz sha256: 400d47969292291d297873a06fb0535ccce70728117463927ddd9452aa849644 - name: opencolorio buildsystem: cmake # ninja build broken (fixed in 2.0) builddir: true build-options: arch: arm: config-opts: - -DOCIO_USE_SSE=OFF aarch64: config-opts: - -DOCIO_USE_SSE=OFF config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DOCIO_BUILD_STATIC=OFF - -DCMAKE_CXX_FLAGS='-Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-error=cast-function-type' cleanup: - /bin sources: - type: archive url: https://github.com/imageworks/OpenColorIO/archive/v1.1.1.tar.gz sha256: c9b5b9def907e1dafb29e37336b702fff22cc6306d445a13b1621b8a754c14c8 - name: vc skip-arches: - aarch64 - arm buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo cleanup: - '*' sources: - type: archive url: https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz sha256: 08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe - type: shell commands: - sed -i 's/x86|/x86|i686|/' CMakeLists.txt - name: poppler-data buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo sources: - type: archive url: https://poppler.freedesktop.org/poppler-data-0.4.9.tar.gz sha256: 1f9c7e7de9ecd0db6ab287349e31bf815ca108a5a175cf906a90163bdbe32012 - name: poppler buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_GTK_TESTS=OFF - -DBUILD_QT5_TESTS=OFF - -DBUILD_CPP_TESTS=OFF - -DENABLE_UTILS=OFF - -DENABLE_CPP=OFF - -DENABLE_GLIB=OFF - -DENABLE_LIBOPENJPEG=none sources: - type: archive url: https://poppler.freedesktop.org/poppler-0.77.0.tar.xz sha256: 7267eb4cbccd64a58244b8211603c1c1b6bf32c7f6a4ced2642865346102f36b - name: gsl config-opts: - --disable-static cleanup: - /bin sources: - type: archive url: https://ftpmirror.gnu.org/gnu/gsl/gsl-2.5.tar.gz sha256: 0460ad7c2542caaddc6729762952d345374784100223995eb14d614861f2258d - name: gmic-qt buildsystem: cmake-ninja builddir: true subdir: gmic-qt config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DGMIC_QT_HOST=krita - -DGMIC_PATH=../src - -DCMAKE_CXX_FLAGS=-lfftw3_threads sources: - type: archive url: https://gmic.eu/files/source/gmic_2.6.4.tar.gz sha256: 4cd88b2dca6b9b1a330ab4556d36656bafb98e4e9814bf0448545b27ef18dae3 - name: x264 config-opts: - --disable-cli - --enable-shared sources: - type: archive url: https://download.videolan.org/x264/snapshots/x264-snapshot-20190305-2245-stable.tar.bz2 sha256: be52c96ef8bd930fbc1ecff03abac9b94976b444ea7641345e08e20d9e594d16 - name: ffmpeg config-opts: - --enable-rpath - --enable-gpl - --disable-static - --enable-shared - --disable-doc - --disable-ffplay - --disable-ffprobe - --enable-libopus - --enable-libvpx - --enable-libx264 cleanup: - /share/ffmpeg/examples sources: - type: archive url: https://www.ffmpeg.org/releases/ffmpeg-4.1.4.tar.xz sha256: f1f049a82fcfbf156564e73a3935d7e750891fab2abf302e735104fd4050a7e1 - name: krita buildsystem: cmake-ninja builddir: true config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_TESTING=OFF build-options: env: PYTHONPATH: /app/lib/python3/dist-packages post-install: - rm -r /app/share/icons/hicolor/{1024x1024,scalable} sources: - type: git url: https://anongit.kde.org/krita branch: krita/4.2 diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index 80961161b5..499b1c97d0 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,56 +1,56 @@ project(kritafilters) add_subdirectory(libkra) if(CMAKE_SIZEOF_VOID_P EQUAL 4) add_definitions( -DCPU_32_BITS ) endif() if(JPEG_FOUND AND HAVE_LCMS2) add_subdirectory(jpeg) endif() if(TIFF_FOUND) add_subdirectory(tiff) endif() if(PNG_FOUND) add_subdirectory(png) add_subdirectory(csv) endif() if(OPENEXR_FOUND) add_subdirectory(exr) endif() if(Poppler_Qt5_FOUND) add_subdirectory(pdf) endif() -if(OpenJPEG_FOUND) +if(OpenJPEG_FOUND AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") add_subdirectory(jp2) endif() if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(qimageio) add_subdirectory(ora) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(kra) if (GIF_FOUND) add_subdirectory(gif) endif() if (HEIF_FOUND) add_subdirectory(heif) endif() diff --git a/plugins/impex/csv/tests/data/writeonlyFile.txt b/plugins/impex/csv/tests/data/writeonlyFile.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop index 846e94d779..750d85fd0b 100644 --- a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop +++ b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop @@ -1,52 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=lastdocumentsdocker X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Last Documents Docker Name[ar]=رصيف بآخر المستندات Name[ca]=Acoblador Darrers documents Name[ca@valencia]=Acoblador Darrers documents Name[el]=Προσάρτηση τελευταίων εγγράφοων Name[en_GB]=Last Documents Docker Name[es]=Panel de últimos documentos Name[eu]=Azken dokumentuen panela Name[fi]=Viimeisimpien tiedostojen telakka Name[fr]=Récemment ouverts Name[gl]=Doca dos últimos documentos Name[it]=Area di aggancio Ultimi documenti Name[ko]=마지막 문서 도킹 패널 Name[nl]=Laatste documenten verankering Name[nn]=Dokk for nyleg opna dokument Name[pl]=Dok ostatnich dokumentów Name[pt]=Área dos Últimos Documentos Name[sv]=Dockningsfönster för senaste dokument Name[tr]=Son Belgeler Doku Name[uk]=Бічна панель останніх документів Name[x-test]=xxLast Documents Dockerxx Name[zh_CN]=最近文档工具面板 Name[zh_TW]=「最後檔案」面板 Comment=A Python-based docker for show thumbnails to last ten documents Comment[ar]=رصيف بِ‍«پيثون» لعرض مصغّرات آخر ١٠ مستندات مفتوحة Comment[ca]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents Comment[ca@valencia]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων Comment[en_GB]=A Python-based docker for show thumbnails to last ten documents Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos Comment[eu]=Azken hamar dokumentuen koadro-txikiak erakusteko Python-oinarridun panel bat Comment[fi]=Python-pohjainen telakka viimeisimpien kymmenen tiedoston pienoiskuvien näyttämiseen Comment[fr]=Panneau en Python pour afficher les vignettes des dix derniers documents Comment[gl]=Unha doca baseada en Python para mostrar as miniaturas dos últimos dez documentos. Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti. Comment[ko]=10개의 문서를 표시할 수 있는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten. Comment[nn]=Python-basert dokk for vising av miniatyrbilete av dei siste ti dokumenta Comment[pl]=Dok oparty na pythonie do wyświetlania miniatur ostatnich dziesięciu dokumentów Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten Comment[tr]=Son on belgenin küçük resmini göstermek için Python-tabanlı bir dok Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx -Comment[zh_CN]=基于 Python 的工具面板,显示最近十个文档的缩略图 +Comment[zh_CN]=这是一个基于 Python 的工具面板插件,它可以显示最近十个文档的缩略图 Comment[zh_TW]=基於 Python 的面板,用於顯示最後 10 個檔案縮圖 diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index 9d5ec5e925..86cce72c36 100644 --- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,53 +1,53 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=quick_settings_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Quick Settings Docker Name[ar]=رصيف بإعدادات سريعة Name[ca]=Acoblador Arranjament ràpid Name[ca@valencia]=Acoblador Arranjament ràpid Name[cs]=Dok pro rychlé nastavení Name[el]=Προσάρτηση γρήγορων ρυθμίσεων Name[en_GB]=Quick Settings Docker Name[es]=Panel de ajustes rápidos Name[eu]=Ezarpen azkarren panela Name[fi]=Pika-asetustelakka Name[fr]=Réglages rapides Name[gl]=Doca de configuración rápida Name[it]=Area di aggancio delle impostazioni rapide Name[ko]=빠른 설정 도킹 패널 Name[nl]=Verankering voor snelle instellingen Name[nn]=Snøgginnstillingar-dokk Name[pl]=Dok szybkich ustawień Name[pt]=Área de Configuração Rápida Name[sv]=Dockningspanel med snabbinställningar Name[tr]=Hızlı Ayarlar Doku Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Name[zh_CN]=快速设置工具面板 Name[zh_TW]=「快速設定」面板 Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة. Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου. Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat. Comment[fi]=Python-pohjainen telakka siveltimen koon ja läpikuultavuuden nopean muuttamiseen. Comment[fr]=Panneau en python pour modifier rapidement la taille et l'opacité des brosses. Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis. Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità. Comment[ko]=브러시 크기와 불투명도를 빠르게 변경할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. Comment[nn]=Python-basert dokk for kjapp endring av penselstorleik/-tettleik Comment[pl]=Dok oparty na Pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. Comment[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok. Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx -Comment[zh_CN]=基于 Python 的用于快速更改笔刷尺寸和透明度的工具面板。 +Comment[zh_CN]=这是一个基于 Python 的工具面板,用于快速更改笔刷尺寸和透明度。 Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。 diff --git a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop index b98a814a39..b37503456d 100644 --- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop +++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop @@ -1,50 +1,50 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scriptdocker X-Python-2-Compatible=false Name=Script Docker Name[ar]=رصيف سكربتات Name[ca]=Acoblador Script Name[ca@valencia]=Acoblador Script Name[cs]=Dok skriptu Name[el]=Προσάρτηση σεναρίων Name[en_GB]=Script Docker Name[es]=Panel de guiones Name[eu]=Script-panela Name[fi]=Skriptitelakka Name[fr]=Panneau de script Name[gl]=Doca de scripts Name[it]=Area di aggancio degli script Name[ko]=스크립트 도킹 패널 Name[nl]=Verankering van scripts Name[nn]=Skriptdokk Name[pl]=Dok skryptów Name[pt]=Área de Programas Name[sv]=Dockningsfönster för skript Name[tr]=Betik Doku Name[uk]=Бічна панель скриптів Name[x-test]=xxScript Dockerxx Name[zh_CN]=脚本工具面板 Name[zh_TW]=「指令稿」面板 Comment=A Python-based docker for create actions and point to Python scripts Comment[ar]=رصيف بِ‍«پيثون» لإنشاء الإجراءات والإشارة إلى سكربتات «پيثون» Comment[ca]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python Comment[ca@valencia]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python Comment[en_GB]=A Python-based docker for create actions and point to Python scripts Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python Comment[eu]=Python-oinarridun panel bat. Comment[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python. Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere script Python Comment[ko]=작업 생성 및 Python 스크립트를 가리키는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts Comment[nn]=Python-basert dokk for å laga handlingar og peika til Python-skript Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript Comment[tr]=Eylemler oluşturmak ve Python betiklerine yönlendirmek için Python-tabanlı bir dok Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python. Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx -Comment[zh_CN]=基于 Python 的用于创建动作并指向 Python 脚本的工具面板 +Comment[zh_CN]=这是一个基于 Python 的工具面板,用于创建操作,并将他们指向 Python 脚本。 Comment[zh_TW]=基於 Python 的面板,用於建立動作並指向 Python 指令稿 diff --git a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop index ac1cd6c622..bb8dd0a6bd 100644 --- a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop +++ b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop @@ -1,52 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenbrushes X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Brushes Name[ar]=عشرُ فُرش Name[ca]=Deu pinzells Name[ca@valencia]=Deu pinzells Name[cs]=Deset štětců Name[el]=Δέκα πινέλα Name[en_GB]=Ten Brushes Name[es]=Diez pinceles Name[eu]=Hamar isipu Name[fi]=Kymmenen sivellintä Name[fr]=Raccourcis des préréglages de brosses Name[gl]=Dez pinceis Name[is]=Tíu penslar Name[it]=Dieci pennelli Name[ko]=10개의 브러시 Name[nl]=Tien penselen Name[nn]=Ti penslar Name[pl]=Dziesięć pędzli Name[pt]=Dez Pincéis Name[sv]=Tio penslar Name[tr]=On Fırça Name[uk]=Десять пензлів Name[x-test]=xxTen Brushesxx -Name[zh_CN]=十大笔刷 +Name[zh_CN]=常用笔刷快捷键 Name[zh_TW]=10 個筆刷 Comment=Assign a preset to ctrl-1 to ctrl-0 Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[ca@valencia]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[el]=Αντιστοίχιση προκαθορισμένου από ctrl-1 στο ctrl-0 Comment[en_GB]=Assign a preset to ctrl-1 to ctrl-0 Comment[es]=Asignar una preselección a Ctrl-1 hasta Ctrl-0 Comment[eu]=Aurrezarpena ezarri ktrl-1'etik ktrl-0'ra arte Comment[fi]=Kytke esiasetukset Ctrl-1:stä Ctrl-0:aan Comment[gl]=Asigne unha predefinición do Ctrl+1 ao Ctrl+0. Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0 Comment[ko]=Ctrl+1부터 Ctrl+0까지 프리셋 할당 Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0 Comment[nn]=Byt til ferdigpenslar med «Ctrl + 1» til «Ctrl + 0» Comment[pl]=Przypisz nastawę do ctrl-1 lub ctrl-0 Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0 Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0 Comment[tr]= ctrl-1 ve ctrl-0 için ayar atama Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0 Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx Comment[zh_CN]=将预设分配给由 Ctrl+1 到 Ctrl+0 的快捷键 Comment[zh_TW]=將預設指定給 ctrl-1 至 ctrl-0 diff --git a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop index abde5797ff..16a93d1691 100644 --- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop @@ -1,51 +1,51 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenscripts X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Scripts Name[ar]=عشرُ سكربتات Name[ca]=Deu scripts Name[ca@valencia]=Deu scripts Name[en_GB]=Ten Scripts Name[es]=Diez guiones Name[eu]=Hamar script Name[fi]=Kymmenen skriptiä Name[fr]=Raccourcis des scripts Name[gl]=Dez scripts Name[is]=Tíu skriftur Name[it]=Dieci script Name[ko]=10개의 스크립트 Name[nl]=Tien scripts Name[nn]=Ti skript Name[pl]=Skrypty Ten Name[pt]=Dez Programas Name[sv]=Tio skript Name[tr]=On Betik Name[uk]=Десять скриптів Name[x-test]=xxTen Scriptsxx -Name[zh_CN]=十大脚本 +Name[zh_CN]=常用脚本快捷键 Name[zh_TW]=10 個指令稿 Comment=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[ar]=ملحقة بِ‍«پيثون» لإنشاء ١٠ إجراءات وإسنادها إلى سكربتات «پيثون» Comment[ca]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[ca@valencia]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python Comment[eu]=Hamar ekintza sortu eta haiek Python-scriptei esleitzeko Python-oinarridun plugin bat Comment[fi]=Python-pohjainen liitännäinen kymmenen toiminnon luomiseksi kytkemiseksi Python-skripteihin Comment[fr]=Module externe basé sur Python pour créer dix actions et les assigner à des scripts Python Comment[gl]=Un complemento escrito en Python para crear dez accións e asignalas a scripts escritos en Python. Comment[it]=Un'estensione basata su Python per creare dieci azioni e assegnarle a script Python Comment[ko]=10개의 작업을 생성하고 이를 Python 스크립트에 할당하는 Python 기반 플러그인 Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts Comment[nn]=Python-basert tillegg som legg til ti handlingar du kan tildela til Python-skript Comment[pl]=Wtyczka oparta na Pythonie do tworzenia działań i przypisywanie ich skryptom Pythona Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript Comment[tr]=On eylem oluşturmak ve Python betiklerine atamak için Python tabanlı bir eklenti Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx -Comment[zh_CN]=基于 Python 的用于创建十个操作并将它们指定到特定 Python 脚本的插件 +Comment[zh_CN]=这是一个基于 Python 编写的插件,它可以创建十种操作,并将它们指定到特定 Python 脚本。 Comment[zh_TW]=基於 Python 的外掛程式,用於建立 10 個動作並且將它們指定給 Python 指令稿 diff --git a/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp b/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp index ca6ac8943f..5d713e86ab 100644 --- a/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp +++ b/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp @@ -1,68 +1,68 @@ /* * Copyright (c) 2019 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 "KisMoveBoundsCalculationJob.h" #include "kis_node.h" #include "kis_selection.h" #include "kis_layer_utils.h" KisMoveBoundsCalculationJob::KisMoveBoundsCalculationJob(KisNodeList nodes, KisSelectionSP selection, QObject *requestedBy) : m_nodes(nodes), m_selection(selection), m_requestedBy(requestedBy) { setExclusive(true); } void KisMoveBoundsCalculationJob::run() { QRect handlesRect; Q_FOREACH (KisNodeSP node, m_nodes) { - handlesRect |= KisLayerUtils::recursiveNodeExactBounds(node); + handlesRect |= KisLayerUtils::recursiveTightNodeVisibleBounds(node); } if (m_selection) { handlesRect &= m_selection->selectedExactRect(); } emit sigCalcualtionFinished(handlesRect); } bool KisMoveBoundsCalculationJob::overrides(const KisSpontaneousJob *_otherJob) { const KisMoveBoundsCalculationJob *otherJob = dynamic_cast(_otherJob); return otherJob && otherJob->m_requestedBy == m_requestedBy; } int KisMoveBoundsCalculationJob::levelOfDetail() const { return 0; } QString KisMoveBoundsCalculationJob::debugName() const { QString result; QDebug dbg(&result); dbg << "KisMoveBoundsCalculationJob" << ppVar(m_requestedBy) << m_nodes; return result; } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index 9390384de0..42d405feae 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,697 +1,717 @@ /* * Copyright (c) 2013 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 "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" #include "kis_sequential_iterator.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_layer_utils.h" #include #include #include "transform_transaction_properties.h" #include "krita_container_utils.h" #include "commands_new/kis_saved_commands.h" #include "kis_command_ids.h" #include "KisRunnableStrokeJobUtils.h" #include "commands_new/KisHoldUIUpdatesCommand.h" +#include "KisDecoratedNodeInterface.h" TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, bool workRecursively, const QString &filterId, bool forceReset, KisNodeSP rootNode, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), m_updatesFacade(updatesFacade), m_mode(mode), m_workRecursively(workRecursively), m_filterId(filterId), m_forceReset(forceReset), m_selection(selection) { KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data())); m_rootNode = rootNode; setMacroId(KisCommandUtils::TransformToolId); } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); PreparePreviewData *ppd = dynamic_cast(data); TransformAllData *runAllData = dynamic_cast(data); if (runAllData) { // here we only save the passed args, actual // transformation will be performed during // finish job m_savedTransformArgs = runAllData->config; } else if (ppd) { KisNodeSP rootNode = m_rootNode; KisNodeList processedNodes = m_processedNodes; KisPaintDeviceSP previewDevice; if (rootNode->childCount() || !rootNode->paintDevice()) { if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { previewDevice = createDeviceCache(tmask->buildPreviewDevice()); KIS_SAFE_ASSERT_RECOVER(!m_selection) { m_selection = 0; } } else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) { const QRect bounds = group->image()->bounds(); KisImageSP clonedImage = new KisImage(0, bounds.width(), bounds.height(), group->colorSpace(), "transformed_image"); KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data()); // In case the group is pass-through, it needs to be disabled for the preview, // otherwise it will crash (no parent for a preview leaf). // Also it needs to be done before setting the root layer for clonedImage. // Result: preview for pass-through group is the same as for standard group // (i.e. filter layers in the group won't affect the layer stack for a moment). clonedGroup->setPassThroughMode(false); clonedImage->setRootLayer(clonedGroup); QQueue linearizedSrcNodes; KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) { linearizedSrcNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) { KisNodeSP srcNode = linearizedSrcNodes.dequeue(); if (!processedNodes.contains(srcNode)) { node->setVisible(false); } }); clonedImage->refreshGraph(); KisLayerUtils::refreshHiddenAreaAsync(clonedImage, clonedGroup, clonedImage->bounds()); KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup); clonedImage->waitForDone(); previewDevice = createDeviceCache(clonedImage->projection()); previewDevice->setDefaultBounds(group->projection()->defaultBounds()); // we delete the cloned image in GUI thread to ensure // no signals are still pending makeKisDeleteLaterWrapper(clonedImage)->deleteLater(); } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); previewDevice = createDeviceCache(rootNode->projection()); } } else { KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); if (dynamic_cast(rootNode.data())) { KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); } previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect srcRect = cacheDevice->exactBounds(); KisSequentialConstIterator srcIt(cacheDevice, srcRect); KisSequentialIterator dstIt(previewDevice, srcRect); const int pixelSize = previewDevice->colorSpace()->pixelSize(); KisImageConfig cfg(true); KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace()); const qreal coeff = 1.0 / 255.0; const qreal baseOpacity = 0.5; while (srcIt.nextPixel() && dstIt.nextPixel()) { qreal gray = srcIt.rawDataConst()[0]; qreal alpha = srcIt.rawDataConst()[1]; pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); memcpy(dstIt.rawData(), pixel.data(), pixelSize); } } else { previewDevice = cacheDevice; } putDeviceCache(rootNode->paintDevice(), cacheDevice); } emit sigPreviewDeviceReady(previewDevice); } else if(td) { if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); /** * Selection masks might have an overlay enabled, we should disable that */ if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { KisSelectionSP selection = mask->selection(); if (selection) { selection->setVisible(false); m_deactivatedSelections.append(selection); mask->setDirty(); } } } else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) { externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true); externalLayer->setDirty(); m_hiddenProjectionLeaves.append(csd->node); } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; KUndo2CommandExtraData* clone() const override { return new TransformExtraData(*this); } }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedTransformArgs); TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = *m_savedTransformArgs; data->rootNode = m_rootNode; data->transformedNodes = m_processedNodes; command->setExtraData(data); KisSavedMacroCommand *macroCommand = dynamic_cast(command); KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand); if (m_overriddenCommand && macroCommand) { macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands); } KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } QList TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { *args = adapter->transformArgs(); result = true; } } return result; } bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode, KisNodeList selectedNodes, QVector *undoJobs) { bool result = false; const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; ToolTransformArgs args; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) && args.mode() == mode && oldRootNode == currentNode) { if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) { args.saveContinuedState(); *outArgs = args; const KisSavedMacroCommand *command = dynamic_cast(lastCommand); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false); // the jobs are fetched as !shouldGoToHistory, // so there is no need to put them into // m_skippedWhileMergeCommands command->getCommandExecutionJobs(undoJobs, true, false); m_overriddenCommand = command; result = true; } } return result; } void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); m_deactivatedSelections.append(m_selection); } KisSelectionMaskSP overlaySelectionMask = dynamic_cast(m_rootNode->graphListener()->graphOverlayNode()); if (overlaySelectionMask) { - overlaySelectionMask->selection()->setVisible(false); - overlaySelectionMask->setDirty(); + overlaySelectionMask->setDecorationsVisible(false); m_deactivatedOverlaySelectionMask = overlaySelectionMask; } - ToolTransformArgs initialTransformArgs; m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively); bool argsAreInitialized = false; QVector lastCommandUndoJobs; if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs, m_mode, m_rootNode, m_processedNodes, &lastCommandUndoJobs)) { argsAreInitialized = true; } else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) { argsAreInitialized = true; } QVector extraInitJobs; extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER); extraInitJobs << lastCommandUndoJobs; KritaUtils::addJobSequential(extraInitJobs, [this]() { /** * We must request shape layers to rerender areas outside image bounds */ KisLayerUtils::forceAllHiddenOriginalsUpdate(m_rootNode); }); KritaUtils::addJobBarrier(extraInitJobs, [this]() { /** * We must ensure that the currently selected subtree * has finished all its updates. */ KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode); }); + /// Disable all decorated nodes to generate outline + /// and preview correctly. We will enable them back + /// as soon as preview generation is finished. + KritaUtils::addJobBarrier(extraInitJobs, [this]() { + Q_FOREACH (KisNodeSP node, m_processedNodes) { + KisDecoratedNodeInterface *decoratedNode = dynamic_cast(node.data()); + if (decoratedNode && decoratedNode->decorationsVisible()) { + decoratedNode->setDecorationsVisible(false); + m_disabledDecoratedNodes << decoratedNode; + } + } + }); + KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable { QRect srcRect; if (m_selection) { srcRect = m_selection->selectedExactRect(); } else { srcRect = QRect(); Q_FOREACH (KisNodeSP node, m_processedNodes) { // group layers may have a projection of layers // that are locked and will not be transformed if (node->inherits("KisGroupLayer")) continue; if (const KisTransformMask *mask = dynamic_cast(node.data())) { srcRect |= mask->sourceDataBounds(); } else if (const KisSelectionMask *mask = dynamic_cast(node.data())) { srcRect |= mask->selection()->selectedExactRect(); } else { srcRect |= node->exactBounds(); } } } TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes); if (!argsAreInitialized) { initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction); } this->m_initialTransformArgs = initialTransformArgs; emit this->sigTransactionGenerated(transaction, initialTransformArgs, this); }); extraInitJobs << new PreparePreviewData(); Q_FOREACH (KisNodeSP node, m_processedNodes) { extraInitJobs << new ClearSelectionData(node); } + /// recover back visibility of decorated nodes + KritaUtils::addJobBarrier(extraInitJobs, [this]() { + Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_disabledDecoratedNodes) { + decoratedNode->setDecorationsVisible(true); + } + m_disabledDecoratedNodes.clear(); + }); + extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER); if (!lastCommandUndoJobs.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_overriddenCommand); for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) { (*it)->setCancellable(false); } } addMutatedJobs(extraInitJobs); } void TransformStrokeStrategy::finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args) { /** * Since our finishStrokeCallback() initiates new jobs, * cancellation request may come even after * finishStrokeCallback() (cancellations may be called * until there are no jobs left in the stroke's queue). * * Therefore we should check for double-entry here and * make sure the finilizing jobs are no cancellable. */ if (m_finalizingActionsStarted) return; m_finalizingActionsStarted = true; QVector mutatedJobs; if (applyTransform) { Q_FOREACH (KisNodeSP node, m_processedNodes) { mutatedJobs << new TransformData(TransformData::PAINT_DEVICE, args, node); } mutatedJobs << new TransformData(TransformData::SELECTION, args, m_rootNode); } KritaUtils::addJobBarrier(mutatedJobs, [this, applyTransform]() { Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } if (m_deactivatedOverlaySelectionMask) { m_deactivatedOverlaySelectionMask->selection()->setVisible(true); m_deactivatedOverlaySelectionMask->setDirty(); } if (applyTransform) { KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } else { KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } }); for (auto it = mutatedJobs.begin(); it != mutatedJobs.end(); ++it) { (*it)->setCancellable(false); } addMutatedJobs(mutatedJobs); } void TransformStrokeStrategy::finishStrokeCallback() { if (!m_savedTransformArgs || m_savedTransformArgs->isIdentity()) { cancelStrokeCallback(); return; } finishStrokeImpl(true, *m_savedTransformArgs); } void TransformStrokeStrategy::cancelStrokeCallback() { const bool shouldRecoverSavedInitialState = !m_initialTransformArgs.isIdentity(); if (shouldRecoverSavedInitialState) { m_savedTransformArgs = m_initialTransformArgs; } finishStrokeImpl(shouldRecoverSavedInitialState, *m_savedTransformArgs); } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h index 6e1d6dc276..3baf28f9cd 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h @@ -1,178 +1,180 @@ /* * Copyright (c) 2013 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 __TRANSFORM_STROKE_STRATEGY_H #define __TRANSFORM_STROKE_STRATEGY_H #include #include #include #include #include #include "tool_transform_args.h" #include #include #include class KisPostExecutionUndoAdapter; class TransformTransactionProperties; class KisUpdatesFacade; +class KisDecoratedNodeInterface; class TransformStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased { Q_OBJECT public: struct TransformAllData : public KisStrokeJobData { TransformAllData(const ToolTransformArgs &_config) : KisStrokeJobData(SEQUENTIAL, NORMAL), config(_config) {} ToolTransformArgs config; }; class TransformData : public KisStrokeJobData { public: enum Destination { PAINT_DEVICE, SELECTION, }; public: TransformData(Destination _destination, const ToolTransformArgs &_config, KisNodeSP _node) : KisStrokeJobData(CONCURRENT, NORMAL), destination(_destination), config(_config), node(_node) { } Destination destination; ToolTransformArgs config; KisNodeSP node; }; class ClearSelectionData : public KisStrokeJobData { public: ClearSelectionData(KisNodeSP _node) : KisStrokeJobData(SEQUENTIAL, NORMAL), node(_node) { } KisNodeSP node; }; class PreparePreviewData : public KisStrokeJobData { public: PreparePreviewData() : KisStrokeJobData(BARRIER, NORMAL) { } }; public: TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, bool workRecursively, const QString &filterId, bool forceReset, KisNodeSP rootNode, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade); ~TransformStrokeStrategy() override; void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; static bool fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes); Q_SIGNALS: void sigTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *cookie); void sigPreviewDeviceReady(KisPaintDeviceSP device); protected: void postProcessToplevelCommand(KUndo2Command *command) override; private: KoUpdaterPtr fetchUpdater(KisNodeSP node); void transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper); void clearSelection(KisPaintDeviceSP device); //void transformDevice(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); bool checkBelongsToSelection(KisPaintDeviceSP device) const; KisPaintDeviceSP createDeviceCache(KisPaintDeviceSP src); bool haveDeviceInCache(KisPaintDeviceSP src); void putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache); KisPaintDeviceSP getDeviceCache(KisPaintDeviceSP src); QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive); ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode, const QString &filterId, const TransformTransactionProperties &transaction); bool tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args); bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode, KisNodeList selectedNodes, QVector *undoJobs); void finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args); private: KisUpdatesFacade *m_updatesFacade; ToolTransformArgs::TransformMode m_mode; bool m_workRecursively; QString m_filterId; bool m_forceReset; KisSelectionSP m_selection; QMutex m_devicesCacheMutex; QHash m_devicesCacheHash; KisTransformMaskSP writeToTransformMask; ToolTransformArgs m_initialTransformArgs; boost::optional m_savedTransformArgs; KisNodeSP m_rootNode; KisNodeList m_processedNodes; QList m_deactivatedSelections; QList m_hiddenProjectionLeaves; KisSelectionMaskSP m_deactivatedOverlaySelectionMask; + QVector m_disabledDecoratedNodes; const KisSavedMacroCommand *m_overriddenCommand = 0; QVector m_skippedWhileMergeCommands; bool m_finalizingActionsStarted = false; }; #endif /* __TRANSFORM_STROKE_STRATEGY_H */ diff --git a/sdk/tests/lod_override.h b/sdk/tests/lod_override.h index e07b3e1f8f..3b5af2cb62 100644 --- a/sdk/tests/lod_override.h +++ b/sdk/tests/lod_override.h @@ -1,134 +1,138 @@ /* * 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 __LOD_OVERRIDE_H #define __LOD_OVERRIDE_H #include "kis_default_bounds_base.h" namespace TestUtil { class LodOverride { private: class LodDefaultBounds : public KisDefaultBoundsBase { public: LodDefaultBounds(int lod, KisDefaultBoundsBaseSP parent) : m_lod(lod), m_parent(parent) { } QRect bounds() const override { return m_parent->bounds(); } bool wrapAroundMode() const override { return m_parent->wrapAroundMode(); } int currentLevelOfDetail() const override { return m_lod; } int currentTime() const override { return m_parent->currentTime(); } bool externalFrameActive() const override { return m_parent->externalFrameActive(); } KisDefaultBoundsBaseSP parent() const { return m_parent; } + void * sourceCookie() const override { + return 0; + } + private: int m_lod; KisDefaultBoundsBaseSP m_parent; }; public: explicit LodOverride(int lod, KisImageSP image) : m_lod(lod), m_image(image) { overrideBounds(m_image->root(), OverrideDevice(m_lod)); } ~LodOverride() { overrideBounds(m_image->root(), RestoreDevice()); } private: template void overrideBounds(KisNodeSP root, OverrideOp op) { op(root->paintDevice()); if (root->original() != root->paintDevice()) { op(root->original()); } if (root->projection() != root->original()) { op(root->projection()); } KisNodeSP node = root->firstChild(); while (node) { overrideBounds(node, op); node = node->nextSibling(); } } struct OverrideDevice { OverrideDevice(int lod) : m_lod(lod) {} void operator() (KisPaintDeviceSP device) { if (!device) return; LodDefaultBounds *bounds = dynamic_cast(device->defaultBounds().data()); if (bounds) return; device->setDefaultBounds(new LodDefaultBounds(m_lod, device->defaultBounds())); } int m_lod; }; struct RestoreDevice { void operator() (KisPaintDeviceSP device) { if (!device) return; LodDefaultBounds *bounds = dynamic_cast(device->defaultBounds().data()); if (!bounds) return; device->setDefaultBounds(bounds->parent()); } }; private: int m_lod; KisImageSP m_image; }; } #endif /* __LOD_OVERRIDE_H */ diff --git a/sdk/tests/testing_timed_default_bounds.h b/sdk/tests/testing_timed_default_bounds.h index f48324acfd..ec0c7d9ab1 100644 --- a/sdk/tests/testing_timed_default_bounds.h +++ b/sdk/tests/testing_timed_default_bounds.h @@ -1,72 +1,76 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * 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 __TESTING_TIMED_DEFAULT_BOUNDS_H #define __TESTING_TIMED_DEFAULT_BOUNDS_H #include "kis_default_bounds_base.h" namespace TestUtil { struct TestingTimedDefaultBounds : public KisDefaultBoundsBase { TestingTimedDefaultBounds(const QRect &bounds = QRect(0,0,100,100)) : m_time(0), m_lod(0), m_bounds(bounds) { } QRect bounds() const override { return m_bounds; } bool wrapAroundMode() const override { return false; } int currentLevelOfDetail() const override { return m_lod; } int currentTime() const override { return m_time; } bool externalFrameActive() const override { return false; } void testingSetTime(int time) { m_time = time; } void testingSetLod(int lod) { m_lod = lod; } + void * sourceCookie() const override { + return 0; + } + private: int m_time; int m_lod; QRect m_bounds; }; } #endif /* __TESTING_TIMED_DEFAULT_BOUNDS_H */